diff options
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.java | 417 |
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; + } + +} |