| /******************************************************************************* |
| * 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: |
| * mkaufman@bea.com - initial API and implementation |
| * |
| *******************************************************************************/ |
| |
| package org.eclipse.jdt.apt.core.internal; |
| |
| import java.util.HashMap; |
| 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.resources.IResourceChangeEvent; |
| import org.eclipse.core.resources.IResourceChangeListener; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| 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<AnnotationProcessorFactory>(); |
| private int _buildRound = 0; |
| private boolean _isBatch = false; |
| private static AptCompilationParticipant INSTANCE; |
| /** |
| * 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; |
| |
| 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. |
| */ |
| public AptCompilationParticipant() |
| { |
| INSTANCE = this; |
| |
| // Bug 180107: there is no CompilationParticipant.buildComplete() method, |
| // so we have to use a resource change listener instead. |
| IResourceChangeListener listener = new IResourceChangeListener() { |
| public void resourceChanged(IResourceChangeEvent event) { |
| buildComplete(); |
| } |
| }; |
| ResourcesPlugin.getWorkspace().addResourceChangeListener(listener, IResourceChangeEvent.POST_BUILD); |
| } |
| |
| 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 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<IFile, CategorizedProblem[]>(); |
| // TODO: (wharley) if the factory path is different we need a full build |
| return CompilationParticipant.READY_FOR_BUILD; |
| } |
| |
| private void buildComplete() { |
| _processedFiles = null; |
| } |
| } |