/******************************************************************************* * Copyright (c) 2005, 2015 BEA Systems, Inc. and others * 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: * mkaufman@bea.com - initial API and implementation * IBM Corporation - modified to split files *******************************************************************************/ package org.eclipse.jdt.apt.core.internal; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.IWorkspaceRunnable; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jdt.apt.core.env.Phase; import org.eclipse.jdt.apt.core.internal.env.AbstractCompilationEnv; import org.eclipse.jdt.apt.core.internal.env.BuildEnv; import org.eclipse.jdt.apt.core.internal.env.EclipseRoundCompleteEvent; import org.eclipse.jdt.apt.core.internal.env.ReconcileEnv; import org.eclipse.jdt.apt.core.internal.env.AbstractCompilationEnv.EnvCallback; import org.eclipse.jdt.apt.core.internal.generatedfile.GeneratedFileManager; import org.eclipse.jdt.apt.core.internal.util.FactoryPath; import org.eclipse.jdt.apt.core.util.AptConfig; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.compiler.BuildContext; import org.eclipse.jdt.core.compiler.CategorizedProblem; import org.eclipse.jdt.core.compiler.ReconcileContext; import com.sun.mirror.apt.AnnotationProcessor; import com.sun.mirror.apt.AnnotationProcessorFactory; import com.sun.mirror.apt.AnnotationProcessorListener; import com.sun.mirror.apt.RoundCompleteListener; import com.sun.mirror.declaration.AnnotationTypeDeclaration; public class APTDispatchRunnable implements IWorkspaceRunnable { /** * This callback method is passed to a ReconcileEnv to be called within * an AST pipeline in order to process a type during reconcile. * Reconciles involve only one type at a time, but can recurse, so * that multiple instances of this class are on the stack at one time. */ private final class ReconcileEnvCallback implements EnvCallback { private final ReconcileContext _context; private final GeneratedFileManager _gfm; private ReconcileEnvCallback(ReconcileContext context, GeneratedFileManager gfm) { _context = context; _gfm = gfm; } public void run(AbstractCompilationEnv env) { // This is a ReconcileEnvCallback, so we better be dealing with a ReconcileEnv! ReconcileEnv reconcileEnv = (ReconcileEnv)env; // Dispatch the annotation processors. Env will keep track of problems and generated types. try { dispatchToFileBasedProcessor(reconcileEnv, true, true); } catch (Throwable t) { AptPlugin.log(t, "Processor failure during reconcile"); //$NON-NLS-1$ } // "Remove" any types that were generated in the past but not on this round. // Because this is a reconcile, if a file exists on disk we can't really remove // it, we can only create a blank WorkingCopy that hides it; thus, we can only // remove Java source files, not arbitrary files. ICompilationUnit parentWC = _context.getWorkingCopy(); Set<IFile> newlyGeneratedFiles = reconcileEnv.getAllGeneratedFiles(); _gfm.deleteObsoleteTypesAfterReconcile(parentWC, newlyGeneratedFiles); // Report problems to the ReconcileContext. final List<? extends CategorizedProblem> problemList = reconcileEnv.getProblems(); final int numProblems = problemList.size(); if (numProblems > 0) { final CategorizedProblem[] aptCatProblems = new CategorizedProblem[numProblems]; _context.putProblems( AptPlugin.APT_COMPILATION_PROBLEM_MARKER, problemList .toArray(aptCatProblems)); } // Tell the Env that the round is complete. // This also calls resetAST() on the context. reconcileEnv.close(); } } private static final BuildContext[] NO_FILES_TO_PROCESS = new BuildContext[0]; private static final int MAX_FILES_PER_ITERATION = 1000; private /*final*/ BuildContext[] _filesWithAnnotation = null; private /*final*/ BuildContext[] _filesWithoutAnnotation = null; private /*final*/ Map<IFile, CategorizedProblem[]> _problemRecorder = null; private final AptProject _aptProject; private final Map<AnnotationProcessorFactory, FactoryPath.Attributes> _factories; /** Batch processor dispatched in the previous rounds */ private final Set<AnnotationProcessorFactory> _dispatchedBatchFactories; /** Batch processor dispatched in the current round */ private Set<AnnotationProcessorFactory> _currentDispatchBatchFactories = Collections.emptySet(); private final boolean _isFullBuild; private static final boolean SPLIT_FILES; private static final String SPLIT_FILES_PROPERTY = "org.eclipse.jdt.apt.core.split_files"; //$NON-NLS-1$ static { String setting = System.getProperty(SPLIT_FILES_PROPERTY); SPLIT_FILES = setting == null || setting.equalsIgnoreCase("true"); //$NON-NLS-1$ } public static Set<AnnotationProcessorFactory> runAPTDuringBuild( BuildContext[] filesWithAnnotations, BuildContext[] filesWithoutAnnotations, Map<IFile, CategorizedProblem[]> problemRecorder, AptProject aptProject, Map<AnnotationProcessorFactory, FactoryPath.Attributes> factories, Set<AnnotationProcessorFactory> dispatchedBatchFactories, boolean isFullBuild){ if( filesWithAnnotations == null ){ filesWithAnnotations = NO_FILES_TO_PROCESS; } // If we're building, types can be generated, so we // want to run this as an atomic workspace operation APTDispatchRunnable runnable = new APTDispatchRunnable( filesWithAnnotations, filesWithoutAnnotations, problemRecorder, aptProject, factories, dispatchedBatchFactories, isFullBuild ); IWorkspace workspace = ResourcesPlugin.getWorkspace(); try { workspace.run(runnable, aptProject.getJavaProject().getResource(), IWorkspace.AVOID_UPDATE, null); } catch (CoreException ce) { AptPlugin.log(ce, "Could not run APT"); //$NON-NLS-1$ } return runnable._currentDispatchBatchFactories; } public static void runAPTDuringReconcile( ReconcileContext reconcileContext, AptProject aptProject, Map<AnnotationProcessorFactory, FactoryPath.Attributes> factories) { // Reconciling, so we do not want to run this as an atomic workspace // operation. If we do, it is easy to have locking issues when someone // calls a reconcile from within a workspace lock APTDispatchRunnable runnable = new APTDispatchRunnable( aptProject, factories ); runnable.reconcile(reconcileContext, aptProject.getJavaProject()); } /** create a runnable used during build */ private APTDispatchRunnable( BuildContext[] filesWithAnnotation, BuildContext[] filesWithoutAnnotation, Map<IFile, CategorizedProblem[]> problemRecorder, AptProject aptProject, Map<AnnotationProcessorFactory, FactoryPath.Attributes> factories, Set<AnnotationProcessorFactory> dispatchedBatchFactories, boolean isFullBuild) { assert filesWithAnnotation != null : "missing files"; //$NON-NLS-1$ _filesWithAnnotation = filesWithAnnotation; _filesWithoutAnnotation = filesWithoutAnnotation; _problemRecorder = problemRecorder; _aptProject = aptProject; _factories = factories; _dispatchedBatchFactories = dispatchedBatchFactories; _isFullBuild = isFullBuild; } /** create a runnable used during reconcile */ private APTDispatchRunnable( AptProject aptProject, Map<AnnotationProcessorFactory, FactoryPath.Attributes> factories) { _aptProject = aptProject; _factories = factories; _isFullBuild = false; // does not apply in reconcile case. we don't generate file during // reconcile and no apt rounding ever occur as a result. _dispatchedBatchFactories = Collections.emptySet(); } private void reconcile(final ReconcileContext reconcileContext, IJavaProject javaProject) { if (_factories.size() == 0) { if (AptPlugin.DEBUG) trace("apt leaving project " + javaProject.getProject() + //$NON-NLS-1$ " early because there are no factories", //$NON-NLS-1$ null); //TODO: clean up generated working copies here? I think not necessary. - WSH 10/06 return; } // Construct a reconcile time environment. This will invoke // dispatch from inside the callback. GeneratedFileManager gfm = _aptProject.getGeneratedFileManager(); gfm.reconcileStarted(); EnvCallback callback = new ReconcileEnvCallback(reconcileContext, gfm); AbstractCompilationEnv.newReconcileEnv(reconcileContext, callback); } public void run(IProgressMonitor monitor) { build(); } /** * Determine whether there are files to be processed. * @return <code>true</code> iff APT processing should occur, return <code>false</code> * otherwise. * * APT should should run one of the following is true * 1) There are files with annotations * 2) There are factories dispatched in an earlier round */ private boolean shouldBuild() { if( (_factories == null || _factories.size() == 0) && _dispatchedBatchFactories.isEmpty() ) return false; int totalFiles = _filesWithAnnotation == null ? 0 : _filesWithAnnotation.length; // We are required to dispatch even though there are no files with annotations. // This is a documented behavior in the mirror spec. return totalFiles > 0 || !_dispatchedBatchFactories.isEmpty(); } private void build(){ if ( !shouldBuild() ) { // tracing if ( AptPlugin.DEBUG ) { String msg; if ( (_factories == null || _factories.size() == 0) && _dispatchedBatchFactories.isEmpty() ) msg = "no AnnotationProcessoryFactory instances registered."; //$NON-NLS-1$ else msg = "no files to dispatch to."; //$NON-NLS-1$ trace( "run(): leaving project " + _aptProject.getJavaProject().getProject() + //$NON-NLS-1$ " early because there are " + msg, //$NON-NLS-1$ null); } cleanupAllGeneratedFiles(); } else { assert _filesWithAnnotation != null : "should never be invoked unless we are in build mode!"; //$NON-NLS-1$ EnvCallback buildCallback = new EnvCallback() { public void run(AbstractCompilationEnv env) { build((BuildEnv)env); } }; boolean split = false; if (SPLIT_FILES && !hasBatchFactory()) { // don't split the files if batch processors are present split = _filesWithAnnotation.length > MAX_FILES_PER_ITERATION ? true : false; } if (!split) { // Construct build environment, this invokes the build inside a callback // in order to keep open the DOM AST pipeline BuildEnv.newBuildEnv( _filesWithAnnotation, _filesWithoutAnnotation, _aptProject.getJavaProject(), buildCallback); } else { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=301894 for (int index = 0; index < _filesWithAnnotation.length;) { int numberToProcess = (index + MAX_FILES_PER_ITERATION) > _filesWithAnnotation.length ? _filesWithAnnotation.length - index : MAX_FILES_PER_ITERATION; BuildContext[] filesToProcess = new BuildContext[numberToProcess]; System.arraycopy(_filesWithAnnotation, index, filesToProcess, 0, numberToProcess); // Construct build environment, this invokes the build inside a callback // in order to keep open the DOM AST pipeline BuildEnv.newBuildEnv( filesToProcess, _filesWithoutAnnotation, _aptProject.getJavaProject(), buildCallback); index += numberToProcess; } } } // We need to save the file dependency state regardless of whether any Java 5 processing // was performed, because it may also contain Java 6 information. _aptProject.getGeneratedFileManager().writeState(); } /** * @param factories * @return <code>true</code> iff there are factories that can only be run in batch mode. */ private boolean hasBatchFactory() { for( FactoryPath.Attributes attr : _factories.values() ){ if( attr.runInBatchMode() ) return true; } return false; } /** * Batch processor should only be invoked during a clean build. * @param factories * @param processorEnv * @return <code>true</code> iff batch processors should be dispatched. * Return <code>false</code> otherwise. Return <code>false</code> if * there are no batch processors. */ private boolean shouldDispatchToBatchProcessor(final AbstractCompilationEnv processorEnv ) { return ( _isFullBuild && processorEnv.getPhase() == Phase.BUILD && hasBatchFactory() ); } private void runAPTInFileBasedMode(final BuildEnv processorEnv) { final BuildContext[] cpResults = processorEnv.getFilesWithAnnotation(); final GeneratedFileManager gfm = _aptProject.getGeneratedFileManager(); boolean projectEnablesReconcile = AptConfig.shouldProcessDuringReconcile(_aptProject.getJavaProject()); for (BuildContext curResult : cpResults ) { processorEnv.beginFileProcessing(curResult); dispatchToFileBasedProcessor(processorEnv, projectEnablesReconcile, false); reportResult( curResult, processorEnv.getAllGeneratedFiles(), processorEnv.getModifiedGeneratedFiles(), processorEnv.getProblems(), processorEnv.getTypeDependencies(), gfm, processorEnv); processorEnv.completedFileProcessing(); } } /** * @param curResult * @param lastGeneratedFiles files generated from previous apt run. * @param generatedFiles all files generated from current apt run. * @param modifiedGeneratedFiles new generated files or files differs from those from * previous run. * @param problems problems from current apt run. * @param deps * @param gfm * @param processorEnv */ private void reportResult( BuildContext curResult, Set<IFile> java5GeneratedFiles, Set<IFile> modifiedGeneratedFiles, List<? extends CategorizedProblem> problems, Set<String> deps, GeneratedFileManager gfm, BuildEnv processorEnv) { // Combine files generated by Java 5 and Java 6 processing phases Set<IFile> allGeneratedFiles = null; Set<IFile> java6GeneratedFiles = AptCompilationParticipant.getInstance().getJava6GeneratedFiles(); if (java5GeneratedFiles == null || java5GeneratedFiles.isEmpty()) { if (java6GeneratedFiles.isEmpty()) { allGeneratedFiles = Collections.emptySet(); } else { allGeneratedFiles = java6GeneratedFiles; } } else { if (java6GeneratedFiles.isEmpty()) { allGeneratedFiles = java5GeneratedFiles; } else { allGeneratedFiles = new HashSet<>(java6GeneratedFiles); allGeneratedFiles.addAll(java5GeneratedFiles); } } // figure out exactly what got deleted final List<IFile> deletedFiles = new ArrayList<>(); IFile parentFile = curResult.getFile(); cleanupNoLongerGeneratedFiles( parentFile, allGeneratedFiles, gfm, processorEnv, deletedFiles); // report newly created or modified generated files int numNewFiles = modifiedGeneratedFiles.size(); if( numNewFiles > 0 ){ final IFile[] newFilesArray = new IFile[numNewFiles]; curResult.recordAddedGeneratedFiles(modifiedGeneratedFiles.toArray(newFilesArray)); } // report deleted file. int numDeletedFiles = deletedFiles.size(); if(numDeletedFiles > 0){ final IFile[] deletedFilesArray = new IFile[numDeletedFiles]; curResult.recordDeletedGeneratedFiles(deletedFiles.toArray(deletedFilesArray)); } // report problems final int numProblems = problems.size(); if( numProblems > 0 ){ final CategorizedProblem[] catProblemsArray = new CategorizedProblem[numProblems]; curResult.recordNewProblems(problems.toArray(catProblemsArray)); // Tell compilation participant about the problems, so it can report them // again without reprocessing if a file is resubmitted. _problemRecorder.put(curResult.getFile(), catProblemsArray); } // report dependency final int numDeps = deps.size(); if( numDeps > 0 ){ final String[] depsArray = new String[numDeps]; curResult.recordDependencies(deps.toArray(depsArray)); } } /** * mixed mode - allow batch processor to be run as well as filed based ones. * @param processorEnv * @param currentRoundDispatchedBatchFactories output parameter. At return contains the * set of batch factories that has been dispatched. */ private void runAPTInMixedMode(final BuildEnv processorEnv) { final BuildContext[] cpResults = processorEnv.getFilesWithAnnotation(); final Map<BuildContext, Set<AnnotationTypeDeclaration>> file2AnnotationDecls = new HashMap<>(cpResults.length * 4/3 + 1); final Map<String, AnnotationTypeDeclaration> annotationDecls = processorEnv.getAllAnnotationTypes(file2AnnotationDecls); if (annotationDecls.isEmpty() && _dispatchedBatchFactories.isEmpty() ) { if ( AptPlugin.DEBUG ) trace( "runAPT: leaving early because annotationDecls is empty", //$NON-NLS-1$ processorEnv); return; } if( AptPlugin.DEBUG ) trace( "annotations found " + annotationDecls.keySet(), processorEnv); //$NON-NLS-1$ // file based processing factory to the set of annotations that it 'claims' final Map<AnnotationProcessorFactory, Set<AnnotationTypeDeclaration>> fileFactory2Annos = new HashMap<>( _factories.size() * 4/3 + 1 ); // batch processing factory to the set of annotations that it 'claims' final Map<AnnotationProcessorFactory, Set<AnnotationTypeDeclaration>> batchFactory2Annos = new HashMap<>( _factories.size() * 4/3 + 1 ); for( Map.Entry<AnnotationProcessorFactory, FactoryPath.Attributes> entry : _factories.entrySet() ){ AnnotationProcessorFactory factory = entry.getKey(); Set<AnnotationTypeDeclaration> annotationTypes = getFactorySupportedAnnotations(factory, annotationDecls); if( annotationTypes != null ){ boolean batch = entry.getValue().runInBatchMode(); Map<AnnotationProcessorFactory, Set<AnnotationTypeDeclaration> > factory2Annos = batch ? batchFactory2Annos : fileFactory2Annos; if( annotationTypes.size() == 0 ){ // this factory is claiming all (remaining) annotations. annotationTypes = new HashSet<>(annotationDecls.values()); factory2Annos.put(factory, annotationTypes); annotationDecls.clear(); break; } else{ factory2Annos.put(factory, annotationTypes); } } if( annotationDecls.isEmpty() ) break; } if( ! annotationDecls.isEmpty() ){ // TODO: (theodora) log unclaimed annotations? } // Dispatch to the batch process factories first. // Batch processors only get executed on a full/clean build if( !batchFactory2Annos.isEmpty() || (_dispatchedBatchFactories != null && !_dispatchedBatchFactories.isEmpty()) ){ processorEnv.beginBatchProcessing(); if( !batchFactory2Annos.isEmpty()){ // Once we figure out which factory claims what annotation, // the order of the factory doesn't matter. // But in order to make things consists between runs, will // dispatch base on factory order. _currentDispatchBatchFactories = new LinkedHashSet<>(); for(AnnotationProcessorFactory factory : _factories.keySet() ){ final Set<AnnotationTypeDeclaration> annotationTypes = batchFactory2Annos.get(factory); if( annotationTypes == null ) continue; final AnnotationProcessor processor = factory.getProcessorFor(annotationTypes, processorEnv); if( processor != null ){ if ( AptPlugin.DEBUG ) trace( "runAPT: invoking batch processor " + processor.getClass().getName(), //$NON-NLS-1$ processorEnv); _currentDispatchBatchFactories.add(factory); processorEnv.setCurrentProcessorFactory(factory, false); processor.process(); processorEnv.setCurrentProcessorFactory(null, false); } } } // We have to dispatch to factories even though we may not have discovered any annotations. // This is a documented APT behavior that we have to observe. for( AnnotationProcessorFactory prevRoundFactory : _dispatchedBatchFactories ){ if(_currentDispatchBatchFactories.contains(prevRoundFactory)) continue; final AnnotationProcessor processor = prevRoundFactory.getProcessorFor(Collections.<AnnotationTypeDeclaration>emptySet(), processorEnv); if( processor != null ){ if ( AptPlugin.DEBUG ) trace( "runAPT: invoking batch processor " + processor.getClass().getName(), //$NON-NLS-1$ processorEnv); processorEnv.setCurrentProcessorFactory(prevRoundFactory, false); processor.process(); processorEnv.setCurrentProcessorFactory(null, false); } } // Currently, we are putting everything in the first file annotations. // TODO: Is this correct? // Why is it ok (today): // 1) Problems are reported as IMarkers and not IProblem thru the // BuildContext API. // 2) jdt is currently not doing anything about the parent->generated file relation // so it doesn't matter which BuildContext we attach the // creation/modification/deletion of generated files. -theodora BuildContext firstResult = null; if( cpResults.length > 0 ) firstResult = cpResults[0]; else{ final BuildContext[] others = processorEnv.getFilesWithoutAnnotation(); if(others != null && others.length > 0 ) firstResult = others[0]; } // If there are no files to be built, apt will not be involved. assert firstResult != null : "don't know where to report results"; //$NON-NLS-1$ if(firstResult != null ){ final GeneratedFileManager gfm = _aptProject.getGeneratedFileManager(); reportResult( firstResult, // just put it all in processorEnv.getAllGeneratedFiles(), processorEnv.getModifiedGeneratedFiles(), processorEnv.getProblems(), // this is empty in batch mode. processorEnv.getTypeDependencies(), // this is empty in batch mode. gfm, processorEnv); } processorEnv.completedBatchProcessing(); } // Now, do the file based dispatch if( !fileFactory2Annos.isEmpty() ){ boolean projectEnablesReconcile = AptConfig.shouldProcessDuringReconcile(_aptProject.getJavaProject()); for(BuildContext curResult : cpResults ){ final Set<AnnotationTypeDeclaration> annotationTypesInFile = file2AnnotationDecls.get(curResult); if( annotationTypesInFile == null || annotationTypesInFile.isEmpty() ) continue; for(AnnotationProcessorFactory factory : _factories.keySet() ){ final Set<AnnotationTypeDeclaration> annotationTypesForFactory = fileFactory2Annos.get(factory); if( annotationTypesForFactory == null || annotationTypesForFactory.isEmpty() ) continue; final Set<AnnotationTypeDeclaration> intersect = setIntersect(annotationTypesInFile, annotationTypesForFactory); if( intersect != null && !intersect.isEmpty() ){ processorEnv.beginFileProcessing(curResult); final AnnotationProcessor processor = factory.getProcessorFor(intersect, processorEnv); if( processor != null ){ if ( AptPlugin.DEBUG ) trace( "runAPT: invoking file-based processor " + processor.getClass().getName(), //$NON-NLS-1$ processorEnv ); //TODO in 3.4: also consider factory path attributes boolean willReconcile = projectEnablesReconcile && AbstractCompilationEnv.doesFactorySupportReconcile(factory); processorEnv.setCurrentProcessorFactory(factory, willReconcile); processor.process(); processorEnv.setCurrentProcessorFactory(null, false); } } } final GeneratedFileManager gfm = _aptProject.getGeneratedFileManager(); reportResult( curResult, processorEnv.getAllGeneratedFiles(), processorEnv.getModifiedGeneratedFiles(), processorEnv.getProblems(), processorEnv.getTypeDependencies(), gfm, processorEnv); processorEnv.completedFileProcessing(); } } } /** * @param projectEnablesReconcile true if reconcile-time processing is enabled in the current project * @param isReconcile true if this call is during reconcile, e.g., processorEnv is a ReconcileEnv */ private void dispatchToFileBasedProcessor( final AbstractCompilationEnv processorEnv, boolean projectEnablesReconcile, boolean isReconcile){ Map<String, AnnotationTypeDeclaration> annotationDecls = processorEnv.getAnnotationTypes(); for( Map.Entry<AnnotationProcessorFactory, FactoryPath.Attributes> entry : _factories.entrySet() ){ if( entry.getValue().runInBatchMode() ) continue; AnnotationProcessorFactory factory = entry.getKey(); //TODO in 3.4: also consider factory path attributes boolean reconcileSupported = projectEnablesReconcile && AbstractCompilationEnv.doesFactorySupportReconcile(factory); if (isReconcile && !reconcileSupported) continue; Set<AnnotationTypeDeclaration> factoryDecls = getFactorySupportedAnnotations(factory, annotationDecls); if( factoryDecls != null ){ if(factoryDecls.size() == 0 ){ factoryDecls = new HashSet<>(annotationDecls.values()); annotationDecls.clear(); } } if (factoryDecls != null && factoryDecls.size() > 0) { final AnnotationProcessor processor = factory .getProcessorFor(factoryDecls, processorEnv); if (processor != null) { if ( AptPlugin.DEBUG ) { trace( "runAPT: invoking file-based processor " + processor.getClass().getName() + " on " + processorEnv.getFile(), //$NON-NLS-1$ //$NON-NLS-2$ processorEnv); } processorEnv.setCurrentProcessorFactory(factory, reconcileSupported); processor.process(); processorEnv.setCurrentProcessorFactory(null, false); } } if (annotationDecls.isEmpty()) break; } if( ! annotationDecls.isEmpty() ){ // TODO: (theodora) log unclaimed annotations. } } /** * @param processorEnv * @param filesWithMissingType * @param internalRound * @param result output parameter */ private Set<AnnotationProcessorFactory> build(final BuildEnv processorEnv) { try { boolean mixedModeDispatch = shouldDispatchToBatchProcessor(processorEnv); if( mixedModeDispatch ){ runAPTInMixedMode(processorEnv); } else{ runAPTInFileBasedMode(processorEnv); } // notify the processor listeners final Set<AnnotationProcessorListener> listeners = processorEnv .getProcessorListeners(); EclipseRoundCompleteEvent event = null; for (AnnotationProcessorListener listener : listeners) { if (listener instanceof RoundCompleteListener) { if (event == null) event = new EclipseRoundCompleteEvent(processorEnv); final RoundCompleteListener rcListener = (RoundCompleteListener) listener; rcListener.roundComplete(event); } } if( _filesWithoutAnnotation != null ){ cleanupAllGeneratedFilesFrom(_filesWithoutAnnotation); } // log unclaimed annotations. } catch (Error t) { // Don't catch junit exceptions. This prevents one from unit // testing a processor if (t.getClass().getName().startsWith("junit.framework")) //$NON-NLS-1$ throw t; AptPlugin.logWarning(t, "Unexpected failure running APT on the file(s): " + getFileNamesForPrinting(processorEnv)); //$NON-NLS-1$ } catch (Throwable t) { AptPlugin.logWarning(t, "Unexpected failure running APT on the file(s): " + getFileNamesForPrinting(processorEnv)); //$NON-NLS-1$ } finally { processorEnv.close(); } return Collections.emptySet(); } /** * @param one * @param two * @return the set intersect of the two given sets */ private Set<AnnotationTypeDeclaration> setIntersect(Set<AnnotationTypeDeclaration> one, Set<AnnotationTypeDeclaration> two ){ Set<AnnotationTypeDeclaration> intersect = null; for( AnnotationTypeDeclaration obj : one ){ if( two.contains(obj) ){ if( intersect == null ) intersect = new HashSet<>(); intersect.add(obj); } } return intersect; } private void cleanupAllGeneratedFiles(){ cleanupAllGeneratedFilesFrom(_filesWithAnnotation); cleanupAllGeneratedFilesFrom(_filesWithoutAnnotation); } private void cleanupAllGeneratedFilesFrom(BuildContext[] cpResults){ if (cpResults == null) { return; } final Set<IFile> deleted = new HashSet<>(); GeneratedFileManager gfm = _aptProject.getGeneratedFileManager(); Set<IFile> java6GeneratedFiles = AptCompilationParticipant.getInstance().getJava6GeneratedFiles(); for( BuildContext cpResult : cpResults){ final IFile parentFile = cpResult.getFile(); cleanupNoLongerGeneratedFiles( parentFile, java6GeneratedFiles, gfm, null, deleted); if( deleted.size() > 0 ){ final IFile[] deletedFilesArray = new IFile[deleted.size()]; cpResult.recordDeletedGeneratedFiles(deleted.toArray(deletedFilesArray)); } } } /** * Remove all the files that were previously generated * from a particular parent file, but that were not generated * in the most recent build pass. * <p> * Must be called during build phase, not reconcile * * @param parent the BuildContext associated with a single * compiled parent file * @param lastGeneratedFiles the files generated from parent * on the previous build; typically obtained from the GFM just * prior to beginning the current build. * @param newGeneratedFiles the files generated from parent * on the current build; typically stored in the BuildEnv, but * an empty set can be passed in to remove all generated files * of this parent. * @param gfm * @param processorEnv * @param deleted */ private void cleanupNoLongerGeneratedFiles( IFile parentFile, Set<IFile> newGeneratedFiles, GeneratedFileManager gfm, BuildEnv processorEnv, Collection<IFile> deleted) { deleted.addAll(gfm.deleteObsoleteFilesAfterBuild(parentFile, newGeneratedFiles)); } /** * @return the set of {@link AnnotationTypeDeclaration} that {@link #factory} supports or null * if the factory doesn't support any of the declarations. * If the factory supports "*", then the empty set will be returned * * This method will destructively modify {@link #declarations}. Entries will be removed from * {@link #declarations} as the declarations are being added into the returned set. */ private static Set<AnnotationTypeDeclaration> getFactorySupportedAnnotations( final AnnotationProcessorFactory factory, final Map<String, AnnotationTypeDeclaration> declarations) { final Collection<String> supportedTypes = factory .supportedAnnotationTypes(); if (supportedTypes == null || supportedTypes.size() == 0) return Collections.emptySet(); final Set<AnnotationTypeDeclaration> fDecls = new HashSet<>(); for (Iterator<String> it = supportedTypes.iterator(); it.hasNext();) { final String typeName = it.next(); if (typeName.equals("*")) { //$NON-NLS-1$ fDecls.addAll(declarations.values()); declarations.clear(); // Warn that * was claimed, which is non-optimal AptPlugin.logWarning(null, "Processor Factory " + factory + //$NON-NLS-1$ " claimed all annotations (*), which prevents any following factories from being dispatched."); //$NON-NLS-1$ } else if (typeName.endsWith("*")) { //$NON-NLS-1$ final String prefix = typeName.substring(0, typeName.length() - 2); for (Iterator<Map.Entry<String, AnnotationTypeDeclaration>> entries = declarations .entrySet().iterator(); entries.hasNext();) { final Map.Entry<String, AnnotationTypeDeclaration> entry = entries .next(); final String key = entry.getKey(); if (key.startsWith(prefix)) { fDecls.add(entry.getValue()); entries.remove(); } } } else { final AnnotationTypeDeclaration decl = declarations .get(typeName); if (decl != null) { fDecls.add(decl); declarations.remove(typeName); } } } return fDecls.isEmpty() ? null : fDecls; } private static void trace( String s, AbstractCompilationEnv processorEnv ) { if (AptPlugin.DEBUG) { if (processorEnv != null) { s = "[ phase = " + processorEnv.getPhase() + ", file = " + getFileNamesForPrinting(processorEnv) +" ] " + s; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } AptPlugin.trace( s ); } } private static String getFileNamesForPrinting(final AbstractCompilationEnv env){ if( env instanceof ReconcileEnv ){ return env.getFile().getName(); } else{ return getFileNamesForPrinting((BuildEnv)env); } } /** * For debugging statements only!! * @return the names of the files that we are currently processing. */ private static String getFileNamesForPrinting(final BuildEnv processorEnv){ final IFile file = processorEnv.getFile(); if( file != null ) return file.getName(); final BuildContext[] results = processorEnv.getFilesWithAnnotation(); final int len = results.length; switch( len ) { case 0: return "no file(s)"; //$NON-NLS-1$ case 1: return results[0].getFile().getName(); default: StringBuilder sb = new StringBuilder(); boolean firstItem = true; for (BuildContext curResult : results) { if (firstItem) { firstItem = false; } else { sb.append(", "); //$NON-NLS-1$ } sb.append(curResult.getFile().getName()); } return sb.toString(); } } }