• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.eclipse.org/org/documents/epl-v10.php
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.ide.eclipse.adt.internal.build;
18 
19 import com.android.ide.eclipse.adt.internal.build.builders.BaseBuilder;
20 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
21 import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
22 import com.android.sdklib.IAndroidTarget;
23 import com.android.sdklib.SdkConstants;
24 
25 import org.eclipse.core.resources.IFile;
26 import org.eclipse.core.resources.IFolder;
27 import org.eclipse.core.resources.IProject;
28 import org.eclipse.core.resources.IResource;
29 import org.eclipse.core.resources.IWorkspaceRoot;
30 import org.eclipse.core.resources.ResourcesPlugin;
31 import org.eclipse.core.runtime.CoreException;
32 import org.eclipse.core.runtime.IPath;
33 import org.eclipse.core.runtime.IProgressMonitor;
34 import org.eclipse.jdt.core.IJavaProject;
35 
36 import java.util.ArrayList;
37 import java.util.Collection;
38 import java.util.HashMap;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Set;
42 
43 /**
44  * Base class to handle generated java code.
45  *
46  * It provides management for modified source file list, deleted source file list, reconciliation
47  * of previous lists, storing the current state of the build.
48  *
49  */
50 public abstract class SourceProcessor {
51 
52     public final static int COMPILE_STATUS_NONE = 0;
53     public final static int COMPILE_STATUS_CODE = 0x1;
54     public final static int COMPILE_STATUS_RES = 0x2;
55 
56     /** List of all source files, their dependencies, and their output. */
57     private final Map<IFile, SourceFileData> mFiles = new HashMap<IFile, SourceFileData>();
58 
59     private final IJavaProject mJavaProject;
60     private final IFolder mGenFolder;
61     private final SourceChangeHandler mDeltaVisitor;
62 
63     /** List of source files pending compilation at the next build */
64     private final List<IFile> mToCompile = new ArrayList<IFile>();
65 
66     /** List of removed source files pending cleaning at the next build. */
67     private final List<IFile> mRemoved = new ArrayList<IFile>();
68 
69     private int mLastCompilationStatus = COMPILE_STATUS_NONE;
70 
71     /**
72      * Quotes a path inside "". If the platform is not windows, the path is returned as is.
73      * @param path the path to quote
74      * @return the quoted path.
75      */
quote(String path)76     public static String quote(String path) {
77         if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) {
78             return "\"" + path + "\"";
79         }
80 
81         return path;
82     }
83 
SourceProcessor(IJavaProject javaProject, IFolder genFolder, SourceChangeHandler deltaVisitor)84     protected SourceProcessor(IJavaProject javaProject, IFolder genFolder,
85             SourceChangeHandler deltaVisitor) {
86         mJavaProject = javaProject;
87         mGenFolder = genFolder;
88         mDeltaVisitor = deltaVisitor;
89 
90         mDeltaVisitor.init(this);
91 
92         IProject project = javaProject.getProject();
93 
94         // get all the source files
95         buildSourceFileList();
96 
97         // load the known dependencies
98         loadOutputAndDependencies();
99 
100         boolean mustCompile = loadState(project);
101 
102         // if we stored that we have to compile some files, we build the list that will compile them
103         // all. For now we have to reuse the full list since we don't know which files needed
104         // compilation.
105         if (mustCompile) {
106             mToCompile.addAll(mFiles.keySet());
107         }
108     }
109 
SourceProcessor(IJavaProject javaProject, IFolder genFolder)110     protected SourceProcessor(IJavaProject javaProject, IFolder genFolder) {
111         this(javaProject, genFolder, new SourceChangeHandler());
112     }
113 
114 
115     /**
116      * Returns whether the given file is an output of this processor by return the source
117      * file that generated it.
118      * @param file the file to test.
119      * @return the source file that generated the given file or null.
120      */
isOutput(IFile file)121     IFile isOutput(IFile file) {
122         for (SourceFileData data : mFiles.values()) {
123             if (data.generated(file)) {
124                 return data.getSourceFile();
125             }
126         }
127 
128         return null;
129     }
130 
131     /**
132      * Returns whether the given file is a dependency for other files by returning a list
133      * of file depending on the given file.
134      * @param file the file to test.
135      * @return a list of files that depend on the given file or an empty list if there
136      *    are no matches.
137      */
isDependency(IFile file)138     List<IFile> isDependency(IFile file) {
139         ArrayList<IFile> files = new ArrayList<IFile>();
140         for (SourceFileData data : mFiles.values()) {
141             if (data.dependsOn(file)) {
142                 files.add(data.getSourceFile());
143             }
144         }
145 
146         return files;
147     }
148 
addData(SourceFileData data)149     void addData(SourceFileData data) {
150         mFiles.put(data.getSourceFile(), data);
151     }
152 
getFileData(IFile file)153     SourceFileData getFileData(IFile file) {
154         return mFiles.get(file);
155     }
156 
getAllFileData()157     Collection<SourceFileData> getAllFileData() {
158         return mFiles.values();
159     }
160 
getChangeHandler()161     public final SourceChangeHandler getChangeHandler() {
162         return mDeltaVisitor;
163     }
164 
getJavaProject()165     final IJavaProject getJavaProject() {
166         return mJavaProject;
167     }
168 
getGenFolder()169     final IFolder getGenFolder() {
170         return mGenFolder;
171     }
172 
getToCompile()173     final List<IFile> getToCompile() {
174         return mToCompile;
175     }
176 
getRemovedFile()177     final List<IFile> getRemovedFile() {
178         return mRemoved;
179     }
180 
addFileToCompile(IFile file)181     final void addFileToCompile(IFile file) {
182         mToCompile.add(file);
183     }
184 
prepareFullBuild(IProject project)185     public final void prepareFullBuild(IProject project) {
186         mDeltaVisitor.reset();
187 
188         mToCompile.clear();
189         mRemoved.clear();
190 
191         // get all the source files
192         buildSourceFileList();
193 
194         mToCompile.addAll(mFiles.keySet());
195 
196         saveState(project);
197     }
198 
doneVisiting(IProject project)199     public final void doneVisiting(IProject project) {
200         // merge the previous file modification lists and the new one.
201         mergeFileModifications(mDeltaVisitor);
202 
203         mDeltaVisitor.reset();
204 
205         saveState(project);
206     }
207 
208     /**
209      * Returns the extension of the source files handled by this processor.
210      * @return
211      */
getExtension()212     protected abstract String getExtension();
213 
getSavePropertyName()214     protected abstract String getSavePropertyName();
215 
216     /**
217      * Compiles the source files and return a status bitmask of the type of file that was generated.
218      *
219      */
compileFiles(BaseBuilder builder, IProject project, IAndroidTarget projectTarget, int minSdkVersion, List<IPath> sourceFolders, IProgressMonitor monitor)220     public final int compileFiles(BaseBuilder builder,
221             IProject project, IAndroidTarget projectTarget, int minSdkVersion,
222             List<IPath> sourceFolders, IProgressMonitor monitor) throws CoreException {
223 
224         mLastCompilationStatus = COMPILE_STATUS_NONE;
225 
226         if (mToCompile.size() == 0 && mRemoved.size() == 0) {
227             return mLastCompilationStatus;
228         }
229 
230         // if a source file is being removed before we managed to compile it, it'll be in
231         // both list. We *need* to remove it from the compile list or it'll never go away.
232         for (IFile sourceFile : mRemoved) {
233             int pos = mToCompile.indexOf(sourceFile);
234             if (pos != -1) {
235                 mToCompile.remove(pos);
236             }
237         }
238 
239         // list of files that have failed compilation.
240         List<IFile> stillNeedCompilation = new ArrayList<IFile>();
241 
242         doCompileFiles(mToCompile, builder, project, projectTarget, minSdkVersion, sourceFolders,
243                 stillNeedCompilation, monitor);
244 
245         mToCompile.clear();
246         mToCompile.addAll(stillNeedCompilation);
247 
248         // Remove the files created from source files that have been removed.
249         for (IFile sourceFile : mRemoved) {
250             // look if we already know the output
251             SourceFileData data = getFileData(sourceFile);
252             if (data != null) {
253                 doRemoveFiles(data);
254             }
255         }
256 
257         // remove the associated file data.
258         for (IFile removedFile : mRemoved) {
259             mFiles.remove(removedFile);
260         }
261 
262         mRemoved.clear();
263 
264         // store the build state. If there are any files that failed to compile, we will
265         // force a full aidl compile on the next project open. (unless a full compilation succeed
266         // before the project is closed/re-opened.)
267         saveState(project);
268 
269         return mLastCompilationStatus;
270     }
271 
doCompileFiles( List<IFile> filesToCompile, BaseBuilder builder, IProject project, IAndroidTarget projectTarget, int targetApi, List<IPath> sourceFolders, List<IFile> notCompiledOut, IProgressMonitor monitor)272     protected abstract void doCompileFiles(
273             List<IFile> filesToCompile, BaseBuilder builder,
274             IProject project, IAndroidTarget projectTarget, int targetApi,
275             List<IPath> sourceFolders, List<IFile> notCompiledOut, IProgressMonitor monitor)
276             throws CoreException;
277 
278     /**
279      * Adds a compilation status. It can be any of (in combination too):
280      * <p/>
281      * {@link #COMPILE_STATUS_CODE} means this processor created source code files.
282      * {@link #COMPILE_STATUS_RES} means this process created resources.
283      */
setCompilationStatus(int status)284     protected void setCompilationStatus(int status) {
285         mLastCompilationStatus |= status;
286     }
287 
doRemoveFiles(SourceFileData data)288     protected void doRemoveFiles(SourceFileData data) throws CoreException {
289         List<IFile> outputFiles = data.getOutputFiles();
290         for (IFile outputFile : outputFiles) {
291             if (outputFile.exists()) {
292                 outputFile.getLocation().toFile().delete();
293             }
294         }
295     }
296 
loadState(IProject project)297     public final boolean loadState(IProject project) {
298         return ProjectHelper.loadBooleanProperty(project, getSavePropertyName(),
299                 true /*defaultValue*/);
300     }
301 
saveState(IProject project)302     public final void saveState(IProject project) {
303         // TODO: Optimize by saving only the files that need compilation
304         ProjectHelper.saveStringProperty(project, getSavePropertyName(),
305                 Boolean.toString(mToCompile.size() > 0));
306     }
307 
loadOutputAndDependencies()308     protected abstract void loadOutputAndDependencies();
309 
310 
getSourceFolderFor(IFile file)311     protected IPath getSourceFolderFor(IFile file) {
312         // find the source folder for the class so that we can infer the package from the
313         // difference between the file and its source folder.
314         List<IPath> sourceFolders = BaseProjectHelper.getSourceClasspaths(getJavaProject());
315         IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
316 
317         for (IPath sourceFolderPath : sourceFolders) {
318             IFolder sourceFolder = root.getFolder(sourceFolderPath);
319             // we don't look in the 'gen' source folder as there will be no source in there.
320             if (sourceFolder.exists() && sourceFolder.equals(getGenFolder()) == false) {
321                 // look for the source file parent, until we find this source folder.
322                 IResource parent = file;
323                 while ((parent = parent.getParent()) != null) {
324                     if (parent.equals(sourceFolder)) {
325                         return sourceFolderPath;
326                     }
327                 }
328             }
329         }
330 
331         return null;
332     }
333 
334     /**
335      * Goes through the build paths and fills the list of files to compile.
336      *
337      * @param project The project.
338      * @param sourceFolderPathList The list of source folder paths.
339      */
buildSourceFileList()340     private final void buildSourceFileList() {
341         mFiles.clear();
342 
343         IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
344         List<IPath> sourceFolderPathList = BaseProjectHelper.getSourceClasspaths(mJavaProject);
345 
346         for (IPath sourceFolderPath : sourceFolderPathList) {
347             IFolder sourceFolder = root.getFolder(sourceFolderPath);
348             // we don't look in the 'gen' source folder as there will be no source in there.
349             if (sourceFolder.exists() && sourceFolder.equals(getGenFolder()) == false) {
350                 scanFolderForSourceFiles(sourceFolder, sourceFolder);
351             }
352         }
353     }
354 
355     /**
356      * Scans a folder and fills the list of files to compile.
357      * @param sourceFolder the root source folder.
358      * @param folder The folder to scan.
359      */
scanFolderForSourceFiles(IFolder sourceFolder, IFolder folder)360     private void scanFolderForSourceFiles(IFolder sourceFolder, IFolder folder) {
361         try {
362             IResource[] members = folder.members();
363             for (IResource r : members) {
364                 // get the type of the resource
365                switch (r.getType()) {
366                    case IResource.FILE:
367                        // if this a file, check that the file actually exist
368                        // and that it's the type of of file that's used in this processor
369                        if (r.exists() &&
370                                getExtension().equalsIgnoreCase(r.getFileExtension())) {
371                            mFiles.put((IFile) r, new SourceFileData((IFile) r));
372                        }
373                        break;
374                    case IResource.FOLDER:
375                        // recursively go through children
376                        scanFolderForSourceFiles(sourceFolder, (IFolder)r);
377                        break;
378                    default:
379                        // this would mean it's a project or the workspace root
380                        // which is unlikely to happen. we do nothing
381                        break;
382                }
383             }
384         } catch (CoreException e) {
385             // Couldn't get the members list for some reason. Just return.
386         }
387     }
388 
389 
390     /**
391      * Merge the current list of source file to compile/remove with the one coming from the
392      * delta visitor
393      * @param visitor the delta visitor.
394      */
mergeFileModifications(SourceChangeHandler visitor)395     private void mergeFileModifications(SourceChangeHandler visitor) {
396         Set<IFile> toRemove = visitor.getRemovedFiles();
397         Set<IFile> toCompile = visitor.getFilesToCompile();
398 
399         // loop through the new toRemove list, and add it to the old one,
400         // plus remove any file that was still to compile and that are now
401         // removed
402         for (IFile r : toRemove) {
403             if (mRemoved.indexOf(r) == -1) {
404                 mRemoved.add(r);
405             }
406 
407             int index = mToCompile.indexOf(r);
408             if (index != -1) {
409                 mToCompile.remove(index);
410             }
411         }
412 
413         // now loop through the new files to compile and add it to the list.
414         // Also look for them in the remove list, this would mean that they
415         // were removed, then added back, and we shouldn't remove them, just
416         // recompile them.
417         for (IFile r : toCompile) {
418             if (mToCompile.indexOf(r) == -1) {
419                 mToCompile.add(r);
420             }
421 
422             int index = mRemoved.indexOf(r);
423             if (index != -1) {
424                 mRemoved.remove(index);
425             }
426         }
427     }
428 }
429