1 19 20 package org.netbeans.modules.ruby.spi.project.support.rake; 21 22 import java.beans.PropertyChangeEvent ; 23 import java.beans.PropertyChangeListener ; 24 import java.io.File ; 25 import java.io.FileInputStream ; 26 import java.io.FileOutputStream ; 27 import java.io.IOException ; 28 import java.io.InputStream ; 29 import java.io.OutputStream ; 30 import java.lang.ref.Reference ; 31 import java.lang.ref.SoftReference ; 32 import java.net.URI ; 33 import java.util.ArrayList ; 34 import java.util.Collections ; 35 import java.util.HashMap ; 36 import java.util.HashSet ; 37 import java.util.LinkedList ; 38 import java.util.List ; 39 import java.util.Map ; 40 import java.util.Properties ; 41 import java.util.Set ; 42 import java.util.StringTokenizer ; 43 import java.util.logging.Level ; 44 import java.util.logging.Logger ; 45 import java.util.regex.Pattern ; 46 import javax.swing.event.ChangeEvent ; 47 import javax.swing.event.ChangeListener ; 48 import org.netbeans.api.project.ProjectManager; 49 import org.netbeans.modules.ruby.modules.project.rake.FileChangeSupport; 50 import org.netbeans.modules.ruby.modules.project.rake.FileChangeSupportEvent; 51 import org.netbeans.modules.ruby.modules.project.rake.FileChangeSupportListener; 52 import org.openide.ErrorManager; 53 import org.openide.filesystems.FileLock; 54 import org.openide.filesystems.FileObject; 55 import org.openide.filesystems.FileUtil; 56 import org.openide.util.Mutex; 57 import org.openide.util.MutexException; 58 import org.openide.util.NbCollections; 59 import org.openide.util.RequestProcessor; 60 import org.openide.util.TopologicalSortException; 61 import org.openide.util.Union2; 62 import org.openide.util.Utilities; 63 import org.openide.util.WeakListeners; 64 65 69 public class PropertyUtils { 70 71 private PropertyUtils() {} 72 73 77 static File userBuildProperties() { 78 String nbuser = System.getProperty("netbeans.user"); if (nbuser != null) { 80 return FileUtil.normalizeFile(new File (nbuser, "build.properties")); } else { 82 return null; 83 } 84 } 85 86 private static Map <File ,Reference <PropertyProvider>> globalPropertyProviders = new HashMap <File ,Reference <PropertyProvider>>(); 87 88 97 public static EditableProperties getGlobalProperties() { 98 return ProjectManager.mutex().readAccess(new Mutex.Action<EditableProperties>() { 99 public EditableProperties run() { 100 File ubp = userBuildProperties(); 101 if (ubp != null && ubp.isFile() && ubp.canRead()) { 102 try { 103 InputStream is = new FileInputStream (ubp); 104 try { 105 EditableProperties properties = new EditableProperties(true); 106 properties.load(is); 107 return properties; 108 } finally { 109 is.close(); 110 } 111 } catch (IOException e) { 112 Logger.getLogger(PropertyUtils.class.getName()).log(Level.INFO, null, e); 113 } 114 } 115 return new EditableProperties(true); 117 } 118 }); 119 } 120 121 129 public static void putGlobalProperties(final EditableProperties properties) throws IOException { 130 try { 131 ProjectManager.mutex().writeAccess(new Mutex.ExceptionAction<Void >() { 132 public Void run() throws IOException { 133 File ubp = userBuildProperties(); 134 if (ubp != null) { 135 FileObject bp = FileUtil.toFileObject(ubp); 136 if (bp == null) { 137 if (!ubp.exists()) { 138 ubp.getParentFile().mkdirs(); 139 new FileOutputStream (ubp).close(); 140 assert ubp.isFile() : "Did not actually make " + ubp; 141 } 142 bp = FileUtil.toFileObject(ubp); 143 if (bp == null) { 144 ErrorManager.getDefault().log(ErrorManager.WARNING, "Warning - cannot properly write to " + ubp + "; might be because your user directory is on a Windows UNC path (issue #46813)? If so, try using mapped drive letters."); 146 OutputStream os = new FileOutputStream (ubp); 147 try { 148 properties.store(os); 149 } finally { 150 os.close(); 151 } 152 return null; 153 } 154 } 155 FileLock lock = bp.lock(); 156 try { 157 OutputStream os = bp.getOutputStream(lock); 158 try { 159 properties.store(os); 160 } finally { 161 os.close(); 162 } 163 } finally { 164 lock.releaseLock(); 165 } 166 } else { 167 throw new IOException ("Do not know where to store build.properties; must set netbeans.user!"); } 169 return null; 170 } 171 }); 172 } catch (MutexException e) { 173 throw (IOException )e.getException(); 174 } 175 } 176 177 184 public static synchronized PropertyProvider globalPropertyProvider() { 185 File ubp = userBuildProperties(); 186 if (ubp != null) { 187 Reference <PropertyProvider> globalPropertyProvider = globalPropertyProviders.get(ubp); 188 if (globalPropertyProvider != null) { 189 PropertyProvider pp = globalPropertyProvider.get(); 190 if (pp != null) { 191 return pp; 192 } 193 } 194 PropertyProvider gpp = propertiesFilePropertyProvider(ubp); 195 globalPropertyProviders.put(ubp, new SoftReference <PropertyProvider>(gpp)); 196 return gpp; 197 } else { 198 return fixedPropertyProvider(Collections.<String ,String >emptyMap()); 199 } 200 } 201 202 210 public static PropertyProvider propertiesFilePropertyProvider(File propertiesFile) { 211 assert propertiesFile != null; 212 return new FilePropertyProvider(propertiesFile); 213 } 214 215 218 private static final class FilePropertyProvider implements PropertyProvider, FileChangeSupportListener { 219 220 private static final RequestProcessor RP = new RequestProcessor("PropertyUtils.FilePropertyProvider.RP"); 222 private final File properties; 223 private final List <ChangeListener > listeners = new ArrayList <ChangeListener >(); 224 private Map <String ,String > cached = null; 225 private long cachedTime = 0L; 226 227 public FilePropertyProvider(File properties) { 228 this.properties = properties; 229 FileChangeSupport.DEFAULT.addListener(this, properties); 230 } 231 232 public Map <String ,String > getProperties() { 233 long currTime = properties.lastModified(); 234 if (cached == null || cachedTime != currTime) { 235 cachedTime = currTime; 236 cached = loadProperties(); 237 } 238 return cached; 239 } 240 241 private Map <String ,String > loadProperties() { 242 if (properties.isFile() && properties.canRead()) { 244 try { 245 InputStream is = new FileInputStream (properties); 246 try { 247 Properties props = new Properties (); 248 props.load(is); 249 return NbCollections.checkedMapByFilter(props, String .class, String .class, true); 250 } finally { 251 is.close(); 252 } 253 } catch (IOException e) { 254 Logger.getLogger(PropertyUtils.class.getName()).log(Level.INFO, null, e); 255 } 256 } 257 return Collections.emptyMap(); 259 } 260 261 private void fireChange() { 262 cachedTime = -1L; final ChangeListener [] ls; 264 synchronized (this) { 265 if (listeners.isEmpty()) { 266 return; 267 } 268 ls = listeners.toArray(new ChangeListener [listeners.size()]); 269 } 270 final ChangeEvent ev = new ChangeEvent (this); 271 final Mutex.Action<Void > action = new Mutex.Action<Void >() { 272 public Void run() { 273 for (ChangeListener l : ls) { 274 l.stateChanged(ev); 275 } 276 return null; 277 } 278 }; 279 if (ProjectManager.mutex().isWriteAccess()) { 280 ProjectManager.mutex().readAccess(action); 282 } else if (ProjectManager.mutex().isReadAccess()) { 283 action.run(); 285 } else { 286 RP.post(new Runnable () { 288 public void run() { 289 ProjectManager.mutex().readAccess(action); 290 } 291 }); 292 } 293 } 294 295 public synchronized void addChangeListener(ChangeListener l) { 296 listeners.add(l); 297 } 298 299 public synchronized void removeChangeListener(ChangeListener l) { 300 listeners.remove(l); 301 } 302 303 public void fileCreated(FileChangeSupportEvent event) { 304 fireChange(); 306 } 307 308 public void fileDeleted(FileChangeSupportEvent event) { 309 fireChange(); 311 } 312 313 public void fileModified(FileChangeSupportEvent event) { 314 fireChange(); 316 } 317 318 public String toString() { 319 return "FilePropertyProvider[" + properties + ":" + getProperties() + "]"; } 321 322 } 323 324 333 private static Map <String ,String > evaluateAll(Map <String ,String > predefs, List <Map <String ,String >> defs) { 334 Map <String ,String > m = new HashMap <String ,String >(predefs); 335 for (Map <String ,String > curr : defs) { 336 Map <String ,Set <String >> dependOnSiblings = new HashMap <String ,Set <String >>(); 338 for (Map.Entry <String ,String > entry : curr.entrySet()) { 339 String prop = entry.getKey(); 340 if (!m.containsKey(prop)) { 341 String rawval = entry.getValue(); 342 Union2<String ,Set <String >> o = substitute(rawval, m, curr.keySet()); 344 if (o.hasFirst()) { 345 m.put(prop, o.first()); 346 } else { 347 dependOnSiblings.put(prop, o.second()); 348 } 349 } 350 } 351 Set <String > toSort = new HashSet <String >(dependOnSiblings.keySet()); 352 for (Set <String > s : dependOnSiblings.values()) { 353 toSort.addAll(s); 354 } 355 List <String > sorted; 356 try { 357 sorted = Utilities.topologicalSort(toSort, dependOnSiblings); 358 } catch (TopologicalSortException e) { 359 return null; 361 } 362 Collections.reverse(sorted); 363 for (String prop : sorted) { 364 if (!m.containsKey(prop)) { 365 String rawval = curr.get(prop); 366 m.put(prop, substitute(rawval, m, curr.keySet()).first()); 367 } 368 } 369 } 370 return m; 371 } 372 373 382 private static Union2<String ,Set <String >> substitute(String rawval, Map <String ,String > predefs, Set <String > siblingProperties) { 383 assert rawval != null : "null rawval passed in"; 384 if (rawval.indexOf('$') == -1) { 385 return Union2.createFirst(rawval); 388 } 389 int idx = 0; 391 StringBuffer val = new StringBuffer (); 393 Set <String > needed = new HashSet <String >(); 395 while (true) { 396 int shell = rawval.indexOf('$', idx); 397 if (shell == -1 || shell == rawval.length() - 1) { 398 if (needed.isEmpty()) { 401 val.append(rawval.substring(idx)); 402 return Union2.createFirst(val.toString()); 403 } else { 404 return Union2.createSecond(needed); 405 } 406 } 407 char c = rawval.charAt(shell + 1); 408 if (c == '$') { 409 if (needed.isEmpty()) { 412 val.append('$'); 413 } 414 idx += 2; 415 } else if (c == '{') { 416 int end = rawval.indexOf('}', shell + 2); 418 if (end != -1) { 419 String otherprop = rawval.substring(shell + 2, end); 421 if (predefs.containsKey(otherprop)) { 423 if (needed.isEmpty()) { 425 val.append(rawval.substring(idx, shell)); 426 val.append(predefs.get(otherprop)); 427 } 428 idx = end + 1; 429 } else if (siblingProperties.contains(otherprop)) { 430 needed.add(otherprop); 431 idx = end + 1; 433 } else { 434 if (needed.isEmpty()) { 436 val.append(rawval.substring(idx, end + 1)); 437 } 438 idx = end + 1; 439 } 440 } else { 441 if (needed.isEmpty()) { 443 val.append(rawval.substring(idx)); 444 return Union2.createFirst(val.toString()); 445 } else { 446 return Union2.createSecond(needed); 447 } 448 } 449 } else { 450 if (needed.isEmpty()) { 453 val.append(rawval.substring(idx, idx + 2)); 454 } 455 idx += 2; 456 } 457 } 458 } 459 460 private static final Pattern RELATIVE_SLASH_SEPARATED_PATH = Pattern.compile("[^:/\\\\.][^:/\\\\]*(/[^:/\\\\.][^:/\\\\]*)*"); 462 470 public static File resolveFile(File basedir, String filename) throws IllegalArgumentException { 471 if (basedir == null) { 472 throw new NullPointerException ("null basedir passed to resolveFile"); } 474 if (filename == null) { 475 throw new NullPointerException ("null filename passed to resolveFile"); } 477 if (!basedir.isAbsolute()) { 478 throw new IllegalArgumentException ("nonabsolute basedir passed to resolveFile: " + basedir); } 480 File f; 481 if (RELATIVE_SLASH_SEPARATED_PATH.matcher(filename).matches()) { 482 f = new File (basedir, filename.replace('/', File.separatorChar)); 484 } else { 485 String machinePath = filename.replace('/', File.separatorChar).replace('\\', File.separatorChar); 487 f = new File (machinePath); 488 if (!f.isAbsolute()) { 489 f = new File (basedir, machinePath); 490 } 491 assert f.isAbsolute(); 492 } 493 return FileUtil.normalizeFile(f); 494 } 495 496 505 public static String relativizeFile(File basedir, File file) { 506 if (basedir.isFile()) { 507 throw new IllegalArgumentException ("Cannot relative w.r.t. a data file " + basedir); } 509 if (basedir.equals(file)) { 510 return "."; } 512 StringBuffer b = new StringBuffer (); 513 File base = basedir; 514 String filepath = file.getAbsolutePath(); 515 while (!filepath.startsWith(slashify(base.getAbsolutePath()))) { 516 base = base.getParentFile(); 517 if (base == null) { 518 return null; 519 } 520 if (base.equals(file)) { 521 b.append(".."); return b.toString(); 524 } 525 b.append("../"); } 527 URI u = base.toURI().relativize(file.toURI()); 528 assert !u.isAbsolute() : u + " from " + basedir + " and " + file + " with common root " + base; 529 b.append(u.getPath()); 530 if (b.charAt(b.length() - 1) == '/') { 531 b.setLength(b.length() - 1); 534 } 535 return b.toString(); 536 } 537 538 private static String slashify(String path) { 539 if (path.endsWith(File.separator)) { 540 return path; 541 } else { 542 return path + File.separatorChar; 543 } 544 } 545 546 static FileObject resolveFileObject(FileObject basedir, String filename) { 547 if (RELATIVE_SLASH_SEPARATED_PATH.matcher(filename).matches()) { 548 return basedir.getFileObject(filename); 550 } else { 551 return FileUtil.toFileObject(resolveFile(FileUtil.toFile(basedir), filename)); 553 } 554 } 555 556 static String resolvePath(File basedir, String path) { 557 StringBuffer b = new StringBuffer (); 558 String [] toks = tokenizePath(path); 559 for (int i = 0; i < toks.length; i++) { 560 if (i > 0) { 561 b.append(File.pathSeparatorChar); 562 } 563 b.append(resolveFile(basedir, toks[i]).getAbsolutePath()); 564 } 565 return b.toString(); 566 } 567 568 576 public static String [] tokenizePath(String path) { 577 List <String > l = new ArrayList <String >(); 578 StringTokenizer tok = new StringTokenizer (path, ":;", true); char dosHack = '\0'; 580 char lastDelim = '\0'; 581 int delimCount = 0; 582 while (tok.hasMoreTokens()) { 583 String s = tok.nextToken(); 584 if (s.length() == 0) { 585 continue; 587 } 588 if (s.length() == 1) { 589 char c = s.charAt(0); 590 if (c == ':' || c == ';') { 591 lastDelim = c; 593 delimCount++; 594 continue; 595 } 596 } 597 if (dosHack != '\0') { 598 if (lastDelim == ':' && delimCount == 1 && (s.charAt(0) == '\\' || s.charAt(0) == '/')) { 600 s = "" + dosHack + ':' + s; 602 } else { 604 l.add(Character.toString(dosHack)); 606 } 608 dosHack = '\0'; 609 } 610 delimCount = 0; 612 if (s.length() == 1) { 613 char c = s.charAt(0); 614 if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { 615 dosHack = c; 617 continue; 618 } 619 } 620 l.add(s); 621 } 622 if (dosHack != '\0') { 623 l.add(Character.toString(dosHack)); 627 } 628 return l.toArray(new String [l.size()]); 629 } 630 631 private static final Pattern VALID_PROPERTY_NAME = Pattern.compile("[-._a-zA-Z0-9]"); 633 638 public static boolean isUsablePropertyName(String name) { 639 return VALID_PROPERTY_NAME.matcher(name).matches(); 640 } 641 642 649 public static String getUsablePropertyName(String name) { 650 if (isUsablePropertyName(name)) { 651 return name; 652 } 653 StringBuffer sb = new StringBuffer (name); 654 for (int i=0; i<sb.length(); i++) { 655 if (!isUsablePropertyName(sb.substring(i,i+1))) { 656 sb.replace(i,i+1,"_"); 657 } 658 } 659 return sb.toString(); 660 } 661 662 669 public static PropertyProvider fixedPropertyProvider(Map <String ,String > defs) { 670 return new FixedPropertyProvider(defs); 671 } 672 673 private static final class FixedPropertyProvider implements PropertyProvider { 674 675 private final Map <String ,String > defs; 676 677 public FixedPropertyProvider(Map <String ,String > defs) { 678 this.defs = defs; 679 } 680 681 public Map <String ,String > getProperties() { 682 return defs; 683 } 684 685 public void addChangeListener(ChangeListener l) {} 686 687 public void removeChangeListener(ChangeListener l) {} 688 689 } 690 691 707 public static PropertyEvaluator sequentialPropertyEvaluator(PropertyProvider preprovider, PropertyProvider... providers) { 708 return new SequentialPropertyEvaluator(preprovider, providers); 709 } 710 711 723 public static PropertyProvider userPropertiesProvider(PropertyEvaluator findUserPropertiesFile, String propertyName, File basedir) { 724 return new UserPropertiesProvider(findUserPropertiesFile, propertyName, basedir); 725 } 726 private static final class UserPropertiesProvider extends FilterPropertyProvider implements PropertyChangeListener { 727 private final PropertyEvaluator findUserPropertiesFile; 728 private final String propertyName; 729 private final File basedir; 730 public UserPropertiesProvider(PropertyEvaluator findUserPropertiesFile, String propertyName, File basedir) { 731 super(computeDelegate(findUserPropertiesFile, propertyName, basedir)); 732 this.findUserPropertiesFile = findUserPropertiesFile; 733 this.propertyName = propertyName; 734 this.basedir = basedir; 735 findUserPropertiesFile.addPropertyChangeListener(this); 736 } 737 public void propertyChange(PropertyChangeEvent ev) { 738 if (propertyName.equals(ev.getPropertyName())) { 739 setDelegate(computeDelegate(findUserPropertiesFile, propertyName, basedir)); 740 } 741 } 742 private static PropertyProvider computeDelegate(PropertyEvaluator findUserPropertiesFile, String propertyName, File basedir) { 743 String userPropertiesFile = findUserPropertiesFile.getProperty(propertyName); 744 if (userPropertiesFile != null) { 745 File f = PropertyUtils.resolveFile(basedir, userPropertiesFile); 747 if (f.equals(PropertyUtils.userBuildProperties())) { 748 return PropertyUtils.globalPropertyProvider(); 750 } else { 751 return PropertyUtils.propertiesFilePropertyProvider(f); 752 } 753 } else { 754 return PropertyUtils.globalPropertyProvider(); 756 } 757 } 758 } 759 760 private static final class SequentialPropertyEvaluator implements PropertyEvaluator, ChangeListener { 761 762 private final PropertyProvider preprovider; 763 private final PropertyProvider[] providers; 764 private Map <String ,String > defs; 765 private final List <PropertyChangeListener > listeners = new ArrayList <PropertyChangeListener >(); 766 767 public SequentialPropertyEvaluator(final PropertyProvider preprovider, final PropertyProvider[] providers) { 768 this.preprovider = preprovider; 769 this.providers = providers; 770 defs = ProjectManager.mutex().readAccess(new Mutex.Action<Map <String ,String >>() { 772 public Map <String ,String > run() { 773 return compose(preprovider, providers); 774 } 775 }); 776 if (preprovider != null) { 778 preprovider.addChangeListener(WeakListeners.change(this, preprovider)); 779 } 780 for (PropertyProvider pp : providers) { 781 pp.addChangeListener(WeakListeners.change(this, pp)); 782 } 783 } 784 785 public String getProperty(final String prop) { 786 return ProjectManager.mutex().readAccess(new Mutex.Action<String >() { 787 public String run() { 788 if (defs == null) { 789 return null; 790 } 791 return defs.get(prop); 792 } 793 }); 794 } 795 796 public String evaluate(final String text) { 797 if (text == null) { 798 throw new NullPointerException ("Attempted to pass null to PropertyEvaluator.evaluate"); } 800 return ProjectManager.mutex().readAccess(new Mutex.Action<String >() { 801 public String run() { 802 if (defs == null) { 803 return null; 804 } 805 Union2<String ,Set <String >> result = substitute(text, defs, Collections.<String >emptySet()); 806 assert result.hasFirst() : "Unexpected result " + result + " from " + text + " on " + defs; 807 return result.first(); 808 } 809 }); 810 } 811 812 public Map <String ,String > getProperties() { 813 return ProjectManager.mutex().readAccess(new Mutex.Action<Map <String ,String >>() { 814 public Map <String ,String > run() { 815 return defs; 816 } 817 }); 818 } 819 820 public void addPropertyChangeListener(PropertyChangeListener listener) { 821 synchronized (listeners) { 822 listeners.add(listener); 823 } 824 } 825 826 public void removePropertyChangeListener(PropertyChangeListener listener) { 827 synchronized (listeners) { 828 listeners.remove(listener); 829 } 830 } 831 832 public void stateChanged(ChangeEvent e) { 833 assert ProjectManager.mutex().isReadAccess() || ProjectManager.mutex().isWriteAccess(); 834 Map <String ,String > newdefs = compose(preprovider, providers); 835 Map <String ,String > _defs = defs != null ? defs : Collections.<String ,String >emptyMap(); 837 Map <String ,String > _newdefs = newdefs != null ? newdefs : Collections.<String ,String >emptyMap(); 838 if (!_defs.equals(_newdefs)) { 839 Set <String > props = new HashSet <String >(_defs.keySet()); 840 props.addAll(_newdefs.keySet()); 841 List <PropertyChangeEvent > events = new LinkedList <PropertyChangeEvent >(); 842 for (String prop : props) { 843 assert prop != null; 844 String oldval = _defs.get(prop); 845 String newval = _newdefs.get(prop); 846 if (newval != null) { 847 if (newval.equals(oldval)) { 848 continue; 849 } 850 } else { 851 assert oldval != null : "should not have had " + prop; 852 } 853 events.add(new PropertyChangeEvent (this, prop, oldval, newval)); 854 } 855 assert !events.isEmpty(); 856 defs = newdefs; 857 PropertyChangeListener [] _listeners; 858 synchronized (listeners) { 859 _listeners = listeners.toArray(new PropertyChangeListener [listeners.size()]); 860 } 861 for (PropertyChangeListener l : _listeners) { 862 for (PropertyChangeEvent ev : events) { 863 l.propertyChange(ev); 864 } 865 } 866 } 867 } 868 869 private static Map <String ,String > compose(PropertyProvider preprovider, PropertyProvider[] providers) { 870 assert ProjectManager.mutex().isReadAccess() || ProjectManager.mutex().isWriteAccess(); 871 Map <String ,String > predefs; 872 if (preprovider != null) { 873 predefs = preprovider.getProperties(); 874 } else { 875 predefs = Collections.emptyMap(); 876 } 877 List <Map <String ,String >> defs = new ArrayList <Map <String ,String >>(providers.length); 878 for (PropertyProvider pp : providers) { 879 defs.add(pp.getProperties()); 880 } 881 return evaluateAll(predefs, defs); 882 } 883 884 } 885 886 887 } 888 | Popular Tags |