blob: 5513be710281bf47615f8d9b61f3e7a77c2cb1b8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005 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.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.apt.core.AptPlugin;
import org.eclipse.jdt.apt.core.internal.APTDispatch.APTResult;
import org.eclipse.jdt.apt.core.internal.declaration.TypeDeclarationImpl;
import org.eclipse.jdt.apt.core.internal.env.EclipseRoundCompleteEvent;
import org.eclipse.jdt.apt.core.internal.env.ProcessorEnvImpl;
import org.eclipse.jdt.apt.core.internal.env.ProcessorEnvImpl.AnnotationVisitor;
import org.eclipse.jdt.apt.core.internal.generatedfile.GeneratedFileManager;
import org.eclipse.jdt.apt.core.internal.util.Factory;
import org.eclipse.jdt.apt.core.util.ScannerUtil;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ITypeBinding;
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;
/*package*/ class APTDispatchRunnable implements IWorkspaceRunnable
{
private final IFile _file;
private final ICompilationUnit _compilationUnit;
private final IJavaProject _javaProject;
private final List<AnnotationProcessorFactory> _factories;
private final String _phaseName;
private final String _fileName;
private APTResult _result;
/*package*/ APTDispatchRunnable( IFile file, IJavaProject javaProject, List<AnnotationProcessorFactory> factories)
{
_compilationUnit = null;
_file = file;
_javaProject = javaProject;
_factories = factories;
_phaseName = "build";
_fileName = _file.toString();
}
/*package*/ APTDispatchRunnable( ICompilationUnit cu, IJavaProject javaProject, List<AnnotationProcessorFactory> factories)
{
_compilationUnit = cu;
_file = null;
_javaProject = javaProject;
_factories = factories;
_phaseName = "reconcile";
_fileName = _compilationUnit.getResource().toString();
}
public APTResult getResult() { return _result; }
public void run(IProgressMonitor monitor)
{
assert( _file == null || _compilationUnit == null ) : "Either _file should be null or _compilationUnit should be null.";
//
// bail-out early if there aren't factories, or if there aren't any annotation instances
//
if ( _factories == null || _factories.size() == 0 ||
( _compilationUnit != null && ! ScannerUtil.hasAnnotationInstance( _compilationUnit ) ) ||
( _file!= null && ! ScannerUtil.hasAnnotationInstance( _file ) ) )
{
// tracing
if ( AptPlugin.DEBUG )
{
String msg;
if ( _factories == null || _factories.size() == 0 )
msg = "no AnnotationProcessoryFactory instances registered.";
else
msg = "no annotation instances in file.";
trace( "run(): leaving early because there are " + msg );
}
IFile f;
if ( _file != null )
f = _file;
else
f = (IFile)_compilationUnit.getResource();
Set<IFile> deletedFiles = cleanupAllGeneratedFilesForParent( f );
if ( deletedFiles.size() == 0 )
_result = EMPTY_APT_RESULT;
else
_result = new APTResult( Collections.<IFile>emptySet(),
deletedFiles,
Collections.<String>emptySet(),
Collections.<IFile, List<IProblem>>emptyMap() );
}
else
{
ProcessorEnvImpl processorEnv;
if ( _compilationUnit != null )
{
processorEnv = ProcessorEnvImpl
.newProcessorEnvironmentForReconcile(_compilationUnit, _javaProject);
}
else
{
processorEnv = ProcessorEnvImpl
.newProcessorEnvironmentForBuild( _file, _javaProject);
}
_result = runAPT(_factories, processorEnv);
}
}
private APTResult runAPT(
final List<AnnotationProcessorFactory> factories,
final ProcessorEnvImpl processorEnv)
{
try {
if (factories.size() == 0)
{
if ( AptPlugin.DEBUG ) trace( "runAPT: leaving early because there are no factories");
return EMPTY_APT_RESULT;
}
if ( ! processorEnv.getFile().exists() )
{
if ( AptPlugin.DEBUG ) trace( "runAPT: leaving early because file doesn't exist");
return EMPTY_APT_RESULT;
}
final Map<String, AnnotationTypeDeclaration> annotationDecls = getAnnotationTypeDeclarations(
processorEnv.getAstCompilationUnit(), processorEnv);
if (annotationDecls.isEmpty())
{
if ( AptPlugin.DEBUG ) trace( "runAPT: leaving early because annotationDecls is empty" );
return EMPTY_APT_RESULT;
}
GeneratedFileManager gfm = GeneratedFileManager.getGeneratedFileManager( processorEnv.getJavaProject().getProject() );
Set<IFile> lastGeneratedFiles = gfm.getGeneratedFilesForParent( processorEnv.getFile() );
for (int i = 0, size = factories.size(); i < size; i++) {
final AnnotationProcessorFactory factory = (AnnotationProcessorFactory) factories.get(i);
Set<AnnotationTypeDeclaration> factoryDecls = getAnnotations(factory, annotationDecls);
boolean done = false;
if( factoryDecls != null ){
if(factoryDecls.size() == 0 ){
done = true;
factoryDecls = new HashSet(annotationDecls.values());
}
}
if (factoryDecls != null && factoryDecls.size() > 0) {
final AnnotationProcessor processor = factory
.getProcessorFor(factoryDecls, processorEnv);
if (processor != null)
{
if ( AptPlugin.DEBUG ) trace( "runAPT: invoking processor " + processor.getClass().getName() );
processor.process();
}
}
if (annotationDecls.isEmpty() || done)
break;
}
// TODO: (theodora) log unclaimed annotations.
// notify the processor listeners
final Set<AnnotationProcessorListener> listeners = processorEnv
.getProcessorListeners();
for (AnnotationProcessorListener listener : listeners) {
EclipseRoundCompleteEvent event = null;
if (listener instanceof RoundCompleteListener) {
if (event == null)
event = new EclipseRoundCompleteEvent(processorEnv);
final RoundCompleteListener rcListener = (RoundCompleteListener) listener;
rcListener.roundComplete(event);
}
}
final Set<IFile> allGeneratedFiles = new HashSet<IFile>();
Set<IFile> modifiedFiles = new HashSet<IFile>();
Map<IFile, Boolean> filesMap = processorEnv.getGeneratedFiles();
for (Map.Entry<IFile, Boolean> entry : filesMap.entrySet()) {
allGeneratedFiles.add(entry.getKey());
if (entry.getValue()) {
modifiedFiles.add(entry.getKey());
}
}
// any files that were generated for this parent on the last
// run, but are no longer generated should be removed
Set<IFile> deletedFiles = cleanupNoLongerGeneratedFiles( processorEnv.getFile(), lastGeneratedFiles, allGeneratedFiles, gfm );
APTResult result = new APTResult( modifiedFiles,
deletedFiles,
processorEnv.getTypeDependencies(),
processorEnv.getProblems() );
processorEnv.close();
return result;
// log unclaimed annotations.
} catch (Throwable t) {
t.printStackTrace();
}
return EMPTY_APT_RESULT;
}
private Set<IFile> cleanupAllGeneratedFilesForParent( IFile parent )
{
GeneratedFileManager gfm = GeneratedFileManager.getGeneratedFileManager( parent.getProject() );
Set<IFile> lastGeneratedFiles = gfm.getGeneratedFilesForParent( parent );
return cleanupNoLongerGeneratedFiles( parent, lastGeneratedFiles, Collections.<IFile>emptySet(), gfm );
}
private Set<IFile> cleanupNoLongerGeneratedFiles(
IFile parent, Set<IFile> lastGeneratedFiles, Set<IFile> newGeneratedFiles,
GeneratedFileManager gfm )
{
HashSet<IFile> deletedFiles = new HashSet<IFile>();
// make a copy into an array to avoid concurrent modification exceptions
IFile[] files = lastGeneratedFiles.toArray( new IFile[ lastGeneratedFiles.size() ] );
for ( int i = 0; i< files.length; i++ )
{
IFile f = files[i];
if ( ! newGeneratedFiles.contains( f ) )
{
if ( AptPlugin.DEBUG ) trace( "runAPT: File " + f + " is no longer a generated file for " + parent );
try
{
if ( gfm.deleteGeneratedFile( f, parent, null ) )
deletedFiles.add( f );
}
catch ( CoreException ce )
{
// TODO - handle this exception nicely
ce.printStackTrace();
}
}
}
return deletedFiles;
}
/**
* invoking annotation processors respecting apt semantics.
*/
private static void checkAnnotations(
final List<AnnotationProcessorFactory> factories,
final Map<String, AnnotationTypeDeclaration> declarations,
final ProcessorEnvImpl env) {
for (int i = 0, size = factories.size(); i < size; i++) {
final AnnotationProcessorFactory factory = (AnnotationProcessorFactory) factories
.get(i);
final Set<AnnotationTypeDeclaration> factoryDecls = getAnnotations(
factory, declarations);
final AnnotationProcessor processor = factory.getProcessorFor(
factoryDecls, env);
processor.process();
if (declarations.isEmpty())
return;
}
// log unclaimed annotations.
}
private static Map<String, AnnotationTypeDeclaration> getAnnotationTypeDeclarations(
CompilationUnit astCompilationUnit, ProcessorEnvImpl env) {
final List<Annotation> instances = new ArrayList<Annotation>();
final AnnotationVisitor visitor = new AnnotationVisitor(instances);
astCompilationUnit.accept(new AnnotationVisitor(instances));
final Map<String, AnnotationTypeDeclaration> decls = new HashMap<String, AnnotationTypeDeclaration>();
for (int i = 0, size = instances.size(); i < size; i++) {
final Annotation instance = instances.get(i);
final ITypeBinding annoType = instance.resolveTypeBinding();
if (annoType == null)
continue;
final TypeDeclarationImpl annoDecl = Factory.createReferenceType(
annoType, env);
if (annoDecl.kind() == EclipseMirrorImpl.MirrorKind.TYPE_ANNOTATION)
decls.put(annoDecl.getQualifiedName(),
(AnnotationTypeDeclaration) annoDecl);
}
return decls;
}
/**
* @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> getAnnotations(
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<AnnotationTypeDeclaration>();
for (Iterator<String> it = supportedTypes.iterator(); it.hasNext();) {
final String typeName = it.next();
if (typeName.equals("*")) {
declarations.clear();
return Collections.emptySet();
} else if (typeName.endsWith("*")) {
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((AnnotationTypeDeclaration) 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 void trace( String s )
{
if (AptPlugin.DEBUG)
{
s = "[ phase = " + _phaseName + ", file = " + _fileName +" ] " + s;
System.out.println( "[" + APTDispatch.class.getName() + "][ thread= " + Thread.currentThread().getName() + " ]"+ s );
}
}
public static final APTResult EMPTY_APT_RESULT = new APTResult();
}