Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: ccc1eee0651fd5737fdb868829737a0b0d5574c6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
 /*******************************************************************************
 * Copyright (c) 2005, 2018 BEA Systems, Inc. and others
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * 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.internal.util.TestCodeUtil;
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()	{
	}
	
	@Override
	public boolean isAnnotationProcessor(){
		return true;
	}
	
	@Override
	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;
	}
	
	@Override
	public void buildFinished(IJavaProject project) {
		buildComplete();
	}
	
	@Override
	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;
			boolean test = false;
			for( int i=0; i<total; i++ ){
				BuildContext bc = allfiles[i];
				if(bc.isTestCode()) {
					test = true;
				}
				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,
						test);
			_previousRoundsBatchFactories.addAll(dispatchedBatchFactories);
		}
		finally {			
			_buildRound ++;
		}
	}
	
	@Override
	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, TestCodeUtil.isTestCode(workingCopy));
	}
	
	@Override
	public void cleanStarting(IJavaProject javaProject){
		IProject p = javaProject.getProject();
		
		AptPlugin.getAptProject(javaProject).projectClean( true, true, 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.
	 */
	@Override
	public boolean isActive(IJavaProject project){
		return AptConfig.isEnabled(project);
	}
	
	@Override
	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;
	}
}

Back to the top