| /******************************************************************************* |
| * 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: |
| * tyeung@bea.com - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.jdt.apt.core.internal.env; |
| |
| import java.io.BufferedInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.util.*; |
| |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IMarker; |
| import org.eclipse.core.resources.IWorkspace; |
| import org.eclipse.core.resources.IWorkspaceRunnable; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.jdt.apt.core.env.Phase; |
| import org.eclipse.jdt.apt.core.internal.AptPlugin; |
| import org.eclipse.jdt.apt.core.internal.declaration.EclipseMirrorObject; |
| import org.eclipse.jdt.apt.core.internal.declaration.TypeDeclarationImpl; |
| import org.eclipse.jdt.apt.core.internal.env.MessagerImpl.Severity; |
| import org.eclipse.jdt.apt.core.internal.util.Factory; |
| import org.eclipse.jdt.apt.core.internal.util.Visitors.AnnotatedNodeVisitor; |
| import org.eclipse.jdt.apt.core.internal.util.Visitors.AnnotationVisitor; |
| import org.eclipse.jdt.core.ICompilationUnit; |
| import org.eclipse.jdt.core.IJavaProject; |
| import org.eclipse.jdt.core.JavaCore; |
| import org.eclipse.jdt.core.compiler.CategorizedProblem; |
| import org.eclipse.jdt.core.compiler.BuildContext; |
| import org.eclipse.jdt.core.dom.*; |
| import com.sun.mirror.apt.Filer; |
| import com.sun.mirror.declaration.AnnotationTypeDeclaration; |
| import com.sun.mirror.declaration.PackageDeclaration; |
| import com.sun.mirror.declaration.TypeDeclaration; |
| |
| public class BuildEnv extends AbstractCompilationEnv |
| { |
| private boolean _hasRaisedErrors = false; |
| |
| private final BuildFilerImpl _filer; |
| |
| /** |
| * Set of strings that indicate new type dependencies introduced on the file |
| * each string is a fully-qualified type name. |
| */ |
| private Set<String> _typeDependencies = new HashSet<String>(); |
| |
| /** |
| * Indicates whether we are in batch mode or not. This gets flipped only |
| * during build and could be flipped back and forth. |
| */ |
| private boolean _batchMode = false; // off by default. |
| |
| /** |
| * Holds all the files that contains annotation that are to be processed during build. |
| * If we are not in batch mode, <code>super._file</code> holds the file |
| * being processed at the time. |
| */ |
| private BuildContext[] _filesWithAnnotation = null; |
| |
| /** |
| * These are files that are part of a build but does not have annotations on it. |
| * During batch mode processing, these files still also need to be included. |
| */ |
| private BuildContext[] _additionFiles = null; |
| /** |
| * This is intialized when <code>_batchMode</code> is set to be <code>true</code> or |
| * when batch processing is expected. <p> |
| * It is also set in build mode for perf reason rather than parsing and resolving |
| * each file individually. |
| * @see #getAllAnnotationTypes(Map) |
| */ |
| private CompilationUnit[] _astRoots = null; |
| private List<MarkerInfo> _markerInfos = null; |
| |
| /** |
| * Constructor for creating a processor environment used during build. |
| * @param filesWithAnnotations |
| * @param additionalFiles |
| * @param units |
| * @param javaProj |
| * @param phase |
| */ |
| BuildEnv( |
| final BuildContext[] filesWithAnnotations, |
| final BuildContext[] additionalFiles, |
| final IJavaProject javaProj) { |
| |
| super(null, null, javaProj, Phase.BUILD); |
| _filer = new BuildFilerImpl(this); |
| _filesWithAnnotation = filesWithAnnotations; |
| _additionFiles = additionalFiles; |
| _problems = new ArrayList<APTProblem>(); |
| _markerInfos = new ArrayList<MarkerInfo>(); |
| |
| if (AptPlugin.DEBUG_COMPILATION_ENV) AptPlugin.trace( |
| "constructed " + this + " for " + _filesWithAnnotation.length + " files"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| } |
| |
| public Filer getFiler() |
| { |
| checkValid(); |
| return _filer; |
| } |
| public PackageDeclaration getPackage(String name) |
| { |
| checkValid(); |
| return super.getPackage(name); |
| } |
| |
| public TypeDeclaration getTypeDeclaration(String name) |
| { |
| checkValid(); |
| TypeDeclaration decl = super.getTypeDeclaration(name); |
| |
| if (!_batchMode) |
| addTypeDependency(name); |
| |
| return decl; |
| } |
| |
| /** |
| * @return true iff errors (MessagerImpl.Severity.Error) has been posted |
| * Always return false when this environment is closed. |
| */ |
| public boolean hasRaisedErrors(){ |
| return _hasRaisedErrors; |
| } |
| |
| public static InputStreamReader getFileReader( final IFile file ) throws IOException, CoreException { |
| return new InputStreamReader(getInputStream(file), file.getCharset()); |
| } |
| |
| public static InputStream getInputStream( final IFile file ) throws IOException, CoreException { |
| return new BufferedInputStream(file.getContents()); |
| } |
| |
| /** |
| * @return true iff class files has been generated. |
| * Always return false when this environment is closed. |
| */ |
| public boolean hasGeneratedClassFiles(){ return _filer.hasGeneratedClassFile(); } |
| |
| /* (non-Javadoc) |
| * Once the environment is closed the following is not allowed |
| * 1) posting messge |
| * 2) generating file |
| * 3) retrieving type or package by name |
| * 4) add or remove listeners |
| */ |
| public void close(){ |
| if( isClosed() ) |
| return; |
| _markerInfos = null; |
| _astRoot = null; |
| _file = null; |
| _astRoots = null; |
| _filesWithAnnotation = null; |
| _problems = null; |
| _modelCompUnit2astCompUnit.clear(); |
| _hasRaisedErrors = false; |
| super.close(); |
| } |
| |
| /** |
| * |
| * @param resource null to indicate current resource |
| * @param start the starting offset of the marker |
| * @param end -1 to indicate unknow ending offset. |
| * @param severity the severity of the marker |
| * @param msg the message on the marker |
| * @param line the line number of where the marker should be |
| */ |
| void addMessage(IFile resource, |
| int start, |
| int end, |
| Severity severity, |
| String msg, |
| int line, |
| String[] arguments) |
| { |
| checkValid(); |
| |
| if( resource == null ) |
| resource = getFile(); |
| |
| _hasRaisedErrors |= severity == MessagerImpl.Severity.ERROR; |
| |
| // Eclipse doesn't support INFO-level IProblems, so we send them to the log instead. |
| if ( severity == Severity.INFO) { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("Informational message reported by annotation processor:\n"); //$NON-NLS-1$ |
| sb.append(msg); |
| sb.append("\n"); //$NON-NLS-1$ |
| if (resource != null) { |
| sb.append("Resource="); //$NON-NLS-1$ |
| sb.append(resource.getName()); |
| sb.append("; "); //$NON-NLS-1$ |
| } |
| sb.append("starting offset="); //$NON-NLS-1$ |
| sb.append(start); |
| sb.append("; ending offset="); //$NON-NLS-1$ |
| sb.append(end); |
| sb.append("; line="); //$NON-NLS-1$ |
| sb.append(line); |
| if (arguments != null) { |
| sb.append("; arguments:"); //$NON-NLS-1$ |
| for (String s : arguments) { |
| sb.append("\n"); //$NON-NLS-1$ |
| sb.append(s); |
| } |
| } |
| else { |
| sb.append("\n"); //$NON-NLS-1$ |
| } |
| IStatus status = AptPlugin.createInfoStatus(null, sb.toString()); |
| AptPlugin.log(status); |
| return; |
| } |
| |
| if( resource == null ){ |
| assert _batchMode : "not in batch mode but don't know about current resource"; //$NON-NLS-1$ |
| addMarker(start, end, severity, msg, line, arguments); |
| } |
| else |
| addProblem(resource, start, end, severity, msg, line, arguments); |
| } |
| |
| private void addProblem( |
| IFile resource, |
| int start, |
| int end, |
| Severity severity, |
| String msg, |
| int line, |
| String[] arguments) |
| { |
| |
| APTProblem problem = createProblem(resource, start, end, severity, msg, line, arguments); |
| _problems.add(problem); |
| } |
| |
| private void addMarker( |
| int start, |
| int end, |
| Severity severity, |
| String msg, |
| int line, |
| String[] arguments) |
| { |
| |
| // Note that the arguments are ignored -- no quick-fix for markers. |
| _markerInfos.add(new MarkerInfo(start, end, severity, msg, line)); |
| } |
| |
| public Map<String, AnnotationTypeDeclaration> getAnnotationTypes() |
| { |
| checkValid(); |
| assert _astRoot != null && _file != null && !_batchMode : |
| "operation not available under batch mode."; //$NON-NLS-1$ |
| return super.getAnnotationTypes(); |
| } |
| |
| /** |
| * Return all annotations at declaration level within all compilation unit(s) |
| * associated with this environment. All the files associated with this environment will |
| * be parsed and resolved for all declaration level elements at the return of this call. |
| * |
| * @param file2Annotations populated by this method to map files to the annotation types |
| * if contains. May be null. |
| * @return the map containing all annotation types found within this environment. |
| */ |
| public Map<String, AnnotationTypeDeclaration> getAllAnnotationTypes( |
| final Map<BuildContext, Set<AnnotationTypeDeclaration>> file2Annotations) { |
| |
| checkValid(); |
| if( _filesWithAnnotation == null ) |
| return getAnnotationTypes(); |
| |
| final List<Annotation> instances = new ArrayList<Annotation>(); |
| final Map<String, AnnotationTypeDeclaration> decls = |
| new HashMap<String, AnnotationTypeDeclaration>(); |
| final AnnotationVisitor visitor = new AnnotationVisitor(instances); |
| for( int astIndex=0, len=_astRoots.length; astIndex<len; astIndex++ ){ |
| if( _astRoots == null || _astRoots[astIndex] == null ) |
| System.err.println(); |
| _astRoots[astIndex].accept(visitor); |
| final Set<AnnotationTypeDeclaration> perFileAnnos = new HashSet<AnnotationTypeDeclaration>(); |
| |
| for (int instanceIndex=0, size = instances.size(); instanceIndex < size; instanceIndex++) { |
| final Annotation instance = instances.get(instanceIndex); |
| final ITypeBinding annoType = instance.resolveTypeBinding(); |
| if (annoType == null) |
| continue; |
| final TypeDeclarationImpl decl = |
| Factory.createReferenceType(annoType, this); |
| if (decl.kind() == EclipseMirrorObject.MirrorKind.TYPE_ANNOTATION){ |
| final AnnotationTypeDeclaration annoDecl = (AnnotationTypeDeclaration)decl; |
| decls.put(annoDecl.getQualifiedName(), annoDecl); |
| perFileAnnos.add(annoDecl); |
| } |
| } |
| if( file2Annotations != null && !perFileAnnos.isEmpty() ) |
| file2Annotations.put(_filesWithAnnotation[astIndex], perFileAnnos); |
| visitor.reset(); |
| } |
| |
| return decls; |
| } |
| |
| /** |
| * @return - the extra type dependencies for the files under compilation |
| */ |
| public Set<String> getTypeDependencies() { return _typeDependencies; } |
| |
| /** |
| * Switch to batch processing mode. |
| * Note: Call to this method will cause all files associated with this environment to be |
| * read and parsed. |
| */ |
| public void beginBatchProcessing(){ |
| if( _phase != Phase.BUILD ) |
| throw new IllegalStateException("No batch processing outside build."); //$NON-NLS-1$ |
| |
| if( _batchMode ) return; |
| checkValid(); |
| |
| _batchMode = true; |
| _file = null; |
| _astRoot = null; |
| } |
| |
| public void completedBatchProcessing(){ |
| postMarkers(); |
| completedProcessing(); |
| } |
| |
| void createASTs(BuildContext[] cpResults){ |
| final int len = cpResults.length; |
| final ICompilationUnit[] units = new ICompilationUnit[len]; |
| for( int i=0; i<len; i++ ){ |
| // may return null if creation failed. this may occur if |
| // the file does not exists. |
| units[i] = JavaCore.createCompilationUnitFrom(cpResults[i].getFile()); |
| } |
| createASTs(_javaProject, units, _requestor = new CallbackRequestor(units)); |
| } |
| |
| public void beginFileProcessing(BuildContext result){ |
| if( result == null ) |
| throw new IllegalStateException("missing compilation result"); //$NON-NLS-1$ |
| _batchMode = false; |
| final IFile file = result.getFile(); |
| if( file.equals(_file) ) // this is a no-op |
| return; |
| |
| _astRoot = null; |
| _file = null; |
| |
| // need to match up the file with the ast. |
| if( _filesWithAnnotation != null ){ |
| for( int i=0, len=_filesWithAnnotation.length; i<len; i++ ){ |
| if( file.equals(_filesWithAnnotation[i].getFile()) ){ |
| _file = file; |
| _astRoot = _astRoots[i]; |
| } |
| } |
| } |
| |
| if( _file == null || _astRoot == null) |
| throw new IllegalStateException( |
| "file " + //$NON-NLS-1$ |
| file.getName() + |
| " is not in the list to be processed."); //$NON-NLS-1$ |
| } |
| |
| public void completedFileProcessing(){ |
| completedProcessing(); |
| } |
| |
| @Override |
| protected void completedProcessing(){ |
| _problems.clear(); |
| _typeDependencies.clear(); |
| super.completedProcessing(); |
| } |
| |
| public List<? extends CategorizedProblem> getProblems(){ |
| if( !_problems.isEmpty() ) |
| EnvUtil.updateProblemLength(_problems, getAstCompilationUnit()); |
| return _problems; |
| } |
| |
| // Implementation for EclipseAnnotationProcessorEnvironment |
| public CompilationUnit getAST() |
| { |
| if( _batchMode ) |
| return null; |
| return _astRoot; |
| } |
| |
| public void addTypeDependency(final String fullyQualifiedTypeName ) |
| { |
| if(!_batchMode){ |
| _typeDependencies.add( fullyQualifiedTypeName ); |
| } |
| } |
| // End of implementation for EclipseAnnotationProcessorEnvironment |
| |
| /** |
| * Include all the types from all files, files with and without annotations on it |
| * if we are in batch mode. Otherwise, just the types from the file that's currently |
| * being processed. |
| */ |
| @SuppressWarnings("unchecked") |
| protected List<AbstractTypeDeclaration> searchLocallyForTypeDeclarations() |
| { |
| if( !_batchMode ) |
| return super.searchLocallyForTypeDeclarations(); |
| final List<AbstractTypeDeclaration> typeDecls = new ArrayList<AbstractTypeDeclaration>(); |
| for( int i=0, len=_astRoots.length; i<len; i++ ) |
| typeDecls.addAll( _astRoots[i].types() ); |
| |
| getTypeDeclarationsFromAdditionFiles(typeDecls); |
| |
| return typeDecls; |
| } |
| |
| @SuppressWarnings("unchecked") |
| private void getTypeDeclarationsFromAdditionFiles(List<AbstractTypeDeclaration> typeDecls){ |
| if( _additionFiles == null || _additionFiles.length == 0 ) return; |
| |
| final int len = _additionFiles.length; |
| final ICompilationUnit[] units = new ICompilationUnit[len]; |
| for( int i=0; i<len; i++ ){ |
| // may return null if creation failed. this may occur if |
| // the file does not exists. |
| units[i] = JavaCore.createCompilationUnitFrom(_additionFiles[i].getFile()); |
| } |
| BaseRequestor r = new BaseRequestor(units); |
| createASTs(_javaProject, units, r); |
| |
| CompilationUnit[] asts = r.asts; |
| for( CompilationUnit ast : asts ){ |
| if( ast != null ){ |
| typeDecls.addAll( ast.types() ); |
| } |
| } |
| } |
| |
| protected Map<ASTNode, List<Annotation>> getASTNodesWithAnnotations() |
| { |
| if( !_batchMode ) |
| return super.getASTNodesWithAnnotations(); |
| final Map<ASTNode, List<Annotation>> astNode2Anno = new HashMap<ASTNode, List<Annotation>>(); |
| final AnnotatedNodeVisitor visitor = new AnnotatedNodeVisitor(astNode2Anno); |
| for( int i=0, len=_astRoots.length; i<len; i++ ) |
| _astRoots[i].accept( visitor ); |
| return astNode2Anno; |
| } |
| |
| protected IFile getFileForNode(final ASTNode node) |
| { |
| if( !_batchMode ) |
| return super.getFileForNode(node); |
| final CompilationUnit curAST = (CompilationUnit)node.getRoot(); |
| for( int i=0, len=_astRoots.length; i<len; i++ ){ |
| if( _astRoots[i] == curAST ) |
| return _filesWithAnnotation[i].getFile(); |
| } |
| throw new IllegalStateException(); |
| } |
| |
| /** |
| * Go through the list of compilation unit in this environment and looking for |
| * the declaration node of the given binding. |
| * @param binding |
| * @return the compilation unit that defines the given binding or null if no |
| * match is found. |
| */ |
| protected CompilationUnit searchLocallyForBinding(final IBinding binding) |
| { |
| if( !_batchMode ) |
| return super.searchLocallyForBinding(binding); |
| |
| for( int i=0, len=_astRoots.length; i<len; i++ ){ |
| ASTNode node = _astRoots[i].findDeclaringNode(binding); |
| if( node != null) |
| return _astRoots[i]; |
| } |
| return null; |
| } |
| |
| /** |
| * Go through the list of compilation unit in this environment and looking for |
| * the declaration node of the given binding. |
| * @param binding |
| * @return the compilation unit that defines the given binding or null if no |
| * match is found. |
| */ |
| protected IFile searchLocallyForIFile(final IBinding binding) |
| { |
| if( !_batchMode ) |
| return super.searchLocallyForIFile(binding); |
| |
| for( int i=0, len=_astRoots.length; i<len; i++ ){ |
| ASTNode node = _astRoots[i].findDeclaringNode(binding); |
| if( node != null) |
| return _filesWithAnnotation[i].getFile(); |
| } |
| return null; |
| } |
| |
| /** |
| * @param file |
| * @return the compilation unit associated with the given file. |
| * If the file is not one of those that this environment is currently processing, |
| * return null; |
| */ |
| public CompilationUnit getASTFrom(final IFile file) |
| { |
| if( file == null ) |
| return null; |
| else if( file.equals(_file) ) |
| return _astRoot; |
| else if( _astRoots != null ){ |
| for( int i=0, len=_filesWithAnnotation.length; i<len; i++ ){ |
| if( file.equals(_filesWithAnnotation[i].getFile()) ) |
| return _astRoots[i]; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * @return the current ast being processed if in per-file mode. |
| * If in batch mode, one of the asts being processed (no guarantee which |
| * one will be returned. |
| */ |
| protected AST getCurrentDietAST(){ |
| |
| if( _astRoot != null ) |
| return _astRoot.getAST(); |
| else{ |
| if( _astRoots == null ) |
| throw new IllegalStateException("no AST is available"); //$NON-NLS-1$ |
| return _astRoots[0].getAST(); |
| } |
| } |
| |
| void postMarkers() |
| { |
| if( _markerInfos == null || _markerInfos.size() == 0 ) |
| return; |
| // Posting all the markers to the workspace. Doing this in a batch process |
| // to minimize the amount of notification. |
| try{ |
| final IWorkspaceRunnable runnable = new IWorkspaceRunnable(){ |
| public void run(IProgressMonitor monitor) |
| { |
| for( MarkerInfo markerInfo : _markerInfos ){ |
| try{ |
| final IMarker marker = _javaProject.getProject().createMarker(AptPlugin.APT_BATCH_PROCESSOR_PROBLEM_MARKER); |
| markerInfo.copyIntoMarker(marker); |
| } |
| catch(CoreException e){ |
| AptPlugin.log(e, "Failure posting markers"); //$NON-NLS-1$ |
| } |
| } |
| } |
| }; |
| IWorkspace ws = _javaProject.getProject().getWorkspace(); |
| ws.run(runnable, null); |
| } |
| catch(CoreException e){ |
| AptPlugin.log(e, "Failed to post markers"); //$NON-NLS-1$ |
| } |
| finally{ |
| _markerInfos.clear(); |
| } |
| } |
| |
| public BuildContext[] getFilesWithAnnotation() |
| { |
| return _filesWithAnnotation; |
| } |
| |
| public BuildContext[] getFilesWithoutAnnotation() |
| { |
| return _additionFiles; |
| } |
| |
| private class CallbackRequestor extends BaseRequestor { |
| CallbackRequestor(ICompilationUnit[] parseUnits) { |
| super(parseUnits); |
| } |
| public void acceptBinding(String bindingKey, IBinding binding) { |
| // If we have recieved the last ast we have requested, |
| // then assign the asts, then begin dispatch |
| _astRoots = asts; |
| _callback.run(BuildEnv.this); |
| } |
| } |
| |
| } |