KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > ruby > spi > project > support > rake > PropertyUtils


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.netbeans.modules.ruby.spi.project.support.rake;
21
22 import java.beans.PropertyChangeEvent JavaDoc;
23 import java.beans.PropertyChangeListener JavaDoc;
24 import java.io.File JavaDoc;
25 import java.io.FileInputStream JavaDoc;
26 import java.io.FileOutputStream JavaDoc;
27 import java.io.IOException JavaDoc;
28 import java.io.InputStream JavaDoc;
29 import java.io.OutputStream JavaDoc;
30 import java.lang.ref.Reference JavaDoc;
31 import java.lang.ref.SoftReference JavaDoc;
32 import java.net.URI JavaDoc;
33 import java.util.ArrayList JavaDoc;
34 import java.util.Collections JavaDoc;
35 import java.util.HashMap JavaDoc;
36 import java.util.HashSet JavaDoc;
37 import java.util.LinkedList JavaDoc;
38 import java.util.List JavaDoc;
39 import java.util.Map JavaDoc;
40 import java.util.Properties JavaDoc;
41 import java.util.Set JavaDoc;
42 import java.util.StringTokenizer JavaDoc;
43 import java.util.logging.Level JavaDoc;
44 import java.util.logging.Logger JavaDoc;
45 import java.util.regex.Pattern JavaDoc;
46 import javax.swing.event.ChangeEvent JavaDoc;
47 import javax.swing.event.ChangeListener JavaDoc;
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 /**
66  * Support for working with Ant properties and property files.
67  * @author Jesse Glick
68  */

