aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java9
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java50
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderService.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/xml/Hyperlinks.java7
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/GlobalProjectMonitor.java10
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/IdeScanningContext.java166
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceManager.java255
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/ProjectState.java27
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java7
-rw-r--r--ide_common/src/com/android/ide/common/resources/IdGeneratingResourceFile.java57
-rw-r--r--ide_common/src/com/android/ide/common/resources/IdResourceParser.java139
-rw-r--r--ide_common/src/com/android/ide/common/resources/MultiResourceFile.java16
-rw-r--r--ide_common/src/com/android/ide/common/resources/ResourceFile.java6
-rw-r--r--ide_common/src/com/android/ide/common/resources/ResourceFolder.java25
-rw-r--r--ide_common/src/com/android/ide/common/resources/ResourceRepository.java88
-rw-r--r--ide_common/src/com/android/ide/common/resources/ScanningContext.java92
-rw-r--r--ide_common/src/com/android/ide/common/resources/SingleResourceFile.java10
19 files changed, 730 insertions, 242 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java
index 933dc40..47215cc 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java
@@ -23,10 +23,10 @@ import com.android.ide.eclipse.adt.internal.build.AaptExecException;
import com.android.ide.eclipse.adt.internal.build.AaptParser;
import com.android.ide.eclipse.adt.internal.build.AaptResultException;
import com.android.ide.eclipse.adt.internal.build.BuildHelper;
+import com.android.ide.eclipse.adt.internal.build.BuildHelper.ResourceMarker;
import com.android.ide.eclipse.adt.internal.build.DexException;
import com.android.ide.eclipse.adt.internal.build.Messages;
import com.android.ide.eclipse.adt.internal.build.NativeLibInJarException;
-import com.android.ide.eclipse.adt.internal.build.BuildHelper.ResourceMarker;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity;
import com.android.ide.eclipse.adt.internal.project.ApkInstallManager;
@@ -377,7 +377,7 @@ public class PostCompilerBuilder extends BaseBuilder {
}
// store the build status in the persistent storage
- saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX , mConvertToDex);
+ saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex);
saveProjectBooleanProperty(PROPERTY_UPDATE_CRUNCH_CACHE, mUpdateCrunchCache);
saveProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, mPackageResources);
saveProjectBooleanProperty(PROPERTY_BUILD_APK, mBuildFinalPackage);
@@ -415,6 +415,7 @@ public class PostCompilerBuilder extends BaseBuilder {
writeLibraryPackage(jarIFile, project, javaOutputFolder,
referencedJavaProjects);
+ saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex = false);
}
return allRefProjects;
@@ -859,7 +860,7 @@ public class PostCompilerBuilder extends BaseBuilder {
}
}
- saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX , mConvertToDex);
+ saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex);
} catch (Exception e) {
AdtPlugin.log(e, "Failed to write jar file %s", jarIFile.getLocation().toOSString());
} finally {
@@ -892,7 +893,7 @@ public class PostCompilerBuilder extends BaseBuilder {
// load the build status. We pass true as the default value to
// force a recompile in case the property was not found
- mConvertToDex = loadProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX , true);
+ mConvertToDex = loadProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, true);
mUpdateCrunchCache = loadProjectBooleanProperty(PROPERTY_UPDATE_CRUNCH_CACHE, true);
mPackageResources = loadProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, true);
mBuildFinalPackage = loadProjectBooleanProperty(PROPERTY_BUILD_APK, true);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java
index 4e7c8f7..195a279 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java
@@ -30,6 +30,8 @@ import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
import com.android.ide.eclipse.adt.internal.project.FixLaunchConfig;
import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
import com.android.ide.eclipse.adt.internal.project.XmlErrorHandler.BasicXmlErrorListener;
+import com.android.ide.eclipse.adt.internal.resources.manager.IdeScanningContext;
+import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
@@ -255,16 +257,40 @@ public class PreCompilerBuilder extends BaseBuilder {
} else {
dv = new PreCompilerDeltaVisitor(this, sourceFolderPathList, mProcessors);
delta.accept(dv);
- // Notify the ResourceManager:
- ResourceManager resManager = ResourceManager.getInstance();
- resManager.processDelta(delta);
- // Check whether this project or its dependencies (libraries) have
- // resources that need compilation
- mMustCompileResources |= resManager.projectNeedsIdGeneration(project);
// Check to see if Manifest.xml, Manifest.java, or R.java have changed:
mMustCompileResources |= dv.getCompileResources();
+ // Notify the ResourceManager:
+ ResourceManager resManager = ResourceManager.getInstance();
+ ProjectResources projectResources = resManager.getProjectResources(project);
+
+ if (ResourceManager.isAutoBuilding()) {
+ IdeScanningContext context = new IdeScanningContext(projectResources, project);
+
+ resManager.processDelta(delta, context);
+
+ // Check whether this project or its dependencies (libraries) have
+ // resources that need compilation
+ if (context.needsFullAapt()) {
+ mMustCompileResources = true;
+
+ assert context.getAaptRequestedProjects() != null &&
+ context.getAaptRequestedProjects().size() == 1 &&
+ context.getAaptRequestedProjects().iterator().next() == project;
+
+ // Must also call markAaptRequested on the project to not just
+ // store "aapt required" on this project, but also on any projects
+ // depending on this project if it's a library project
+ ResourceManager.markAaptRequested(project);
+ }
+
+ // Update error markers in the source editor
+ if (!mMustCompileResources) {
+ context.updateMarkers(false /* async */);
+ }
+ } // else: already processed the deltas in ResourceManager's IRawDeltaListener
+
for (SourceProcessor processor : mProcessors) {
processor.doneVisiting(project);
}
@@ -275,8 +301,12 @@ public class PreCompilerBuilder extends BaseBuilder {
}
}
+ // Has anyone marked this project as needing aapt? Typically done when
+ // one of the library projects this project depends on has changed
+ mMustCompileResources |= ResourceManager.isAaptRequested(project);
+
// store the build status in the persistent storage
- saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES , mMustCompileResources);
+ saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES, mMustCompileResources);
// if there was some XML errors, we just return w/o doing
// anything since we've put some markers in the files anyway.
@@ -456,7 +486,7 @@ public class PreCompilerBuilder extends BaseBuilder {
processor.prepareFullBuild(project);
}
- saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES , mMustCompileResources);
+ saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES, mMustCompileResources);
}
// run the source processors
@@ -474,7 +504,7 @@ public class PreCompilerBuilder extends BaseBuilder {
if ((processorStatus & SourceProcessor.COMPILE_STATUS_RES) != 0) {
mMustCompileResources = true;
// save the current state before attempting the compilation
- saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES , mMustCompileResources);
+ saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES, mMustCompileResources);
}
// handle the resources, after the processors are run since some (renderscript)
@@ -660,6 +690,7 @@ public class PreCompilerBuilder extends BaseBuilder {
String osResPath, String osManifestPath, IFolder packageFolder,
ArrayList<IFolder> libResFolders, String libraryPackages, boolean isLibrary)
throws AbortBuildException {
+
// We actually need to delete the manifest.java as it may become empty and
// in this case aapt doesn't generate an empty one, but instead doesn't
// touch it.
@@ -793,6 +824,7 @@ public class PreCompilerBuilder extends BaseBuilder {
// run it again, unless there's a new resource change.
saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES,
mMustCompileResources = false);
+ ResourceManager.clearAaptRequest(project);
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java
index 3f5a318..9b81090 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java
@@ -696,7 +696,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
// inside the quotes.
// Special case for attributes: insert ="" stuff and locate caret inside ""
proposals.add(new CompletionProposal(
- keyword + suffix , // String replacementString
+ keyword + suffix, // String replacementString
offset - wordPrefix.length(), // int replacementOffset
wordPrefix.length() + replaceLength,// int replacementLength
cursorPosition, // cursorPosition
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderService.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderService.java
index fc3d7d7..b4b4433 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderService.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderService.java
@@ -303,7 +303,7 @@ public class RenderService {
// Find the layout file.
ResourceValue contextLayout = mResourceResolver.findResValue(
- LAYOUT_PREFIX + contextLayoutName , false /* forceFrameworkOnly*/);
+ LAYOUT_PREFIX + contextLayoutName, false /* forceFrameworkOnly*/);
if (contextLayout != null) {
File layoutFile = new File(contextLayout.getValue());
if (layoutFile.isFile()) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/xml/Hyperlinks.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/xml/Hyperlinks.java
index 92485cd..ec68323 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/xml/Hyperlinks.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/xml/Hyperlinks.java
@@ -1493,7 +1493,12 @@ public class Hyperlinks {
IRegion region = null;
String extension = file.getFileExtension();
if (mType != null && mName != null && EXT_XML.equals(extension)) {
- Pair<IFile, IRegion> target = findValueInXml(mType, mName, file);
+ Pair<IFile, IRegion> target;
+ if (mType == ResourceType.ID) {
+ target = findIdInXml(mName, file);
+ } else {
+ target = findValueInXml(mType, mName, file);
+ }
if (target != null) {
region = target.getSecond();
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/GlobalProjectMonitor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/GlobalProjectMonitor.java
index 6e9881f..f92f431 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/GlobalProjectMonitor.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/GlobalProjectMonitor.java
@@ -132,7 +132,6 @@ public final class GlobalProjectMonitor {
/**
* Interface for a listener that gets passed the raw delta without processing.
- *
*/
public interface IRawDeltaListener {
public void visitDelta(IResourceDelta delta);
@@ -195,10 +194,6 @@ public final class GlobalProjectMonitor {
private final class DeltaVisitor implements IResourceDeltaVisitor {
public boolean visit(IResourceDelta delta) {
- // notify the raw delta listeners
- for (IRawDeltaListener listener : mRawDeltaListeners) {
- listener.visitDelta(delta);
- }
// Find the other resource listeners to notify
IResource r = delta.getResource();
int type = r.getType();
@@ -480,6 +475,11 @@ public final class GlobalProjectMonitor {
// this a regular resource change. We get the delta and go through it with a visitor.
IResourceDelta delta = event.getDelta();
+ // notify the raw delta listeners
+ for (IRawDeltaListener listener : mRawDeltaListeners) {
+ listener.visitDelta(delta);
+ }
+
DeltaVisitor visitor = new DeltaVisitor();
try {
delta.accept(visitor);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/IdeScanningContext.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/IdeScanningContext.java
new file mode 100644
index 0000000..fb8caf2
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/IdeScanningContext.java
@@ -0,0 +1,166 @@
+/*
+ * 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.resources.manager;
+
+import static com.android.ide.eclipse.adt.AdtConstants.MARKER_AAPT_COMPILE;
+import static org.eclipse.core.resources.IResource.DEPTH_ONE;
+import static org.eclipse.core.resources.IResource.DEPTH_ZERO;
+
+import com.android.ide.common.resources.ResourceRepository;
+import com.android.ide.common.resources.ScanningContext;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.build.AaptParser;
+import com.android.util.Pair;
+
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * An {@link IdeScanningContext} is a specialized {@link ScanningContext} which
+ * carries extra information about the scanning state, such as which file is
+ * currently being scanned, and which files have been scanned in the past, such
+ * that at the end of a scan we can mark and clear errors, etc.
+ */
+public class IdeScanningContext extends ScanningContext {
+ private final IProject mProject;
+ private final List<IResource> mScannedResources = new ArrayList<IResource>();
+ private IResource mCurrentFile;
+ private List<Pair<IResource, String>> mErrors;
+ private Set<IProject> mFullAaptProjects;
+
+ /**
+ * Constructs a new {@link IdeScanningContext}
+ *
+ * @param repository the associated {@link ResourceRepository}
+ * @param project the associated project
+ */
+ public IdeScanningContext(ResourceRepository repository, IProject project) {
+ super(repository);
+ mProject = project;
+ }
+
+ @Override
+ public void addError(String error) {
+ super.addError(error);
+
+ if (mErrors == null) {
+ mErrors = new ArrayList<Pair<IResource,String>>();
+ }
+ mErrors.add(Pair.of(mCurrentFile, error));
+ }
+
+ /**
+ * Notifies the context that the given resource is about to be scanned.
+ *
+ * @param resource the resource about to be scanned
+ */
+ public void startScanning(IResource resource) {
+ assert mCurrentFile == null : mCurrentFile;
+ mCurrentFile = resource;
+ mScannedResources.add(resource);
+ }
+
+ /**
+ * Notifies the context that the given resource has been scanned.
+ *
+ * @param resource the resource that was scanned
+ */
+ public void finishScanning(IResource resource) {
+ assert mCurrentFile != null;
+ mCurrentFile = null;
+ }
+
+ /**
+ * Process any errors found to add error markers in the affected files (and
+ * also clear up any aapt errors in files that are no longer applicable)
+ *
+ * @param async if true, delay updating markers until the next display
+ * thread event loop update
+ */
+ public void updateMarkers(boolean async) {
+ // Run asynchronously? This is necessary for example when adding markers
+ // as the result of a resource change notification, since at that point the
+ // resource tree is locked for modifications and attempting to create a
+ // marker will throw a org.eclipse.core.internal.resources.ResourceException.
+ if (async) {
+ AdtPlugin.getDisplay().asyncExec(new Runnable() {
+ public void run() {
+ updateMarkers(false);
+ }
+ });
+ return;
+ }
+
+ // First clear out old/previous markers
+ for (IResource resource :mScannedResources) {
+ try {
+ if (resource.exists()) {
+ int depth = resource instanceof IFolder ? DEPTH_ONE : DEPTH_ZERO;
+ resource.deleteMarkers(MARKER_AAPT_COMPILE, true, depth);
+ }
+ } catch (CoreException ce) {
+ // Pass
+ }
+ }
+
+ // Add new errors
+ if (mErrors != null && mErrors.size() > 0) {
+ List<String> errors = new ArrayList<String>();
+ for (Pair<IResource, String> pair : mErrors) {
+ errors.add(pair.getSecond());
+ }
+ AaptParser.parseOutput(errors, mProject);
+ }
+ }
+
+ @Override
+ public boolean needsFullAapt() {
+ return super.needsFullAapt();
+ }
+
+ @Override
+ protected void requestFullAapt() {
+ super.requestFullAapt();
+
+ if (mCurrentFile != null) {
+ if (mFullAaptProjects == null) {
+ mFullAaptProjects = new HashSet<IProject>();
+ }
+ mFullAaptProjects.add(mCurrentFile.getProject());
+ } else {
+ assert false : "No current context to apply IdeScanningContext to";
+ }
+ }
+
+ /**
+ * Returns the collection of projects that scanned resources have requested
+ * a full aapt for.
+ *
+ * @return a collection of projects that scanned resources requested full
+ * aapt runs for, or null
+ */
+ public Collection<IProject> getAaptRequestedProjects() {
+ return mFullAaptProjects;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java
index 6967e67..8e3fe24 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java
@@ -287,9 +287,5 @@ public class ProjectResources extends ResourceRepository {
mResourceValueMap = resourceValueMap;
mResIdValueToNameMap = resIdValueToNameMap;
mStyleableValueToNameMap = styleableValueMap;
-
- // Our resource IDs should now be in sync
- setIdsRefreshed();
}
-
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceManager.java
index edf55cd..d689a10 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceManager.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceManager.java
@@ -20,12 +20,12 @@ import com.android.ide.common.resources.FrameworkResources;
import com.android.ide.common.resources.ResourceFile;
import com.android.ide.common.resources.ResourceFolder;
import com.android.ide.common.resources.ResourceRepository;
+import com.android.ide.common.resources.ScanningContext;
import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IProjectListener;
import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IRawDeltaListener;
-import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IResourceEventListener;
import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.ide.eclipse.adt.io.IFileWrapper;
@@ -46,11 +46,14 @@ import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.QualifiedName;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
-import java.util.List;
+import java.util.Map;
/**
* The ResourceManager tracks resources for all opened projects.
@@ -79,7 +82,7 @@ public final class ResourceManager {
* <p/><b>All accesses must be inside a synchronized(mMap) block</b>, and do as a little as
* possible and <b>not call out to other classes</b>.
*/
- private final HashMap<IProject, ProjectResources> mMap =
+ private final Map<IProject, ProjectResources> mMap =
new HashMap<IProject, ProjectResources>();
/**
@@ -112,7 +115,6 @@ public final class ResourceManager {
* @param monitor The global project monitor
*/
public static void setup(GlobalProjectMonitor monitor) {
- monitor.addResourceEventListener(sThis.mResourceEventListener);
monitor.addProjectListener(sThis.mProjectListener);
monitor.addRawDeltaListener(sThis.mRawDeltaListener);
@@ -159,24 +161,23 @@ public final class ResourceManager {
/**
* Update the resource repository with a delta
+ *
* @param delta the resource changed delta to process.
+ * @param context a context object with state for the current update, such
+ * as a place to stash errors encountered
*/
- public void processDelta(IResourceDelta delta) {
+ public void processDelta(IResourceDelta delta, IdeScanningContext context) {
// Skip over deltas that don't fit our mask
int mask = IResourceDelta.ADDED | IResourceDelta.REMOVED | IResourceDelta.CHANGED;
int kind = delta.getKind();
if ( (mask & kind) == 0) {
return;
}
- // If our delta was handed to us from the PreCompiler then it's a single delta
- // with lots of children. GlobalProjectMonitor will hand us a delta for each
- // item in the tree of modifications so we only need to recurse into delta
- // children if we're autobuilding.
- if (ResourcesPlugin.getWorkspace().getDescription().isAutoBuilding()) {
- IResourceDelta[] children = delta.getAffectedChildren();
- for (IResourceDelta child : children) {
- processDelta(child);
- }
+
+ // Process children recursively
+ IResourceDelta[] children = delta.getAffectedChildren();
+ for (IResourceDelta child : children) {
+ processDelta(child, context);
}
// Process this delta
@@ -184,55 +185,21 @@ public final class ResourceManager {
int type = r.getType();
if (type == IResource.FILE) {
- updateFile((IFile)r, delta.getMarkerDeltas(), kind);
+ context.startScanning(r);
+ updateFile((IFile)r, delta.getMarkerDeltas(), kind, context);
+ context.finishScanning(r);
} else if (type == IResource.FOLDER) {
- updateFolder((IFolder)r, kind);
+ updateFolder((IFolder)r, kind, context);
} // We only care about files and folders.
// Project deltas are handled by our project listener
}
/**
- * Private implementation of a resource event listener that registers with
- * GlobalProjectMonitor.
- *
- */
- private class ResourceEventListener implements IResourceEventListener {
- private final List<IProject> mChangedProjects = new ArrayList<IProject>();
-
- public void resourceChangeEventEnd() {
- for (IProject project : mChangedProjects) {
- ProjectResources resources;
- synchronized (mMap) {
- resources = mMap.get(project);
- }
- }
-
- mChangedProjects.clear();
- }
-
- public void resourceChangeEventStart() {
- // pass
- }
-
- void addProject(IProject project) {
- if (mChangedProjects.contains(project) == false) {
- mChangedProjects.add(project);
- }
- }
- }
-
- /**
- * Delegate listener for resource changes. This is called before and after any calls to the
- * project and file listeners (for a given resource change event).
- */
- private final ResourceEventListener mResourceEventListener = new ResourceEventListener();
-
- /**
* Update a resource folder that we know about
* @param folder the folder that was updated
* @param kind the delta type (added/removed/updated)
*/
- private void updateFolder(IFolder folder, int kind) {
+ private void updateFolder(IFolder folder, int kind, IdeScanningContext context) {
ProjectResources resources;
final IProject project = folder.getProject();
@@ -246,8 +213,6 @@ public final class ResourceManager {
return;
}
- mResourceEventListener.addProject(project);
-
switch (kind) {
case IResourceDelta.ADDED:
// checks if the folder is under res.
@@ -296,8 +261,10 @@ public final class ResourceManager {
ResourceFolderType type = ResourceFolderType.getFolderType(
folder.getName());
+ context.startScanning(folder);
ResourceFolder removedFolder = resources.removeFolder(type,
- new IFolderWrapper(folder));
+ new IFolderWrapper(folder), context);
+ context.finishScanning(folder);
if (removedFolder != null) {
notifyListenerOnFolderChange(project, removedFolder, kind);
}
@@ -307,17 +274,19 @@ public final class ResourceManager {
}
/**
- * Called when a delta indicates that a file has changed.
- * Depending on the file being changed, and the type of change
- * (ADDED, REMOVED, CHANGED), the file change is processed to update the resource
- * manager data.
+ * Called when a delta indicates that a file has changed. Depending on the
+ * file being changed, and the type of change (ADDED, REMOVED, CHANGED), the
+ * file change is processed to update the resource manager data.
*
* @param file The file that changed.
* @param markerDeltas The marker deltas for the file.
* @param kind The change kind. This is equivalent to
- * {@link IResourceDelta#accept(IResourceDeltaVisitor)}
+ * {@link IResourceDelta#accept(IResourceDeltaVisitor)}
+ * @param context a context object with state for the current update, such
+ * as a place to stash errors encountered
*/
- private void updateFile(IFile file, IMarkerDelta[] markerDeltas, int kind) {
+ private void updateFile(IFile file, IMarkerDelta[] markerDeltas, int kind,
+ ScanningContext context) {
final IProject project = file.getProject();
try {
@@ -356,7 +325,7 @@ public final class ResourceManager {
if (folder != null) {
ResourceFile resFile = folder.processFile(
new IFileWrapper(file),
- ResourceHelper.getResourceDeltaKind(kind));
+ ResourceHelper.getResourceDeltaKind(kind), context);
notifyListenerOnFileChange(project, resFile, kind);
}
}
@@ -400,12 +369,38 @@ public final class ResourceManager {
* accessed through the {@link ResourceManager#visitDelta(IResourceDelta delta)} method.
*/
private final IRawDeltaListener mRawDeltaListener = new IRawDeltaListener() {
- public void visitDelta(IResourceDelta delta) {
- // If we're autobuilding, then PreCompilerBuilder will pass us deltas
- if (ResourcesPlugin.getWorkspace().getDescription().isAutoBuilding()) {
+ public void visitDelta(IResourceDelta workspaceDelta) {
+ // If we're auto-building, then PreCompilerBuilder will pass us deltas and
+ // they will be processed as part of the build.
+ if (isAutoBuilding()) {
return;
}
- processDelta(delta);
+
+ // When *not* auto building, we need to process the deltas immediately on save,
+ // even if the user is not building yet, such that for example resource ids
+ // are updated in the resource repositories so rendering etc. can work for
+ // those new ids.
+
+ IResourceDelta[] projectDeltas = workspaceDelta.getAffectedChildren();
+ for (IResourceDelta delta : projectDeltas) {
+ if (delta.getResource() instanceof IProject) {
+ IProject project = (IProject) delta.getResource();
+ IdeScanningContext context =
+ new IdeScanningContext(getProjectResources(project), project);
+
+ processDelta(delta, context);
+
+ Collection<IProject> projects = context.getAaptRequestedProjects();
+ if (projects != null) {
+ for (IProject p : projects) {
+ markAaptRequested(p);
+ }
+ }
+ } else {
+ AdtPlugin.log(IStatus.WARNING, "Unexpected delta type: %1$s",
+ delta.getResource().toString());
+ }
+ }
}
};
@@ -466,50 +461,6 @@ public final class ResourceManager {
}
/**
- * Checks the ResourceRepositories associated with the given project and its dependencies
- * and returns whether or not a resource regeneration is needed for that project
- * @param project the project to check
- * @return true if the project or any of its dependencies says it has new or deleted resources
- */
- public boolean projectNeedsIdGeneration(IProject project) {
- // Get a list of repositories to check through
- List<ProjectResources> repositories = getAllProjectResourcesAssociatedWith(project);
- for (ProjectResources repository : repositories) {
- if (repository.needsIdRefresh()) {
- return true;
- }
- }
- // If we've gotten to here, all repositories are in sync, return false
- return false;
- }
-
- /**
- * Get all the resource repositories representing this project and any included libraries
- * @param project the project to get along with its dependencies
- * @return a list of all ProjectResources ordered lowest to highest priority that need to be
- * included in this project.
- */
- private List<ProjectResources> getAllProjectResourcesAssociatedWith(IProject project) {
- List<ProjectResources> toRet = new ArrayList<ProjectResources>();
- // if the project contains libraries, we need to add the libraries resources here
- if (project != null) {
- ProjectState state = Sdk.getProjectState(project);
- if (state != null) {
- List<IProject> libraries = state.getFullLibraryProjects();
- for (IProject library : libraries) {
- ProjectResources libRes = mMap.get(library);
- if (libRes != null) {
- toRet.add(libRes);
- }
- }
- }
- }
- // Add the queried current project last
- toRet.add(mMap.get(project));
- return toRet;
- }
-
- /**
* Initial project parsing to gather resource info.
* @param project
*/
@@ -534,6 +485,7 @@ public final class ResourceManager {
mMap.put(project, projectResources);
}
}
+ IdeScanningContext context = new IdeScanningContext(projectResources, project);
if (resourceFolder != null && resourceFolder.exists()) {
try {
@@ -553,9 +505,13 @@ public final class ResourceManager {
if (fileRes.getType() == IResource.FILE) {
IFile file = (IFile)fileRes;
+ context.startScanning(file);
+
resFolder.processFile(new IFileWrapper(file),
ResourceHelper.getResourceDeltaKind(
- IResourceDelta.ADDED));
+ IResourceDelta.ADDED), context);
+
+ context.finishScanning(file);
}
}
}
@@ -625,4 +581,79 @@ public final class ResourceManager {
return Integer.toString(kind);
}
+
+ /**
+ * Returns true if the Project > Build Automatically option is turned on
+ * (default).
+ *
+ * @return true if the Project > Build Automatically option is turned on
+ * (default).
+ */
+ public static boolean isAutoBuilding() {
+ return ResourcesPlugin.getWorkspace().getDescription().isAutoBuilding();
+ }
+
+ /** Qualified name for the per-project persistent property "needs aapt" */
+ private final static QualifiedName NEED_AAPT = new QualifiedName(AdtPlugin.PLUGIN_ID,
+ "aapt");//$NON-NLS-1$
+
+ /**
+ * Mark the given project, and any projects which depend on it as a library
+ * project, as needing a full aapt build the next time the project is built.
+ *
+ * @param project the project to mark as needing aapt
+ */
+ public static void markAaptRequested(IProject project) {
+ try {
+ String needsAapt = Boolean.TRUE.toString();
+ project.setPersistentProperty(NEED_AAPT, needsAapt);
+
+ ProjectState state = Sdk.getProjectState(project);
+ if (state.isLibrary()) {
+ // For library projects also mark the dependent projects as needing full aapt
+ for (ProjectState parent : state.getFullParentProjects()) {
+ parent.getProject().setPersistentProperty(NEED_AAPT, needsAapt);
+ }
+ }
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+
+ /**
+ * Clear the "needs aapt" flag set by {@link #markAaptRequested(IProject)}.
+ * This is usually called when a project is built. Note that this will only
+ * clean the build flag on the given project, not on any downstream projects
+ * that depend on this project as a library project.
+ *
+ * @param project the project to clear from the needs aapt list
+ */
+ public static void clearAaptRequest(IProject project) {
+ try {
+ project.setPersistentProperty(NEED_AAPT, null);
+ // Note that even if this project is a library project, we -don't- clear
+ // the aapt flags on the dependent projects since they may still depend
+ // on other dirty projects. When they are built, they will issue their
+ // own clear flag requests.
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+
+ /**
+ * Returns whether the given project needs a full aapt build.
+ *
+ * @param project the project to check
+ * @return true if the project needs a full aapt run
+ */
+ public static boolean isAaptRequested(IProject project) {
+ try {
+ String b = project.getPersistentProperty(NEED_AAPT);
+ return b != null && Boolean.valueOf(b);
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+
+ return false;
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/ProjectState.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/ProjectState.java
index ae606ed..84ee341 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/ProjectState.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/ProjectState.java
@@ -28,8 +28,11 @@ import org.eclipse.core.runtime.Status;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import java.util.regex.Matcher;
/**
@@ -547,6 +550,30 @@ public final class ProjectState {
}
/**
+ * Computes the transitive closure of projects referencing this project as a
+ * library project
+ *
+ * @return a collection (in any order) of project states for projects that
+ * directly or indirectly include this project state's project as a
+ * library project
+ */
+ public Collection<ProjectState> getFullParentProjects() {
+ Set<ProjectState> result = new HashSet<ProjectState>();
+ addParentProjects(result, this);
+ return result;
+ }
+
+ /** Adds all parent projects of the given project, transitively, into the given parent set */
+ private static void addParentProjects(Set<ProjectState> parents, ProjectState state) {
+ for (ProjectState s : state.mParentProjects) {
+ if (!parents.contains(s)) {
+ parents.add(s);
+ addParentProjects(parents, s);
+ }
+ }
+ }
+
+ /**
* Update the value of a library dependency.
* <p/>This loops on all current dependency looking for the value to replace and then replaces
* it.
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java
index 666860b..550f53f 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java
@@ -40,8 +40,8 @@ import com.android.sdklib.SdkConstants;
import com.android.sdklib.SdkManager;
import com.android.sdklib.internal.avd.AvdManager;
import com.android.sdklib.internal.project.ProjectProperties;
-import com.android.sdklib.internal.project.ProjectPropertiesWorkingCopy;
import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
+import com.android.sdklib.internal.project.ProjectPropertiesWorkingCopy;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarkerDelta;
@@ -66,8 +66,8 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import java.util.Map.Entry;
+import java.util.Set;
/**
* Central point to load, manipulate and deal with the Android SDK. Only one SDK can be used
@@ -1063,6 +1063,9 @@ public final class Sdk {
}
public void resourceChangeEventEnd() {
+ if (mModifiedProjects.size() == 0) {
+ return;
+ }
// first make sure all the parents are updated
updateParentProjects();
diff --git a/ide_common/src/com/android/ide/common/resources/IdGeneratingResourceFile.java b/ide_common/src/com/android/ide/common/resources/IdGeneratingResourceFile.java
index 6706715..e4b6730 100644
--- a/ide_common/src/com/android/ide/common/resources/IdGeneratingResourceFile.java
+++ b/ide_common/src/com/android/ide/common/resources/IdGeneratingResourceFile.java
@@ -24,8 +24,6 @@ import com.android.io.IAbstractFile;
import com.android.io.StreamException;
import com.android.resources.ResourceType;
-import org.xml.sax.SAXException;
-
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
@@ -33,10 +31,6 @@ import java.util.HashSet;
import java.util.Map;
import java.util.Set;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
-
/**
* Represents a resource file that also generates ID resources.
* <p/>
@@ -45,11 +39,6 @@ import javax.xml.parsers.SAXParserFactory;
public final class IdGeneratingResourceFile extends ResourceFile
implements IValueResourceRepository {
- private final static SAXParserFactory sParserFactory = SAXParserFactory.newInstance();
- static {
- sParserFactory.setNamespaceAware(true);
- }
-
private final Map<String, ResourceValue> mIdResources =
new HashMap<String, ResourceValue>();
@@ -75,44 +64,49 @@ public final class IdGeneratingResourceFile extends ResourceFile
mFileName = getFileName(type);
// Get the resource value of this file as a whole layout
- mFileValue = getFileValue(file,folder);
+ mFileValue = getFileValue(file, folder);
}
@Override
- protected void load() {
+ protected void load(ScanningContext context) {
// Parse the file and look for @+id/ entries
- parseFileForIds();
+ parseFileForIds(context);
// create the resource items in the repository
- updateResourceItems();
+ updateResourceItems(context);
}
@Override
- protected void update() {
+ protected void update(ScanningContext context) {
// Copy the previous list of ID names
- Set<String> oldIdNames = mIdResources.keySet();
+ Set<String> oldIdNames = new HashSet<String>(mIdResources.keySet());
// reset current content.
mIdResources.clear();
// need to parse the file and find the IDs.
- parseFileForIds();
+ if (!parseFileForIds(context)) {
+ context.requestFullAapt();
+ return;
+ }
// We only need to update the repository if our IDs have changed
- if (oldIdNames.equals(mIdResources.keySet()) == false) {
- updateResourceItems();
+ Set<String> keySet = mIdResources.keySet();
+ assert keySet != oldIdNames;
+ if (oldIdNames.equals(keySet) == false) {
+ updateResourceItems(context);
}
}
@Override
- protected void dispose() {
+ protected void dispose(ScanningContext context) {
ResourceRepository repository = getRepository();
// Remove declarations from this file from the repository
repository.removeFile(mResourceTypeList, this);
// Ask for an ID refresh since we'll be taking away ID generating items
- repository.markForIdRefresh();
+ context.requestFullAapt();
}
@Override
@@ -145,22 +139,27 @@ public final class IdGeneratingResourceFile extends ResourceFile
/**
* Looks through the file represented for Ids and adds them to
* our id repository
+ *
+ * @return true if parsing succeeds and false if it fails
*/
- private void parseFileForIds() {
+ private boolean parseFileForIds(ScanningContext context) {
+ IdResourceParser parser = new IdResourceParser(this, context, isFramework());
try {
- SAXParser parser = sParserFactory.newSAXParser();
- parser.parse(getFile().getContents(), new IdResourceParser(this, isFramework()));
- } catch (ParserConfigurationException e) {
- } catch (SAXException e) {
+ IAbstractFile file = getFile();
+ return parser.parse(mFileType, file.getOsLocation(), file.getContents());
} catch (IOException e) {
+ // Pass
} catch (StreamException e) {
+ // Pass
}
+
+ return false;
}
/**
* Add the resources represented by this file to the repository
*/
- private void updateResourceItems() {
+ private void updateResourceItems(ScanningContext context) {
ResourceRepository repository = getRepository();
// remove this file from all existing ResourceItem.
@@ -178,7 +177,7 @@ public final class IdGeneratingResourceFile extends ResourceFile
}
// Ask the repository for an ID refresh
- repository.markForIdRefresh();
+ context.requestFullAapt();
}
/**
diff --git a/ide_common/src/com/android/ide/common/resources/IdResourceParser.java b/ide_common/src/com/android/ide/common/resources/IdResourceParser.java
index 27195a9..324ad2b 100644
--- a/ide_common/src/com/android/ide/common/resources/IdResourceParser.java
+++ b/ide_common/src/com/android/ide/common/resources/IdResourceParser.java
@@ -20,35 +20,136 @@ import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.resources.ValueResourceParser.IValueResourceRepository;
import com.android.resources.ResourceType;
-import org.xml.sax.Attributes;
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.DefaultHandler;
+import org.kxml2.io.KXmlParser;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
-public class IdResourceParser extends DefaultHandler {
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+/**
+ * Parser for scanning an id-generating resource file such as a layout or a menu
+ * file, which registers any ids it encounters with an
+ * {@link IValueResourceRepository}, and which registers errors with a
+ * {@link ScanningContext}.
+ */
+public class IdResourceParser {
private final IValueResourceRepository mRepository;
private final boolean mIsFramework;
+ private ScanningContext mContext;
- public IdResourceParser(IValueResourceRepository repository, boolean isFramework) {
- super();
+ /**
+ * Creates a new {@link IdResourceParser}
+ *
+ * @param repository value repository for registering resource declaration
+ * @param context a context object with state for the current update, such
+ * as a place to stash errors encountered
+ * @param isFramework true if scanning a framework resource
+ */
+ public IdResourceParser(IValueResourceRepository repository, ScanningContext context,
+ boolean isFramework) {
mRepository = repository;
+ mContext = context;
mIsFramework = isFramework;
}
- @Override
- public void startElement(String uri, String localName, String qName, Attributes attributes)
- throws SAXException {
- for (int i = 0; i < attributes.getLength(); ++i) {
- // Let's look up the value to look for the @+*id/ pattern
- String candidate = attributes.getValue(i);
- // Right now the only things that start with the @+ pattern are IDs. If this changes
- // in the future we'll have to change this line
- if (candidate != null && candidate.startsWith("@+")) {
- // Strip out the @+id/ or @+android:id/ section
- String id = candidate.substring(candidate.indexOf('/') + 1);
- ResourceValue newId = new ResourceValue(ResourceType.ID, id, mIsFramework);
- mRepository.addResourceValue(newId);
+ /**
+ * Parse the given input and register ids with the given
+ * {@link IValueResourceRepository}.
+ *
+ * @param type the type of resource being scanned
+ * @param path the full OS path to the file being parsed
+ * @param input the input stream of the XML to be parsed
+ * @return true if parsing succeeds and false if it fails
+ * @throws IOException if reading the contents fails
+ */
+ public boolean parse(ResourceType type, final String path, InputStream input)
+ throws IOException {
+ KXmlParser parser = new KXmlParser();
+ try {
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+
+ if (input instanceof FileInputStream) {
+ input = new BufferedInputStream(input);
}
+ parser.setInput(input, "UTF-8"); //$NON-NLS-1$
+
+ return parse(type, path, parser);
+ } catch (XmlPullParserException e) {
+ String message = e.getMessage();
+
+ // Strip off position description
+ int index = message.indexOf("(position:"); //$NON-NLS-1$ (Hardcoded in KXml)
+ if (index != -1) {
+ message = message.substring(0, index);
+ }
+
+ String error = String.format("%1$s:%2$d: Error: %3$s", //$NON-NLS-1$
+ path, parser.getLineNumber(), message);
+ mContext.addError(error);
+ return false;
+ } catch (RuntimeException e) {
+ // Some exceptions are thrown by the KXmlParser that are not XmlPullParserExceptions,
+ // such as this one:
+ // java.lang.RuntimeException: Undefined Prefix: w in org.kxml2.io.KXmlParser@...
+ // at org.kxml2.io.KXmlParser.adjustNsp(Unknown Source)
+ // at org.kxml2.io.KXmlParser.parseStartTag(Unknown Source)
+ String message = e.getMessage();
+ String error = String.format("%1$s:%2$d: Error: %3$s", //$NON-NLS-1$
+ path, parser.getLineNumber(), message);
+ mContext.addError(error);
+ return false;
}
}
+
+ private boolean parse(ResourceType type, String path, KXmlParser parser)
+ throws XmlPullParserException, IOException {
+ boolean valid = true;
+ ResourceRepository resources = mContext.getRepository();
+ boolean checkForErrors = !mIsFramework && !mContext.needsFullAapt();
+
+ while (true) {
+ int event = parser.next();
+ if (event == XmlPullParser.START_TAG) {
+ for (int i = 0, n = parser.getAttributeCount(); i < n; i++) {
+ String attribute = parser.getAttributeName(i);
+ String value = parser.getAttributeValue(i);
+ assert value != null : attribute;
+
+ if (value.startsWith("@")) { //$NON-NLS-1$
+ // Gather IDs
+ if (value.startsWith("@+")) { //$NON-NLS-1$
+ // Strip out the @+id/ or @+android:id/ section
+ String id = value.substring(value.indexOf('/') + 1);
+ ResourceValue newId = new ResourceValue(ResourceType.ID, id,
+ mIsFramework);
+ mRepository.addResourceValue(newId);
+ } else if (checkForErrors){
+ // Validate resource references (unless we're scanning a framework
+ // resource or if we've already scheduled a full aapt run)
+ boolean exists = resources.hasResourceItem(value);
+ if (!exists) {
+ String error = String.format(
+ // Don't localize because the exact pattern matches AAPT's
+ // output which has hardcoded regexp matching in
+ // AaptParser.
+ "%1$s:%2$d: Error: No resource found that matches " + //$NON-NLS-1$
+ "the given name (at '%3$s' with value '%4$s')", //$NON-NLS-1$
+ path, parser.getLineNumber(),
+ attribute, value);
+ mContext.addError(error);
+ valid = false;
+ }
+ }
+ }
+ }
+ } else if (event == XmlPullParser.END_DOCUMENT) {
+ break;
+ }
+ }
+
+ return valid;
+ }
}
diff --git a/ide_common/src/com/android/ide/common/resources/MultiResourceFile.java b/ide_common/src/com/android/ide/common/resources/MultiResourceFile.java
index b3e35d9..b95a98c 100644
--- a/ide_common/src/com/android/ide/common/resources/MultiResourceFile.java
+++ b/ide_common/src/com/android/ide/common/resources/MultiResourceFile.java
@@ -59,7 +59,7 @@ public final class MultiResourceFile extends ResourceFile implements IValueResou
private boolean mNeedIdRefresh;
@Override
- protected void load() {
+ protected void load(ScanningContext context) {
// need to parse the file and find the content.
parseFile();
@@ -70,11 +70,11 @@ public final class MultiResourceFile extends ResourceFile implements IValueResou
mNeedIdRefresh = true;
// create/update the resource items.
- updateResourceItems();
+ updateResourceItems(context);
}
@Override
- protected void update() {
+ protected void update(ScanningContext context) {
// Reset the ID generation flag
mNeedIdRefresh = false;
@@ -107,18 +107,18 @@ public final class MultiResourceFile extends ResourceFile implements IValueResou
mNeedIdRefresh = true;
}
// create/update the resource items.
- updateResourceItems();
+ updateResourceItems(context);
}
@Override
- protected void dispose() {
+ protected void dispose(ScanningContext context) {
ResourceRepository repository = getRepository();
// only remove this file from all existing ResourceItem.
repository.removeFile(mResourceTypeList, this);
// We'll need an ID refresh because we deleted items
- repository.markForIdRefresh();
+ context.requestFullAapt();
// don't need to touch the content, it'll get reclaimed as this objects disappear.
// In the mean time other objects may need to access it.
@@ -135,7 +135,7 @@ public final class MultiResourceFile extends ResourceFile implements IValueResou
return (list != null && list.size() > 0);
}
- private void updateResourceItems() {
+ private void updateResourceItems(ScanningContext context) {
ResourceRepository repository = getRepository();
// remove this file from all existing ResourceItem.
@@ -157,7 +157,7 @@ public final class MultiResourceFile extends ResourceFile implements IValueResou
// If we need an ID refresh, ask the repository for that now
if (mNeedIdRefresh) {
- repository.markForIdRefresh();
+ context.requestFullAapt();
}
}
diff --git a/ide_common/src/com/android/ide/common/resources/ResourceFile.java b/ide_common/src/com/android/ide/common/resources/ResourceFile.java
index 03f0b34..bc5b750 100644
--- a/ide_common/src/com/android/ide/common/resources/ResourceFile.java
+++ b/ide_common/src/com/android/ide/common/resources/ResourceFile.java
@@ -37,9 +37,9 @@ public abstract class ResourceFile implements Configurable {
mFolder = folder;
}
- protected abstract void load();
- protected abstract void update();
- protected abstract void dispose();
+ protected abstract void load(ScanningContext context);
+ protected abstract void update(ScanningContext context);
+ protected abstract void dispose(ScanningContext context);
public FolderConfiguration getConfiguration() {
return mFolder.getConfiguration();
diff --git a/ide_common/src/com/android/ide/common/resources/ResourceFolder.java b/ide_common/src/com/android/ide/common/resources/ResourceFolder.java
index e55e14c..b8e0cda 100644
--- a/ide_common/src/com/android/ide/common/resources/ResourceFolder.java
+++ b/ide_common/src/com/android/ide/common/resources/ResourceFolder.java
@@ -47,7 +47,7 @@ public final class ResourceFolder implements Configurable {
* @param type The type of the folder
* @param config The configuration of the folder
* @param folder The associated {@link IAbstractFolder} object.
- * @param isFrameworkRepository
+ * @param repository The associated {@link ResourceRepository}
*/
protected ResourceFolder(ResourceFolderType type, FolderConfiguration config,
IAbstractFolder folder, ResourceRepository repository) {
@@ -59,12 +59,15 @@ public final class ResourceFolder implements Configurable {
/**
* Processes a file and adds it to its parent folder resource.
+ *
* @param file the underlying resource file.
- * @param folder the parent of the resource file.
* @param kind the file change kind.
+ * @param context a context object with state for the current update, such
+ * as a place to stash errors encountered
* @return the {@link ResourceFile} that was created.
*/
- public ResourceFile processFile(IAbstractFile file, ResourceDeltaKind kind) {
+ public ResourceFile processFile(IAbstractFile file, ResourceDeltaKind kind,
+ ScanningContext context) {
// look for this file if it's already been created
ResourceFile resFile = getFile(file);
@@ -84,7 +87,7 @@ public final class ResourceFolder implements Configurable {
if (types.size() == 1) {
resFile = new SingleResourceFile(file, this);
- } else if (types.contains(ResourceType.LAYOUT)){
+ } else if (types.contains(ResourceType.LAYOUT)) {
resFile = new IdGeneratingResourceFile(file, this, ResourceType.LAYOUT);
} else if (types.contains(ResourceType.MENU)) {
resFile = new IdGeneratingResourceFile(file, this, ResourceType.MENU);
@@ -92,16 +95,16 @@ public final class ResourceFolder implements Configurable {
resFile = new MultiResourceFile(file, this);
}
- resFile.load();
+ resFile.load(context);
// add it to the folder
addFile(resFile);
}
} else {
if (kind == ResourceDeltaKind.REMOVED) {
- removeFile(resFile);
+ removeFile(resFile, context);
} else {
- resFile.update();
+ resFile.update(context);
}
}
@@ -122,15 +125,15 @@ public final class ResourceFolder implements Configurable {
mFiles.add(file);
}
- protected void removeFile(ResourceFile file) {
- file.dispose();
+ protected void removeFile(ResourceFile file, ScanningContext context) {
+ file.dispose(context);
mFiles.remove(file);
}
- protected void dispose() {
+ protected void dispose(ScanningContext context) {
if (mFiles != null) {
for (ResourceFile file : mFiles) {
- file.dispose();
+ file.dispose(context);
}
mFiles.clear();
diff --git a/ide_common/src/com/android/ide/common/resources/ResourceRepository.java b/ide_common/src/com/android/ide/common/resources/ResourceRepository.java
index 4af4a1a..d78f1d1 100644
--- a/ide_common/src/com/android/ide/common/resources/ResourceRepository.java
+++ b/ide_common/src/com/android/ide/common/resources/ResourceRepository.java
@@ -68,8 +68,6 @@ public abstract class ResourceRepository {
protected final IntArrayWrapper mWrapper = new IntArrayWrapper(null);
- private boolean mNeedsIdRefresh;
-
/**
* Makes a resource repository
* @param isFrameworkRepository whether the repository is for framework resources.
@@ -129,7 +127,8 @@ public abstract class ResourceRepository {
* @param removedFolder the IAbstractFolder object.
* @return the {@link ResourceFolder} that was removed, or null if no matches were found.
*/
- public ResourceFolder removeFolder(ResourceFolderType type, IAbstractFolder removedFolder) {
+ public ResourceFolder removeFolder(ResourceFolderType type, IAbstractFolder removedFolder,
+ ScanningContext context) {
// get the list of folders for the resource type.
List<ResourceFolder> list = mFolderMap.get(type);
@@ -143,7 +142,7 @@ public abstract class ResourceRepository {
list.remove(i);
// remove its content
- resFolder.dispose();
+ resFolder.dispose(context);
return resFolder;
}
@@ -154,6 +153,60 @@ public abstract class ResourceRepository {
}
/**
+ * Returns true if this resource repository contains a resource of the given
+ * name.
+ *
+ * @param url the resource URL
+ * @return true if the resource is known
+ */
+ public boolean hasResourceItem(String url) {
+ assert url.startsWith("@") : url;
+
+ int typeEnd = url.indexOf('/', 1);
+ if (typeEnd != -1) {
+ int nameBegin = typeEnd + 1;
+
+ // Skip @ and @+
+ int typeBegin = url.startsWith("@+") ? 2 : 1; //$NON-NLS-1$
+
+ int colon = url.lastIndexOf(':', typeEnd);
+ if (colon != -1) {
+ typeBegin = colon + 1;
+ }
+ String typeName = url.substring(typeBegin, typeEnd);
+ ResourceType type = ResourceType.getEnum(typeName);
+ if (type != null) {
+ String name = url.substring(nameBegin);
+ return hasResourceItem(type, name);
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns true if this resource repository contains a resource of the given
+ * name.
+ *
+ * @param type the type of resource to look up
+ * @param name the name of the resource
+ * @return true if the resource is known
+ */
+ public boolean hasResourceItem(ResourceType type, String name) {
+ List<ResourceItem> list = mResourceMap.get(type);
+
+ if (list != null) {
+ for (ResourceItem item : list) {
+ if (name.equals(item.getName())) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
* Returns a {@link ResourceItem} matching the given {@link ResourceType} and name. If none
* exist, it creates one.
*
@@ -196,29 +249,6 @@ public abstract class ResourceRepository {
protected abstract ResourceItem createResourceItem(String name);
/**
- * Sets a flag which determines whether aapt needs to be run to regenerate resource IDs
- */
- protected void markForIdRefresh() {
- mNeedsIdRefresh = true;
- }
-
- /**
- * Returns whether this repository has been marked as "dirty"; if one or more of the constituent
- * files have declared that the resource item names that they provide have changed.
- */
- public boolean needsIdRefresh() {
- return mNeedsIdRefresh;
- }
-
- /**
- * Indicates that the resources IDs have been regenerated, so the repository is now in a clean
- * state
- */
- public void setIdsRefreshed() {
- mNeedsIdRefresh = false;
- }
-
- /**
* Processes a folder and adds it to the list of existing folders.
* @param folder the folder to process
* @return the ResourceFolder created from this folder, or null if the process failed.
@@ -496,6 +526,8 @@ public abstract class ResourceRepository {
*/
public void loadResources(IAbstractFolder rootFolder)
throws IOException {
+ ScanningContext context = new ScanningContext(this);
+
IAbstractResource[] files = rootFolder.listMembers();
for (IAbstractResource file : files) {
if (file instanceof IAbstractFolder) {
@@ -509,7 +541,7 @@ public abstract class ResourceRepository {
for (IAbstractResource childRes : children) {
if (childRes instanceof IAbstractFile) {
resFolder.processFile((IAbstractFile) childRes,
- ResourceDeltaKind.ADDED);
+ ResourceDeltaKind.ADDED, context);
}
}
}
diff --git a/ide_common/src/com/android/ide/common/resources/ScanningContext.java b/ide_common/src/com/android/ide/common/resources/ScanningContext.java
new file mode 100644
index 0000000..e4ed275
--- /dev/null
+++ b/ide_common/src/com/android/ide/common/resources/ScanningContext.java
@@ -0,0 +1,92 @@
+/*
+ * 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.common.resources;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A {@link ScanningContext} keeps track of state during a resource file scan,
+ * such as any parsing errors encountered, whether Android ids have changed, and
+ * so on.
+ */
+public class ScanningContext {
+ private final ResourceRepository mRepository;
+ private boolean mNeedsFullAapt;
+ private List<String> mErrors = null;
+
+ /**
+ * Constructs a new {@link ScanningContext}
+ *
+ * @param repository the associated resource repository
+ */
+ public ScanningContext(ResourceRepository repository) {
+ super();
+ mRepository = repository;
+ }
+
+ /**
+ * Returns a list of errors encountered during scanning
+ *
+ * @return a list of errors encountered during scanning (or null)
+ */
+ public List<String> getErrors() {
+ return mErrors;
+ }
+
+ /**
+ * Adds the given error to the scanning context. The error should use the
+ * same syntax as real aapt error messages such that the aapt parser can
+ * properly detect the filename, line number, etc.
+ *
+ * @param error the error message, including file name and line number at
+ * the beginning
+ */
+ public void addError(String error) {
+ if (mErrors == null) {
+ mErrors = new ArrayList<String>();
+ }
+ mErrors.add(error);
+ }
+
+ /**
+ * Returns the repository associated with this scanning context
+ *
+ * @return the associated repository, never null
+ */
+ public ResourceRepository getRepository() {
+ return mRepository;
+ }
+
+ /**
+ * Marks that a full aapt compilation of the resources is necessary because it has
+ * detected a change that cannot be incrementally handled.
+ */
+ protected void requestFullAapt() {
+ mNeedsFullAapt = true;
+ }
+
+ /**
+ * Returns whether this repository has been marked as "dirty"; if one or
+ * more of the constituent files have declared that the resource item names
+ * that they provide have changed.
+ *
+ * @return true if a full aapt compilation is required
+ */
+ public boolean needsFullAapt() {
+ return mNeedsFullAapt;
+ }
+}
diff --git a/ide_common/src/com/android/ide/common/resources/SingleResourceFile.java b/ide_common/src/com/android/ide/common/resources/SingleResourceFile.java
index b589b35..6b663e9 100644
--- a/ide_common/src/com/android/ide/common/resources/SingleResourceFile.java
+++ b/ide_common/src/com/android/ide/common/resources/SingleResourceFile.java
@@ -73,7 +73,7 @@ public class SingleResourceFile extends ResourceFile {
}
@Override
- protected void load() {
+ protected void load(ScanningContext context) {
// get a resource item matching the given type and name
ResourceItem item = getRepository().getResourceItem(mType, mResourceName);
@@ -81,22 +81,22 @@ public class SingleResourceFile extends ResourceFile {
item.add(this);
// Ask for an ID refresh since we're adding an item that will generate an ID
- getRepository().markForIdRefresh();
+ context.requestFullAapt();
}
@Override
- protected void update() {
+ protected void update(ScanningContext context) {
// when this happens, nothing needs to be done since the file only generates
// a single resources that doesn't actually change (its content is the file path)
}
@Override
- protected void dispose() {
+ protected void dispose(ScanningContext context) {
// only remove this file from the existing ResourceItem.
getFolder().getRepository().removeFile(mType, this);
// Ask for an ID refresh since we're removing an item that previously generated an ID
- getRepository().markForIdRefresh();
+ context.requestFullAapt();
// don't need to touch the content, it'll get reclaimed as this objects disappear.
// In the mean time other objects may need to access it.