/******************************************************************************* * Copyright (c) 2005, 2015 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: * mkaufman@bea.com - initial API and implementation * het@google.com - Bug 459601 - [clean up] Use CompilationParticipant.buildFinished() in AptCompilationParticipant *******************************************************************************/ package org.eclipse.jdt.apt.core.internal; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; 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.JavaCore; import org.eclipse.jdt.core.compiler.BuildContext; import org.eclipse.jdt.core.compiler.CategorizedProblem; import org.eclipse.jdt.core.compiler.CompilationParticipant; import org.eclipse.jdt.core.compiler.ReconcileContext; import com.sun.mirror.apt.AnnotationProcessorFactory; /** * A singleton object, created by callback through the * org.eclipse.jdt.core.compilationParticipants extension point. */ public class AptCompilationParticipant extends CompilationParticipant { /** * Batch factories that claimed some annotation in a previous round of APT processing. * This currently only apply to the build case since are only generating types during build * and hence cause APT rounding. * The set is an order preserving. The order is determined by their first invocation. */ private Set<AnnotationProcessorFactory> _previousRoundsBatchFactories = new LinkedHashSet<>(); private int _buildRound = 0; private boolean _isBatch = false; private static final AptCompilationParticipant INSTANCE = new AptCompilationParticipant(); /** * Files that has been processed by apt during the current build. * Files that has been compiled may need re-compilation (from jdt's perspective) * because of newly generated types. APT only process each file once during a build and * this set will prevent unnecessary/incorrect compilation of already processed files. */ private Map<IFile, CategorizedProblem[]> _processedFiles = null; /** * Files generated by Java 6 annotation processing during the current build. * These must be stored here in order to communicate between the Java 6 annotation * processing phase, which runs during Java compilation, and the Java 5 phase, * which runs afterwards. This member is reinitialized during aboutToBuild() * and cleared in buildComplete(). * * Doing it this way implies that files can only be generated by Java 6 processing * during a build. That is true as of 8/07. If that changes, then this may need * to be stored as a thread local, or elsewhere entirely. */ private HashSet<IFile> _java6GeneratedFiles = null; public static AptCompilationParticipant getInstance() { return INSTANCE; } /** * This class is constructed indirectly, by registering an extension to the * org.eclipse.jdt.core.compilationParticipants extension point. Other * clients should NOT construct this object. */ private AptCompilationParticipant() { } public boolean isAnnotationProcessor(){ return true; } public void buildStarting(BuildContext[] files, boolean isBatch){ // this gets called multiple times during a build. // This gets called: // 1) after "aboutToBuild" is called. // 2) everytime an incremental build occur because of newly generated files // this gets called. if( _buildRound == 0 ) _isBatch = isBatch; } public void buildFinished(IJavaProject project) { buildComplete(); } public void processAnnotations(BuildContext[] allfiles) { // This should not happen. There should always be file that that needs // building when final int total = allfiles == null ? 0 : allfiles.length; if( total == 0 ) return; final IProject project = allfiles[0].getFile().getProject(); final IJavaProject javaProject = JavaCore.create(project); // Don't dispatch on pre-1.5 project. They cannot legally have annotations String javaVersion = javaProject.getOption("org.eclipse.jdt.core.compiler.source", true); //$NON-NLS-1$ // Check for 1.3 or 1.4, as we don't want this to break in the future when 1.6 // is a possibility if ("1.3".equals(javaVersion) || "1.4".equals(javaVersion)) { //$NON-NLS-1$ //$NON-NLS-2$ return; } if ( _isBatch && _buildRound == 0 ) { AnnotationProcessorFactoryLoader.getLoader().resetBatchProcessors(javaProject); _previousRoundsBatchFactories.clear(); } try { // split up the list of files with annotations from those that don't // also exclude files that has already been processed. int annoFileCount = 0; int noAnnoFileCount = 0; for( int i=0; i<total; i++ ){ BuildContext bc = allfiles[i]; if( _buildRound > 0 && _processedFiles.containsKey( bc.getFile() )){ // We've already processed this file; we'll skip reprocessing it, on // the assumption that nothing would change, but we need to re-report // any problems we reported earlier because JDT will have cleared them. CategorizedProblem[] problems = _processedFiles.get(bc.getFile()); if (null != problems && problems.length > 0) { bc.recordNewProblems(problems); } continue; } if( bc.hasAnnotations() ) annoFileCount ++; else noAnnoFileCount ++; } // apt has already processed all files // files that are reported at this point is triggered by // dependencies introduced by type creation. if( annoFileCount == 0 && noAnnoFileCount == 0 ) return; BuildContext[] withAnnotation = null; BuildContext[] withoutAnnotation = null; if( annoFileCount != 0 ) withAnnotation = new BuildContext[annoFileCount]; if(noAnnoFileCount != 0 ) withoutAnnotation = new BuildContext[noAnnoFileCount]; int wIndex = 0; // index for 'withAnnotation' array int woIndex = 0; // index of 'withoutAnnotation' array for( int i=0; i<total; i++ ){ if( _processedFiles.containsKey( allfiles[i].getFile() ) ) continue; if( allfiles[i].hasAnnotations() ) withAnnotation[wIndex ++] = allfiles[i]; else withoutAnnotation[woIndex ++] = allfiles[i]; } for( BuildContext file : allfiles ) _processedFiles.put(file.getFile(), null); Map<AnnotationProcessorFactory, FactoryPath.Attributes> factories = AnnotationProcessorFactoryLoader.getLoader().getJava5FactoriesAndAttributesForProject(javaProject); AptProject aptProject = AptPlugin.getAptProject(javaProject); Set<AnnotationProcessorFactory> dispatchedBatchFactories = APTDispatchRunnable.runAPTDuringBuild( withAnnotation, withoutAnnotation, _processedFiles, aptProject, factories, _previousRoundsBatchFactories, _isBatch); _previousRoundsBatchFactories.addAll(dispatchedBatchFactories); } finally { _buildRound ++; } } public void reconcile(ReconcileContext context){ final ICompilationUnit workingCopy = context.getWorkingCopy(); if( workingCopy == null ) return; IJavaProject javaProject = workingCopy.getJavaProject(); if( javaProject == null ) return; if (!AptConfig.shouldProcessDuringReconcile(javaProject)) { AptPlugin.trace("Reconcile-time processing is disabled for project: " + javaProject.getElementName()); //$NON-NLS-1$ return; } AptProject aptProject = AptPlugin.getAptProject(javaProject); Map<AnnotationProcessorFactory, FactoryPath.Attributes> factories = AnnotationProcessorFactoryLoader.getLoader().getJava5FactoriesAndAttributesForProject( javaProject ); APTDispatchRunnable.runAPTDuringReconcile(context, aptProject, factories); } public void cleanStarting(IJavaProject javaProject){ IProject p = javaProject.getProject(); AptPlugin.getAptProject(javaProject).projectClean( true ); try{ // clear out all markers during a clean. IMarker[] markers = p.findMarkers(AptPlugin.APT_BATCH_PROCESSOR_PROBLEM_MARKER, true, IResource.DEPTH_INFINITE); if( markers != null ){ for( IMarker marker : markers ) marker.delete(); } } catch(CoreException e){ AptPlugin.log(e, "Unable to delete batch annotation processor markers"); //$NON-NLS-1$ } } /** * Does APT have anything to do for this project? * Even if there are no processors on the factory path, apt may still * be involved during a clean. */ public boolean isActive(IJavaProject project){ return AptConfig.isEnabled(project); } public int aboutToBuild(IJavaProject project) { if (AptConfig.isEnabled(project)) { // setup the classpath and make sure the generated source folder is on disk. AptPlugin.getAptProject(project).compilationStarted(); } _buildRound = 0; // reset // Note that for each project build, we blow away the last project's processed files. _processedFiles = new HashMap<>(); _java6GeneratedFiles = new HashSet<>(); // TODO: (wharley) if the factory path is different we need a full build return CompilationParticipant.READY_FOR_BUILD; } /** * Called during Java 6 annotation processing phase to register newly-generated files. * This information is then used in the Java 5 (post-compilation) phase when * determining no-longer-generated files to delete. The list of files is discarded * at the end of each build. */ public void addJava6GeneratedFile(IFile file) { _java6GeneratedFiles.add(file); } /** * Get the files generated during this build by Java 6 processors. * This is only meaningful in the context of a build, not a reconcile. * @return an unmodifiable, non-null but possibly empty, set of IFiles. */ public Set<IFile> getJava6GeneratedFiles() { if (null == _java6GeneratedFiles) { return Collections.emptySet(); } return Collections.unmodifiableSet(_java6GeneratedFiles); } private void buildComplete() { _processedFiles = null; _java6GeneratedFiles = null; } }