aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/SourceProcessor.java
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/SourceProcessor.java')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/SourceProcessor.java454
1 files changed, 454 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/SourceProcessor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/SourceProcessor.java
new file mode 100644
index 000000000..1fc3e4e53
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/SourceProcessor.java
@@ -0,0 +1,454 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.build;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.ide.eclipse.adt.internal.build.builders.BaseBuilder;
+import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
+import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.IAndroidTarget;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jdt.core.IJavaProject;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Base class to handle generated java code.
+ *
+ * It provides management for modified source file list, deleted source file list, reconciliation
+ * of previous lists, storing the current state of the build.
+ *
+ */
+public abstract class SourceProcessor {
+
+ public final static int COMPILE_STATUS_NONE = 0;
+ public final static int COMPILE_STATUS_CODE = 0x1;
+ public final static int COMPILE_STATUS_RES = 0x2;
+
+ /** List of all source files, their dependencies, and their output. */
+ private final Map<IFile, SourceFileData> mFiles = new HashMap<IFile, SourceFileData>();
+
+ private final IJavaProject mJavaProject;
+ private BuildToolInfo mBuildToolInfo;
+ private final IFolder mGenFolder;
+ private final DefaultSourceChangeHandler mDeltaVisitor;
+
+ /** List of source files pending compilation at the next build */
+ private final List<IFile> mToCompile = new ArrayList<IFile>();
+
+ /** List of removed source files pending cleaning at the next build. */
+ private final List<IFile> mRemoved = new ArrayList<IFile>();
+
+ private int mLastCompilationStatus = COMPILE_STATUS_NONE;
+
+ /**
+ * Quotes a path inside "". If the platform is not windows, the path is returned as is.
+ * @param path the path to quote
+ * @return the quoted path.
+ */
+ public static String quote(String path) {
+ if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) {
+ if (path.endsWith(File.separator)) {
+ path = path.substring(0, path.length() -1);
+ }
+ return "\"" + path + "\"";
+ }
+
+ return path;
+ }
+
+ protected SourceProcessor(
+ @NonNull IJavaProject javaProject,
+ @NonNull BuildToolInfo buildToolInfo,
+ @NonNull IFolder genFolder,
+ @NonNull DefaultSourceChangeHandler deltaVisitor) {
+ mJavaProject = javaProject;
+ mBuildToolInfo = buildToolInfo;
+ mGenFolder = genFolder;
+ mDeltaVisitor = deltaVisitor;
+
+ mDeltaVisitor.init(this);
+
+ IProject project = javaProject.getProject();
+
+ // get all the source files
+ buildSourceFileList();
+
+ // load the known dependencies
+ loadOutputAndDependencies();
+
+ boolean mustCompile = loadState(project);
+
+ // if we stored that we have to compile some files, we build the list that will compile them
+ // all. For now we have to reuse the full list since we don't know which files needed
+ // compilation.
+ if (mustCompile) {
+ mToCompile.addAll(mFiles.keySet());
+ }
+ }
+
+ protected SourceProcessor(
+ @NonNull IJavaProject javaProject,
+ @NonNull BuildToolInfo buildToolInfo,
+ @NonNull IFolder genFolder) {
+ this(javaProject, buildToolInfo, genFolder, new DefaultSourceChangeHandler());
+ }
+
+ public void setBuildToolInfo(BuildToolInfo buildToolInfo) {
+ mBuildToolInfo = buildToolInfo;
+ }
+
+
+ /**
+ * Returns whether the given file is an output of this processor by return the source
+ * file that generated it.
+ * @param file the file to test.
+ * @return the source file that generated the given file or null.
+ */
+ IFile isOutput(IFile file) {
+ for (SourceFileData data : mFiles.values()) {
+ if (data.generated(file)) {
+ return data.getSourceFile();
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns whether the given file is a dependency for other files by returning a list
+ * of file depending on the given file.
+ * @param file the file to test.
+ * @return a list of files that depend on the given file or an empty list if there
+ * are no matches.
+ */
+ List<IFile> isDependency(IFile file) {
+ ArrayList<IFile> files = new ArrayList<IFile>();
+ for (SourceFileData data : mFiles.values()) {
+ if (data.dependsOn(file)) {
+ files.add(data.getSourceFile());
+ }
+ }
+
+ return files;
+ }
+
+ void addData(SourceFileData data) {
+ mFiles.put(data.getSourceFile(), data);
+ }
+
+ SourceFileData getFileData(IFile file) {
+ return mFiles.get(file);
+ }
+
+ Collection<SourceFileData> getAllFileData() {
+ return mFiles.values();
+ }
+
+ public final DefaultSourceChangeHandler getChangeHandler() {
+ return mDeltaVisitor;
+ }
+
+ final IJavaProject getJavaProject() {
+ return mJavaProject;
+ }
+
+ final BuildToolInfo getBuildToolInfo() {
+ return mBuildToolInfo;
+ }
+
+ final IFolder getGenFolder() {
+ return mGenFolder;
+ }
+
+ final List<IFile> getToCompile() {
+ return mToCompile;
+ }
+
+ final List<IFile> getRemovedFile() {
+ return mRemoved;
+ }
+
+ final void addFileToCompile(IFile file) {
+ mToCompile.add(file);
+ }
+
+ public final void prepareFullBuild(IProject project) {
+ mDeltaVisitor.reset();
+
+ mToCompile.clear();
+ mRemoved.clear();
+
+ // get all the source files
+ buildSourceFileList();
+
+ mToCompile.addAll(mFiles.keySet());
+
+ saveState(project);
+ }
+
+ public final void doneVisiting(IProject project) {
+ // merge the previous file modification lists and the new one.
+ mergeFileModifications(mDeltaVisitor);
+
+ mDeltaVisitor.reset();
+
+ saveState(project);
+ }
+
+ /**
+ * Returns the extension of the source files handled by this processor.
+ * @return
+ */
+ protected abstract Set<String> getExtensions();
+
+ protected abstract String getSavePropertyName();
+
+ /**
+ * Compiles the source files and return a status bitmask of the type of file that was generated.
+ *
+ */
+ public final int compileFiles(BaseBuilder builder,
+ IProject project, IAndroidTarget projectTarget,
+ List<IPath> sourceFolders, List<File> libraryProjectsOut, IProgressMonitor monitor)
+ throws CoreException {
+
+ mLastCompilationStatus = COMPILE_STATUS_NONE;
+
+ if (mToCompile.size() == 0 && mRemoved.size() == 0) {
+ return mLastCompilationStatus;
+ }
+
+ // if a source file is being removed before we managed to compile it, it'll be in
+ // both list. We *need* to remove it from the compile list or it'll never go away.
+ for (IFile sourceFile : mRemoved) {
+ int pos = mToCompile.indexOf(sourceFile);
+ if (pos != -1) {
+ mToCompile.remove(pos);
+ }
+ }
+
+ // list of files that have failed compilation.
+ List<IFile> stillNeedCompilation = new ArrayList<IFile>();
+
+ doCompileFiles(mToCompile, builder, project, projectTarget, sourceFolders,
+ stillNeedCompilation, libraryProjectsOut, monitor);
+
+ mToCompile.clear();
+ mToCompile.addAll(stillNeedCompilation);
+
+ // Remove the files created from source files that have been removed.
+ for (IFile sourceFile : mRemoved) {
+ // look if we already know the output
+ SourceFileData data = getFileData(sourceFile);
+ if (data != null) {
+ doRemoveFiles(data);
+ }
+ }
+
+ // remove the associated file data.
+ for (IFile removedFile : mRemoved) {
+ mFiles.remove(removedFile);
+ }
+
+ mRemoved.clear();
+
+ // store the build state. If there are any files that failed to compile, we will
+ // force a full aidl compile on the next project open. (unless a full compilation succeed
+ // before the project is closed/re-opened.)
+ saveState(project);
+
+ return mLastCompilationStatus;
+ }
+
+ protected abstract void doCompileFiles(
+ List<IFile> filesToCompile, BaseBuilder builder,
+ IProject project, IAndroidTarget projectTarget,
+ List<IPath> sourceFolders, List<IFile> notCompiledOut,
+ List<File> libraryProjectsOut, IProgressMonitor monitor) throws CoreException;
+
+ /**
+ * Adds a compilation status. It can be any of (in combination too):
+ * <p/>
+ * {@link #COMPILE_STATUS_CODE} means this processor created source code files.
+ * {@link #COMPILE_STATUS_RES} means this process created resources.
+ */
+ protected void setCompilationStatus(int status) {
+ mLastCompilationStatus |= status;
+ }
+
+ protected void doRemoveFiles(SourceFileData data) throws CoreException {
+ List<IFile> outputFiles = data.getOutputFiles();
+ for (IFile outputFile : outputFiles) {
+ if (outputFile.exists()) {
+ outputFile.getLocation().toFile().delete();
+ }
+ }
+ }
+
+ public final boolean loadState(IProject project) {
+ return ProjectHelper.loadBooleanProperty(project, getSavePropertyName(),
+ true /*defaultValue*/);
+ }
+
+ public final void saveState(IProject project) {
+ // TODO: Optimize by saving only the files that need compilation
+ ProjectHelper.saveStringProperty(project, getSavePropertyName(),
+ Boolean.toString(mToCompile.size() > 0));
+ }
+
+ protected abstract void loadOutputAndDependencies();
+
+
+ protected IPath getSourceFolderFor(IFile file) {
+ // find the source folder for the class so that we can infer the package from the
+ // difference between the file and its source folder.
+ List<IPath> sourceFolders = BaseProjectHelper.getSourceClasspaths(getJavaProject());
+ IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
+
+ for (IPath sourceFolderPath : sourceFolders) {
+ IFolder sourceFolder = root.getFolder(sourceFolderPath);
+ // we don't look in the 'gen' source folder as there will be no source in there.
+ if (sourceFolder.exists() && sourceFolder.equals(getGenFolder()) == false) {
+ // look for the source file parent, until we find this source folder.
+ IResource parent = file;
+ while ((parent = parent.getParent()) != null) {
+ if (parent.equals(sourceFolder)) {
+ return sourceFolderPath;
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Goes through the build paths and fills the list of files to compile.
+ *
+ * @param project The project.
+ * @param sourceFolderPathList The list of source folder paths.
+ */
+ private final void buildSourceFileList() {
+ mFiles.clear();
+
+ IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
+ List<IPath> sourceFolderPathList = BaseProjectHelper.getSourceClasspaths(mJavaProject);
+
+ for (IPath sourceFolderPath : sourceFolderPathList) {
+ IFolder sourceFolder = root.getFolder(sourceFolderPath);
+ // we don't look in the 'gen' source folder as there will be no source in there.
+ if (sourceFolder.exists() && sourceFolder.equals(getGenFolder()) == false) {
+ scanFolderForSourceFiles(sourceFolder, sourceFolder);
+ }
+ }
+ }
+
+ /**
+ * Scans a folder and fills the list of files to compile.
+ * @param sourceFolder the root source folder.
+ * @param folder The folder to scan.
+ */
+ private void scanFolderForSourceFiles(IFolder sourceFolder, IFolder folder) {
+ try {
+ IResource[] members = folder.members();
+ for (IResource r : members) {
+ // get the type of the resource
+ switch (r.getType()) {
+ case IResource.FILE: {
+ // if this a file, check that the file actually exist
+ // and that it's the type of of file that's used in this processor
+ String extension = r.exists() ? r.getFileExtension() : null;
+ if (extension != null &&
+ getExtensions().contains(extension.toLowerCase(Locale.US))) {
+ mFiles.put((IFile) r, new SourceFileData((IFile) r));
+ }
+ break;
+ }
+ case IResource.FOLDER:
+ // recursively go through children
+ scanFolderForSourceFiles(sourceFolder, (IFolder)r);
+ break;
+ default:
+ // this would mean it's a project or the workspace root
+ // which is unlikely to happen. we do nothing
+ break;
+ }
+ }
+ } catch (CoreException e) {
+ // Couldn't get the members list for some reason. Just return.
+ }
+ }
+
+
+ /**
+ * Merge the current list of source file to compile/remove with the one coming from the
+ * delta visitor
+ * @param visitor the delta visitor.
+ */
+ private void mergeFileModifications(DefaultSourceChangeHandler visitor) {
+ Set<IFile> toRemove = visitor.getRemovedFiles();
+ Set<IFile> toCompile = visitor.getFilesToCompile();
+
+ // loop through the new toRemove list, and add it to the old one,
+ // plus remove any file that was still to compile and that are now
+ // removed
+ for (IFile r : toRemove) {
+ if (mRemoved.indexOf(r) == -1) {
+ mRemoved.add(r);
+ }
+
+ int index = mToCompile.indexOf(r);
+ if (index != -1) {
+ mToCompile.remove(index);
+ }
+ }
+
+ // now loop through the new files to compile and add it to the list.
+ // Also look for them in the remove list, this would mean that they
+ // were removed, then added back, and we shouldn't remove them, just
+ // recompile them.
+ for (IFile r : toCompile) {
+ if (mToCompile.indexOf(r) == -1) {
+ mToCompile.add(r);
+ }
+
+ int index = mRemoved.indexOf(r);
+ if (index != -1) {
+ mRemoved.remove(index);
+ }
+ }
+ }
+}