| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.netbeans.modules.java.freeform; |
| |
| import java.beans.PropertyChangeEvent; |
| import java.beans.PropertyChangeListener; |
| import java.beans.PropertyChangeSupport; |
| import java.io.File; |
| import java.net.URI; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.WeakHashMap; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.TimeUnit; |
| import java.util.function.Function; |
| import java.util.logging.Logger; |
| import org.netbeans.api.annotations.common.NonNull; |
| import org.netbeans.api.java.classpath.ClassPath; |
| import org.netbeans.api.java.classpath.GlobalPathRegistry; |
| import org.netbeans.api.java.classpath.JavaClassPathConstants; |
| import org.netbeans.api.java.platform.JavaPlatform; |
| import org.netbeans.api.java.platform.JavaPlatformManager; |
| import org.netbeans.api.java.project.JavaProjectConstants; |
| import org.netbeans.api.project.FileOwnerQuery; |
| import org.netbeans.api.project.Project; |
| import org.netbeans.api.project.ProjectManager; |
| import org.netbeans.api.project.ProjectUtils; |
| import org.netbeans.api.project.SourceGroup; |
| import org.netbeans.modules.ant.freeform.spi.support.Util; |
| import org.netbeans.modules.java.freeform.jdkselection.JdkConfiguration; |
| import org.netbeans.spi.java.classpath.ClassPathFactory; |
| import org.netbeans.spi.java.classpath.ClassPathImplementation; |
| import org.netbeans.spi.java.classpath.ClassPathProvider; |
| import org.netbeans.spi.java.classpath.FilteringPathResourceImplementation; |
| import org.netbeans.spi.java.classpath.PathResourceImplementation; |
| import org.netbeans.spi.java.classpath.support.ClassPathSupport; |
| import org.netbeans.spi.project.AuxiliaryConfiguration; |
| import org.netbeans.spi.project.support.ant.AntProjectEvent; |
| import org.netbeans.spi.project.support.ant.AntProjectHelper; |
| import org.netbeans.spi.project.support.ant.AntProjectListener; |
| import org.netbeans.spi.project.support.ant.PathMatcher; |
| import org.netbeans.spi.project.support.ant.PropertyEvaluator; |
| import org.netbeans.spi.project.support.ant.PropertyUtils; |
| import org.openide.ErrorManager; |
| import org.openide.filesystems.FileObject; |
| import org.openide.filesystems.FileUtil; |
| import org.openide.util.Mutex; |
| import org.openide.util.Utilities; |
| import org.openide.util.WeakListeners; |
| import org.openide.xml.XMLUtil; |
| import org.w3c.dom.Element; |
| |
| /** |
| * Handle classpaths for the freeform project. |
| * Keeps three caches: |
| * <ol> |
| * <li>Classpaths registered when the project is opened. The same are unregistered |
| * when it is closed again, regardless of what might have changed since. |
| * <li>A map from abstract compilation units (keyed by the literal text of the |
| * <code><package-root></code> elements) to implementations which do the |
| * actual listening and (re-)computation of roots. |
| * <li>A map from actual package roots to the matching classpath. |
| * </ol> |
| * The complexity here is needed because |
| * <ol> |
| * <li>It is necessary to always unregister the exact same set of ClassPath objects |
| * you initially registered (even if some have since become invalid, etc.). |
| * Ideally, adding or removing whole paths would dynamically register or unregister |
| * them (if the project is currently open); the current code does not do this. |
| * <li>A given ClassPath object must fire changes if its list of roots changes. |
| * <li>It is necessary to return the same ClassPath object for the same FileObject. |
| * </ol> |
| * @author Jesse Glick |
| */ |
| final class Classpaths implements ClassPathProvider, AntProjectListener, PropertyChangeListener { |
| |
| private static final ErrorManager err = ErrorManager.getDefault().getInstance(Classpaths.class.getName()); |
| |
| //for tests only: |
| static CountDownLatch TESTING_LATCH = null; |
| |
| private final AntProjectHelper helper; |
| private final PropertyEvaluator evaluator; |
| private final AuxiliaryConfiguration aux; |
| private final SourceForBinaryQueryImpl sfbqImpl; |
| |
| /** |
| * Map from classpath types to maps from package roots to classpaths. |
| */ |
| private final Map<String,Map<FileObject,ClassPath>> classpaths = new HashMap<String,Map<FileObject,ClassPath>>(); |
| |
| /** |
| * Map from classpath types to maps from lists of package root names to classpath impls. |
| */ |
| private final Map<String,Map<List<String>,MutableClassPathImplementation>> mutablePathImpls = new HashMap<String,Map<List<String>,MutableClassPathImplementation>>(); |
| |
| private final Map<MutableClassPathImplementation,ClassPath> mutableClassPathImpl2ClassPath = new HashMap<MutableClassPathImplementation,ClassPath>(); |
| |
| /** |
| * Map from classpath types to sets of classpaths we last registered to GlobalPathRegistry. |
| */ |
| //@GuardedBy(this) |
| private Map<String,Set<ClassPath>> registeredClasspaths = null; |
| |
| public Classpaths( |
| @NonNull final AntProjectHelper helper, |
| @NonNull final PropertyEvaluator evaluator, |
| @NonNull final AuxiliaryConfiguration aux, |
| @NonNull final SourceForBinaryQueryImpl sfbqImpl) { |
| this.helper = helper; |
| this.evaluator = evaluator; |
| this.aux = aux; |
| this.sfbqImpl = sfbqImpl; |
| helper.addAntProjectListener(this); |
| evaluator.addPropertyChangeListener(this); |
| } |
| |
| public ClassPath findClassPath(final FileObject file, final String type) { |
| //#77015: the findClassPathImpl method takes read access on ProjectManager.mutex |
| //taking the read access before the private lock to prevent deadlocks. |
| return ProjectManager.mutex().readAccess(new Mutex.Action<ClassPath>() { |
| public ClassPath run() { |
| return findClassPathImpl(file, type); |
| } |
| }); |
| } |
| |
| private synchronized ClassPath findClassPathImpl(FileObject file, String type) { |
| if (TESTING_LATCH != null) { |
| //only for tests: |
| TESTING_LATCH.countDown(); |
| try { |
| TESTING_LATCH.await(2, TimeUnit.SECONDS); |
| } catch (InterruptedException e) { |
| ErrorManager.getDefault().notify(e); |
| } |
| |
| classpaths.clear(); |
| } |
| |
| Map<FileObject,ClassPath> classpathsByType = classpaths.get(type); |
| if (classpathsByType == null) { |
| classpathsByType = new WeakHashMap<FileObject,ClassPath>(); |
| classpaths.put(type, classpathsByType); |
| } |
| // Check for cached value. |
| Iterator it = classpathsByType.entrySet().iterator(); |
| while (it.hasNext()) { |
| Map.Entry entry = (Map.Entry)it.next(); |
| FileObject root = (FileObject)entry.getKey(); |
| if (root == file || FileUtil.isParentOf(root, file)) { |
| // Already have it. |
| return (ClassPath)entry.getValue(); |
| } |
| } |
| // Need to create it. |
| Element java = aux.getConfigurationFragment(JavaProjectNature.EL_JAVA, JavaProjectNature.NS_JAVA_LASTEST, true); |
| if (java == null) { |
| return null; |
| } |
| List<Element> compilationUnits = XMLUtil.findSubElements(java); |
| it = compilationUnits.iterator(); |
| while (it.hasNext()) { |
| Element compilationUnitEl = (Element)it.next(); |
| assert compilationUnitEl.getLocalName().equals("compilation-unit") : compilationUnitEl; |
| List<FileObject> packageRoots = findPackageRoots(helper, evaluator, compilationUnitEl); |
| Iterator it2 = packageRoots.iterator(); |
| while (it2.hasNext()) { |
| FileObject root = (FileObject)it2.next(); |
| if (root == file || FileUtil.isParentOf(root, file)) { |
| // Got it. Compute classpath and cache it (for each root). |
| ClassPath cp = getPath(compilationUnitEl, packageRoots, type); |
| it2 = packageRoots.iterator(); |
| while (it2.hasNext()) { |
| FileObject root2 = (FileObject)it2.next(); |
| classpathsByType.put(root2, cp); |
| } |
| return cp; |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** All classpath types we handle. */ |
| private static final String[] TYPES = { |
| ClassPath.SOURCE, |
| ClassPath.BOOT, |
| ClassPath.EXECUTE, |
| ClassPath.COMPILE, |
| JavaClassPathConstants.PROCESSOR_PATH, |
| }; |
| |
| /** |
| * Called when project is opened. |
| * Tries to find all compilation units and calculate all the paths needed |
| * for each of them and register them all. |
| */ |
| public void opened() { |
| // #97366: taking read access to prevent deadlock, same trick as #77015 |
| ProjectManager.mutex().readAccess(new Mutex.Action<Void>() { |
| public Void run() { |
| openedImpl(); |
| return null; |
| } |
| }); |
| } |
| |
| private void openedImpl() { |
| //Threading: confinement within a local scope |
| Map<String,Set<ClassPath>> _registeredClasspaths; |
| synchronized (this) { |
| if (registeredClasspaths != null) { |
| //Threading: When already assigned the thread has to leave the method in |
| //the synchronized block to prevent a multiple registration of class paths. |
| return; |
| } |
| _registeredClasspaths = new HashMap<String,Set<ClassPath>>(); |
| for (String type : TYPES) { |
| _registeredClasspaths.put(type, new HashSet<ClassPath>()); |
| } |
| Element java = aux.getConfigurationFragment(JavaProjectNature.EL_JAVA, JavaProjectNature.NS_JAVA_LASTEST, true); |
| if (java == null) { |
| return; |
| } |
| for (Element compilationUnitEl : XMLUtil.findSubElements(java)) { |
| assert compilationUnitEl.getLocalName().equals("compilation-unit") : compilationUnitEl; |
| // For each compilation unit, find the package roots first. |
| List<FileObject> packageRoots = findPackageRoots(helper, evaluator, compilationUnitEl); |
| for (String type : TYPES) { |
| // Then for each type, collect the classpath (creating it as needed). |
| Map<FileObject,ClassPath> classpathsByType = classpaths.get(type); |
| if (classpathsByType == null) { |
| classpathsByType = new WeakHashMap<FileObject,ClassPath>(); |
| classpaths.put(type, classpathsByType); |
| } |
| Set<ClassPath> registeredClasspathsOfType = _registeredClasspaths.get(type); |
| assert registeredClasspathsOfType != null; |
| // Check if there is already a ClassPath registered to one of these roots. |
| ClassPath cp = null; |
| for (FileObject root : packageRoots) { |
| cp = classpathsByType.get(root); |
| if (cp != null) { |
| break; |
| } |
| } |
| if (cp == null) { |
| // Nope. Calculate and register it now. |
| cp = getPath(compilationUnitEl, packageRoots, type); |
| for (FileObject root : packageRoots) { |
| classpathsByType.put(root, cp); |
| } |
| } |
| assert cp != null; |
| registeredClasspathsOfType.add(cp); |
| } |
| } |
| if (err.isLoggable(ErrorManager.INFORMATIONAL)) { |
| err.log("classpaths for " + helper.getProjectDirectory() + ": " + classpaths); |
| } |
| // Don't do this before it is calculated, or a runtime error above might corrupt state: |
| this.registeredClasspaths = _registeredClasspaths; |
| } //End synchronized (this), POST: _registeredClasspaths != null |
| assert _registeredClasspaths != null; |
| // Register all of the classpaths we found. |
| GlobalPathRegistry gpr = GlobalPathRegistry.getDefault(); |
| for (String type : TYPES) { |
| Set<ClassPath> registeredClasspathsOfType = _registeredClasspaths.get(type); |
| gpr.register(type, registeredClasspathsOfType.toArray(new ClassPath[registeredClasspathsOfType.size()])); |
| } |
| } |
| |
| //XXX: Threading: calls non private code under lock |
| private synchronized void registerNewClasspath(String type, ClassPath cp) { |
| if (registeredClasspaths == null) { |
| return; |
| } |
| Set<ClassPath> s = registeredClasspaths.get(type); |
| s.add(cp); |
| GlobalPathRegistry.getDefault().register(type, new ClassPath[] {cp}); |
| } |
| |
| /** |
| * Called when project is closed. |
| * Unregisters any previously registered classpaths. |
| */ |
| public void closed() { |
| Map<String,Set<ClassPath>> toUnregister; |
| synchronized (this) { |
| if (registeredClasspaths == null) { |
| return; |
| } |
| toUnregister = new HashMap<String,Set<ClassPath>>(registeredClasspaths); |
| registeredClasspaths = null; |
| } |
| GlobalPathRegistry gpr = GlobalPathRegistry.getDefault(); |
| for (String type : TYPES) { |
| Set<ClassPath> registeredClasspathsOfType = toUnregister.get(type); |
| gpr.unregister(type, registeredClasspathsOfType.toArray(new ClassPath[registeredClasspathsOfType.size()])); |
| } |
| } |
| |
| static List<String> findPackageRootNames(Element compilationUnitEl) { |
| List<String> names = new ArrayList<String>(); |
| Iterator it = XMLUtil.findSubElements(compilationUnitEl).iterator(); |
| while (it.hasNext()) { |
| Element e = (Element) it.next(); |
| if (!e.getLocalName().equals("package-root")) { // NOI18N |
| continue; |
| } |
| String location = XMLUtil.findText(e); |
| names.add(location); |
| } |
| return names; |
| } |
| |
| static Map<String,FileObject> findPackageRootsByName(AntProjectHelper helper, PropertyEvaluator evaluator, List<String> packageRootNames) { |
| Map<String,FileObject> roots = new LinkedHashMap<String,FileObject>(); |
| Iterator it = packageRootNames.iterator(); |
| while (it.hasNext()) { |
| String location = (String) it.next(); |
| String locationEval = evaluator.evaluate(location); |
| if (locationEval != null) { |
| File locationFile = helper.resolveFile(locationEval); |
| FileObject locationFileObject = FileUtil.toFileObject(locationFile); |
| if (locationFileObject != null) { |
| if (FileUtil.isArchiveFile(locationFileObject)) { |
| locationFileObject = FileUtil.getArchiveRoot(locationFileObject); |
| } |
| roots.put(location, locationFileObject); |
| } |
| } |
| } |
| return roots; |
| } |
| |
| private static List<FileObject> findPackageRoots(AntProjectHelper helper, PropertyEvaluator evaluator, List<String> packageRootNames) { |
| return new ArrayList<FileObject>(findPackageRootsByName(helper, evaluator, packageRootNames).values()); |
| } |
| |
| public static List<FileObject> findPackageRoots(AntProjectHelper helper, PropertyEvaluator evaluator, Element compilationUnitEl) { |
| return findPackageRoots(helper, evaluator, findPackageRootNames(compilationUnitEl)); |
| } |
| |
| private ClassPath getPath(Element compilationUnitEl, List<FileObject> packageRoots, String type) { |
| if (type.equals(ClassPath.SOURCE) || type.equals(ClassPath.COMPILE) || |
| type.equals(ClassPath.EXECUTE) || type.equals(ClassPath.BOOT) || |
| type.equals(JavaClassPathConstants.PROCESSOR_PATH)) { |
| List<String> packageRootNames = findPackageRootNames(compilationUnitEl); |
| Map<List<String>,MutableClassPathImplementation> mutablePathImplsByType; |
| synchronized (this) { |
| mutablePathImplsByType = mutablePathImpls.get(type); |
| if (mutablePathImplsByType == null) { |
| mutablePathImplsByType = new HashMap<List<String>,MutableClassPathImplementation>(); |
| mutablePathImpls.put(type, mutablePathImplsByType); |
| } |
| MutableClassPathImplementation impl = mutablePathImplsByType.get(packageRootNames); |
| if (impl == null) { |
| // XXX will it ever not be null? |
| impl = new MutableClassPathImplementation(packageRootNames, type, compilationUnitEl); |
| mutablePathImplsByType.put(packageRootNames, impl); |
| } |
| ClassPath cp = mutableClassPathImpl2ClassPath.get(impl); |
| if (cp == null) { |
| cp = ClassPathFactory.createClassPath(impl); |
| mutableClassPathImpl2ClassPath.put(impl, cp); |
| registerNewClasspath(type, cp); |
| } |
| return cp; |
| } |
| } else { |
| // Unknown. |
| return null; |
| } |
| } |
| |
| private List<URL> createSourcePath(List<String> packageRootNames) { |
| List<URL> roots = new ArrayList<URL>(packageRootNames.size()); |
| for (String location : packageRootNames) { |
| String locationEval = evaluator.evaluate(location); |
| if (locationEval != null) { |
| roots.add(createClasspathEntry(locationEval)); |
| } |
| } |
| return roots; |
| } |
| |
| private List<URL> createCompileClasspath(Element compilationUnitEl) { |
| for (Element e : XMLUtil.findSubElements(compilationUnitEl)) { |
| if (e.getLocalName().equals("classpath") && e.getAttribute("mode").equals("compile")) { // NOI18N |
| return createClasspath(e, new RemoveSources(helper, sfbqImpl)); |
| } |
| } |
| // None specified; assume it is empty. |
| return Collections.emptyList(); |
| } |
| |
| /** |
| * Create a classpath from a <classpath> element. |
| */ |
| private List<URL> createClasspath( |
| final Element classpathEl, |
| final Function<URL,Collection<URL>> translate) { |
| String cp = XMLUtil.findText(classpathEl); |
| if (cp == null) { |
| cp = ""; |
| } |
| String cpEval = evaluator.evaluate(cp); |
| if (cpEval == null) { |
| return Collections.emptyList(); |
| } |
| final String[] path = PropertyUtils.tokenizePath(cpEval); |
| final List<URL> res = new ArrayList<>(); |
| for (String pathElement : path) { |
| res.addAll(translate.apply(createClasspathEntry(pathElement))); |
| } |
| return res; |
| } |
| |
| private URL createClasspathEntry(String text) { |
| File entryFile = helper.resolveFile(text); |
| return FileUtil.urlForArchiveOrDir(entryFile); |
| } |
| |
| private List<URL> createExecuteClasspath(List<String> packageRoots, Element compilationUnitEl) { |
| for (Element e : XMLUtil.findSubElements(compilationUnitEl)) { |
| if (e.getLocalName().equals("classpath") && e.getAttribute("mode").equals("execute")) { // NOI18N |
| return createClasspath(e, new RemoveSources(helper, sfbqImpl)); |
| } |
| } |
| // None specified; assume it is same as compile classpath plus (cf. #49113) <built-to> dirs/JARs |
| // if there are any (else include the source dir(s) as a fallback for the I18N wizard to work). |
| Set<URL> urls = new LinkedHashSet<>(); |
| urls.addAll(createCompileClasspath(compilationUnitEl)); |
| final Project prj = FileOwnerQuery.getOwner(helper.getProjectDirectory()); |
| if (prj != null) { |
| for (URL src : createSourcePath(packageRoots)) { |
| urls.addAll(sfbqImpl.findBinaryRoots(src)); |
| } |
| } |
| return new ArrayList<>(urls); |
| } |
| |
| private List<URL> createProcessorClasspath(Element compilationUnitEl) { |
| final Element ap = XMLUtil.findElement(compilationUnitEl, AnnotationProcessingQueryImpl.EL_ANNOTATION_PROCESSING, JavaProjectNature.NS_JAVA_LASTEST); |
| if (ap != null) { |
| final Element path = XMLUtil.findElement(ap, AnnotationProcessingQueryImpl.EL_PROCESSOR_PATH, JavaProjectNature.NS_JAVA_LASTEST); |
| if (path != null) { |
| return createClasspath(path, new RemoveSources(helper, sfbqImpl)); |
| } |
| } |
| // None specified; assume it is the same as the compile classpath. |
| return createCompileClasspath(compilationUnitEl); |
| } |
| |
| private List<URL> createBootClasspath(Element compilationUnitEl) { |
| for (Element e : XMLUtil.findSubElements(compilationUnitEl)) { |
| if (e.getLocalName().equals("classpath") && e.getAttribute("mode").equals("boot")) { // NOI18N |
| return createClasspath(e, new Function<URL,Collection<URL>>() { |
| @Override |
| public Collection<URL> apply(URL p) { |
| return Collections.singleton(p); |
| } |
| }); |
| } |
| } |
| // None specified; |
| // First check whether user has configured a specific JDK. |
| JavaPlatform platform = new JdkConfiguration(null, helper, evaluator).getSelectedPlatform(); |
| if (platform == null) { |
| // Nope; Use default one |
| JavaPlatformManager jpm = JavaPlatformManager.getDefault(); |
| platform = jpm.getDefaultPlatform(); // fallback |
| // #126216: source level guessing logic removed |
| } |
| if (platform != null) { |
| // XXX this is not ideal; should try to reuse the ClassPath as is? |
| // The current impl will not listen to changes in the platform classpath correctly. |
| List<ClassPath.Entry> entries = platform.getBootstrapLibraries().entries(); |
| List<URL> urls = new ArrayList<URL>(entries.size()); |
| for (ClassPath.Entry entry : entries) { |
| urls.add(entry.getURL()); |
| } |
| return urls; |
| } else { |
| assert false : "JavaPlatformManager has no default platform"; |
| return Collections.emptyList(); |
| } |
| } |
| |
| public void configurationXmlChanged(AntProjectEvent ev) { |
| pathsChanged(); |
| } |
| |
| public void propertiesChanged(AntProjectEvent ev) { |
| pathsChanged(); // in case it is nbjdk.properties |
| } |
| |
| public void propertyChange(PropertyChangeEvent evt) { |
| pathsChanged(); |
| } |
| |
| private void pathsChanged() { |
| synchronized (this) { |
| classpaths.clear(); |
| for (Map<List<String>,MutableClassPathImplementation> m : mutablePathImpls.values()) { |
| for (MutableClassPathImplementation impl : m.values()) { |
| impl.change(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Representation of one path. |
| * Listens to changes in project.xml and/or evaluator and responds. |
| */ |
| private final class MutableClassPathImplementation implements ClassPathImplementation { |
| |
| private final List<String> packageRootNames; |
| private final String type; |
| private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); |
| private List<URL> roots; // should always be non-null |
| private List<PathResourceImplementation> resources; |
| |
| public MutableClassPathImplementation(List<String> packageRootNames, String type, Element initialCompilationUnit) { |
| this.packageRootNames = packageRootNames; |
| this.type = type; |
| initRoots(initialCompilationUnit); |
| } |
| |
| private Element findCompilationUnit() { |
| Element java = aux.getConfigurationFragment(JavaProjectNature.EL_JAVA, JavaProjectNature.NS_JAVA_LASTEST, true); |
| if (java == null) { |
| return null; |
| } |
| List<Element> compilationUnits = XMLUtil.findSubElements(java); |
| Iterator it = compilationUnits.iterator(); |
| while (it.hasNext()) { |
| Element compilationUnitEl = (Element)it.next(); |
| assert compilationUnitEl.getLocalName().equals("compilation-unit") : compilationUnitEl; |
| if (packageRootNames.equals(findPackageRootNames(compilationUnitEl))) { |
| // Found a matching compilation unit. |
| return compilationUnitEl; |
| } |
| } |
| // Did not find it. |
| return null; |
| } |
| |
| /** |
| * Initialize list of URL roots. |
| */ |
| private boolean initRoots(Element compilationUnitEl) { |
| List<URL> oldRoots = roots; |
| if (compilationUnitEl != null) { |
| if (type.equals(ClassPath.SOURCE)) { |
| roots = createSourcePath(packageRootNames); |
| } else if (type.equals(ClassPath.COMPILE)) { |
| roots = createCompileClasspath(compilationUnitEl); |
| } else if (type.equals(ClassPath.EXECUTE)) { |
| roots = createExecuteClasspath(packageRootNames, compilationUnitEl); |
| } else if (type.equals(JavaClassPathConstants.PROCESSOR_PATH)) { |
| roots = createProcessorClasspath(compilationUnitEl); |
| } else { |
| assert type.equals(ClassPath.BOOT) : type; |
| roots = createBootClasspath(compilationUnitEl); |
| } |
| } else { |
| // Dead. |
| roots = Collections.emptyList(); |
| } |
| assert roots != null; |
| if (!roots.equals(oldRoots)) { |
| resources = new ArrayList<PathResourceImplementation>(roots.size()); |
| for (URL root : roots) { |
| if (root != null) { |
| assert root.toExternalForm().endsWith("/") : "Had bogus roots " + roots + " for type " + type + " in " + helper.getProjectDirectory(); |
| PathResourceImplementation pri; |
| if (type.equals(ClassPath.SOURCE)) { |
| pri = new SourcePRI(root); |
| } else { |
| pri = ClassPathSupport.createResource(root); |
| } |
| resources.add(pri); |
| } |
| } |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| public List<PathResourceImplementation> getResources() { |
| assert resources != null; |
| return resources; |
| } |
| |
| /** |
| * Notify impl of a possible change in data. |
| */ |
| public void change() { |
| if (initRoots(findCompilationUnit())) { |
| if (err.isLoggable(ErrorManager.INFORMATIONAL)) { |
| err.log("MutableClassPathImplementation.change: packageRootNames=" + packageRootNames + " type=" + type + " roots=" + roots); |
| } |
| pcs.firePropertyChange(ClassPathImplementation.PROP_RESOURCES, null, null); |
| } |
| } |
| |
| public void addPropertyChangeListener(PropertyChangeListener listener) { |
| pcs.addPropertyChangeListener(listener); |
| } |
| |
| public void removePropertyChangeListener(PropertyChangeListener listener) { |
| pcs.removePropertyChangeListener(listener); |
| } |
| |
| } |
| |
| private final class SourcePRI implements FilteringPathResourceImplementation, PropertyChangeListener, AntProjectListener { |
| private final URL root; |
| private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); |
| private PathMatcher matcher; |
| private String includes, excludes; |
| public SourcePRI(URL root) { |
| this.root = root; |
| helper.addAntProjectListener(WeakListeners.create(AntProjectListener.class, this, helper)); |
| evaluator.addPropertyChangeListener(WeakListeners.propertyChange(this, evaluator)); |
| computeMatcher(); |
| } |
| private boolean computeMatcher() { |
| String incl = null; |
| String excl = null; |
| URI rootURI = URI.create(root.toExternalForm()); |
| // Annoying to duplicate logic from FreeformSources. |
| // But using SourceGroup.contains is not an option since that requires FileObject creation. |
| File rootFolder; |
| try { |
| rootFolder = Utilities.toFile(rootURI); |
| } catch (IllegalArgumentException x) { |
| Logger.getLogger(Classpaths.class.getName()).warning("Illegal source root: " + rootURI); |
| rootFolder = null; |
| } |
| Element genldata = Util.getPrimaryConfigurationData(helper); |
| Element foldersE = XMLUtil.findElement(genldata, "folders", Util.NAMESPACE); // NOI18N |
| if (foldersE != null) { |
| for (Element folderE : XMLUtil.findSubElements(foldersE)) { |
| if (folderE.getLocalName().equals("source-folder")) { |
| Element typeE = XMLUtil.findElement(folderE, "type", Util.NAMESPACE); // NOI18N |
| if (typeE != null) { |
| String type = XMLUtil.findText(typeE); |
| if (type.equals(JavaProjectConstants.SOURCES_TYPE_JAVA)) { |
| Element locationE = XMLUtil.findElement(folderE, "location", Util.NAMESPACE); // NOI18N |
| String location = evaluator.evaluate(XMLUtil.findText(locationE)); |
| if (location != null && helper.resolveFile(location).equals(rootFolder)) { |
| Element includesE = XMLUtil.findElement(folderE, "includes", Util.NAMESPACE); // NOI18N |
| if (includesE != null) { |
| incl = evaluator.evaluate(XMLUtil.findText(includesE)); |
| if (incl != null && incl.matches("\\$\\{[^}]+\\}")) { // NOI18N |
| // Clearly intended to mean "include everything". |
| incl = null; |
| } |
| } |
| Element excludesE = XMLUtil.findElement(folderE, "excludes", Util.NAMESPACE); // NOI18N |
| if (excludesE != null) { |
| excl = evaluator.evaluate(XMLUtil.findText(excludesE)); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| if (!Utilities.compareObjects(incl, includes) || !Utilities.compareObjects(excl, excludes)) { |
| includes = incl; |
| excludes = excl; |
| matcher = new PathMatcher(incl, excl, rootFolder); |
| return true; |
| } else { |
| if (matcher == null) { |
| matcher = new PathMatcher(incl, excl, rootFolder); |
| } |
| return false; |
| } |
| } |
| public URL[] getRoots() { |
| return new URL[] {root}; |
| } |
| public boolean includes(URL root, String resource) { |
| return matcher.matches(resource, true); |
| } |
| public void addPropertyChangeListener(PropertyChangeListener listener) { |
| pcs.addPropertyChangeListener(listener); |
| } |
| public void removePropertyChangeListener(PropertyChangeListener listener) { |
| pcs.removePropertyChangeListener(listener); |
| } |
| public ClassPathImplementation getContent() { |
| return null; |
| } |
| public void propertyChange(PropertyChangeEvent ev) { |
| change(ev); |
| } |
| public void configurationXmlChanged(AntProjectEvent ev) { |
| change(ev); |
| } |
| public void propertiesChanged(AntProjectEvent ev) {} |
| private void change(Object propid) { |
| if (computeMatcher()) { |
| PropertyChangeEvent ev = new PropertyChangeEvent(this, FilteringPathResourceImplementation.PROP_INCLUDES, null, null); |
| ev.setPropagationId(propid); |
| pcs.firePropertyChange(ev); |
| } |
| } |
| } |
| |
| private static final class RemoveSources implements Function<URL,Collection<URL>> { |
| private final Set<URL> sourceRoots; |
| private final SourceForBinaryQueryImpl sfbqImpl; |
| |
| RemoveSources( |
| @NonNull final AntProjectHelper helper, |
| @NonNull final SourceForBinaryQueryImpl sfbqImpl) { |
| this.sourceRoots = new HashSet<>(); |
| this.sfbqImpl = sfbqImpl; |
| final Project prj = FileOwnerQuery.getOwner(helper.getProjectDirectory()); |
| if (prj != null) { |
| for (SourceGroup sg : ProjectUtils.getSources(prj).getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA)) { |
| final FileObject root = sg.getRootFolder(); |
| if (root != null) { |
| sourceRoots.add(root.toURL()); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public Collection<URL> apply(URL p) { |
| Collection<URL> res = Collections.emptySet(); |
| if (sourceRoots.contains(p)) { |
| res = sfbqImpl.findBinaryRoots(p); |
| } |
| if (res.isEmpty()) { |
| res = Collections.singletonList(p); |
| } |
| return res; |
| } |
| } |
| } |