| /******************************************************************************* |
| * Copyright (c) 2005, 2007 BEA Systems, Inc. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * wharley@bea.com - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.jdt.apt.core.internal; |
| |
| import java.io.File; |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.net.URLClassLoader; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.Map.Entry; |
| |
| import org.eclipse.core.resources.IMarker; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.IResourceChangeEvent; |
| import org.eclipse.core.resources.IResourceChangeListener; |
| import org.eclipse.core.resources.IResourceDelta; |
| import org.eclipse.core.resources.IResourceDeltaVisitor; |
| import org.eclipse.core.resources.IWorkspaceRoot; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.jdt.apt.core.internal.util.FactoryContainer; |
| import org.eclipse.jdt.apt.core.internal.util.FactoryPath; |
| import org.eclipse.jdt.apt.core.internal.util.FactoryPathUtil; |
| import org.eclipse.jdt.apt.core.internal.util.FactoryContainer.FactoryType; |
| import org.eclipse.jdt.apt.core.internal.util.FactoryPath.Attributes; |
| import org.eclipse.jdt.core.IJavaProject; |
| import org.eclipse.jdt.core.JavaCore; |
| |
| import com.sun.mirror.apt.AnnotationProcessorFactory; |
| |
| /** |
| * Stores annotation processor factories, and handles mapping from projects |
| * to them. This is a singleton object, created by the first call to getLoader(). |
| * <p> |
| * Factories contained in plugins are loaded at APT initialization time. |
| * Factories contained in jar files are loaded for a given project the first time |
| * getFactoriesForProject() is called, and cached thereafter. Factories are loaded |
| * from one of two custom classloaders depending on whether the factory container |
| * is to be run in batch processing mode or normal (iterative) mode; the batch |
| * classloader for a project is parented by the iterative classloader for that |
| * project. |
| * <p> |
| * <strong>Processor Factories</strong> |
| * <p> |
| * This class is compilable against a Java 1.5 runtime. However, it includes |
| * support for discovering and loading both Java 5 and Java 6 annotation |
| * processors. Java 5 annotation processors include a factory object, the |
| * AnnotationProcessorFactory, so for Java 5 we simply cache the factory; the |
| * client code uses the factory to produce an actual AnnotationProcessor. |
| * Java 6 processors do not have a separate factory, so we cache the Class |
| * object of the processor implementation and use it to produce new instances. |
| * This is wrapped within an IServiceFactory interface, for flexibility in |
| * loading from various sources. The actual Processor class does not exist |
| * in the Java 1.5 runtime, so all access to it must be done via reflection. |
| * <p> |
| * <strong>Caches</strong> |
| * <p> |
| * Factory classes and iterative-mode classloaders are cached for each project, |
| * the first time that the classes are needed (e.g., during a build or reconcile). |
| * The cache is cleared when the project's factory path changes, when a resource |
| * listed on the factory path is changed, or when the project is deleted. |
| * If a project contains batch-mode processors, the cache is also cleared at |
| * the beginning of every full build (batch-mode processors do not run at all |
| * during reconcile). |
| * <p> |
| * If a project's factory path includes containers which cannot be located on |
| * disk, problem markers will be added to the project. This validation process |
| * occurs when the cache for a project is first loaded, and whenever the cache |
| * is invalidated. We do not validate the workspace-level factory path as such; |
| * it is only used to construct a project-specific factory path for projects |
| * that do not have their own factory path. |
| * <p> |
| * In order to efficiently perform re-validation when resources change, we keep |
| * track of which projects' factory paths mention which containers. This is |
| * stored as a map from canonicalized resource path to project. Entries are |
| * created and updated during factory path validation, and removed upon project |
| * deletion. |
| * <p> |
| * Resource changes are presented as delta trees which may contain more than |
| * one change. When a change arrives, we build up a list of all potentially |
| * affected projects, and then perform re-validation after the list is complete. |
| * That way we avoid redundant validations if a project is affected by more |
| * than one change. |
| * <p> |
| * Note that markers and factory classes have different lifetimes: they are |
| * discarded at the same time (when something changes), but markers are recreated |
| * immediately (as a result of validation) while factory classes are not reloaded |
| * until the next time a build or reconcile occurs. |
| * <p> |
| * <strong>Synchronization</strong> |
| * <p> |
| * The loader is often accessed on multiple threads, e.g., a build thread, a |
| * reconcile thread, and a change notification thread all at once. It is |
| * important to maintain consistency across the various cache objects. |
| */ |
| public class AnnotationProcessorFactoryLoader { |
| |
| /** Loader instance -- holds all workspace and project data */ |
| private static AnnotationProcessorFactoryLoader LOADER; |
| |
| private static final String JAR_EXTENSION = "jar"; //$NON-NLS-1$ |
| |
| // Caches the factory classes associated with each project. |
| // See class comments for lifecycle of items in this cache. |
| private final Map<IJavaProject, Map<AnnotationProcessorFactory, FactoryPath.Attributes>> _project2Java5Factories = |
| new HashMap<IJavaProject, Map<AnnotationProcessorFactory, FactoryPath.Attributes>>(); |
| |
| private final Map<IJavaProject, Map<IServiceFactory, FactoryPath.Attributes>> _project2Java6Factories = |
| new HashMap<IJavaProject, Map<IServiceFactory, FactoryPath.Attributes>>(); |
| |
| // Caches the iterative classloaders so that iterative processors |
| // are not reloaded on every batch build, unlike batch processors |
| // which are. |
| // See class comments for lifecycle of items in this cache. |
| private final Map<IJavaProject, ClassLoader> _iterativeLoaders = |
| new HashMap<IJavaProject, ClassLoader>(); |
| |
| private final Map<IJavaProject,ClassLoader> _batchLoaders = |
| new HashMap<IJavaProject,ClassLoader>(); |
| |
| // Caches information about which resources affect which projects' |
| // factory paths. |
| // See class comments for lifecycle of items in this cache. |
| private final Map<String, Set<IJavaProject>> _container2Project = |
| new HashMap<String, Set<IJavaProject>>(); |
| |
| |
| /** |
| * Listen for changes that would affect the factory caches or |
| * build markers. |
| */ |
| private class ResourceListener implements IResourceChangeListener { |
| |
| public void resourceChanged(IResourceChangeEvent event) { |
| Map<IJavaProject, LoadFailureHandler> failureHandlers = new HashMap<IJavaProject, LoadFailureHandler>(); |
| synchronized (AnnotationProcessorFactoryLoader.this) { |
| switch (event.getType()) { |
| |
| // Project deletion |
| case (IResourceChangeEvent.PRE_DELETE) : |
| IResource project = event.getResource(); |
| if (project != null && project instanceof IProject) { |
| IJavaProject jproj = JavaCore.create((IProject)project); |
| if (jproj != null) { |
| uncacheProject(jproj); |
| } |
| } |
| break; |
| |
| // Changes to jar files or .factorypath files |
| case (IResourceChangeEvent.PRE_BUILD) : |
| IResourceDelta rootDelta = event.getDelta(); |
| FactoryPathDeltaVisitor visitor = new FactoryPathDeltaVisitor(); |
| try { |
| rootDelta.accept(visitor); |
| } catch (CoreException e) { |
| AptPlugin.log(e, "Unable to determine whether resource change affects annotation processor factory path"); //$NON-NLS-1$ |
| } |
| Set<IJavaProject> affected = visitor.getAffectedProjects(); |
| if (affected != null) { |
| processChanges(affected, failureHandlers); |
| } |
| break; |
| |
| } |
| } |
| for (LoadFailureHandler handler : failureHandlers.values()) { |
| handler.reportFailureMarkers(); |
| } |
| } |
| |
| } |
| |
| /** |
| * Walk the delta tree to see if there have been changes to |
| * a factory path or the containers it references. If so, |
| * re-validate the affected projects' factory paths. |
| */ |
| private class FactoryPathDeltaVisitor implements IResourceDeltaVisitor { |
| |
| // List of projects affected by this change. |
| // Lazy construction because we assume most changes won't affect any projects. |
| private Set<IJavaProject> _affected = null; |
| |
| private void addAffected(Set<IJavaProject> projects) { |
| if (_affected == null) { |
| _affected = new HashSet<IJavaProject>(5); |
| } |
| _affected.addAll(projects); |
| } |
| |
| /** |
| * Get the list of IJavaProject affected by the delta we visited. |
| * Not valid until done visiting. |
| * @return null if there were no affected projects, or a non-empty |
| * set of IJavaProject otherwise. |
| */ |
| public Set<IJavaProject> getAffectedProjects() { |
| return _affected; |
| } |
| |
| /** |
| * @return true to visit children |
| */ |
| public boolean visit(IResourceDelta delta) { |
| switch (delta.getKind()) { |
| default: |
| return true; |
| case IResourceDelta.ADDED : |
| case IResourceDelta.REMOVED : |
| case IResourceDelta.CHANGED : |
| break; |
| } |
| // If the resource is a factory path file, then the project it |
| // belongs to is affected. |
| IResource res = delta.getResource(); |
| if (res == null) { |
| return true; |
| } |
| IProject proj = res.getProject(); |
| if (FactoryPathUtil.isFactoryPathFile(res)) { |
| addAffected(Collections.singleton(JavaCore.create(proj))); |
| return true; |
| } |
| // If the resource is a jar file named in at least one factory |
| // path, then the projects owning those factorypaths are affected. |
| if (res.getType() != IResource.FILE) { |
| return true; |
| } |
| IPath relativePath = res.getFullPath(); |
| String ext = relativePath.getFileExtension(); |
| try { |
| if (JAR_EXTENSION.equals(ext)) { |
| IPath absolutePath = res.getLocation(); |
| if (absolutePath == null) { |
| // Jar file within a deleted project. In this case getLocation() |
| // returns null, so we can't get a canonical path. Bounce every |
| // factory path that contains anything resembling this jar. |
| for (Entry<String, Set<IJavaProject>> entry : _container2Project.entrySet()) { |
| IPath jarPath = new Path(entry.getKey()); |
| if (relativePath.lastSegment().equals(jarPath.lastSegment())) { |
| addAffected(entry.getValue()); |
| } |
| } |
| } |
| else { |
| // Lookup key is the canonical path of the resource |
| String key = null; |
| key = absolutePath.toFile().getCanonicalPath(); |
| Set<IJavaProject> projects = _container2Project.get(key); |
| if (projects != null) { |
| addAffected(projects); |
| } |
| } |
| } |
| } catch (Exception e) { |
| AptPlugin.log(e, |
| "Couldn't determine whether any factory paths were affected by change to resource " + res.getName()); //$NON-NLS-1$ |
| } |
| return true; |
| } |
| |
| } |
| |
| /** |
| * Singleton |
| */ |
| public static synchronized AnnotationProcessorFactoryLoader getLoader() { |
| if ( LOADER == null ) { |
| LOADER = new AnnotationProcessorFactoryLoader(); |
| LOADER.registerListener(); |
| } |
| return LOADER; |
| } |
| |
| private void registerListener() { |
| ResourcesPlugin.getWorkspace().addResourceChangeListener( |
| new ResourceListener(), |
| IResourceChangeEvent.PRE_DELETE |
| | IResourceChangeEvent.PRE_BUILD); |
| } |
| |
| /** |
| * Called when workspace preferences change. Resource changes, including |
| * changes to project-specific factory paths, are picked up through the |
| * ResourceChangedListener mechanism instead. |
| */ |
| public synchronized void resetAll() { |
| removeAptBuildProblemMarkers( null ); |
| _project2Java5Factories.clear(); |
| _project2Java6Factories.clear(); |
| // Need to close the iterative classloaders |
| for (ClassLoader cl : _iterativeLoaders.values()) { |
| if (cl instanceof JarClassLoader) |
| ((JarClassLoader)cl).close(); |
| } |
| _iterativeLoaders.clear(); |
| _container2Project.clear(); |
| |
| for (ClassLoader cl : _batchLoaders.values()) { |
| if (cl instanceof JarClassLoader) |
| ((JarClassLoader)cl).close(); |
| } |
| _batchLoaders.clear(); |
| |
| // Validate all projects |
| IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); |
| for (IProject proj : root.getProjects()) { |
| verifyFactoryPath(JavaCore.create(proj)); |
| } |
| } |
| |
| /** |
| * Called when doing a clean build -- resets |
| * the classloaders for the batch processors |
| */ |
| public synchronized void resetBatchProcessors(IJavaProject javaProj) { |
| Iterable<Attributes> attrs = null; |
| Map<AnnotationProcessorFactory, Attributes> factories = _project2Java5Factories.get(javaProj); |
| if (factories != null) { |
| attrs = factories.values(); |
| } |
| else { |
| Map<IServiceFactory, Attributes> java6factories = _project2Java6Factories.get(javaProj); |
| if (java6factories != null) { |
| attrs = java6factories.values(); |
| } |
| else { |
| // This project's factories have already been cleared. |
| return; |
| } |
| } |
| boolean batchProcsFound = false; |
| for (Attributes attr : attrs) { |
| if (attr.runInBatchMode()) { |
| batchProcsFound = true; |
| break; |
| } |
| } |
| if (batchProcsFound) { |
| _project2Java5Factories.remove(javaProj); |
| _project2Java6Factories.remove(javaProj); |
| } |
| |
| ClassLoader c = _batchLoaders.remove(javaProj); |
| if (c instanceof JarClassLoader) ((JarClassLoader)c).close(); |
| } |
| |
| /** |
| * @param jproj must not be null |
| * @return order preserving map of annotation processor factories to their attributes. |
| * The order of the annotation processor factories respects the order of factory |
| * containers in <code>jproj</code>. The map is unmodifiable, and may be empty but |
| * will not be null. |
| */ |
| public Map<AnnotationProcessorFactory, FactoryPath.Attributes> |
| getJava5FactoriesAndAttributesForProject(IJavaProject jproj){ |
| |
| // We can't create problem markers inside synchronization -- see https://bugs.eclipse.org/bugs/show_bug.cgi?id=184923 |
| LoadFailureHandler failureHandler = new LoadFailureHandler(jproj); |
| |
| synchronized (this) { |
| Map<AnnotationProcessorFactory, FactoryPath.Attributes> factories = _project2Java5Factories.get(jproj); |
| if( factories != null ) |
| return Collections.unmodifiableMap(factories); |
| |
| // Load the project |
| FactoryPath fp = FactoryPathUtil.getFactoryPath(jproj); |
| Map<FactoryContainer, FactoryPath.Attributes> containers = fp.getEnabledContainers(); |
| loadFactories(containers, jproj, failureHandler); |
| } |
| |
| failureHandler.reportFailureMarkers(); |
| return Collections.unmodifiableMap(_project2Java5Factories.get(jproj)); |
| |
| } |
| |
| /** |
| * @param jproj must not be null |
| * @return order preserving map of annotation processor factories to their attributes. |
| * The order of the annotation processor factories respects the order of factory |
| * containers in <code>jproj</code>. The map is unmodifiable, and may be empty but |
| * will not be null. |
| */ |
| public Map<IServiceFactory, FactoryPath.Attributes> |
| getJava6FactoriesAndAttributesForProject(IJavaProject jproj){ |
| |
| // We can't create problem markers inside synchronization -- see https://bugs.eclipse.org/bugs/show_bug.cgi?id=184923 |
| LoadFailureHandler failureHandler = new LoadFailureHandler(jproj); |
| |
| synchronized (this) { |
| |
| Map<IServiceFactory, FactoryPath.Attributes> factories = _project2Java6Factories.get(jproj); |
| if( factories != null ) |
| return Collections.unmodifiableMap(factories); |
| |
| // Load the project |
| FactoryPath fp = FactoryPathUtil.getFactoryPath(jproj); |
| Map<FactoryContainer, FactoryPath.Attributes> containers = fp.getEnabledContainers(); |
| loadFactories(containers, jproj, failureHandler); |
| } |
| |
| failureHandler.reportFailureMarkers(); |
| return Collections.unmodifiableMap(_project2Java6Factories.get(jproj)); |
| |
| } |
| |
| /** |
| * Convenience method: get the key set of the map returned by |
| * @see #getJava5FactoriesAndAttributesForProject(IJavaProject) as a List. |
| */ |
| public synchronized List<AnnotationProcessorFactory> getJava5FactoriesForProject( IJavaProject jproj ) { |
| |
| Map<AnnotationProcessorFactory, FactoryPath.Attributes> factoriesAndAttrs = |
| getJava5FactoriesAndAttributesForProject(jproj); |
| final List<AnnotationProcessorFactory> factories = |
| new ArrayList<AnnotationProcessorFactory>(factoriesAndAttrs.keySet()); |
| return Collections.unmodifiableList(factories); |
| } |
| |
| /** |
| * Add the resource/project pair 'key' -> 'jproj' to the |
| * _container2Project map. |
| * @param key the canonicalized pathname of the resource |
| * @param jproj must not be null |
| */ |
| private void addToResourcesMap(String key, IJavaProject jproj) { |
| Set<IJavaProject> s = _container2Project.get(key); |
| if (s == null) { |
| s = new HashSet<IJavaProject>(); |
| _container2Project.put(key, s); |
| } |
| s.add(jproj); |
| } |
| |
| /** |
| * Wrapper around ClassLoader.loadClass().newInstance() to handle reporting of errors. |
| */ |
| private Object loadInstance( String factoryName, ClassLoader cl, IJavaProject jproj, LoadFailureHandler failureHandler ) |
| { |
| Object f = null; |
| try |
| { |
| Class<?> c = cl.loadClass( factoryName ); |
| f = c.newInstance(); |
| } |
| catch( Exception e ) |
| { |
| AptPlugin.trace("Failed to load factory " + factoryName, e); //$NON-NLS-1$ |
| failureHandler.addFailedFactory(factoryName); |
| } |
| catch ( NoClassDefFoundError ncdfe ) |
| { |
| AptPlugin.trace("Failed to load " + factoryName, ncdfe); //$NON-NLS-1$ |
| failureHandler.addFailedFactory(factoryName); |
| } |
| return f; |
| } |
| |
| /** |
| * Load all Java 5 and Java 6 processors on the factory path. This also resets the |
| * APT-related build problem markers. Results are saved in the factory caches. |
| * @param containers an ordered map. |
| */ |
| private void loadFactories( |
| Map<FactoryContainer, FactoryPath.Attributes> containers, |
| IJavaProject project, |
| LoadFailureHandler failureHandler) |
| { |
| Map<AnnotationProcessorFactory, FactoryPath.Attributes> java5Factories = |
| new LinkedHashMap<AnnotationProcessorFactory, FactoryPath.Attributes>(); |
| Map<IServiceFactory, FactoryPath.Attributes> java6Factories = |
| new LinkedHashMap<IServiceFactory, FactoryPath.Attributes>(); |
| |
| removeAptBuildProblemMarkers(project); |
| Set<FactoryContainer> badContainers = verifyFactoryPath(project); |
| if (badContainers != null) { |
| for (FactoryContainer badFC : badContainers) { |
| failureHandler.addFailedFactory(badFC.getId()); |
| containers.remove(badFC); |
| } |
| } |
| |
| // Need to use the cached classloader if we have one |
| ClassLoader iterativeClassLoader = _iterativeLoaders.get(project); |
| if (iterativeClassLoader == null) { |
| iterativeClassLoader = _createIterativeClassLoader(containers); |
| _iterativeLoaders.put(project, iterativeClassLoader); |
| } |
| |
| _createBatchClassLoader(containers, project); |
| ClassLoader batchClassLoader = _batchLoaders.get(project); |
| |
| for ( Map.Entry<FactoryContainer, FactoryPath.Attributes> entry : containers.entrySet() ) |
| { |
| try { |
| final FactoryContainer fc = entry.getKey(); |
| final FactoryPath.Attributes attr = entry.getValue(); |
| assert !attr.runInBatchMode() || (batchClassLoader != null); |
| ClassLoader cl = attr.runInBatchMode() ? batchClassLoader : iterativeClassLoader; |
| |
| // First the Java 5 factories in this container... |
| List<AnnotationProcessorFactory> java5FactoriesInContainer; |
| java5FactoriesInContainer = loadJava5FactoryClasses(fc, cl, project, failureHandler); |
| for ( AnnotationProcessorFactory apf : java5FactoriesInContainer ) { |
| java5Factories.put( apf, entry.getValue() ); |
| } |
| |
| if (AptPlugin.canRunJava6Processors()) { |
| // Now the Java 6 factories. Use the same classloader for the sake of sanity. |
| List<IServiceFactory> java6FactoriesInContainer; |
| java6FactoriesInContainer = loadJava6FactoryClasses(fc, cl, project, failureHandler); |
| for ( IServiceFactory isf : java6FactoriesInContainer ) { |
| java6Factories.put( isf, entry.getValue() ); |
| } |
| } |
| } |
| catch (FileNotFoundException fnfe) { |
| // it would be bizarre to get this, given that we already checked for file existence up above. |
| AptPlugin.log(fnfe, Messages.AnnotationProcessorFactoryLoader_jarNotFound + fnfe.getLocalizedMessage()); |
| } |
| catch (IOException ioe) { |
| AptPlugin.log(ioe, Messages.AnnotationProcessorFactoryLoader_ioError + ioe.getLocalizedMessage()); |
| } |
| } |
| _project2Java5Factories.put(project, java5Factories); |
| _project2Java6Factories.put(project, java6Factories); |
| } |
| |
| private List<AnnotationProcessorFactory> loadJava5FactoryClasses( |
| FactoryContainer fc, ClassLoader classLoader, IJavaProject jproj, LoadFailureHandler failureHandler ) |
| throws IOException |
| { |
| Map<String, String> factoryNames = fc.getFactoryNames(); |
| List<AnnotationProcessorFactory> factories = new ArrayList<AnnotationProcessorFactory>(); |
| for ( Entry<String, String> entry : factoryNames.entrySet() ) |
| { |
| if (AptPlugin.JAVA5_FACTORY_NAME.equals(entry.getValue())) { |
| String factoryName = entry.getKey(); |
| AnnotationProcessorFactory factory; |
| if ( fc.getType() == FactoryType.PLUGIN ) |
| factory = FactoryPluginManager.getJava5FactoryFromPlugin( factoryName ); |
| else |
| factory = (AnnotationProcessorFactory)loadInstance( factoryName, classLoader, jproj, failureHandler ); |
| |
| if ( factory != null ) |
| factories.add( factory ); |
| } |
| } |
| return factories; |
| } |
| |
| private List<IServiceFactory> loadJava6FactoryClasses( |
| FactoryContainer fc, ClassLoader classLoader, IJavaProject jproj, LoadFailureHandler failureHandler ) |
| throws IOException |
| { |
| Map<String, String> factoryNames = fc.getFactoryNames(); |
| List<IServiceFactory> factories = new ArrayList<IServiceFactory>(); |
| for ( Entry<String, String> entry : factoryNames.entrySet() ) |
| { |
| if (AptPlugin.JAVA6_FACTORY_NAME.equals(entry.getValue())) { |
| String factoryName = entry.getKey(); |
| IServiceFactory factory = null; |
| if ( fc.getType() == FactoryType.PLUGIN ) { |
| factory = FactoryPluginManager.getJava6FactoryFromPlugin( factoryName ); |
| } |
| else { |
| Class<?> clazz; |
| try { |
| clazz = classLoader.loadClass(factoryName); |
| factory = new ClassServiceFactory(clazz); |
| } catch (ClassNotFoundException e) { |
| AptPlugin.trace("Unable to load annotation processor " + factoryName, e); //$NON-NLS-1$ |
| failureHandler.addFailedFactory(factoryName); |
| } |
| } |
| |
| if ( factory != null ) |
| factories.add( factory ); |
| } |
| } |
| return factories; |
| } |
| |
| /** |
| * Re-validate projects whose factory paths may have been affected |
| * by a resource change (e.g., adding a previously absent jar file). |
| * This will cause build problem markers to be removed and regenerated, |
| * and factory class caches to be cleared. |
| */ |
| private void processChanges(Set<IJavaProject> affected, Map<IJavaProject,LoadFailureHandler> handlers) { |
| for (IJavaProject jproj : affected) { |
| removeAptBuildProblemMarkers(jproj); |
| uncacheProject(jproj); |
| } |
| // We will do another clear and re-verify when loadFactories() |
| // is called. But we have to do it then, because things might |
| // have changed in the interim; and if we don't do it here, then |
| // we'll have an empty _resources2Project cache, so we'll ignore |
| // all resource changes until the next build. Is that a problem? |
| for (IJavaProject jproj : affected) { |
| if (jproj.exists()) { |
| Set<FactoryContainer> badContainers = verifyFactoryPath(jproj); |
| if (badContainers != null) { |
| LoadFailureHandler handler = handlers.get(jproj); |
| if (handler == null) { |
| handler = new LoadFailureHandler(jproj); |
| handlers.put(jproj, handler); |
| } |
| for (FactoryContainer container : badContainers) { |
| handler.addMissingLibrary(container.getId()); |
| } |
| } |
| } |
| } |
| |
| // TODO: flag the affected projects for rebuild. |
| } |
| |
| /** |
| * When a project is deleted, remove its factory path information from the loader. |
| * @param jproj |
| */ |
| private void uncacheProject(IJavaProject jproj) { |
| _project2Java5Factories.remove(jproj); |
| _project2Java6Factories.remove(jproj); |
| ClassLoader c = _iterativeLoaders.remove(jproj); |
| if (c instanceof JarClassLoader) |
| ((JarClassLoader)c).close(); |
| |
| ClassLoader cl = _batchLoaders.remove(jproj); |
| if (cl instanceof JarClassLoader) ((JarClassLoader)cl).close(); |
| |
| removeProjectFromResourceMap(jproj); |
| } |
| |
| /** |
| * Remove APT build problem markers, e.g., "missing factory jar". |
| * @param jproj if null, remove markers from all projects that have |
| * factory paths associated with them. |
| */ |
| private void removeAptBuildProblemMarkers( IJavaProject jproj ) { |
| // note that _project2Java6Factories.keySet() should be same as that for Java5. |
| Set<IJavaProject> jprojects = (jproj == null) ? _project2Java5Factories.keySet() : Collections.singleton(jproj); |
| try { |
| for (IJavaProject jp : jprojects) { |
| if (jp.exists()) { |
| IProject p = jp.getProject(); |
| IMarker[] markers = p.findMarkers(AptPlugin.APT_LOADER_PROBLEM_MARKER, false, IResource.DEPTH_ZERO); |
| if( markers != null ){ |
| for( IMarker marker : markers ) |
| marker.delete(); |
| } |
| } |
| } |
| } |
| catch(CoreException e){ |
| AptPlugin.log(e, "Unable to delete APT build problem marker"); //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * Remove references to the project from _container2Project. This is done |
| * when a project is deleted, or before re-verifying the project's |
| * factory path. |
| */ |
| private void removeProjectFromResourceMap(IJavaProject jproj) { |
| Iterator<Entry<String, Set<IJavaProject>>> i = _container2Project.entrySet().iterator(); |
| while (i.hasNext()) { |
| Entry<String, Set<IJavaProject>> e = i.next(); |
| Set<IJavaProject> s = e.getValue(); |
| s.remove(jproj); |
| // Remove any resulting orphaned resources. |
| if (s.isEmpty()) { |
| i.remove(); |
| } |
| } |
| } |
| |
| /** |
| * Check the factory path for a project and ensure that all the |
| * containers it lists are available. Adds jar factory container |
| * resources to the _container2Project cache, whether or not the |
| * resource can actually be found. |
| * |
| * @param jproj the project, or null to check all projects that |
| * are in the cache. |
| * @return a Set of all invalid containers, or null if all containers |
| * on the path were valid. |
| */ |
| private Set<FactoryContainer> verifyFactoryPath(IJavaProject jproj) { |
| Set<FactoryContainer> badContainers = null; |
| FactoryPath fp = FactoryPathUtil.getFactoryPath(jproj); |
| Map<FactoryContainer, FactoryPath.Attributes> containers = fp.getEnabledContainers(); |
| for (FactoryContainer fc : containers.keySet()) { |
| if (fc instanceof JarFactoryContainer) { |
| try { |
| final File jarFile = ((JarFactoryContainer)fc).getJarFile(); |
| // if null, will add to bad container set below. |
| if( jarFile != null ){ |
| String key = jarFile.getCanonicalPath(); |
| addToResourcesMap(key, jproj); |
| } |
| } catch (IOException e) { |
| // If there's something this malformed on the factory path, |
| // don't bother putting it on the resources map; we'll never |
| // get notified about a change to it anyway. It should get |
| // reported either as a bad container (below) or as a failure |
| // to load (later on). |
| } |
| } |
| if ( !fc.exists() ) { |
| if (badContainers == null) { |
| badContainers = new HashSet<FactoryContainer>(); |
| } |
| badContainers.add(fc); |
| } |
| } |
| return badContainers; |
| } |
| |
| /** |
| * @param containers an ordered map. |
| */ |
| private ClassLoader _createIterativeClassLoader( Map<FactoryContainer, FactoryPath.Attributes> containers ) |
| { |
| ArrayList<File> fileList = new ArrayList<File>( containers.size() ); |
| for (Map.Entry<FactoryContainer, FactoryPath.Attributes> entry : containers.entrySet()) { |
| FactoryPath.Attributes attr = entry.getValue(); |
| FactoryContainer fc = entry.getKey(); |
| if (!attr.runInBatchMode() && fc instanceof JarFactoryContainer) { |
| JarFactoryContainer jfc = (JarFactoryContainer)fc; |
| fileList.add( jfc.getJarFile() ); |
| } |
| } |
| |
| ClassLoader cl; |
| if ( fileList.size() > 0 ) { |
| cl = createClassLoader( fileList, AnnotationProcessorFactoryLoader.class.getClassLoader() ); |
| } |
| else { |
| cl = AnnotationProcessorFactoryLoader.class.getClassLoader(); |
| } |
| return cl; |
| } |
| |
| private void _createBatchClassLoader(Map<FactoryContainer, FactoryPath.Attributes> containers, |
| IJavaProject p) |
| { |
| |
| ArrayList<File> fileList = new ArrayList<File>( containers.size() ); |
| for (Map.Entry<FactoryContainer, FactoryPath.Attributes> entry : containers.entrySet()) { |
| FactoryPath.Attributes attr = entry.getValue(); |
| FactoryContainer fc = entry.getKey(); |
| if (attr.runInBatchMode() && fc instanceof JarFactoryContainer) { |
| |
| JarFactoryContainer jfc = (JarFactoryContainer)fc; |
| File f = jfc.getJarFile(); |
| fileList.add( f ); |
| |
| } |
| } |
| |
| // Try to use the iterative CL as parent, so we can resolve classes within it |
| ClassLoader parentCL = _iterativeLoaders.get(p); |
| if (parentCL == null) { |
| parentCL = AnnotationProcessorFactoryLoader.class.getClassLoader(); |
| } |
| |
| if ( fileList.size() > 0 ) { |
| _batchLoaders.put(p,createClassLoader( fileList, parentCL)); |
| } |
| } |
| |
| private static ClassLoader createClassLoader(List<File> files, ClassLoader parentCL) { |
| //return new JarClassLoader(files, parentCL); |
| List<URL> urls = new ArrayList<URL>(files.size()); |
| for (int i=0;i<files.size();i++) { |
| try { |
| urls.add(files.get(i).toURI().toURL()); |
| } |
| catch (MalformedURLException mue) { |
| // ignore |
| } |
| } |
| URL[] urlArray = urls.toArray(new URL[urls.size()]); |
| return new URLClassLoader(urlArray, parentCL); |
| } |
| } |