1 19 20 package org.netbeans.api.java.classpath; 21 22 import java.beans.PropertyChangeEvent ; 23 import java.beans.PropertyChangeListener ; 24 import java.beans.PropertyChangeSupport ; 25 import java.io.File ; 26 import java.io.IOException ; 27 import java.lang.ref.Reference ; 28 import java.lang.ref.SoftReference ; 29 import java.lang.ref.WeakReference ; 30 import java.net.URI ; 31 import java.net.URISyntaxException ; 32 import java.net.URL ; 33 import java.text.MessageFormat ; 34 import java.util.ArrayList ; 35 import java.util.Arrays ; 36 import java.util.Collection ; 37 import java.util.Collections ; 38 import java.util.HashSet ; 39 import java.util.LinkedHashSet ; 40 import java.util.List ; 41 import java.util.Map ; 42 import java.util.Set ; 43 import java.util.WeakHashMap ; 44 import org.netbeans.modules.java.classpath.ClassPathAccessor; 45 import org.netbeans.spi.java.classpath.ClassPathImplementation; 46 import org.netbeans.spi.java.classpath.ClassPathProvider; 47 import org.netbeans.spi.java.classpath.FilteringPathResourceImplementation; 48 import org.netbeans.spi.java.classpath.PathResourceImplementation; 49 import org.openide.ErrorManager; 50 import org.openide.filesystems.FileAttributeEvent; 51 import org.openide.filesystems.FileChangeListener; 52 import org.openide.filesystems.FileEvent; 53 import org.openide.filesystems.FileObject; 54 import org.openide.filesystems.FileRenameEvent; 55 import org.openide.filesystems.FileStateInvalidException; 56 import org.openide.filesystems.FileSystem; 57 import org.openide.filesystems.FileUtil; 58 import org.openide.filesystems.URLMapper; 59 import org.openide.util.Lookup; 60 import org.openide.util.Utilities; 61 62 83 public final class ClassPath { 84 85 static { 86 ClassPathAccessor.DEFAULT = new ClassPathAccessor() { 87 public ClassPath createClassPath(ClassPathImplementation spiClasspath) { 88 return new ClassPath(spiClasspath); 89 } 90 public ClassPathImplementation getClassPathImpl(ClassPath cp) { 91 return cp == null ? null : cp.impl; 92 } 93 }; 94 } 95 96 108 public static final String EXECUTE = "classpath/execute"; 109 110 114 @Deprecated 115 public static final String DEBUG = "classpath/debug"; 116 117 126 public static final String COMPILE = "classpath/compile"; 127 128 148 public static final String SOURCE = "classpath/source"; 149 150 166 public static final String BOOT = "classpath/boot"; 167 168 171 public static final String PROP_ROOTS = "roots"; 172 173 176 public static final String PROP_ENTRIES = "entries"; 177 178 183 public static final String PROP_INCLUDES = "includes"; 184 185 private static final ErrorManager ERR = ErrorManager.getDefault().getInstance(ClassPath.class.getName()); 186 187 private static final Lookup.Result<? extends ClassPathProvider> implementations = 188 Lookup.getDefault().lookupResult(ClassPathProvider.class); 189 190 private ClassPathImplementation impl; 191 private FileObject[] rootsCache; 192 197 private Map <FileObject,FilteringPathResourceImplementation> root2Filter = new WeakHashMap <FileObject,FilteringPathResourceImplementation>(); 198 private PropertyChangeListener pListener; 199 private RootsListener rootsListener; 200 private List <ClassPath.Entry> entriesCache; 201 private long invalidEntries; private long invalidRoots; 204 213 public FileObject[] getRoots() { 214 long current; 215 synchronized (this) { 216 if (rootsCache != null) { 217 return this.rootsCache; 218 } 219 current = this.invalidRoots; 220 } 221 List <ClassPath.Entry> entries = this.entries(); 222 FileObject[] ret; 223 synchronized (this) { 224 if (this.invalidRoots == current) { 225 if (rootsCache == null || rootsListener == null) { 226 attachRootsListener(); 227 this.rootsCache = createRoots (entries); 228 } 229 ret = this.rootsCache; 230 } 231 else { 232 ret = createRoots (entries); 233 } 234 } 235 assert ret != null; 236 return ret; 237 } 238 239 private FileObject[] createRoots (final List <ClassPath.Entry> entries) { 240 List <FileObject> l = new ArrayList <FileObject> (); 241 for (Entry entry : entries) { 242 RootsListener rootsListener = this.getRootsListener(); 243 if (rootsListener != null) { 244 rootsListener.addRoot (entry.getURL()); 245 } 246 FileObject fo = entry.getRoot(); 247 if (fo != null) { 248 l.add(fo); 249 root2Filter.put(fo, entry.filter); 250 } 251 } 252 return l.toArray (new FileObject[l.size()]); 253 } 254 255 262 public List <ClassPath.Entry> entries() { 263 long current; 264 synchronized (this) { 265 if (this.entriesCache != null) { 266 return this.entriesCache; 267 } 268 current = this.invalidEntries; 269 } 270 List <? extends PathResourceImplementation> resources = impl.getResources(); 271 List <ClassPath.Entry> result; 272 synchronized (this) { 273 if (this.invalidEntries == current) { 274 if (this.entriesCache == null) { 275 this.entriesCache = createEntries (resources); 276 } 277 result = this.entriesCache; 278 } 279 else { 280 result = createEntries (resources); 281 } 282 } 283 assert result != null; 284 return result; 285 } 286 287 private List <ClassPath.Entry> createEntries (final List <? extends PathResourceImplementation> resources) { 288 if (resources == null) { 291 return Collections.<ClassPath.Entry>emptyList(); 292 } 293 else { 294 List <ClassPath.Entry> cache = new ArrayList <ClassPath.Entry> (); 295 for (PathResourceImplementation pr : resources) { 296 for (URL root : pr.getRoots()) { 297 cache.add(new Entry(root, 298 pr instanceof FilteringPathResourceImplementation ? (FilteringPathResourceImplementation) pr : null)); 299 } 300 } 301 return Collections.unmodifiableList(cache); 302 } 303 } 304 305 private ClassPath (ClassPathImplementation impl) { 306 if (impl == null) 307 throw new IllegalArgumentException (); 308 this.impl = impl; 309 this.pListener = new SPIListener (); 310 this.impl.addPropertyChangeListener (this.pListener); 311 listenToPRIs(); 312 } 313 314 private void listenToPRIs() { 315 for (PathResourceImplementation pri : impl.getResources()) { 316 pri.removePropertyChangeListener(pListener); 317 pri.addPropertyChangeListener(pListener); 318 } 319 } 320 321 336 public final FileObject findResource(String resourceName) { 337 return findResourceImpl(getRoots(), new int[] {0}, parseResourceName(resourceName)); 338 } 339 340 350 public final List <FileObject> findAllResources(String resourceName) { 351 FileObject[] roots = getRoots(); 352 List <FileObject> l = new ArrayList <FileObject>(roots.length); 353 int[] idx = new int[] { 0 }; 354 String [] namec = parseResourceName(resourceName); 355 while (idx[0] < roots.length) { 356 FileObject f = findResourceImpl(roots, idx, namec); 357 if (f != null) 358 l.add(f); 359 } 360 return l; 361 } 362 363 375 public final String getResourceName(FileObject f) { 376 return getResourceName(f, '/', true); 377 } 378 379 389 public final String getResourceName(FileObject f, char dirSep, boolean includeExt) { 390 FileObject owner = findOwnerRoot(f); 391 if (owner == null) 392 return null; 393 String partName = FileUtil.getRelativePath(owner, f); 394 assert partName != null; 395 if (!includeExt) { 396 int index = partName.lastIndexOf('.'); 397 if (index >= 0 && index > partName.lastIndexOf('/')) { 398 partName = partName.substring (0, index); 399 } 400 } 401 if (dirSep!='/') { 402 partName = partName.replace('/',dirSep); 403 } 404 return partName; 405 } 406 407 418 public final FileObject findOwnerRoot(FileObject resource) { 419 FileObject[] roots = getRoots(); 420 Set <FileObject> rootsSet = new HashSet <FileObject>(Arrays.asList(roots)); 421 for (FileObject f = resource; f != null; f = f.getParent()) { 422 if (rootsSet.contains(f)) { 423 return f; 424 } 425 } 426 return null; 427 } 428 429 436 public final boolean contains(FileObject f) { 437 FileObject root = findOwnerRoot(f); 438 if (root == null) { 439 return false; 440 } 441 FilteringPathResourceImplementation filter = root2Filter.get(root); 442 if (filter == null) { 443 return true; 444 } 445 String path = FileUtil.getRelativePath(root, f); 446 assert path != null : "could not find " + f + " in " + root; 447 if (f.isFolder()) { 448 path += "/"; } 450 try { 451 return filter.includes(root.getURL(), path); 452 } catch (FileStateInvalidException x) { 453 throw new AssertionError (x); 454 } 455 } 456 457 466 public final boolean isResourceVisible(FileObject resource) { 467 String resourceName = getResourceName(resource); 468 if (resourceName == null) 469 return false; 470 return findResource(resourceName) == resource; 471 } 472 473 476 public final synchronized void addPropertyChangeListener(PropertyChangeListener l) { 477 attachRootsListener (); 478 propSupport.addPropertyChangeListener(l); 479 } 480 481 484 public final void removePropertyChangeListener(PropertyChangeListener l) { 485 propSupport.removePropertyChangeListener(l); 486 } 487 488 511 public static ClassPath getClassPath(FileObject f, String id) { 512 if (f == null) { 513 Thread.dumpStack(); 515 return null; 516 } 517 boolean log = ERR.isLoggable(ErrorManager.INFORMATIONAL); 518 if (log) ERR.log("CP.getClassPath: " + f + " of type " + id); 519 for (ClassPathProvider impl : implementations.allInstances()) { 520 ClassPath cp = impl.findClassPath(f, id); 521 if (cp != null) { 522 if (log) ERR.log(" got result " + cp + " from " + impl); 523 return cp; 524 } 525 } 526 return null; 527 } 528 529 536 final void firePropertyChange(String what, Object oldV, Object newV) { 537 propSupport.firePropertyChange(what, oldV, newV); 538 } 539 540 public String toString() { 541 return "ClassPath" + entries(); } 543 544 552 public final class Entry { 553 554 private URL url; 555 private FileObject root; 556 private IOException lastError; 557 private FilteringPathResourceImplementation filter; 558 559 565 public ClassPath getDefiningClassPath() { 566 return ClassPath.this; 567 } 568 569 575 public synchronized FileObject getRoot() { 576 if (root == null || !root.isValid()) { 577 root = URLMapper.findFileObject(this.url); 578 if (root == null) { 579 this.lastError = new IOException (MessageFormat.format("The package root {0} does not exist or can not be read.", 580 new Object [] {this.url})); 581 return null; 582 } 583 else if (root.isData()) { 584 throw new IllegalArgumentException ("Invalid ClassPath root: "+this.url+". The root must be a folder."); 585 } 586 } 587 return root; 588 } 589 590 594 public boolean isValid() { 595 FileObject root = getRoot(); 596 return root != null && root.isValid(); 597 } 598 599 604 public IOException getError() { 605 IOException error = this.lastError; 606 this.lastError = null; 607 return error; 608 } 609 610 617 public URL getURL () { 618 return this.url; 619 } 620 621 627 public boolean includes(String resource) { 628 return filter == null || filter.includes(url, resource); 629 } 630 631 638 public boolean includes(URL file) { 639 if (!file.toExternalForm().startsWith(url.toExternalForm())) { 640 throw new IllegalArgumentException (file + " not in " + url); 641 } 642 URI relative; 643 try { 644 relative = url.toURI().relativize(file.toURI()); 645 } catch (URISyntaxException x) { 646 throw new AssertionError (x); 647 } 648 assert !relative.isAbsolute() : "could not locate " + file + " in " + url; 649 return filter == null || filter.includes(url, relative.toString()); 650 } 651 652 659 public boolean includes(FileObject file) { 660 FileObject root = getRoot(); 661 if (root == null) { 662 throw new IllegalArgumentException ("no root in " + url); 663 } 664 String path = FileUtil.getRelativePath(root, file); 665 if (path == null) { 666 throw new IllegalArgumentException (file + " not in " + root); 667 } 668 if (file.isFolder()) { 669 path += "/"; } 671 return filter == null || filter.includes(url, path); 672 } 673 674 Entry(URL url, FilteringPathResourceImplementation filter) { 675 if (url == null) 676 throw new IllegalArgumentException (); 677 this.url = url; 678 this.filter = filter; 679 } 680 681 public String toString() { 682 return "Entry[" + url + "]"; } 684 } 685 686 688 private final PropertyChangeSupport propSupport = new PropertyChangeSupport (this); 689 690 691 695 private void attachRootsListener () { 696 if (this.rootsListener == null) { 697 assert this.rootsCache == null; 698 this.rootsListener = new RootsListener (this); 699 } 700 } 701 702 706 private static String [] parseResourceName(String name) { 707 Collection <String > parsed = new ArrayList <String >(name.length() / 4); 708 char[] chars = name.toCharArray(); 709 char ch; 710 int pos = 0; 711 int dotPos = -1; 712 int startPos = 0; 713 714 while (pos < chars.length) { 715 ch = chars[pos]; 716 switch (ch) { 717 case '.': 718 dotPos = pos; 719 break; 720 case '/': 721 if (dotPos != -1) { 723 parsed.add(name.substring(startPos, dotPos)); 724 parsed.add(name.substring(dotPos + 1, pos)); 725 } else { 726 parsed.add(name.substring(startPos, pos)); 727 parsed.add(null); 728 } 729 startPos = pos + 1; 731 dotPos = -1; 732 break; 733 } 734 pos++; 735 } 736 if (pos > startPos) { 738 if (dotPos != -1) { 739 parsed.add(name.substring(startPos, dotPos)); 740 parsed.add(name.substring(dotPos + 1, pos)); 741 } else { 742 parsed.add(name.substring(startPos, pos)); 743 parsed.add(null); 744 } 745 } 746 if ((parsed.size() % 2) != 0) { 747 System.err.println("parsed size is not even!!"); 748 System.err.println("input = " + name); 749 } 750 return parsed.toArray(new String [parsed.size()]); 751 } 752 753 758 private static FileObject findPath(FileObject parent, String [] nameParts) { 759 FileObject child; 760 761 for (int i = 0; i < nameParts.length && parent != null; i += 2, parent = child) { 762 child = parent.getFileObject(nameParts[i], nameParts[i + 1]); 763 } 764 return parent; 765 } 766 767 771 private FileObject findResourceImpl(FileObject[] roots, 772 int[] rootIndex, String [] nameComponents) { 773 int ridx; 774 FileObject f = null; 775 for (ridx = rootIndex[0]; ridx < roots.length && f == null; ridx++) { 776 f = findPath(roots[ridx], nameComponents); 777 FilteringPathResourceImplementation filter = root2Filter.get(roots[ridx]); 778 if (filter != null) { 779 try { 780 if (f != null) { 781 String path = FileUtil.getRelativePath(roots[ridx], f); 782 assert path != null; 783 if (f.isFolder()) { 784 path += "/"; } 786 if (!filter.includes(roots[ridx].getURL(), path)) { 787 f = null; 788 } 789 } 790 } catch (FileStateInvalidException x) { 791 throw new AssertionError (x); 792 } 793 } 794 } 795 rootIndex[0] = ridx; 796 return f; 797 } 798 799 private static final Reference <ClassLoader > EMPTY_REF = new SoftReference <ClassLoader >(null); 800 801 private Reference <ClassLoader > refClassLoader = EMPTY_REF; 802 803 synchronized void resetClassLoader(ClassLoader cl) { 804 if (refClassLoader.get() == cl) 805 refClassLoader = EMPTY_REF; 806 } 807 808 819 public final synchronized ClassLoader getClassLoader(boolean cache) { 820 ClassLoader o = refClassLoader.get(); 822 if (!cache || o == null) { 823 o = ClassLoaderSupport.create(this); 824 refClassLoader = new SoftReference <ClassLoader >(o); 825 } 826 return o; 827 } 828 829 830 private class SPIListener implements PropertyChangeListener { 831 private Object propIncludesPropagationId; 832 public void propertyChange(PropertyChangeEvent evt) { 833 String prop = evt.getPropertyName(); 834 if (ClassPathImplementation.PROP_RESOURCES.equals(prop) || PathResourceImplementation.PROP_ROOTS.equals(prop)) { 835 synchronized (ClassPath.this) { 836 if (rootsListener != null) { 837 rootsListener.removeAllRoots (); 838 } 839 entriesCache = null; 840 rootsCache = null; 841 invalidEntries++; 842 invalidRoots++; 843 } 844 firePropertyChange (PROP_ENTRIES,null,null); 845 firePropertyChange (PROP_ROOTS,null,null); 846 } else if (FilteringPathResourceImplementation.PROP_INCLUDES.equals(prop)) { 847 boolean fire; 848 synchronized (this) { 849 Object id = evt.getPropagationId(); 850 fire = propIncludesPropagationId == null || !propIncludesPropagationId.equals(id); 851 propIncludesPropagationId = id; 852 } 853 if (fire) { 854 firePropertyChange(PROP_INCLUDES, null, null); 855 } 856 } 857 if (ClassPathImplementation.PROP_RESOURCES.equals(prop)) { 858 listenToPRIs(); 859 } 860 } 861 } 862 863 864 private synchronized RootsListener getRootsListener () { 865 return this.rootsListener; 866 } 867 868 869 private static class RootsListener extends WeakReference <ClassPath> implements FileChangeListener, Runnable { 870 871 private boolean initialized; 872 private Set <String > roots; 873 874 private RootsListener (ClassPath owner) { 875 super (owner, Utilities.activeReferenceQueue()); 876 roots = new HashSet <String > (); 877 } 878 879 public void addRoot (URL url) { 880 if (!isInitialized()) { 881 FileSystem[] fss = getFileSystems (); 882 if (fss != null && fss.length > 0) { 883 for (FileSystem fs : fss) { 884 if (fs != null) { 885 fs.addFileChangeListener (this); 886 } 887 } 888 setInitialized(true); 889 } else { 890 ErrorManager.getDefault().log (ErrorManager.ERROR,"Can not find file system, not able to listen on changes."); } 892 } 893 if ("jar".equals(url.getProtocol())) { url = FileUtil.getArchiveFile(url); 895 } 896 String path = url.getPath(); 897 if (path.endsWith("/")) { path = path.substring(0,path.length()-1); 899 } 900 roots.add (path); 901 } 902 903 public void removeRoot (URL url) { 904 if ("jar".equals(url.getProtocol())) { url = FileUtil.getArchiveFile(url); 906 } 907 String path = url.getPath(); 908 if (path.endsWith("/")) { path = path.substring(0,path.length()-1); 910 } 911 roots.remove (path); 912 } 913 914 public void removeAllRoots () { 915 this.roots.clear(); 916 FileSystem[] fss = getFileSystems (); 917 for (FileSystem fs : fss) { 918 if (fs != null) { 919 fs.removeFileChangeListener (this); 920 } 921 } 922 initialized = false; } 924 925 public void fileFolderCreated(FileEvent fe) { 926 this.processEvent (fe); 927 } 928 929 public void fileDataCreated(FileEvent fe) { 930 this.processEvent (fe); 931 } 932 933 public void fileChanged(FileEvent fe) { 934 if (!isInitialized()) { 935 return; } 937 String path = getPath (fe.getFile()); 938 if (this.roots.contains(path)) { 939 ClassPath cp = get(); 940 if (cp != null) { 941 synchronized (cp) { 942 cp.rootsCache = null; 943 cp.invalidRoots++; 944 this.removeAllRoots(); } 946 cp.firePropertyChange(PROP_ROOTS,null,null); 947 } 948 } 949 } 950 951 public void fileDeleted(FileEvent fe) { 952 this.processEvent (fe); 953 } 954 955 public void fileRenamed(FileRenameEvent fe) { 956 this.processEvent (fe); 957 } 958 959 public void fileAttributeChanged(FileAttributeEvent fe) { 960 } 961 962 public void run() { 963 if (isInitialized()) { 964 for (FileSystem fs : getFileSystems()) { 965 if (fs != null) { 966 fs.removeFileChangeListener (this); 967 } 968 } 969 } 970 } 971 972 private void processEvent (FileEvent fe) { 973 if (!isInitialized()) { 974 return; } 976 String path = getPath (fe.getFile()); 977 if (path == null) 978 return; 979 ClassPath cp = get(); 980 if (cp == null) { 981 return; 982 } 983 boolean fire = false; 984 synchronized (cp) { 985 for (String rootPath : roots) { 986 if (rootPath.startsWith (path)) { 987 cp.rootsCache = null; 988 cp.invalidRoots++; 989 this.removeAllRoots(); fire = true; 991 break; 992 } 993 } 994 } 995 if (fire) { 996 cp.firePropertyChange(PROP_ROOTS,null,null); 997 } 998 } 999 1000 private static String getPath (FileObject fo) { 1001 if (fo == null) 1002 return null; 1003 try { 1004 URL url = fo.getURL(); 1005 String path = url.getPath(); 1006 if (path.endsWith("/")) { path=path.substring(0,path.length()-1); 1008 } 1009 return path; 1010 } catch (FileStateInvalidException e) { 1011 ErrorManager.getDefault().notify (e); 1012 return null; 1013 } 1014 } 1015 1016 private synchronized boolean isInitialized () { 1017 return this.initialized; 1018 } 1019 1020 private synchronized void setInitialized (boolean newValue) { 1021 this.initialized = newValue; 1022 } 1023 1024 1031 private static FileSystem[] fileSystems; 1032 1033 private static FileSystem[] getFileSystems() { 1034 if (fileSystems != null) { 1035 return fileSystems; 1036 } 1037 File [] roots = File.listRoots(); 1038 Set <FileSystem> allRoots = new LinkedHashSet <FileSystem>(); 1039 assert roots != null && roots.length > 0 : "Could not list file roots"; 1041 for (File root : roots) { 1042 FileObject random = FileUtil.toFileObject(root); 1043 if (random == null) continue; 1044 1045 FileSystem fs; 1046 try { 1047 fs = random.getFileSystem(); 1048 allRoots.add(fs); 1049 1050 1054 1055 if (fs != null) { 1056 break; 1057 } 1058 } catch (FileStateInvalidException e) { 1059 throw new AssertionError (e); 1060 } 1061 } 1062 FileSystem[] retVal = new FileSystem [allRoots.size()]; 1063 allRoots.toArray(retVal); 1064 assert retVal.length > 0 : "Could not get any filesystem"; 1066 return fileSystems = retVal; 1067 } 1068 } 1069 1070} 1071 | Popular Tags |