aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerDeltaVisitor.java
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerDeltaVisitor.java')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerDeltaVisitor.java417
1 files changed, 417 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerDeltaVisitor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerDeltaVisitor.java
new file mode 100644
index 000000000..57316f568
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerDeltaVisitor.java
@@ -0,0 +1,417 @@
+/*
+ * Copyright (C) 2007 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.builders;
+
+import com.android.SdkConstants;
+import com.android.ide.common.xml.ManifestData;
+import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.build.Messages;
+import com.android.ide.eclipse.adt.internal.build.SourceChangeHandler;
+import com.android.ide.eclipse.adt.internal.build.builders.BaseBuilder.BaseDeltaVisitor;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity;
+import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
+import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
+import com.android.ide.eclipse.adt.io.IFileWrapper;
+import com.google.common.collect.Lists;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.core.resources.IResourceDeltaVisitor;
+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 java.util.Arrays;
+import java.util.List;
+
+/**
+ * Resource Delta visitor for the pre-compiler.
+ * <p/>This delta visitor only cares about files that are the source or the result of actions of the
+ * {@link PreCompilerBuilder}:
+ * <ul><li>R.java/Manifest.java generated by compiling the resources</li>
+ * <li>Any Java files generated by <code>aidl</code></li></ul>.
+ *
+ * Therefore it looks for the following:
+ * <ul><li>Any modification in the resource folder</li>
+ * <li>Removed files from the source folder receiving generated Java files</li>
+ * <li>Any modification to aidl files.</li>
+ *
+ */
+class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements IResourceDeltaVisitor {
+
+ // Result fields.
+ private boolean mChangedManifest = false;
+
+ /**
+ * Compile flag. This is set to true if one of the changed/added/removed
+ * files is Manifest.java, or R.java. All other file changes
+ * will be taken care of by ResourceManager.
+ */
+ private boolean mCompileResources = false;
+
+ /** Manifest check/parsing flag. */
+ private boolean mCheckedManifestXml = false;
+
+ /** Application Package, gathered from the parsing of the manifest */
+ private String mJavaPackage = null;
+ /** minSDKVersion attribute value, gathered from the parsing of the manifest */
+ private String mMinSdkVersion = null;
+
+ // Internal usage fields.
+ /**
+ * In Resource folder flag. This allows us to know if we're in the
+ * resource folder.
+ */
+ private boolean mInRes = false;
+
+ /**
+ * Current Source folder. This allows us to know if we're in a source
+ * folder, and which folder.
+ */
+ private IFolder mSourceFolder = null;
+
+ /** List of source folders. */
+ private final List<IPath> mSourceFolders;
+ private boolean mIsGenSourceFolder = false;
+
+ private final List<SourceChangeHandler> mSourceChangeHandlers = Lists.newArrayList();
+ private final IWorkspaceRoot mRoot;
+
+ private IFolder mAndroidOutputFolder;
+
+ public PreCompilerDeltaVisitor(BaseBuilder builder, List<IPath> sourceFolders,
+ SourceChangeHandler... handlers) {
+ super(builder);
+ mSourceFolders = sourceFolders;
+ mRoot = ResourcesPlugin.getWorkspace().getRoot();
+
+ mSourceChangeHandlers.addAll(Arrays.asList(handlers));
+
+ mAndroidOutputFolder = BaseProjectHelper.getAndroidOutputFolder(builder.getProject());
+ }
+
+ /**
+ * Get whether Manifest.java, Manifest.xml, or R.java have changed
+ * @return true if any of Manifest.xml, Manifest.java, or R.java have been modified
+ */
+ public boolean getCompileResources() {
+ return mCompileResources || mChangedManifest;
+ }
+
+ public boolean hasManifestChanged() {
+ return mChangedManifest;
+ }
+
+ /**
+ * Returns whether the manifest file was parsed/checked for error during the resource delta
+ * visiting.
+ */
+ public boolean getCheckedManifestXml() {
+ return mCheckedManifestXml;
+ }
+
+ /**
+ * Returns the manifest package if the manifest was checked/parsed.
+ * <p/>
+ * This can return null in two cases:
+ * <ul>
+ * <li>The manifest was not part of the resource change delta, and the manifest was
+ * not checked/parsed ({@link #getCheckedManifestXml()} returns <code>false</code>)</li>
+ * <li>The manifest was parsed ({@link #getCheckedManifestXml()} returns <code>true</code>),
+ * but the package declaration is missing</li>
+ * </ul>
+ * @return the manifest package or null.
+ */
+ public String getManifestPackage() {
+ return mJavaPackage;
+ }
+
+ /**
+ * Returns the minSDkVersion attribute from the manifest if it was checked/parsed.
+ * <p/>
+ * This can return null in two cases:
+ * <ul>
+ * <li>The manifest was not part of the resource change delta, and the manifest was
+ * not checked/parsed ({@link #getCheckedManifestXml()} returns <code>false</code>)</li>
+ * <li>The manifest was parsed ({@link #getCheckedManifestXml()} returns <code>true</code>),
+ * but the package declaration is missing</li>
+ * </ul>
+ * @return the minSdkVersion or null.
+ */
+ public String getMinSdkVersion() {
+ return mMinSdkVersion;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.core.resources.IResourceDeltaVisitor
+ * #visit(org.eclipse.core.resources.IResourceDelta)
+ */
+ @Override
+ public boolean visit(IResourceDelta delta) throws CoreException {
+ // we are only going to look for changes in res/, source folders and in
+ // AndroidManifest.xml since the delta visitor goes through the main
+ // folder before its children we can check when the path segment
+ // count is 2 (format will be /$Project/folder) and make sure we are
+ // processing res/, source folders or AndroidManifest.xml
+
+ IResource resource = delta.getResource();
+ IPath path = resource.getFullPath();
+ String[] segments = path.segments();
+
+ // since the delta visitor also visits the root we return true if
+ // segments.length = 1
+ if (segments.length == 1) {
+ // this is always the Android project since we call
+ // Builder#getDelta(IProject) on the project itself.
+ return true;
+ } else if (segments.length == 2) {
+ // if we are at an item directly under the root directory,
+ // then we are not yet in a source or resource folder
+ mInRes = false;
+ mSourceFolder = null;
+
+ if (SdkConstants.FD_RESOURCES.equalsIgnoreCase(segments[1])) {
+ // this is the resource folder that was modified. we want to
+ // see its content.
+
+ // since we're going to visit its children next, we set the
+ // flag
+ mInRes = true;
+ mSourceFolder = null;
+ return true;
+ } else if (SdkConstants.FN_ANDROID_MANIFEST_XML.equalsIgnoreCase(segments[1])) {
+ // any change in the manifest could trigger a new R.java
+ // class, so we don't need to check the delta kind
+ if (delta.getKind() != IResourceDelta.REMOVED) {
+ // clean the error markers on the file.
+ IFile manifestFile = (IFile)resource;
+
+ if (manifestFile.exists()) {
+ manifestFile.deleteMarkers(AdtConstants.MARKER_XML, true,
+ IResource.DEPTH_ZERO);
+ manifestFile.deleteMarkers(AdtConstants.MARKER_ANDROID, true,
+ IResource.DEPTH_ZERO);
+ }
+
+ // parse the manifest for data and error
+ ManifestData manifestData = AndroidManifestHelper.parse(
+ new IFileWrapper(manifestFile), true /*gatherData*/, this);
+
+ if (manifestData != null) {
+ mJavaPackage = manifestData.getPackage();
+ mMinSdkVersion = manifestData.getMinSdkVersionString();
+ }
+
+ mCheckedManifestXml = true;
+ }
+ mChangedManifest = true;
+
+ // we don't want to go to the children, not like they are
+ // any for this resource anyway.
+ return false;
+ }
+ }
+
+ // at this point we can either be in the source folder or in the
+ // resource folder or in a different folder that contains a source
+ // folder.
+ // This is due to not all source folder being src/. Some could be
+ // something/somethingelse/src/
+
+ // so first we test if we already know we are in a source or
+ // resource folder.
+
+ if (mSourceFolder != null) {
+ // if we are in the res folder, we are looking for the following changes:
+ // - added/removed/modified aidl files.
+ // - missing R.java file
+
+ // if the resource is a folder, we just go straight to the children
+ if (resource.getType() == IResource.FOLDER) {
+ return true;
+ }
+
+ if (resource.getType() != IResource.FILE) {
+ return false;
+ }
+ IFile file = (IFile)resource;
+
+ // get the modification kind
+ int kind = delta.getKind();
+
+ // we process normal source folder and the 'gen' source folder differently.
+ if (mIsGenSourceFolder) {
+ // this is the generated java file source folder.
+ // - if R.java/Manifest.java are removed/modified, we recompile the resources
+ // - if aidl files are removed/modified, we recompile them.
+
+ boolean outputWarning = false;
+
+ String fileName = resource.getName();
+
+ // Special case of R.java/Manifest.java.
+ if (SdkConstants.FN_RESOURCE_CLASS.equals(fileName) ||
+ SdkConstants.FN_MANIFEST_CLASS.equals(fileName)) {
+ // if it was removed, there's a possibility that it was removed due to a
+ // package change, or an aidl that was removed, but the only thing
+ // that will happen is that we'll have an extra build. Not much of a problem.
+ mCompileResources = true;
+
+ // we want a warning
+ outputWarning = true;
+ } else {
+ // look to see if this file was generated by a processor.
+ for (SourceChangeHandler handler : mSourceChangeHandlers) {
+ if (handler.handleGeneratedFile(file, kind)) {
+ outputWarning = true;
+ break; // there shouldn't be 2 processors that handle the same file.
+ }
+ }
+ }
+
+ if (outputWarning) {
+ if (kind == IResourceDelta.REMOVED) {
+ // We print an error just so that it's red, but it's just a warning really.
+ String msg = String.format(Messages.s_Removed_Recreating_s, fileName);
+ AdtPlugin.printErrorToConsole(mBuilder.getProject(), msg);
+ } else if (kind == IResourceDelta.CHANGED) {
+ // the file was modified manually! we can't allow it.
+ String msg = String.format(Messages.s_Modified_Manually_Recreating_s,
+ fileName);
+ AdtPlugin.printErrorToConsole(mBuilder.getProject(), msg);
+ }
+ }
+ } else {
+ // this is another source folder.
+ for (SourceChangeHandler handler : mSourceChangeHandlers) {
+ handler.handleSourceFile(file, kind);
+ }
+ }
+
+ // no children.
+ return false;
+ } else if (mInRes) {
+ // if we are in the res folder, we are looking for the following
+ // changes:
+ // - added/removed/modified xml files.
+ // - added/removed files of any other type
+
+ // if the resource is a folder, we just go straight to the
+ // children
+ if (resource.getType() == IResource.FOLDER) {
+ return true;
+ }
+
+ // get the extension of the resource
+ String ext = resource.getFileExtension();
+ int kind = delta.getKind();
+
+ String p = resource.getProjectRelativePath().toString();
+ String message = null;
+ switch (kind) {
+ case IResourceDelta.CHANGED:
+ // display verbose message
+ message = String.format(Messages.s_Modified_Recreating_s, p);
+ break;
+ case IResourceDelta.ADDED:
+ // display verbose message
+ message = String.format(Messages.Added_s_s_Needs_Updating, p,
+ SdkConstants.FN_RESOURCE_CLASS);
+ break;
+ case IResourceDelta.REMOVED:
+ // display verbose message
+ message = String.format(Messages.s_Removed_s_Needs_Updating, p,
+ SdkConstants.FN_RESOURCE_CLASS);
+ break;
+ }
+ if (message != null) {
+ AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE,
+ mBuilder.getProject(), message);
+ }
+
+ // If it's an XML resource, check the syntax
+ if (SdkConstants.EXT_XML.equalsIgnoreCase(ext) && kind != IResourceDelta.REMOVED) {
+ // check xml Validity
+ mBuilder.checkXML(resource, this);
+ }
+ // Whether or not to generate R.java for a changed resource is taken care of by the
+ // Resource Manager.
+ } else if (resource instanceof IFolder) {
+ // first check if we are in the android output folder.
+ if (resource.equals(mAndroidOutputFolder)) {
+ // we want to visit the merged manifest.
+ return true;
+ }
+
+ // in this case we may be inside a folder that contains a source
+ // folder, go through the list of known source folders
+
+ for (IPath sourceFolderPath : mSourceFolders) {
+ // first check if they match exactly.
+ if (sourceFolderPath.equals(path)) {
+ // this is a source folder!
+ mInRes = false;
+ mSourceFolder = getFolder(sourceFolderPath); // all non null due to test above
+ mIsGenSourceFolder = path.segmentCount() == 2 &&
+ path.segment(1).equals(SdkConstants.FD_GEN_SOURCES);
+ return true;
+ }
+
+ // check if we are on the way to a source folder.
+ int count = sourceFolderPath.matchingFirstSegments(path);
+ if (count == path.segmentCount()) {
+ mInRes = false;
+ return true;
+ }
+ }
+
+ // if we're here, we are visiting another folder
+ // like /$Project/bin/ for instance (we get notified for changes
+ // in .class!)
+ // This could also be another source folder and we have found
+ // R.java in a previous source folder
+ // We don't want to visit its children
+ return false;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns a handle to the folder identified by the given path in this container.
+ * <p/>The different with {@link IContainer#getFolder(IPath)} is that this returns a non
+ * null object only if the resource actually exists and is a folder (and not a file)
+ * @param path the path of the folder to return.
+ * @return a handle to the folder if it exists, or null otherwise.
+ */
+ private IFolder getFolder(IPath path) {
+ IResource resource = mRoot.findMember(path);
+ if (resource != null && resource.exists() && resource.getType() == IResource.FOLDER) {
+ return (IFolder)resource;
+ }
+
+ return null;
+ }
+
+}