KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > jdt > apt > core > internal > AnnotationProcessorFactoryLoader


1 /*******************************************************************************
2  * Copyright (c) 2005, 2007 BEA Systems, Inc.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * wharley@bea.com - initial API and implementation
10  *******************************************************************************/

11
12 package org.eclipse.jdt.apt.core.internal;
13
14 import java.io.File JavaDoc;
15 import java.io.FileNotFoundException JavaDoc;
16 import java.io.IOException JavaDoc;
17 import java.net.MalformedURLException JavaDoc;
18 import java.net.URL JavaDoc;
19 import java.net.URLClassLoader JavaDoc;
20 import java.util.ArrayList JavaDoc;
21 import java.util.Collections JavaDoc;
22 import java.util.HashMap JavaDoc;
23 import java.util.HashSet JavaDoc;
24 import java.util.Iterator JavaDoc;
25 import java.util.LinkedHashMap JavaDoc;
26 import java.util.List JavaDoc;
27 import java.util.Map JavaDoc;
28 import java.util.Set JavaDoc;
29 import java.util.Map.Entry;
30
31 import org.eclipse.core.resources.IMarker;
32 import org.eclipse.core.resources.IProject;
33 import org.eclipse.core.resources.IResource;
34 import org.eclipse.core.resources.IResourceChangeEvent;
35 import org.eclipse.core.resources.IResourceChangeListener;
36 import org.eclipse.core.resources.IResourceDelta;
37 import org.eclipse.core.resources.IResourceDeltaVisitor;
38 import org.eclipse.core.resources.IWorkspaceRoot;
39 import org.eclipse.core.resources.ResourcesPlugin;
40 import org.eclipse.core.runtime.CoreException;
41 import org.eclipse.core.runtime.IPath;
42 import org.eclipse.core.runtime.Path;
43 import org.eclipse.jdt.apt.core.internal.util.FactoryContainer;
44 import org.eclipse.jdt.apt.core.internal.util.FactoryPath;
45 import org.eclipse.jdt.apt.core.internal.util.FactoryPathUtil;
46 import org.eclipse.jdt.apt.core.internal.util.FactoryContainer.FactoryType;
47 import org.eclipse.jdt.apt.core.internal.util.FactoryPath.Attributes;
48 import org.eclipse.jdt.core.IJavaProject;
49 import org.eclipse.jdt.core.JavaCore;
50
51 import com.sun.mirror.apt.AnnotationProcessorFactory;
52
53 /**
54  * Stores annotation processor factories, and handles mapping from projects
55  * to them. This is a singleton object, created by the first call to getLoader().
56  * <p>
57  * Factories contained in plugins are loaded at APT initialization time.
58  * Factories contained in jar files are loaded for a given project the first time
59  * getFactoriesForProject() is called, and cached thereafter. Factories are loaded
60  * from one of two custom classloaders depending on whether the factory container
61  * is to be run in batch processing mode or normal (iterative) mode; the batch
62  * classloader for a project is parented by the iterative classloader for that
63  * project.
64  * <p>
65  * <strong>Processor Factories</strong>
66  * <p>
67  * This class is compilable against a Java 1.5 runtime. However, it includes
68  * support for discovering and loading both Java 5 and Java 6 annotation
69  * processors. Java 5 annotation processors include a factory object, the
70  * AnnotationProcessorFactory, so for Java 5 we simply cache the factory; the
71  * client code uses the factory to produce an actual AnnotationProcessor.
72  * Java 6 processors do not have a separate factory, so we cache the Class
73  * object of the processor implementation and use it to produce new instances.
74  * This is wrapped within an IServiceFactory interface, for flexibility in
75  * loading from various sources. The actual Processor class does not exist
76  * in the Java 1.5 runtime, so all access to it must be done via reflection.
77  * <p>
78  * <strong>Caches</strong>
79  * <p>
80  * Factory classes and iterative-mode classloaders are cached for each project,
81  * the first time that the classes are needed (e.g., during a build or reconcile).
82  * The cache is cleared when the project's factory path changes, when a resource
83  * listed on the factory path is changed, or when the project is deleted.
84  * If a project contains batch-mode processors, the cache is also cleared at
85  * the beginning of every full build (batch-mode processors do not run at all
86  * during reconcile).
87  * <p>
88  * If a project's factory path includes containers which cannot be located on
89  * disk, problem markers will be added to the project. This validation process
90  * occurs when the cache for a project is first loaded, and whenever the cache
91  * is invalidated. We do not validate the workspace-level factory path as such;
92  * it is only used to construct a project-specific factory path for projects
93  * that do not have their own factory path.
94  * <p>
95  * In order to efficiently perform re-validation when resources change, we keep
96  * track of which projects' factory paths mention which containers. This is
97  * stored as a map from canonicalized resource path to project. Entries are
98  * created and updated during factory path validation, and removed upon project
99  * deletion.
100  * <p>
101  * Resource changes are presented as delta trees which may contain more than
102  * one change. When a change arrives, we build up a list of all potentially
103  * affected projects, and then perform re-validation after the list is complete.
104  * That way we avoid redundant validations if a project is affected by more
105  * than one change.
106  * <p>
107  * Note that markers and factory classes have different lifetimes: they are
108  * discarded at the same time (when something changes), but markers are recreated
109  * immediately (as a result of validation) while factory classes are not reloaded
110  * until the next time a build or reconcile occurs.
111  * <p>
112  * <strong>Synchronization</strong>
113  * <p>
114  * The loader is often accessed on multiple threads, e.g., a build thread, a
115  * reconcile thread, and a change notification thread all at once. It is
116  * important to maintain consistency across the various cache objects.
117  */

