blob: 4eb6b613aa6e73c3172514f37b86c394476da95b [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:
* 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;
}
}