blob: 4d5666ee9f40c778cb21520b21a76e8867f3a7d2 [file] [log] [blame]
/*******************************************************************************
* 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);
}
}