118 public class AnnotationProcessorFactoryLoader {
119     
120     /** Loader instance -- holds all workspace and project data */
121     private static AnnotationProcessorFactoryLoader LOADER;
122     
123     private static final String JavaDoc JAR_EXTENSION = "jar"; //$NON-NLS-1$
124

125     // Caches the factory classes associated with each project.
126
// See class comments for lifecycle of items in this cache.
127
private final Map JavaDoc<IJavaProject, Map JavaDoc<AnnotationProcessorFactory, FactoryPath.Attributes>> _project2Java5Factories =
128         new HashMap JavaDoc<IJavaProject, Map JavaDoc<AnnotationProcessorFactory, FactoryPath.Attributes>>();
129     
130     private final Map JavaDoc<IJavaProject, Map JavaDoc<IServiceFactory, FactoryPath.Attributes>> _project2Java6Factories =
131         new HashMap JavaDoc<IJavaProject, Map JavaDoc<IServiceFactory, FactoryPath.Attributes>>();
132     
133     // Caches the iterative classloaders so that iterative processors
134
// are not reloaded on every batch build, unlike batch processors
135
// which are.
136
// See class comments for lifecycle of items in this cache.
137
private final Map JavaDoc<IJavaProject, ClassLoader JavaDoc> _iterativeLoaders =
138         new HashMap JavaDoc<IJavaProject, ClassLoader JavaDoc>();
139     
140     private final Map JavaDoc<IJavaProject,ClassLoader JavaDoc> _batchLoaders =
141         new HashMap JavaDoc<IJavaProject,ClassLoader JavaDoc>();
142     
143     // Caches information about which resources affect which projects'
144
// factory paths.
145
// See class comments for lifecycle of items in this cache.
146
private final Map JavaDoc<String JavaDoc, Set JavaDoc<IJavaProject>> _container2Project =
147         new HashMap JavaDoc<String JavaDoc, Set JavaDoc<IJavaProject>>();
148     
149    
150     /**
151      * Listen for changes that would affect the factory caches or
152      * build markers.
153      */

154     private class ResourceListener implements IResourceChangeListener {
155
156         public void resourceChanged(IResourceChangeEvent event) {
157             Map JavaDoc<IJavaProject, LoadFailureHandler> failureHandlers = new HashMap JavaDoc<IJavaProject, LoadFailureHandler>();
158             synchronized (AnnotationProcessorFactoryLoader.this) {
159                 switch (event.getType()) {
160                 
161                 // Project deletion
162
case (IResourceChangeEvent.PRE_DELETE) :
163                     IResource project = event.getResource();
164                     if (project != null && project instanceof IProject) {
165                         IJavaProject jproj = JavaCore.create((IProject)project);
166                         if (jproj != null) {
167                             uncacheProject(jproj);
168                         }
169                     }
170                     break;
171                     
172                 // Changes to jar files or .factorypath files
173
case (IResourceChangeEvent.PRE_BUILD) :
174                     IResourceDelta rootDelta = event.getDelta();
175                     FactoryPathDeltaVisitor visitor = new FactoryPathDeltaVisitor();
176                     try {
177                         rootDelta.accept(visitor);
178                     } catch (CoreException e) {
179                         AptPlugin.log(e, "Unable to determine whether resource change affects annotation processor factory path"); //$NON-NLS-1$
180
}
181                     Set JavaDoc<IJavaProject> affected = visitor.getAffectedProjects();
182                     if (affected != null) {
183                         processChanges(affected, failureHandlers);
184                     }
185                     break;
186     
187                 }
188             }
189             for (LoadFailureHandler handler : failureHandlers.values()) {
190                 handler.reportFailureMarkers();
191             }
192         }
193         
194     }
195     
196     /**
197      * Walk the delta tree to see if there have been changes to
198      * a factory path or the containers it references. If so,
199      * re-validate the affected projects' factory paths.
200      */

201     private class FactoryPathDeltaVisitor implements IResourceDeltaVisitor {
202         
203         // List of projects affected by this change.
204
// Lazy construction because we assume most changes won't affect any projects.
205
private Set JavaDoc<IJavaProject> _affected = null;
206         
207         private void addAffected(Set JavaDoc<IJavaProject> projects) {
208             if (_affected == null) {
209                  _affected = new HashSet JavaDoc<IJavaProject>(5);
210             }
211             _affected.addAll(projects);
212         }
213         
214         /**
215          * Get the list of IJavaProject affected by the delta we visited.
216          * Not valid until done visiting.
217          * @return null if there were no affected projects, or a non-empty
218          * set of IJavaProject otherwise.
219          */

220         public Set JavaDoc<IJavaProject> getAffectedProjects() {
221             return _affected;
222         }
223         
224         /**
225          * @return true to visit children
226          */

227         public boolean visit(IResourceDelta delta) {
228             switch (delta.getKind()) {
229             default:
230                 return true;
231             case IResourceDelta.ADDED :
232             case IResourceDelta.REMOVED :
233             case IResourceDelta.CHANGED :
234                 break;
235             }
236             // If the resource is a factory path file, then the project it
237
// belongs to is affected.
238
IResource res = delta.getResource();
239             if (res == null) {
240                 return true;
241             }
242             IProject proj = res.getProject();
243             if (FactoryPathUtil.isFactoryPathFile(res)) {
244                 addAffected(Collections.singleton(JavaCore.create(proj)));
245                 return true;
246             }
247             // If the resource is a jar file named in at least one factory
248
// path, then the projects owning those factorypaths are affected.
249
if (res.getType() != IResource.FILE) {
250                 return true;
251             }
252             IPath relativePath = res.getFullPath();
253             String JavaDoc ext = relativePath.getFileExtension();
254             try {
255                 if (JAR_EXTENSION.equals(ext)) {
256                     IPath absolutePath = res.getLocation();
257                     if (absolutePath == null) {
258                         // Jar file within a deleted project. In this case getLocation()
259
// returns null, so we can't get a canonical path. Bounce every
260
// factory path that contains anything resembling this jar.
261
for (Entry<String JavaDoc, Set JavaDoc<IJavaProject>> entry : _container2Project.entrySet()) {
262                             IPath jarPath = new Path(entry.getKey());
263                             if (relativePath.lastSegment().equals(jarPath.lastSegment())) {
264                                 addAffected(entry.getValue());
265                             }
266                         }
267                     }
268                     else {
269                         // Lookup key is the canonical path of the resource
270
String JavaDoc key = null;
271                         key = absolutePath.toFile().getCanonicalPath();
272                         Set JavaDoc<IJavaProject> projects = _container2Project.get(key);
273                         if (projects != null) {
274                             addAffected(projects);
275                         }
276                     }
277                 }
278             } catch (Exception JavaDoc e) {
279                 AptPlugin.log(e,
280                     "Couldn't determine whether any factory paths were affected by change to resource " + res.getName()); //$NON-NLS-1$
281
}
282             return true;
283         }
284         
285     }
286     
287     /**
288      * Singleton
289      */

290     public static synchronized AnnotationProcessorFactoryLoader getLoader() {
291         if ( LOADER == null ) {
292             LOADER = new AnnotationProcessorFactoryLoader();
293             LOADER.registerListener();
294         }
295         return LOADER;
296     }
297     
298     private void registerListener() {
299         ResourcesPlugin.getWorkspace().addResourceChangeListener(
300             new ResourceListener(),
301             IResourceChangeEvent.PRE_DELETE
302             | IResourceChangeEvent.PRE_BUILD);
303     }
304
305     /**
306      * Called when workspace preferences change. Resource changes, including
307      * changes to project-specific factory paths, are picked up through the
308      * ResourceChangedListener mechanism instead.
309      */

310     public synchronized void resetAll() {
311         removeAptBuildProblemMarkers( null );
312         _project2Java5Factories.clear();
313         _project2Java6Factories.clear();
314         // Need to close the iterative classloaders
315
for (ClassLoader JavaDoc cl : _iterativeLoaders.values()) {
316             if (cl instanceof JarClassLoader)
317                 ((JarClassLoader)cl).close();
318         }
319         _iterativeLoaders.clear();
320         _container2Project.clear();
321         
322         for (ClassLoader JavaDoc cl : _batchLoaders.values()) {
323             if (cl instanceof JarClassLoader)
324                 ((JarClassLoader)cl).close();
325         }
326         _batchLoaders.clear();
327         
328         // Validate all projects
329
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
330         for (IProject proj : root.getProjects()) {
331             verifyFactoryPath(JavaCore.create(proj));
332         }
333     }
334     
335     /**
336      * Called when doing a clean build -- resets
337      * the classloaders for the batch processors
338      */

339     public synchronized void resetBatchProcessors(IJavaProject javaProj) {
340         Iterable JavaDoc<Attributes> attrs = null;
341         Map JavaDoc<AnnotationProcessorFactory, Attributes> factories = _project2Java5Factories.get(javaProj);
342         if (factories != null) {
343             attrs = factories.values();
344         }
345         else {
346             Map JavaDoc<IServiceFactory, Attributes> java6factories = _project2Java6Factories.get(javaProj);
347             if (java6factories != null) {
348                 attrs = java6factories.values();
349             }
350             else {
351                 // This project's factories have already been cleared.
352
return;
353             }
354         }
355         boolean batchProcsFound = false;
356         for (Attributes attr : attrs) {
357             if (attr.runInBatchMode()) {
358                 batchProcsFound = true;
359                 break;
360             }
361         }
362         if (batchProcsFound) {
363             _project2Java5Factories.remove(javaProj);
364             _project2Java6Factories.remove(javaProj);
365         }
366
367         ClassLoader JavaDoc c = _batchLoaders.remove(javaProj);
368         if (c instanceof JarClassLoader) ((JarClassLoader)c).close();
369     }
370     
371     /**
372      * @param jproj must not be null
373      * @return order preserving map of annotation processor factories to their attributes.
374      * The order of the annotation processor factories respects the order of factory
375      * containers in <code>jproj</code>. The map is unmodifiable, and may be empty but
376      * will not be null.
377      */

378     public Map JavaDoc<AnnotationProcessorFactory, FactoryPath.Attributes>
379         getJava5FactoriesAndAttributesForProject(IJavaProject jproj){
380         
381         // We can't create problem markers inside synchronization -- see https://bugs.eclipse.org/bugs/show_bug.cgi?id=184923
382
LoadFailureHandler failureHandler = new LoadFailureHandler(jproj);
383         
384         synchronized (this) {
385             Map JavaDoc<AnnotationProcessorFactory, FactoryPath.Attributes> factories = _project2Java5Factories.get(jproj);
386             if( factories != null )
387                 return Collections.unmodifiableMap(factories);
388             
389             // Load the project
390
FactoryPath fp = FactoryPathUtil.getFactoryPath(jproj);
391             Map JavaDoc<FactoryContainer, FactoryPath.Attributes> containers = fp.getEnabledContainers();
392             loadFactories(containers, jproj, failureHandler);
393         }
394         
395         failureHandler.reportFailureMarkers();
396         return Collections.unmodifiableMap(_project2Java5Factories.get(jproj));
397         
398     }
399     
400     /**
401      * @param jproj must not be null
402      * @return order preserving map of annotation processor factories to their attributes.
403      * The order of the annotation processor factories respects the order of factory
404      * containers in <code>jproj</code>. The map is unmodifiable, and may be empty but
405      * will not be null.
406      */

407     public Map JavaDoc<IServiceFactory, FactoryPath.Attributes>
408         getJava6FactoriesAndAttributesForProject(IJavaProject jproj){
409         
410         // We can't create problem markers inside synchronization -- see https://bugs.eclipse.org/bugs/show_bug.cgi?id=184923
411
LoadFailureHandler failureHandler = new LoadFailureHandler(jproj);
412         
413         synchronized (this) {
414         
415             Map JavaDoc<IServiceFactory, FactoryPath.Attributes> factories = _project2Java6Factories.get(jproj);
416             if( factories != null )
417                 return Collections.unmodifiableMap(factories);
418             
419             // Load the project
420
FactoryPath fp = FactoryPathUtil.getFactoryPath(jproj);
421             Map JavaDoc<FactoryContainer, FactoryPath.Attributes> containers = fp.getEnabledContainers();
422             loadFactories(containers, jproj, failureHandler);
423         }
424         
425         failureHandler.reportFailureMarkers();
426         return Collections.unmodifiableMap(_project2Java6Factories.get(jproj));
427         
428     }
429     
430 /**
431      * Convenience method: get the key set of the map returned by
432      * @see #getJava5FactoriesAndAttributesForProject(IJavaProject) as a List.
433      */

434     public synchronized List JavaDoc<AnnotationProcessorFactory> getJava5FactoriesForProject( IJavaProject jproj ) {
435         
436         Map JavaDoc<AnnotationProcessorFactory, FactoryPath.Attributes> factoriesAndAttrs =
437             getJava5FactoriesAndAttributesForProject(jproj);
438         final List JavaDoc<AnnotationProcessorFactory> factories =
439             new ArrayList JavaDoc<AnnotationProcessorFactory>(factoriesAndAttrs.keySet());
440         return Collections.unmodifiableList(factories);
441     }
442     
443     /**
444      * Add the resource/project pair 'key' -> 'jproj' to the
445      * _container2Project map.
446      * @param key the canonicalized pathname of the resource
447      * @param jproj must not be null
448      */

449     private void addToResourcesMap(String JavaDoc key, IJavaProject jproj) {
450         Set JavaDoc<IJavaProject> s = _container2Project.get(key);
451         if (s == null) {
452             s = new HashSet JavaDoc<IJavaProject>();
453             _container2Project.put(key, s);
454         }
455         s.add(jproj);
456     }
457
458     /**
459      * Wrapper around ClassLoader.loadClass().newInstance() to handle reporting of errors.
460      */

461     private Object JavaDoc loadInstance( String JavaDoc factoryName, ClassLoader JavaDoc cl, IJavaProject jproj, LoadFailureHandler failureHandler )
462     {
463         Object JavaDoc f = null;
464         try
465         {
466             Class JavaDoc<?> c = cl.loadClass( factoryName );
467             f = c.newInstance();
468         }
469         catch( Exception JavaDoc e )
470         {
471             AptPlugin.trace("Failed to load factory " + factoryName, e); //$NON-NLS-1$
472
failureHandler.addFailedFactory(factoryName);
473         }
474         catch ( NoClassDefFoundError JavaDoc ncdfe )
475         {
476             AptPlugin.trace("Failed to load " + factoryName, ncdfe); //$NON-NLS-1$
477
failureHandler.addFailedFactory(factoryName);
478         }
479         return f;
480     }
481     
482     /**
483      * Load all Java 5 and Java 6 processors on the factory path. This also resets the
484      * APT-related build problem markers. Results are saved in the factory caches.
485      * @param containers an ordered map.
486      */

487     private void loadFactories(
488             Map JavaDoc<FactoryContainer, FactoryPath.Attributes> containers,
489             IJavaProject project,
490             LoadFailureHandler failureHandler)
491     {
492         Map JavaDoc<AnnotationProcessorFactory, FactoryPath.Attributes> java5Factories =
493             new LinkedHashMap JavaDoc<AnnotationProcessorFactory, FactoryPath.Attributes>();
494         Map JavaDoc<IServiceFactory, FactoryPath.Attributes> java6Factories =
495             new LinkedHashMap JavaDoc<IServiceFactory, FactoryPath.Attributes>();
496         
497         removeAptBuildProblemMarkers(project);
498         Set JavaDoc<FactoryContainer> badContainers = verifyFactoryPath(project);
499         if (badContainers != null) {
500             for (FactoryContainer badFC : badContainers) {
501                 failureHandler.addFailedFactory(badFC.getId());
502                 containers.remove(badFC);
503             }
504         }
505         
506         // Need to use the cached classloader if we have one
507
ClassLoader JavaDoc iterativeClassLoader = _iterativeLoaders.get(project);
508         if (iterativeClassLoader == null) {
509             iterativeClassLoader = _createIterativeClassLoader(containers);
510             _iterativeLoaders.put(project, iterativeClassLoader);
511         }
512         
513         _createBatchClassLoader(containers, project);
514         ClassLoader JavaDoc batchClassLoader = _batchLoaders.get(project);
515         
516         for ( Map.Entry JavaDoc<FactoryContainer, FactoryPath.Attributes> entry : containers.entrySet() )
517         {
518             try {
519                 final FactoryContainer fc = entry.getKey();
520                 final FactoryPath.Attributes attr = entry.getValue();
521                 assert !attr.runInBatchMode() || (batchClassLoader != null);
522                 ClassLoader JavaDoc cl = attr.runInBatchMode() ? batchClassLoader : iterativeClassLoader;
523                 
524                 // First the Java 5 factories in this container...
525
List JavaDoc<AnnotationProcessorFactory> java5FactoriesInContainer;
526                 java5FactoriesInContainer = loadJava5FactoryClasses(fc, cl, project, failureHandler);
527                 for ( AnnotationProcessorFactory apf : java5FactoriesInContainer ) {
528                     java5Factories.put( apf, entry.getValue() );
529                 }
530                 
531                 if (AptPlugin.canRunJava6Processors()) {
532                     // Now the Java 6 factories. Use the same classloader for the sake of sanity.
533
List JavaDoc<IServiceFactory> java6FactoriesInContainer;
534                     java6FactoriesInContainer = loadJava6FactoryClasses(fc, cl, project, failureHandler);
535                     for ( IServiceFactory isf : java6FactoriesInContainer ) {
536                         java6Factories.put( isf, entry.getValue() );
537                     }
538                 }
539             }
540             catch (FileNotFoundException JavaDoc fnfe) {
541                 // it would be bizarre to get this, given that we already checked for file existence up above.
542
AptPlugin.log(fnfe, Messages.AnnotationProcessorFactoryLoader_jarNotFound + fnfe.getLocalizedMessage());
543             }
544             catch (IOException JavaDoc ioe) {
545                 AptPlugin.log(ioe, Messages.AnnotationProcessorFactoryLoader_ioError + ioe.getLocalizedMessage());
546             }
547         }
548         _project2Java5Factories.put(project, java5Factories);
549         _project2Java6Factories.put(project, java6Factories);
550     }
551
552     private List JavaDoc<AnnotationProcessorFactory> loadJava5FactoryClasses(
553             FactoryContainer fc, ClassLoader JavaDoc classLoader, IJavaProject jproj, LoadFailureHandler failureHandler )
554             throws IOException JavaDoc
555     {
556         Map JavaDoc<String JavaDoc, String JavaDoc> factoryNames = fc.getFactoryNames();
557         List JavaDoc<AnnotationProcessorFactory> factories = new ArrayList JavaDoc<AnnotationProcessorFactory>();
558         for ( Entry<String JavaDoc, String JavaDoc> entry : factoryNames.entrySet() )
559         {
560             if (AptPlugin.JAVA5_FACTORY_NAME.equals(entry.getValue())) {
561                 String JavaDoc factoryName = entry.getKey();
562                 AnnotationProcessorFactory factory;
563                 if ( fc.getType() == FactoryType.PLUGIN )
564                     factory = FactoryPluginManager.getJava5FactoryFromPlugin( factoryName );
565                 else
566                     factory = (AnnotationProcessorFactory)loadInstance( factoryName, classLoader, jproj, failureHandler );
567                 
568                 if ( factory != null )
569                     factories.add( factory );
570             }
571         }
572         return factories;
573     }
574     
575     private List JavaDoc<IServiceFactory> loadJava6FactoryClasses(
576             FactoryContainer fc, ClassLoader JavaDoc classLoader, IJavaProject jproj, LoadFailureHandler failureHandler )
577             throws IOException JavaDoc
578     {
579         Map JavaDoc<String JavaDoc, String JavaDoc> factoryNames = fc.getFactoryNames();
580         List JavaDoc<IServiceFactory> factories = new ArrayList JavaDoc<IServiceFactory>();
581         for ( Entry<String JavaDoc, String JavaDoc> entry : factoryNames.entrySet() )
582         {
583             if (AptPlugin.JAVA6_FACTORY_NAME.equals(entry.getValue())) {
584                 String JavaDoc factoryName = entry.getKey();
585                 IServiceFactory factory = null;
586                 if ( fc.getType() == FactoryType.PLUGIN ) {
587                     factory = FactoryPluginManager.getJava6FactoryFromPlugin( factoryName );
588                 }
589                 else {
590                     Class JavaDoc<?> clazz;
591                     try {
592                         clazz = classLoader.loadClass(factoryName);
593                         factory = new ClassServiceFactory(clazz);
594                     } catch (ClassNotFoundException JavaDoc e) {
595                         AptPlugin.trace("Unable to load annotation processor " + factoryName, e); //$NON-NLS-1$
596
failureHandler.addFailedFactory(factoryName);
597                     }
598                 }
599                 
600                 if ( factory != null )
601                     factories.add( factory );
602             }
603         }
604         return factories;
605     }
606     
607     /**
608      * Re-validate projects whose factory paths may have been affected
609      * by a resource change (e.g., adding a previously absent jar file).
610      * This will cause build problem markers to be removed and regenerated,
611      * and factory class caches to be cleared.
612      */

613     private void processChanges(Set JavaDoc<IJavaProject> affected, Map JavaDoc<IJavaProject,LoadFailureHandler> handlers) {
614         for (IJavaProject jproj : affected) {
615             removeAptBuildProblemMarkers(jproj);
616             uncacheProject(jproj);
617         }
618         // We will do another clear and re-verify when loadFactories()
619
// is called. But we have to do it then, because things might
620
// have changed in the interim; and if we don't do it here, then
621
// we'll have an empty _resources2Project cache, so we'll ignore
622
// all resource changes until the next build. Is that a problem?
623
for (IJavaProject jproj : affected) {
624             if (jproj.exists()) {
625                 Set JavaDoc<FactoryContainer> badContainers = verifyFactoryPath(jproj);
626                 if (badContainers != null) {
627                     LoadFailureHandler handler = handlers.get(jproj);
628                     if (handler == null) {
629                         handler = new LoadFailureHandler(jproj);
630                         handlers.put(jproj, handler);
631                     }
632                     for (FactoryContainer container : badContainers) {
633                         handler.addMissingLibrary(container.getId());
634                     }
635                 }
636             }
637         }
638     
639         // TODO: flag the affected projects for rebuild.
640
}
641
642     /**
643      * When a project is deleted, remove its factory path information from the loader.
644      * @param jproj
645      */

646     private void uncacheProject(IJavaProject jproj) {
647         _project2Java5Factories.remove(jproj);
648         _project2Java6Factories.remove(jproj);
649         ClassLoader JavaDoc c = _iterativeLoaders.remove(jproj);
650         if (c instanceof JarClassLoader)
651             ((JarClassLoader)c).close();
652         
653         ClassLoader JavaDoc cl = _batchLoaders.remove(jproj);
654         if (cl instanceof JarClassLoader) ((JarClassLoader)cl).close();
655         
656         removeProjectFromResourceMap(jproj);
657     }
658
659     /**
660      * Remove APT build problem markers, e.g., "missing factory jar".
661      * @param jproj if null, remove markers from all projects that have
662      * factory paths associated with them.
663      */

664     private void removeAptBuildProblemMarkers( IJavaProject jproj ) {
665         // note that _project2Java6Factories.keySet() should be same as that for Java5.
666
Set JavaDoc<IJavaProject> jprojects = (jproj == null) ? _project2Java5Factories.keySet() : Collections.singleton(jproj);
667         try {
668             for (IJavaProject jp : jprojects) {
669                 if (jp.exists()) {
670                     IProject p = jp.getProject();
671                     IMarker[] markers = p.findMarkers(AptPlugin.APT_LOADER_PROBLEM_MARKER, false, IResource.DEPTH_ZERO);
672                     if( markers != null ){
673                         for( IMarker marker : markers )
674                             marker.delete();
675                     }
676                 }
677             }
678         }
679         catch(CoreException e){
680             AptPlugin.log(e, "Unable to delete APT build problem marker"); //$NON-NLS-1$
681
}
682     }
683
684     /**
685      * Remove references to the project from _container2Project. This is done
686      * when a project is deleted, or before re-verifying the project's
687      * factory path.
688      */

689     private void removeProjectFromResourceMap(IJavaProject jproj) {
690         Iterator JavaDoc<Entry<String JavaDoc, Set JavaDoc<IJavaProject>>> i = _container2Project.entrySet().iterator();
691         while (i.hasNext()) {
692             Entry<String JavaDoc, Set JavaDoc<IJavaProject>> e = i.next();
693             Set JavaDoc<IJavaProject> s = e.getValue();
694             s.remove(jproj);
695             // Remove any resulting orphaned resources.
696
if (s.isEmpty()) {
697                 i.remove();
698             }
699         }
700     }
701     
702     /**
703      * Check the factory path for a project and ensure that all the
704      * containers it lists are available. Adds jar factory container
705      * resources to the _container2Project cache, whether or not the
706      * resource can actually be found.
707      *
708      * @param jproj the project, or null to check all projects that
709      * are in the cache.
710      * @return a Set of all invalid containers, or null if all containers
711      * on the path were valid.
712      */

713     private Set JavaDoc<FactoryContainer> verifyFactoryPath(IJavaProject jproj) {
714         Set JavaDoc<FactoryContainer> badContainers = null;
715         FactoryPath fp = FactoryPathUtil.getFactoryPath(jproj);
716         Map JavaDoc<FactoryContainer, FactoryPath.Attributes> containers = fp.getEnabledContainers();
717         for (FactoryContainer fc : containers.keySet()) {
718             if (fc instanceof JarFactoryContainer) {
719                 try {
720                     final File JavaDoc jarFile = ((JarFactoryContainer)fc).getJarFile();
721                     // if null, will add to bad container set below.
722
if( jarFile != null ){
723                         String JavaDoc key = jarFile.getCanonicalPath();
724                         addToResourcesMap(key, jproj);
725                     }
726                 } catch (IOException JavaDoc e) {
727                     // If there's something this malformed on the factory path,
728
// don't bother putting it on the resources map; we'll never
729
// get notified about a change to it anyway. It should get
730
// reported either as a bad container (below) or as a failure
731
// to load (later on).
732
}
733             }
734             if ( !fc.exists() ) {
735                 if (badContainers == null) {
736                     badContainers = new HashSet JavaDoc<FactoryContainer>();
737                 }
738                 badContainers.add(fc);
739             }
740         }
741         return badContainers;
742     }
743     
744     /**
745      * @param containers an ordered map.
746      */

747     private ClassLoader JavaDoc _createIterativeClassLoader( Map JavaDoc<FactoryContainer, FactoryPath.Attributes> containers )
748     {
749         ArrayList JavaDoc<File JavaDoc> fileList = new ArrayList JavaDoc<File JavaDoc>( containers.size() );
750         for (Map.Entry JavaDoc<FactoryContainer, FactoryPath.Attributes> entry : containers.entrySet()) {
751             FactoryPath.Attributes attr = entry.getValue();
752             FactoryContainer fc = entry.getKey();
753             if (!attr.runInBatchMode() && fc instanceof JarFactoryContainer) {
754                 JarFactoryContainer jfc = (JarFactoryContainer)fc;
755                 fileList.add( jfc.getJarFile() );
756             }
757         }
758         
759         ClassLoader JavaDoc cl;
760         if ( fileList.size() > 0 ) {
761             cl = createClassLoader( fileList, AnnotationProcessorFactoryLoader.class.getClassLoader() );
762         }
763         else {
764             cl = AnnotationProcessorFactoryLoader.class.getClassLoader();
765         }
766         return cl;
767     }
768     
769     private void _createBatchClassLoader(Map JavaDoc<FactoryContainer, FactoryPath.Attributes> containers,
770             IJavaProject p)
771     {
772         
773         ArrayList JavaDoc<File JavaDoc> fileList = new ArrayList JavaDoc<File JavaDoc>( containers.size() );
774         for (Map.Entry JavaDoc<FactoryContainer, FactoryPath.Attributes> entry : containers.entrySet()) {
775             FactoryPath.Attributes attr = entry.getValue();
776             FactoryContainer fc = entry.getKey();
777             if (attr.runInBatchMode() && fc instanceof JarFactoryContainer) {
778                 
779                 JarFactoryContainer jfc = (JarFactoryContainer)fc;
780                 File JavaDoc f = jfc.getJarFile();
781                 fileList.add( f );
782                 
783             }
784         }
785         
786         // Try to use the iterative CL as parent, so we can resolve classes within it
787
ClassLoader JavaDoc parentCL = _iterativeLoaders.get(p);
788         if (parentCL == null) {
789             parentCL = AnnotationProcessorFactoryLoader.class.getClassLoader();
790         }
791         
792         if ( fileList.size() > 0 ) {
793             _batchLoaders.put(p,createClassLoader( fileList, parentCL));
794         }
795     }
796     
797     private static ClassLoader JavaDoc createClassLoader(List JavaDoc<File JavaDoc> files, ClassLoader JavaDoc parentCL) {
798         //return new JarClassLoader(files, parentCL);
799
List JavaDoc<URL JavaDoc> urls = new ArrayList JavaDoc<URL JavaDoc>(files.size());
800         for (int i=0;i<files.size();i++) {
801             try {
802                 urls.add(files.get(i).toURI().toURL());
803             }
804             catch (MalformedURLException JavaDoc mue) {
805                 // ignore
806
}
807         }
808         URL JavaDoc[] urlArray = urls.toArray(new URL JavaDoc[urls.size()]);
809         return new URLClassLoader JavaDoc(urlArray, parentCL);
810     }
811 }
812
Popular Tags