69 public class PropertyUtils {
70     
71     private PropertyUtils() {}
72     
73     /**
74      * Location in user directory of per-user global properties.
75      * May be null if <code>netbeans.user</code> is not set.
76      */

77     static File JavaDoc userBuildProperties() {
78         String JavaDoc nbuser = System.getProperty("netbeans.user"); // NOI18N
79
if (nbuser != null) {
80             return FileUtil.normalizeFile(new File JavaDoc(nbuser, "build.properties")); // NOI18N
81
} else {
82             return null;
83         }
84     }
85     
86     private static Map JavaDoc<File JavaDoc,Reference JavaDoc<PropertyProvider>> globalPropertyProviders = new HashMap JavaDoc<File JavaDoc,Reference JavaDoc<PropertyProvider>>();
87     
88     /**
89      * Load global properties defined by the IDE in the user directory.
90      * Currently loads ${netbeans.user}/build.properties if it exists.
91      * <p>
92      * Acquires read access.
93      * <p>
94      * To listen to changes use {@link #globalPropertyProvider}.
95      * @return user properties (empty if missing or malformed)
96      */

97     public static EditableProperties getGlobalProperties() {
98         return ProjectManager.mutex().readAccess(new Mutex.Action<EditableProperties>() {
99             public EditableProperties run() {
100                 File JavaDoc ubp = userBuildProperties();
101                 if (ubp != null && ubp.isFile() && ubp.canRead()) {
102                     try {
103                         InputStream JavaDoc is = new FileInputStream JavaDoc(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 JavaDoc e) {
112                         Logger.getLogger(PropertyUtils.class.getName()).log(Level.INFO, null, e);
113                     }
114                 }
115                 // Missing or erroneous.
116
return new EditableProperties(true);
117             }
118         });
119     }
120     
121     /**
122      * Edit global properties defined by the IDE in the user directory.
123      * <p>
124      * Acquires write access.
125      * @param properties user properties to set
126      * @throws IOException if they could not be stored
127      * @see #getGlobalProperties
128      */

129     public static void putGlobalProperties(final EditableProperties properties) throws IOException JavaDoc {
130         try {
131             ProjectManager.mutex().writeAccess(new Mutex.ExceptionAction<Void JavaDoc>() {
132                 public Void JavaDoc run() throws IOException JavaDoc {
133                     File JavaDoc 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 JavaDoc(ubp).close();
140                                 assert ubp.isFile() : "Did not actually make " + ubp;
141                             }
142                             bp = FileUtil.toFileObject(ubp);
143                             if (bp == null) {
144                                 // XXX ugly (and will not correctly notify changes) but better than nothing:
145
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 JavaDoc os = new FileOutputStream JavaDoc(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 JavaDoc 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 JavaDoc("Do not know where to store build.properties; must set netbeans.user!"); // NOI18N
168
}
169                     return null;
170                 }
171             });
172         } catch (MutexException e) {
173             throw (IOException JavaDoc)e.getException();
174         }
175     }
176     
177     /**
178      * Create a property evaluator based on {@link #getGlobalProperties}
179      * and {@link #putGlobalProperties}.
180      * It will supply global properties and fire changes when this file
181      * is changed.
182      * @return a property producer
183      */

184     public static synchronized PropertyProvider globalPropertyProvider() {
185         File JavaDoc ubp = userBuildProperties();
186         if (ubp != null) {
187             Reference JavaDoc<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 JavaDoc<PropertyProvider>(gpp));
196             return gpp;
197         } else {
198             return fixedPropertyProvider(Collections.<String JavaDoc,String JavaDoc>emptyMap());
199         }
200     }
201
202     /**
203      * Create a property provider based on a properties file.
204      * The file need not exist at the moment; if it is created or deleted an appropriate
205      * change will be fired. If its contents are changed on disk a change will also be fired.
206      * @param propertiesFile a path to a (possibly nonexistent) *.properties file
207      * @return a supplier of properties from such a file
208      * @see Properties#load
209      */

210     public static PropertyProvider propertiesFilePropertyProvider(File JavaDoc propertiesFile) {
211         assert propertiesFile != null;
212         return new FilePropertyProvider(propertiesFile);
213     }
214     
215     /**
216      * Provider based on a named properties file.
217      */

218     private static final class FilePropertyProvider implements PropertyProvider, FileChangeSupportListener {
219         
220         private static final RequestProcessor RP = new RequestProcessor("PropertyUtils.FilePropertyProvider.RP"); // NOI18N
221

222         private final File JavaDoc properties;
223         private final List JavaDoc<ChangeListener JavaDoc> listeners = new ArrayList JavaDoc<ChangeListener JavaDoc>();
224         private Map JavaDoc<String JavaDoc,String JavaDoc> cached = null;
225         private long cachedTime = 0L;
226         
227         public FilePropertyProvider(File JavaDoc properties) {
228             this.properties = properties;
229             FileChangeSupport.DEFAULT.addListener(this, properties);
230         }
231         
232         public Map JavaDoc<String JavaDoc,String JavaDoc> 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 JavaDoc<String JavaDoc,String JavaDoc> loadProperties() {
242             // XXX does this need to run in PM.mutex.readAccess?
243
if (properties.isFile() && properties.canRead()) {
244                 try {
245                     InputStream JavaDoc is = new FileInputStream JavaDoc(properties);
246                     try {
247                         Properties JavaDoc props = new Properties JavaDoc();
248                         props.load(is);
249                         return NbCollections.checkedMapByFilter(props, String JavaDoc.class, String JavaDoc.class, true);
250                     } finally {
251                         is.close();
252                     }
253                 } catch (IOException JavaDoc e) {
254                     Logger.getLogger(PropertyUtils.class.getName()).log(Level.INFO, null, e);
255                 }
256             }
257             // Missing or erroneous.
258
return Collections.emptyMap();
259         }
260         
261         private void fireChange() {
262             cachedTime = -1L; // force reload
263
final ChangeListener JavaDoc[] ls;
264             synchronized (this) {
265                 if (listeners.isEmpty()) {
266                     return;
267                 }
268                 ls = listeners.toArray(new ChangeListener JavaDoc[listeners.size()]);
269             }
270             final ChangeEvent JavaDoc ev = new ChangeEvent JavaDoc(this);
271             final Mutex.Action<Void JavaDoc> action = new Mutex.Action<Void JavaDoc>() {
272                 public Void JavaDoc run() {
273                     for (ChangeListener JavaDoc l : ls) {
274                         l.stateChanged(ev);
275                     }
276                     return null;
277                 }
278             };
279             if (ProjectManager.mutex().isWriteAccess()) {
280                 // Run it right now. postReadRequest would be too late.
281
ProjectManager.mutex().readAccess(action);
282             } else if (ProjectManager.mutex().isReadAccess()) {
283                 // Run immediately also. No need to switch to read access.
284
action.run();
285             } else {
286                 // Not safe to acquire a new lock, so run later in read access.
287
RP.post(new Runnable JavaDoc() {
288                     public void run() {
289                         ProjectManager.mutex().readAccess(action);
290                     }
291                 });
292             }
293         }
294         
295         public synchronized void addChangeListener(ChangeListener JavaDoc l) {
296             listeners.add(l);
297         }
298         
299         public synchronized void removeChangeListener(ChangeListener JavaDoc l) {
300             listeners.remove(l);
301         }
302
303         public void fileCreated(FileChangeSupportEvent event) {
304             //System.err.println("FPP: " + event);
305
fireChange();
306         }
307
308         public void fileDeleted(FileChangeSupportEvent event) {
309             //System.err.println("FPP: " + event);
310
fireChange();
311         }
312
313         public void fileModified(FileChangeSupportEvent event) {
314             //System.err.println("FPP: " + event);
315
fireChange();
316         }
317         
318         public String JavaDoc toString() {
319             return "FilePropertyProvider[" + properties + ":" + getProperties() + "]"; // NOI18N
320
}
321         
322     }
323     
324     /**
325      * Evaluate all properties in a list of property mappings.
326      * <p>
327      * If there are any cyclic definitions within a single mapping,
328      * the evaluation will fail and return null.
329      * @param defs an ordered list of property mappings, e.g. {@link EditableProperties} instances
330      * @param predefs an unevaluated set of initial definitions
331      * @return values for all defined properties, or null if a circularity error was detected
332      */

333     private static Map JavaDoc<String JavaDoc,String JavaDoc> evaluateAll(Map JavaDoc<String JavaDoc,String JavaDoc> predefs, List JavaDoc<Map JavaDoc<String JavaDoc,String JavaDoc>> defs) {
334         Map JavaDoc<String JavaDoc,String JavaDoc> m = new HashMap JavaDoc<String JavaDoc,String JavaDoc>(predefs);
335         for (Map JavaDoc<String JavaDoc,String JavaDoc> curr : defs) {
336             // Set of properties which we are deferring because they subst sibling properties:
337
Map JavaDoc<String JavaDoc,Set JavaDoc<String JavaDoc>> dependOnSiblings = new HashMap JavaDoc<String JavaDoc,Set JavaDoc<String JavaDoc>>();
338             for (Map.Entry JavaDoc<String JavaDoc,String JavaDoc> entry : curr.entrySet()) {
339                 String JavaDoc prop = entry.getKey();
340                 if (!m.containsKey(prop)) {
341                     String JavaDoc rawval = entry.getValue();
342                     //System.err.println("subst " + prop + "=" + rawval + " with " + m);
343
Union2<String JavaDoc,Set JavaDoc<String JavaDoc>> 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 JavaDoc<String JavaDoc> toSort = new HashSet JavaDoc<String JavaDoc>(dependOnSiblings.keySet());
352             for (Set JavaDoc<String JavaDoc> s : dependOnSiblings.values()) {
353                 toSort.addAll(s);
354             }
355             List JavaDoc<String JavaDoc> sorted;
356             try {
357                 sorted = Utilities.topologicalSort(toSort, dependOnSiblings);
358             } catch (TopologicalSortException e) {
359                 //System.err.println("Cyclic property refs: " + Arrays.asList(e.unsortableSets()));
360
return null;
361             }
362             Collections.reverse(sorted);
363             for (String JavaDoc prop : sorted) {
364                 if (!m.containsKey(prop)) {
365                     String JavaDoc rawval = curr.get(prop);
366                     m.put(prop, substitute(rawval, m, /*Collections.EMPTY_SET*/curr.keySet()).first());
367                 }
368             }
369         }
370         return m;
371     }
372     
373     /**
374      * Try to substitute property references etc. in an Ant property value string.
375      * @param rawval the raw value to be substituted
376      * @param predefs a set of properties already defined
377      * @param siblingProperties a set of property names that are yet to be defined
378      * @return either a String, in case everything can be evaluated now;
379      * or a Set<String> of elements from siblingProperties in case those properties
380      * need to be defined in order to evaluate this one
381      */

382     private static Union2<String JavaDoc,Set JavaDoc<String JavaDoc>> substitute(String JavaDoc rawval, Map JavaDoc<String JavaDoc,String JavaDoc> predefs, Set JavaDoc<String JavaDoc> siblingProperties) {
383         assert rawval != null : "null rawval passed in";
384         if (rawval.indexOf('$') == -1) {
385             // Shortcut:
386
//System.err.println("shortcut");
387
return Union2.createFirst(rawval);
388         }
389         // May need to subst something.
390
int idx = 0;
391         // Result in progress, if it is to be a String:
392
StringBuffer JavaDoc val = new StringBuffer JavaDoc();
393         // Or, result in progress, if it is to be a Set<String>:
394
Set JavaDoc<String JavaDoc> needed = new HashSet JavaDoc<String JavaDoc>();
395         while (true) {
396             int shell = rawval.indexOf('$', idx);
397             if (shell == -1 || shell == rawval.length() - 1) {
398                 // No more $, or only as last char -> copy all.
399
//System.err.println("no more $");
400
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                 // $$ -> $
410
//System.err.println("$$");
411
if (needed.isEmpty()) {
412                     val.append('$');
413                 }
414                 idx += 2;
415             } else if (c == '{') {
416                 // Possibly a property ref.
417
int end = rawval.indexOf('}', shell + 2);
418                 if (end != -1) {
419                     // Definitely a property ref.
420
String JavaDoc otherprop = rawval.substring(shell + 2, end);
421                     //System.err.println("prop ref to " + otherprop);
422
if (predefs.containsKey(otherprop)) {
423                         // Well-defined.
424
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                         // don't bother updating val, it will not be used anyway
432
idx = end + 1;
433                     } else {
434                         // No def, leave as is.
435
if (needed.isEmpty()) {
436                             val.append(rawval.substring(idx, end + 1));
437                         }
438                         idx = end + 1;
439                     }
440                 } else {
441                     // Unclosed ${ sequence, leave as is.
442
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                 // $ followed by some other char, leave as is.
451
// XXX is this actually right?
452
if (needed.isEmpty()) {
453                     val.append(rawval.substring(idx, idx + 2));
454                 }
455                 idx += 2;
456             }
457         }
458     }
459     
460     private static final Pattern JavaDoc RELATIVE_SLASH_SEPARATED_PATH = Pattern.compile("[^:/\\\\.][^:/\\\\]*(/[^:/\\\\.][^:/\\\\]*)*"); // NOI18N
461

462     /**
463      * Find an absolute file path from a possibly relative path.
464      * @param basedir base file for relative filename resolving; must be an absolute path
465      * @param filename a pathname which may be relative or absolute and may
466      * use / or \ as the path separator
467      * @return an absolute file corresponding to it
468      * @throws IllegalArgumentException if basedir is not absolute
469      */

470     public static File JavaDoc resolveFile(File JavaDoc basedir, String JavaDoc filename) throws IllegalArgumentException JavaDoc {
471         if (basedir == null) {
472             throw new NullPointerException JavaDoc("null basedir passed to resolveFile"); // NOI18N
473
}
474         if (filename == null) {
475             throw new NullPointerException JavaDoc("null filename passed to resolveFile"); // NOI18N
476
}
477         if (!basedir.isAbsolute()) {
478             throw new IllegalArgumentException JavaDoc("nonabsolute basedir passed to resolveFile: " + basedir); // NOI18N
479
}
480         File JavaDoc f;
481         if (RELATIVE_SLASH_SEPARATED_PATH.matcher(filename).matches()) {
482             // Shortcut - simple relative path. Potentially faster.
483
f = new File JavaDoc(basedir, filename.replace('/', File.separatorChar));
484         } else {
485             // All other cases.
486
String JavaDoc machinePath = filename.replace('/', File.separatorChar).replace('\\', File.separatorChar);
487             f = new File JavaDoc(machinePath);
488             if (!f.isAbsolute()) {
489                 f = new File JavaDoc(basedir, machinePath);
490             }
491             assert f.isAbsolute();
492         }
493         return FileUtil.normalizeFile(f);
494     }
495     
496     /**
497      * Produce a machine-independent relativized version of a filename from a basedir.
498      * Unlike {@link URI#relativize} this will produce "../" sequences as needed.
499      * @param basedir a directory to resolve relative to (need not exist on disk)
500      * @param file a file or directory to find a relative path for
501      * @return a relativized path (slash-separated), or null if it is not possible (e.g. different DOS drives);
502      * just <samp>.</samp> in case the paths are the same
503      * @throws IllegalArgumentException if the basedir is known to be a file and not a directory
504      */

505     public static String JavaDoc relativizeFile(File JavaDoc basedir, File JavaDoc file) {
506         if (basedir.isFile()) {
507             throw new IllegalArgumentException JavaDoc("Cannot relative w.r.t. a data file " + basedir); // NOI18N
508
}
509         if (basedir.equals(file)) {
510             return "."; // NOI18N
511
}
512         StringBuffer JavaDoc b = new StringBuffer JavaDoc();
513         File JavaDoc base = basedir;
514         String JavaDoc 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                 // #61687: file is a parent of basedir
522
b.append(".."); // NOI18N
523
return b.toString();
524             }
525             b.append("../"); // NOI18N
526
}
527         URI JavaDoc 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             // file is an existing directory and file.toURI ends in /
532
// we do not want the trailing slash
533
b.setLength(b.length() - 1);
534         }
535         return b.toString();
536     }
537     
538     private static String JavaDoc slashify(String JavaDoc path) {
539         if (path.endsWith(File.separator)) {
540             return path;
541         } else {
542             return path + File.separatorChar;
543         }
544     }
545     
546     /*public? */ static FileObject resolveFileObject(FileObject basedir, String JavaDoc filename) {
547         if (RELATIVE_SLASH_SEPARATED_PATH.matcher(filename).matches()) {
548             // Shortcut. Potentially much faster.
549
return basedir.getFileObject(filename);
550         } else {
551             // Might be an absolute path, or \-separated, or . or .. components, etc.; use the safer method.
552
return FileUtil.toFileObject(resolveFile(FileUtil.toFile(basedir), filename));
553         }
554     }
555     
556     /*public? */ static String JavaDoc resolvePath(File JavaDoc basedir, String JavaDoc path) {
557         StringBuffer JavaDoc b = new StringBuffer JavaDoc();
558         String JavaDoc[] 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     /**
569      * Split an Ant-style path specification into components.
570      * Tokenizes on <code>:</code> and <code>;</code>, paying
571      * attention to DOS-style components such as <samp>C:\FOO</samp>.
572      * Also removes any empty components.
573      * @param path an Ant-style path (elements arbitrary) using DOS or Unix separators
574      * @return a tokenization of that path into components
575      */

576     public static String JavaDoc[] tokenizePath(String JavaDoc path) {
577         List JavaDoc<String JavaDoc> l = new ArrayList JavaDoc<String JavaDoc>();
578         StringTokenizer JavaDoc tok = new StringTokenizer JavaDoc(path, ":;", true); // NOI18N
579
char dosHack = '\0';
580         char lastDelim = '\0';
581         int delimCount = 0;
582         while (tok.hasMoreTokens()) {
583             String JavaDoc s = tok.nextToken();
584             if (s.length() == 0) {
585                 // Strip empty components.
586
continue;
587             }
588             if (s.length() == 1) {
589                 char c = s.charAt(0);
590                 if (c == ':' || c == ';') {
591                     // Just a delimiter.
592
lastDelim = c;
593                     delimCount++;
594                     continue;
595                 }
596             }
597             if (dosHack != '\0') {
598                 // #50679 - "C:/something" is also accepted as DOS path
599
if (lastDelim == ':' && delimCount == 1 && (s.charAt(0) == '\\' || s.charAt(0) == '/')) {
600                     // We had a single letter followed by ':' now followed by \something or /something
601
s = "" + dosHack + ':' + s;
602                     // and use the new token with the drive prefix...
603
} else {
604                     // Something else, leave alone.
605
l.add(Character.toString(dosHack));
606                     // and continue with this token too...
607
}
608                 dosHack = '\0';
609             }
610             // Reset count of # of delimiters in a row.
611
delimCount = 0;
612             if (s.length() == 1) {
613                 char c = s.charAt(0);
614                 if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
615                     // Probably a DOS drive letter. Leave it with the next component.
616
dosHack = c;
617                     continue;
618                 }
619             }
620             l.add(s);
621         }
622         if (dosHack != '\0') {
623             //the dosHack was the last letter in the input string (not followed by the ':')
624
//so obviously not a drive letter.
625
//Fix for issue #57304
626
l.add(Character.toString(dosHack));
627         }
628         return l.toArray(new String JavaDoc[l.size()]);
629     }
630
631     private static final Pattern JavaDoc VALID_PROPERTY_NAME = Pattern.compile("[-._a-zA-Z0-9]"); // NOI18N
632

633     /**
634      * Checks whether the name is usable as Ant property name.
635      * @param name name to check for usability as Ant property
636      * @return true if name is usable otherwise false
637      */

638     public static boolean isUsablePropertyName(String JavaDoc name) {
639         return VALID_PROPERTY_NAME.matcher(name).matches();
640     }
641
642     /**
643      * Returns name usable as Ant property which is based on the given
644      * name. All forbidden characters are either removed or replaced with
645      * suitable ones.
646      * @param name name to use as base for Ant property name
647      * @return name usable as Ant property name
648      */

649     public static String JavaDoc getUsablePropertyName(String JavaDoc name) {
650         if (isUsablePropertyName(name)) {
651             return name;
652         }
653         StringBuffer JavaDoc sb = new StringBuffer JavaDoc(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     /**
663      * Create a trivial property producer using only a fixed list of property definitions.
664      * Its values are constant, and it never fires changes.
665      * @param defs a map from property names to values (it is illegal to modify this map
666      * after passing it to this method)
667      * @return a matching property producer
668      */

669     public static PropertyProvider fixedPropertyProvider(Map JavaDoc<String JavaDoc,String JavaDoc> defs) {
670         return new FixedPropertyProvider(defs);
671     }
672     
673     private static final class FixedPropertyProvider implements PropertyProvider {
674         
675         private final Map JavaDoc<String JavaDoc,String JavaDoc> defs;
676         
677         public FixedPropertyProvider(Map JavaDoc<String JavaDoc,String JavaDoc> defs) {
678             this.defs = defs;
679         }
680         
681         public Map JavaDoc<String JavaDoc,String JavaDoc> getProperties() {
682             return defs;
683         }
684         
685         public void addChangeListener(ChangeListener JavaDoc l) {}
686         
687         public void removeChangeListener(ChangeListener JavaDoc l) {}
688         
689     }
690     
691     /**
692      * Create a property evaluator based on a series of definitions.
693      * <p>
694      * Each batch of definitions can refer to properties within itself
695      * (so long as there is no cycle) or any previous batch.
696      * However the special first provider cannot refer to properties within itself.
697      * </p>
698      * <p>
699      * This implementation acquires {@link ProjectManager#mutex} for all operations, in read mode,
700      * and fires changes synchronously. It also expects changes to be fired from property
701      * providers in read (or write) access.
702      * </p>
703      * @param preprovider an initial context (may be null)
704      * @param providers a sequential list of property groups
705      * @return an evaluator
706      */

707     public static PropertyEvaluator sequentialPropertyEvaluator(PropertyProvider preprovider, PropertyProvider... providers) {
708         return new SequentialPropertyEvaluator(preprovider, providers);
709     }
710
711     /**
712      * Creates a property provider similar to {@link #globalPropertyProvider}
713      * but which can use a different global properties file.
714      * If a specific file is pointed to, that is loaded; otherwise behaves like {@link #globalPropertyProvider}.
715      * Permits behavior similar to command-line Ant where not erroneous, but using the IDE's
716      * default global properties for projects which do not yet have this property registered.
717      * @param findUserPropertiesFile an evaluator in which to look up <code>propertyName</code>
718      * @param propertyName a property pointing to the global properties file (typically <code>"user.properties.file"</code>)
719      * @param basedir a base directory to use when resolving the path to the global properties file, if relative
720      * @return a provider of global properties
721      * @since org.netbeans.modules.ruby.modules.project.rake/1 1.14
722      */

723     public static PropertyProvider userPropertiesProvider(PropertyEvaluator findUserPropertiesFile, String JavaDoc propertyName, File JavaDoc basedir) {
724         return new UserPropertiesProvider(findUserPropertiesFile, propertyName, basedir);
725     }
726     private static final class UserPropertiesProvider extends FilterPropertyProvider implements PropertyChangeListener JavaDoc {
727         private final PropertyEvaluator findUserPropertiesFile;
728         private final String JavaDoc propertyName;
729         private final File JavaDoc basedir;
730         public UserPropertiesProvider(PropertyEvaluator findUserPropertiesFile, String JavaDoc propertyName, File JavaDoc 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 JavaDoc ev) {
738             if (propertyName.equals(ev.getPropertyName())) {
739                 setDelegate(computeDelegate(findUserPropertiesFile, propertyName, basedir));
740             }
741         }
742         private static PropertyProvider computeDelegate(PropertyEvaluator findUserPropertiesFile, String JavaDoc propertyName, File JavaDoc basedir) {
743             String JavaDoc userPropertiesFile = findUserPropertiesFile.getProperty(propertyName);
744             if (userPropertiesFile != null) {
745                 // Have some defined global properties file, so read it and listen to changes in it.
746
File JavaDoc f = PropertyUtils.resolveFile(basedir, userPropertiesFile);
747                 if (f.equals(PropertyUtils.userBuildProperties())) {
748                     // Just to share the cache.
749
return PropertyUtils.globalPropertyProvider();
750                 } else {
751                     return PropertyUtils.propertiesFilePropertyProvider(f);
752                 }
753             } else {
754                 // Use the in-IDE default.
755
return PropertyUtils.globalPropertyProvider();
756             }
757         }
758     }
759     
760     private static final class SequentialPropertyEvaluator implements PropertyEvaluator, ChangeListener JavaDoc {
761         
762         private final PropertyProvider preprovider;
763         private final PropertyProvider[] providers;
764         private Map JavaDoc<String JavaDoc,String JavaDoc> defs;
765         private final List JavaDoc<PropertyChangeListener JavaDoc> listeners = new ArrayList JavaDoc<PropertyChangeListener JavaDoc>();
766         
767         public SequentialPropertyEvaluator(final PropertyProvider preprovider, final PropertyProvider[] providers) {
768             this.preprovider = preprovider;
769             this.providers = providers;
770             // XXX defer until someone asks for them
771
defs = ProjectManager.mutex().readAccess(new Mutex.Action<Map JavaDoc<String JavaDoc,String JavaDoc>>() {
772                 public Map JavaDoc<String JavaDoc,String JavaDoc> run() {
773                     return compose(preprovider, providers);
774                 }
775             });
776             // XXX defer until someone is listening?
777
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 JavaDoc getProperty(final String JavaDoc prop) {
786             return ProjectManager.mutex().readAccess(new Mutex.Action<String JavaDoc>() {
787                 public String JavaDoc run() {
788                     if (defs == null) {
789                         return null;
790                     }
791                     return defs.get(prop);
792                 }
793             });
794         }
795         
796         public String JavaDoc evaluate(final String JavaDoc text) {
797             if (text == null) {
798                 throw new NullPointerException JavaDoc("Attempted to pass null to PropertyEvaluator.evaluate"); // NOI18N
799
}
800             return ProjectManager.mutex().readAccess(new Mutex.Action<String JavaDoc>() {
801                 public String JavaDoc run() {
802                     if (defs == null) {
803                         return null;
804                     }
805                     Union2<String JavaDoc,Set JavaDoc<String JavaDoc>> result = substitute(text, defs, Collections.<String JavaDoc>emptySet());
806                     assert result.hasFirst() : "Unexpected result " + result + " from " + text + " on " + defs;
807                     return result.first();
808                 }
809             });
810         }
811         
812         public Map JavaDoc<String JavaDoc,String JavaDoc> getProperties() {
813             return ProjectManager.mutex().readAccess(new Mutex.Action<Map JavaDoc<String JavaDoc,String JavaDoc>>() {
814                 public Map JavaDoc<String JavaDoc,String JavaDoc> run() {
815                     return defs;
816                 }
817             });
818         }
819         
820         public void addPropertyChangeListener(PropertyChangeListener JavaDoc listener) {
821             synchronized (listeners) {
822                 listeners.add(listener);
823             }
824         }
825         
826         public void removePropertyChangeListener(PropertyChangeListener JavaDoc listener) {
827             synchronized (listeners) {
828                 listeners.remove(listener);
829             }
830         }
831         
832         public void stateChanged(ChangeEvent JavaDoc e) {
833             assert ProjectManager.mutex().isReadAccess() || ProjectManager.mutex().isWriteAccess();
834             Map JavaDoc<String JavaDoc,String JavaDoc> newdefs = compose(preprovider, providers);
835             // compose() may return null upon circularity errors
836
Map JavaDoc<String JavaDoc,String JavaDoc> _defs = defs != null ? defs : Collections.<String JavaDoc,String JavaDoc>emptyMap();
837             Map JavaDoc<String JavaDoc,String JavaDoc> _newdefs = newdefs != null ? newdefs : Collections.<String JavaDoc,String JavaDoc>emptyMap();
838             if (!_defs.equals(_newdefs)) {
839                 Set JavaDoc<String JavaDoc> props = new HashSet JavaDoc<String JavaDoc>(_defs.keySet());
840                 props.addAll(_newdefs.keySet());
841                 List JavaDoc<PropertyChangeEvent JavaDoc> events = new LinkedList JavaDoc<PropertyChangeEvent JavaDoc>();
842                 for (String JavaDoc prop : props) {
843                     assert prop != null;
844                     String JavaDoc oldval = _defs.get(prop);
845                     String JavaDoc 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 JavaDoc(this, prop, oldval, newval));
854                 }
855                 assert !events.isEmpty();
856                 defs = newdefs;
857                 PropertyChangeListener JavaDoc[] _listeners;
858                 synchronized (listeners) {
859                     _listeners = listeners.toArray(new PropertyChangeListener JavaDoc[listeners.size()]);
860                 }
861                 for (PropertyChangeListener JavaDoc l : _listeners) {
862                     for (PropertyChangeEvent JavaDoc ev : events) {
863                         l.propertyChange(ev);
864                     }
865                 }
866             }
867         }
868         
869         private static Map JavaDoc<String JavaDoc,String JavaDoc> compose(PropertyProvider preprovider, PropertyProvider[] providers) {
870             assert ProjectManager.mutex().isReadAccess() || ProjectManager.mutex().isWriteAccess();
871             Map JavaDoc<String JavaDoc,String JavaDoc> predefs;
872             if (preprovider != null) {
873                 predefs = preprovider.getProperties();
874             } else {
875                 predefs = Collections.emptyMap();
876             }
877             List JavaDoc<Map JavaDoc<String JavaDoc,String JavaDoc>> defs = new ArrayList JavaDoc<Map JavaDoc<String JavaDoc,String JavaDoc>>(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