aboutsummaryrefslogtreecommitdiffstats
path: root/sdk_common/src
diff options
context:
space:
mode:
Diffstat (limited to 'sdk_common/src')
-rw-r--r--sdk_common/src/com/android/ide/common/resources/IdResourceParser.java45
-rwxr-xr-xsdk_common/src/com/android/ide/common/resources/ResourceRepository.java2
-rw-r--r--sdk_common/src/com/android/ide/common/resources/ScanningContext.java24
-rw-r--r--sdk_common/src/com/android/ide/common/resources/SingleResourceFile.java26
-rw-r--r--sdk_common/src/com/android/ide/common/resources/ValidatingResourceParser.java136
5 files changed, 202 insertions, 31 deletions
diff --git a/sdk_common/src/com/android/ide/common/resources/IdResourceParser.java b/sdk_common/src/com/android/ide/common/resources/IdResourceParser.java
index 1de664e..66a72ce 100644
--- a/sdk_common/src/com/android/ide/common/resources/IdResourceParser.java
+++ b/sdk_common/src/com/android/ide/common/resources/IdResourceParser.java
@@ -16,6 +16,7 @@
package com.android.ide.common.resources;
+import com.android.annotations.NonNull;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.resources.ValueResourceParser.IValueResourceRepository;
import com.android.resources.ResourceType;
@@ -48,7 +49,9 @@ public class IdResourceParser {
* as a place to stash errors encountered
* @param isFramework true if scanning a framework resource
*/
- public IdResourceParser(IValueResourceRepository repository, ScanningContext context,
+ public IdResourceParser(
+ @NonNull IValueResourceRepository repository,
+ @NonNull ScanningContext context,
boolean isFramework) {
mRepository = repository;
mContext = context;
@@ -107,7 +110,6 @@ public class IdResourceParser {
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) {
@@ -118,33 +120,22 @@ public class IdResourceParser {
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 && !mRepository.hasResourceValue(ResourceType.ID,
- value.substring(value.indexOf('/') + 1))) {
- 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;
- }
+ if (checkForErrors) {
+ String uri = parser.getAttributeNamespace(i);
+ if (!mContext.checkValue(uri, attribute, value)) {
+ mContext.requestFullAapt();
+ checkForErrors = false;
+ valid = false;
}
}
+
+ 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 (event == XmlPullParser.END_DOCUMENT) {
break;
diff --git a/sdk_common/src/com/android/ide/common/resources/ResourceRepository.java b/sdk_common/src/com/android/ide/common/resources/ResourceRepository.java
index ac0614d..4f50f63 100755
--- a/sdk_common/src/com/android/ide/common/resources/ResourceRepository.java
+++ b/sdk_common/src/com/android/ide/common/resources/ResourceRepository.java
@@ -171,7 +171,7 @@ public abstract class ResourceRepository {
* @return true if the resource is known
*/
public boolean hasResourceItem(@NonNull String url) {
- assert url.startsWith("@") : url;
+ assert url.startsWith("@") || url.startsWith("?") : url;
int typeEnd = url.indexOf('/', 1);
if (typeEnd != -1) {
diff --git a/sdk_common/src/com/android/ide/common/resources/ScanningContext.java b/sdk_common/src/com/android/ide/common/resources/ScanningContext.java
index e4ed275..43561e8 100644
--- a/sdk_common/src/com/android/ide/common/resources/ScanningContext.java
+++ b/sdk_common/src/com/android/ide/common/resources/ScanningContext.java
@@ -15,6 +15,9 @@
*/
package com.android.ide.common.resources;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
import java.util.ArrayList;
import java.util.List;
@@ -24,7 +27,7 @@ import java.util.List;
* so on.
*/
public class ScanningContext {
- private final ResourceRepository mRepository;
+ protected final ResourceRepository mRepository;
private boolean mNeedsFullAapt;
private List<String> mErrors = null;
@@ -33,7 +36,7 @@ public class ScanningContext {
*
* @param repository the associated resource repository
*/
- public ScanningContext(ResourceRepository repository) {
+ public ScanningContext(@NonNull ResourceRepository repository) {
super();
mRepository = repository;
}
@@ -43,6 +46,7 @@ public class ScanningContext {
*
* @return a list of errors encountered during scanning (or null)
*/
+ @Nullable
public List<String> getErrors() {
return mErrors;
}
@@ -55,7 +59,7 @@ public class ScanningContext {
* @param error the error message, including file name and line number at
* the beginning
*/
- public void addError(String error) {
+ public void addError(@NonNull String error) {
if (mErrors == null) {
mErrors = new ArrayList<String>();
}
@@ -67,6 +71,7 @@ public class ScanningContext {
*
* @return the associated repository, never null
*/
+ @NonNull
public ResourceRepository getRepository() {
return mRepository;
}
@@ -89,4 +94,17 @@ public class ScanningContext {
public boolean needsFullAapt() {
return mNeedsFullAapt;
}
+
+ /**
+ * Asks the context to check whether the given attribute name and value is valid
+ * in this context.
+ *
+ * @param uri the XML namespace URI
+ * @param name the attribute local name
+ * @param value the attribute value
+ * @return true if the attribute is valid
+ */
+ public boolean checkValue(@Nullable String uri, @NonNull String name, @NonNull String value) {
+ return true;
+ }
}
diff --git a/sdk_common/src/com/android/ide/common/resources/SingleResourceFile.java b/sdk_common/src/com/android/ide/common/resources/SingleResourceFile.java
index 6b663e9..eb643f6 100644
--- a/sdk_common/src/com/android/ide/common/resources/SingleResourceFile.java
+++ b/sdk_common/src/com/android/ide/common/resources/SingleResourceFile.java
@@ -88,6 +88,10 @@ public class SingleResourceFile extends ResourceFile {
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)
+
+ // However, we should check for newly introduced errors
+ // Parse the file and look for @+id/ entries
+ validateAttributes(context);
}
@Override
@@ -139,4 +143,26 @@ public class SingleResourceFile extends ResourceFile {
return name;
}
+
+ /**
+ * Validates the associated resource file to make sure the attribute references are valid
+ *
+ * @return true if parsing succeeds and false if it fails
+ */
+ private boolean validateAttributes(ScanningContext context) {
+ // We only need to check if it's a non-framework file
+ if (!isFramework()) {
+ ValidatingResourceParser parser = new ValidatingResourceParser(context, false);
+ try {
+ IAbstractFile file = getFile();
+ return parser.parse(file.getOsLocation(), file.getContents());
+ } catch (Exception e) {
+ context.needsFullAapt();
+ }
+
+ return false;
+ }
+
+ return true;
+ }
}
diff --git a/sdk_common/src/com/android/ide/common/resources/ValidatingResourceParser.java b/sdk_common/src/com/android/ide/common/resources/ValidatingResourceParser.java
new file mode 100644
index 0000000..c1e45a8
--- /dev/null
+++ b/sdk_common/src/com/android/ide/common/resources/ValidatingResourceParser.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 com.android.annotations.NonNull;
+
+import org.kxml2.io.KXmlParser;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Parser for scanning an XML resource file and validating all framework
+ * attribute references in it. If an error is found, the associated context
+ * is marked as needing a full AAPT run.
+ */
+public class ValidatingResourceParser {
+ private final boolean mIsFramework;
+ private ScanningContext mContext;
+
+ /**
+ * Creates a new {@link ValidatingResourceParser}
+ *
+ * @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 ValidatingResourceParser(
+ @NonNull ScanningContext context,
+ boolean isFramework) {
+ mContext = context;
+ mIsFramework = isFramework;
+ }
+
+ /**
+ * Parse the given input and return false if it contains errors, <b>or</b> if
+ * the context is already tagged as needing a full aapt run.
+ *
+ * @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(final String path, InputStream input)
+ throws IOException {
+ // No need to validate framework files
+ if (mIsFramework) {
+ return true;
+ }
+ if (mContext.needsFullAapt()) {
+ return false;
+ }
+
+ 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(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(String path, KXmlParser parser)
+ throws XmlPullParserException, IOException {
+ 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 (checkForErrors) {
+ String uri = parser.getAttributeNamespace(i);
+ if (!mContext.checkValue(uri, attribute, value)) {
+ mContext.requestFullAapt();
+ return false;
+ }
+ }
+ }
+ } else if (event == XmlPullParser.END_DOCUMENT) {
+ break;
+ }
+ }
+
+ return true;
+ }
+}