diff options
66 files changed, 1654 insertions, 298 deletions
diff --git a/anttasks/src/com/android/ant/AaptExecTask.java b/anttasks/src/com/android/ant/AaptExecTask.java index 5fd57b8..c82f0d6 100644 --- a/anttasks/src/com/android/ant/AaptExecTask.java +++ b/anttasks/src/com/android/ant/AaptExecTask.java @@ -675,8 +675,9 @@ public final class AaptExecTask extends SingleDependencyTask { task.execute(); // now if the project has libraries, R needs to be created for each libraries + // but only if the project is not a library. try { - if (libPkgProp != null && !libPkgProp.isEmpty()) { + if (!mNonConstantId && libPkgProp != null && !libPkgProp.isEmpty()) { SymbolLoader symbolValues = new SymbolLoader(new File(mBinFolder, "R.txt")); symbolValues.load(); diff --git a/build/tools.atree b/build/tools.atree index adb666c..78da7fc 100644 --- a/build/tools.atree +++ b/build/tools.atree @@ -173,7 +173,6 @@ framework/annotations.jar tools/support/annotations.jar # systrace external/chromium-trace/systrace.py tools/systrace/systrace.py -external/chromium-trace/config.py tools/systrace/config.py external/chromium-trace/script.js tools/systrace/script.js external/chromium-trace/style.css tools/systrace/style.css external/chromium-trace/LICENSE tools/systrace/LICENSE diff --git a/common/src/com/android/SdkConstants.java b/common/src/com/android/SdkConstants.java index 1aa3853..cf64967 100644 --- a/common/src/com/android/SdkConstants.java +++ b/common/src/com/android/SdkConstants.java @@ -816,6 +816,9 @@ public final class SdkConstants { public static final String UNIT_DIP = "dip"; //$NON-NLS-1$ public static final String UNIT_SP = "sp"; //$NON-NLS-1$ public static final String UNIT_PX = "px"; //$NON-NLS-1$ + public static final String UNIT_IN = "in"; //$NON-NLS-1$ + public static final String UNIT_MM = "mm"; //$NON-NLS-1$ + public static final String UNIT_PT = "pt"; //$NON-NLS-1$ // Filenames and folder names public static final String ANDROID_MANIFEST_XML = "AndroidManifest.xml"; //$NON-NLS-1$ diff --git a/common/src/com/android/utils/SdkUtils.java b/common/src/com/android/utils/SdkUtils.java new file mode 100644 index 0000000..b49c120 --- /dev/null +++ b/common/src/com/android/utils/SdkUtils.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2012 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.utils; + +import com.android.annotations.NonNull; + +public class SdkUtils { + /** + * Returns true if the given string ends with the given suffix, using a + * case-insensitive comparison. + * + * @param string the full string to be checked + * @param suffix the suffix to be checked for + * @return true if the string case-insensitively ends with the given suffix + */ + public static boolean endsWithIgnoreCase(String string, String suffix) { + return string.regionMatches(true /* ignoreCase */, string.length() - suffix.length(), + suffix, 0, suffix.length()); + } + + /** + * Returns true if the given sequence ends with the given suffix (case + * sensitive). + * + * @param sequence the character sequence to be checked + * @param suffix the suffix to look for + * @return true if the given sequence ends with the given suffix + */ + public static boolean endsWith(CharSequence sequence, CharSequence suffix) { + return endsWith(sequence, sequence.length(), suffix); + } + + /** + * Returns true if the given sequence ends at the given offset with the given suffix (case + * sensitive) + * + * @param sequence the character sequence to be checked + * @param endOffset the offset at which the sequence is considered to end + * @param suffix the suffix to look for + * @return true if the given sequence ends with the given suffix + */ + public static boolean endsWith(CharSequence sequence, int endOffset, CharSequence suffix) { + if (endOffset < suffix.length()) { + return false; + } + + for (int i = endOffset - 1, j = suffix.length() - 1; j >= 0; i--, j--) { + if (sequence.charAt(i) != suffix.charAt(j)) { + return false; + } + } + + return true; + } + + /** + * Returns true if the given string starts with the given prefix, using a + * case-insensitive comparison. + * + * @param string the full string to be checked + * @param prefix the prefix to be checked for + * @return true if the string case-insensitively starts with the given prefix + */ + public static boolean startsWithIgnoreCase(String string, String prefix) { + return string.regionMatches(true /* ignoreCase */, 0, prefix, 0, prefix.length()); + } + + /** + * Returns true if the given string starts at the given offset with the + * given prefix, case insensitively. + * + * @param string the full string to be checked + * @param offset the offset in the string to start looking + * @param prefix the prefix to be checked for + * @return true if the string case-insensitively starts at the given offset + * with the given prefix + */ + public static boolean startsWith(String string, int offset, String prefix) { + return string.regionMatches(true /* ignoreCase */, offset, prefix, 0, prefix.length()); + } + + /** + * Strips the whitespace from the given string + * + * @param string the string to be cleaned up + * @return the string, without whitespace + */ + public static String stripWhitespace(String string) { + StringBuilder sb = new StringBuilder(string.length()); + for (int i = 0, n = string.length(); i < n; i++) { + char c = string.charAt(i); + if (!Character.isWhitespace(c)) { + sb.append(c); + } + } + + return sb.toString(); + } + + /** For use by {@link #getLineSeparator()} */ + private static String sLineSeparator; + + /** + * Returns the default line separator to use. + * <p> + * NOTE: If you have an associated {@link IDocument}, it is better to call + * {@link TextUtilities#getDefaultLineDelimiter(IDocument)} since that will + * allow (for example) editing a \r\n-delimited document on a \n-delimited + * platform and keep a consistent usage of delimiters in the file. + * + * @return the delimiter string to use + */ + @NonNull + public static String getLineSeparator() { + if (sLineSeparator == null) { + // This is guaranteed to exist: + sLineSeparator = System.getProperty("line.separator"); //$NON-NLS-1$ + } + + return sLineSeparator; + } +} diff --git a/common/src/com/android/utils/XmlUtils.java b/common/src/com/android/utils/XmlUtils.java index 73e1d11..94b7405 100644 --- a/common/src/com/android/utils/XmlUtils.java +++ b/common/src/com/android/utils/XmlUtils.java @@ -182,6 +182,27 @@ public class XmlUtils { } /** + * Converts the given attribute value to an XML-text-safe value, meaning that + * less than and ampersand characters are escaped. + * + * @param textValue the text value to be escaped + * @return the escaped value + */ + @NonNull + public static String toXmlTextValue(@NonNull String textValue) { + for (int i = 0, n = textValue.length(); i < n; i++) { + char c = textValue.charAt(i); + if (c == '<' || c == '&') { + StringBuilder sb = new StringBuilder(2 * textValue.length()); + appendXmlTextValue(sb, textValue); + return sb.toString(); + } + } + + return textValue; + } + + /** * Appends text to the given {@link StringBuilder} and escapes it as required for a * DOM attribute node. * diff --git a/common/tests/src/com/android/utils/SdkUtilsTest.java b/common/tests/src/com/android/utils/SdkUtilsTest.java new file mode 100644 index 0000000..29a4d51 --- /dev/null +++ b/common/tests/src/com/android/utils/SdkUtilsTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2012 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.utils; + +import junit.framework.TestCase; + +public class SdkUtilsTest extends TestCase { + public void testEndsWithIgnoreCase() { + assertTrue(SdkUtils.endsWithIgnoreCase("foo", "foo")); + assertTrue(SdkUtils.endsWithIgnoreCase("foo", "Foo")); + assertTrue(SdkUtils.endsWithIgnoreCase("foo", "foo")); + assertTrue(SdkUtils.endsWithIgnoreCase("Barfoo", "foo")); + assertTrue(SdkUtils.endsWithIgnoreCase("BarFoo", "foo")); + assertTrue(SdkUtils.endsWithIgnoreCase("BarFoo", "foO")); + + assertFalse(SdkUtils.endsWithIgnoreCase("foob", "foo")); + assertFalse(SdkUtils.endsWithIgnoreCase("foo", "fo")); + } + + public void testStartsWithIgnoreCase() { + assertTrue(SdkUtils.startsWithIgnoreCase("foo", "foo")); + assertTrue(SdkUtils.startsWithIgnoreCase("foo", "Foo")); + assertTrue(SdkUtils.startsWithIgnoreCase("foo", "foo")); + assertTrue(SdkUtils.startsWithIgnoreCase("barfoo", "bar")); + assertTrue(SdkUtils.startsWithIgnoreCase("BarFoo", "bar")); + assertTrue(SdkUtils.startsWithIgnoreCase("BarFoo", "bAr")); + + assertFalse(SdkUtils.startsWithIgnoreCase("bfoo", "foo")); + assertFalse(SdkUtils.startsWithIgnoreCase("fo", "foo")); + } + + public void testStartsWith() { + assertTrue(SdkUtils.startsWith("foo", 0, "foo")); + assertTrue(SdkUtils.startsWith("foo", 0, "Foo")); + assertTrue(SdkUtils.startsWith("Foo", 0, "foo")); + assertTrue(SdkUtils.startsWith("aFoo", 1, "foo")); + + assertFalse(SdkUtils.startsWith("aFoo", 0, "foo")); + assertFalse(SdkUtils.startsWith("aFoo", 2, "foo")); + } + + public void testEndsWith() { + assertTrue(SdkUtils.endsWith("foo", "foo")); + assertTrue(SdkUtils.endsWith("foobar", "obar")); + assertTrue(SdkUtils.endsWith("foobar", "bar")); + assertTrue(SdkUtils.endsWith("foobar", "ar")); + assertTrue(SdkUtils.endsWith("foobar", "r")); + assertTrue(SdkUtils.endsWith("foobar", "")); + + assertTrue(SdkUtils.endsWith(new StringBuilder("foobar"), "bar")); + assertTrue(SdkUtils.endsWith(new StringBuilder("foobar"), new StringBuffer("obar"))); + assertTrue(SdkUtils.endsWith("foobar", new StringBuffer("obar"))); + + assertFalse(SdkUtils.endsWith("foo", "fo")); + assertFalse(SdkUtils.endsWith("foobar", "Bar")); + assertFalse(SdkUtils.endsWith("foobar", "longfoobar")); + } + + public void testEndsWith2() { + assertTrue(SdkUtils.endsWith("foo", "foo".length(), "foo")); + assertTrue(SdkUtils.endsWith("foo", "fo".length(), "fo")); + assertTrue(SdkUtils.endsWith("foo", "f".length(), "f")); + } + + public void testStripWhitespace() { + assertEquals("foo", SdkUtils.stripWhitespace("foo")); + assertEquals("foobar", SdkUtils.stripWhitespace("foo bar")); + assertEquals("foobar", SdkUtils.stripWhitespace(" foo bar \n\t")); + } +} diff --git a/common/tests/src/com/android/utils/XmlUtilsTest.java b/common/tests/src/com/android/utils/XmlUtilsTest.java index 6c28451..ea33346 100644 --- a/common/tests/src/com/android/utils/XmlUtilsTest.java +++ b/common/tests/src/com/android/utils/XmlUtilsTest.java @@ -16,7 +16,6 @@ package com.android.utils; import com.android.SdkConstants; -import com.android.utils.XmlUtils; import org.w3c.dom.Attr; import org.w3c.dom.Document; @@ -79,6 +78,10 @@ public class XmlUtilsTest extends TestCase { assertEquals("<"'>&", sb.toString()); } + public void testToXmlTextValue() throws Exception { + assertEquals("<\"'>&", XmlUtils.toXmlTextValue("<\"'>&")); + } + public void testAppendXmlTextValue() throws Exception { StringBuilder sb = new StringBuilder(); XmlUtils.appendXmlTextValue(sb, "<\"'>&"); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttributeInfo.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttributeInfo.java index 05f0adf..be928b0 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttributeInfo.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttributeInfo.java @@ -16,10 +16,31 @@ package com.android.ide.common.resources.platform; +import static com.android.SdkConstants.ANDROID_PREFIX; +import static com.android.SdkConstants.ANDROID_THEME_PREFIX; +import static com.android.SdkConstants.ID_PREFIX; +import static com.android.SdkConstants.NEW_ID_PREFIX; +import static com.android.SdkConstants.VALUE_FALSE; +import static com.android.SdkConstants.VALUE_TRUE; +import static com.android.ide.common.api.IAttributeInfo.Format.BOOLEAN; +import static com.android.ide.common.api.IAttributeInfo.Format.COLOR; +import static com.android.ide.common.api.IAttributeInfo.Format.DIMENSION; +import static com.android.ide.common.api.IAttributeInfo.Format.ENUM; +import static com.android.ide.common.api.IAttributeInfo.Format.FLAG; +import static com.android.ide.common.api.IAttributeInfo.Format.FLOAT; +import static com.android.ide.common.api.IAttributeInfo.Format.FRACTION; +import static com.android.ide.common.api.IAttributeInfo.Format.INTEGER; +import static com.android.ide.common.api.IAttributeInfo.Format.STRING; + import com.android.annotations.NonNull; +import com.android.annotations.Nullable; import com.android.ide.common.api.IAttributeInfo; +import com.android.ide.common.resources.ResourceRepository; +import com.android.resources.ResourceType; +import com.google.common.base.Splitter; import java.util.EnumSet; +import java.util.regex.Pattern; /** @@ -160,4 +181,161 @@ public class AttributeInfo implements IAttributeInfo { public @NonNull String getDefinedBy() { return mDefinedBy; } + + private final static Pattern INTEGER_PATTERN = Pattern.compile("-?[0-9]+"); //$NON-NLS-1$ + private final static Pattern FLOAT_PATTERN = + Pattern.compile("-?[0-9]?(\\.[0-9]+)?"); //$NON-NLS-1$ + private final static Pattern DIMENSION_PATTERN = + Pattern.compile("-?[0-9]+(\\.[0-9]+)?(dp|dip|sp|px|pt|in|mm)"); //$NON-NLS-1$ + + /** + * Checks the given value and returns true only if it is a valid XML value + * for this attribute. + * + * @param value the XML value to check + * @param projectResources project resources to validate resource URLs with, + * if any + * @param frameworkResources framework resources to validate resource URLs + * with, if any + * @return true if the value is valid, false otherwise + */ + public boolean isValid( + @NonNull String value, + @Nullable ResourceRepository projectResources, + @Nullable ResourceRepository frameworkResources) { + + if (mFormats.contains(STRING) || mFormats.isEmpty()) { + // Anything is allowed + return true; + } + + // All other formats require a nonempty string + if (value.isEmpty()) { + return false; + } + char first = value.charAt(0); + + // There are many attributes which are incorrectly marked in the attrs.xml + // file, such as "duration", "minHeight", etc. These are marked as only + // accepting "integer", but also appear to accept "reference". Therefore, + // in these cases, be more lenient. (This happens for theme references too, + // such as ?android:attr/listPreferredItemHeight) + if ((first == '@' || first == '?') /* && mFormats.contains(REFERENCE)*/) { + if (value.equals("@null")) { + return true; + } + + if (value.startsWith(NEW_ID_PREFIX) || value.startsWith(ID_PREFIX)) { + // These are handled in the IdGeneratingResourceFile; we shouldn't + // complain about not finding ids in the repository yet since they may + // not yet have been defined (@+id's can be defined in the same layout, + // later on.) + return true; + } + + if (value.startsWith(ANDROID_PREFIX) || value.startsWith(ANDROID_THEME_PREFIX)) { + if (frameworkResources != null) { + return frameworkResources.hasResourceItem(value); + } + } else if (projectResources != null) { + return projectResources.hasResourceItem(value); + } + + // Validate resource string + String url = value; + int typeEnd = url.indexOf('/', 1); + if (typeEnd != -1) { + 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) { + // TODO: Validate that the name portion conforms to the rules + // (is an identifier but not a keyword, etc.) + // Also validate that the prefix before the colon is either + // not there or is "android" + + //int nameBegin = typeEnd + 1; + //String name = url.substring(nameBegin); + return true; + } + } + } + + if (mFormats.contains(ENUM) && mEnumValues != null) { + for (String e : mEnumValues) { + if (value.equals(e)) { + return true; + } + } + } + + if (mFormats.contains(FLAG) && mFlagValues != null) { + for (String v : Splitter.on('|').split(value)) { + for (String e : mFlagValues) { + if (v.equals(e)) { + return true; + } + } + } + } + + if (mFormats.contains(DIMENSION)) { + if (DIMENSION_PATTERN.matcher(value).matches()) { + return true; + } + } + + if (mFormats.contains(BOOLEAN)) { + if (value.equals(VALUE_TRUE) || value.equals(VALUE_FALSE)) { + return true; + } + } + + if (mFormats.contains(FLOAT)) { + if (Character.isDigit(first) || first == '-' || first == '.') { + if (FLOAT_PATTERN.matcher(value).matches()) { + return true; + } + // AAPT accepts more general floats, such as ".1", + try { + Float.parseFloat(value); + return true; + } catch (NumberFormatException nufe) { + // Not a float + } + } + } + + if (mFormats.contains(INTEGER)) { + if (Character.isDigit(first) || first == '-') { + if (INTEGER_PATTERN.matcher(value).matches()) { + return true; + } + } + } + + if (mFormats.contains(COLOR)) { + if (first == '#' && value.length() <= 9) { // Only allowed 32 bit ARGB + try { + // Use Long.parseLong rather than Integer.parseInt to not overflow on + // 32 big hex values like "ff191919" + Long.parseLong(value.substring(1), 16); + return true; + } catch (NumberFormatException nufe) { + // Not a valid color number + } + } + } + + if (mFormats.contains(FRACTION)) { + // should end with % or %p + return true; + } + + return false; + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttrsXmlParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttrsXmlParser.java index 3c1fa97..1330c50 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttrsXmlParser.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttrsXmlParser.java @@ -23,6 +23,7 @@ import com.android.ide.common.api.IAttributeInfo.Format; import com.android.ide.common.resources.platform.ViewClassInfo.LayoutParamsInfo; import com.android.ide.eclipse.adt.AdtUtils; import com.android.utils.ILogger; +import com.google.common.collect.Maps; import org.w3c.dom.Document; import org.w3c.dom.Node; @@ -54,7 +55,7 @@ public final class AttrsXmlParser { // all attributes that have the same name are supposed to have the same // parameters so we'll keep a cache of them to avoid processing them twice. - private HashMap<String, AttributeInfo> mAttributeMap; + private Map<String, AttributeInfo> mAttributeMap; /** Map of all attribute names for a given element */ private final Map<String, DeclareStyleableInfo> mStyleMap = @@ -75,7 +76,6 @@ public final class AttrsXmlParser { */ private final ILogger mLog; - /** * Creates a new {@link AttrsXmlParser}, set to load things from the given * XML file. Nothing has been parsed yet. Callers should call {@link #preload()} @@ -84,9 +84,19 @@ public final class AttrsXmlParser { * @param osAttrsXmlPath The path of the <code>attrs.xml</code> file to parse. * Must not be null. Should point to an existing valid XML document. * @param log A logger object. Must not be null. + * @param expectedAttributeCount expected number of attributes in the file + */ + public AttrsXmlParser(String osAttrsXmlPath, ILogger log, int expectedAttributeCount) { + this(osAttrsXmlPath, null /* inheritableAttributes */, log, expectedAttributeCount); + } + + /** + * Returns the parsed map of attribute infos + * + * @return a map from string name to {@link AttributeInfo} */ - public AttrsXmlParser(String osAttrsXmlPath, ILogger log) { - this(osAttrsXmlPath, null /* inheritableAttributes */, log); + public Map<String, AttributeInfo> getAttributeMap() { + return mAttributeMap; } /** @@ -103,24 +113,26 @@ public final class AttrsXmlParser { * If not null, the parser must have had its {@link #preload()} method * invoked prior to being used here. * @param log A logger object. Must not be null. + * @param expectedAttributeCount expected number of attributes in the file */ public AttrsXmlParser( String osAttrsXmlPath, AttrsXmlParser inheritableAttributes, - ILogger log) { + ILogger log, + int expectedAttributeCount) { mOsAttrsXmlPath = osAttrsXmlPath; mLog = log; assert osAttrsXmlPath != null; assert log != null; + mAttributeMap = Maps.newHashMapWithExpectedSize(expectedAttributeCount); if (inheritableAttributes == null) { - mAttributeMap = new HashMap<String, AttributeInfo>(); mEnumFlagValues = new HashMap<String, Map<String,Integer>>(); } else { - mAttributeMap = new HashMap<String, AttributeInfo>(inheritableAttributes.mAttributeMap); + mAttributeMap.putAll(inheritableAttributes.mAttributeMap); mEnumFlagValues = new HashMap<String, Map<String,Integer>>( - inheritableAttributes.mEnumFlagValues); + inheritableAttributes.mEnumFlagValues); } // Pre-compute the set of format names such that we don't have to compute the uppercase diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java index cf79c90..d5fa567 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java @@ -53,7 +53,6 @@ import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; -import org.eclipse.jface.text.TextUtilities; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.swt.widgets.Display; @@ -90,98 +89,6 @@ import java.util.Locale; @SuppressWarnings("restriction") // WST API public class AdtUtils { /** - * Returns true if the given string ends with the given suffix, using a - * case-insensitive comparison. - * - * @param string the full string to be checked - * @param suffix the suffix to be checked for - * @return true if the string case-insensitively ends with the given suffix - */ - public static boolean endsWithIgnoreCase(String string, String suffix) { - return string.regionMatches(true /* ignoreCase */, string.length() - suffix.length(), - suffix, 0, suffix.length()); - } - - /** - * Returns true if the given sequence ends with the given suffix (case - * sensitive). - * - * @param sequence the character sequence to be checked - * @param suffix the suffix to look for - * @return true if the given sequence ends with the given suffix - */ - public static boolean endsWith(CharSequence sequence, CharSequence suffix) { - return endsWith(sequence, sequence.length(), suffix); - } - - /** - * Returns true if the given sequence ends at the given offset with the given suffix (case - * sensitive) - * - * @param sequence the character sequence to be checked - * @param endOffset the offset at which the sequence is considered to end - * @param suffix the suffix to look for - * @return true if the given sequence ends with the given suffix - */ - public static boolean endsWith(CharSequence sequence, int endOffset, CharSequence suffix) { - if (endOffset < suffix.length()) { - return false; - } - - for (int i = endOffset - 1, j = suffix.length() - 1; j >= 0; i--, j--) { - if (sequence.charAt(i) != suffix.charAt(j)) { - return false; - } - } - - return true; - } - - /** - * Returns true if the given string starts with the given prefix, using a - * case-insensitive comparison. - * - * @param string the full string to be checked - * @param prefix the prefix to be checked for - * @return true if the string case-insensitively starts with the given prefix - */ - public static boolean startsWithIgnoreCase(String string, String prefix) { - return string.regionMatches(true /* ignoreCase */, 0, prefix, 0, prefix.length()); - } - - /** - * Returns true if the given string starts at the given offset with the - * given prefix, case insensitively. - * - * @param string the full string to be checked - * @param offset the offset in the string to start looking - * @param prefix the prefix to be checked for - * @return true if the string case-insensitively starts at the given offset - * with the given prefix - */ - public static boolean startsWith(String string, int offset, String prefix) { - return string.regionMatches(true /* ignoreCase */, offset, prefix, 0, prefix.length()); - } - - /** - * Strips the whitespace from the given string - * - * @param string the string to be cleaned up - * @return the string, without whitespace - */ - public static String stripWhitespace(String string) { - StringBuilder sb = new StringBuilder(string.length()); - for (int i = 0, n = string.length(); i < n; i++) { - char c = string.charAt(i); - if (!Character.isWhitespace(c)) { - sb.append(c); - } - } - - return sb.toString(); - } - - /** * Creates a Java class name out of the given string, if possible. For * example, "My Project" becomes "MyProject", "hello" becomes "Hello", * "Java's" becomes "Java", and so on. @@ -344,29 +251,6 @@ public class AdtUtils { return sb.toString(); } - /** For use by {@link #getLineSeparator()} */ - private static String sLineSeparator; - - /** - * Returns the default line separator to use. - * <p> - * NOTE: If you have an associated {@link IDocument}, it is better to call - * {@link TextUtilities#getDefaultLineDelimiter(IDocument)} since that will - * allow (for example) editing a \r\n-delimited document on a \n-delimited - * platform and keep a consistent usage of delimiters in the file. - * - * @return the delimiter string to use - */ - @NonNull - public static String getLineSeparator() { - if (sLineSeparator == null) { - // This is guaranteed to exist: - sLineSeparator = System.getProperty("line.separator"); //$NON-NLS-1$ - } - - return sLineSeparator; - } - /** * Returns the current editor (the currently visible and active editor), or null if * not found diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/DexDumpAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/DexDumpAction.java index 98d931f..a483e9b 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/DexDumpAction.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/DexDumpAction.java @@ -19,12 +19,12 @@ package com.android.ide.eclipse.adt.internal.actions; import com.android.SdkConstants; import com.android.annotations.Nullable; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AdtUtils; import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity; import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.sdklib.util.GrabProcessOutput; import com.android.sdklib.util.GrabProcessOutput.IProcessOutput; import com.android.sdklib.util.GrabProcessOutput.Wait; +import com.android.utils.SdkUtils; import org.eclipse.core.filesystem.EFS; import org.eclipse.core.filesystem.IFileStore; @@ -166,7 +166,7 @@ public class DexDumpAction implements IObjectActionDelegate { final BufferedWriter writer = new BufferedWriter(new FileWriter(dstFile)); try { - final String lineSep = AdtUtils.getLineSeparator(); + final String lineSep = SdkUtils.getLineSeparator(); int err = GrabProcessOutput.grabProcessOutput( process, 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 51ae496..1507a8d 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 @@ -325,7 +325,7 @@ public class PreCompilerBuilder extends BaseBuilder { if (ResourceManager.isAutoBuilding()) { IdeScanningContext context = new IdeScanningContext(projectResources, - project); + project, true); resManager.processDelta(delta, context); 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 2ea70fc..b39c4cb 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 @@ -19,6 +19,9 @@ package com.android.ide.eclipse.adt.internal.editors; import static com.android.SdkConstants.ATTR_LAYOUT_RESOURCE_PREFIX; import static com.android.SdkConstants.PREFIX_RESOURCE_REF; import static com.android.SdkConstants.UNIT_DP; +import static com.android.SdkConstants.UNIT_IN; +import static com.android.SdkConstants.UNIT_MM; +import static com.android.SdkConstants.UNIT_PT; import static com.android.SdkConstants.UNIT_PX; import static com.android.SdkConstants.UNIT_SP; import static com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor.ATTRIBUTE_ICON_FILENAME; @@ -1149,13 +1152,13 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { "<b>Scale-independent Pixels</b> - this is like the dp unit, but it is also scaled by " + "the user's font size preference.", - "pt", //$NON-NLS-1$ + UNIT_PT, "<b>Points</b> - 1/72 of an inch based on the physical size of the screen.", - "mm", //$NON-NLS-1$ + UNIT_MM, "<b>Millimeters</b> - based on the physical size of the screen.", - "in", //$NON-NLS-1$ + UNIT_IN, "<b>Inches</b> - based on the physical size of the screen.", UNIT_PX, diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlPrettyPrinter.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlPrettyPrinter.java index 6f15d83..1dd32c7 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlPrettyPrinter.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlPrettyPrinter.java @@ -24,8 +24,8 @@ import static com.android.SdkConstants.XMLNS; import com.android.annotations.NonNull; import com.android.annotations.Nullable; -import com.android.ide.eclipse.adt.AdtUtils; import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities; +import com.android.utils.SdkUtils; import com.android.utils.XmlUtils; import org.eclipse.wst.xml.core.internal.document.DocumentTypeImpl; @@ -86,7 +86,7 @@ public class XmlPrettyPrinter { mPrefs = prefs; mStyle = style; if (lineSeparator == null) { - lineSeparator = AdtUtils.getLineSeparator(); + lineSeparator = SdkUtils.getLineSeparator(); } mLineSeparator = lineSeparator; } @@ -877,8 +877,8 @@ public class XmlPrettyPrinter { return false; } - return AdtUtils.endsWith(mOut, mLineSeparator) && - AdtUtils.endsWith(mOut, mOut.length() - mLineSeparator.length(), mLineSeparator); + return SdkUtils.endsWith(mOut, mLineSeparator) && + SdkUtils.endsWith(mOut, mOut.length() - mLineSeparator.length(), mLineSeparator); } private boolean newlineAfterElementClose(Element element, int depth) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParser.java index de09d00..9553bc8 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParser.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParser.java @@ -22,6 +22,13 @@ import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT; import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH; import static com.android.SdkConstants.ATTR_PADDING; import static com.android.SdkConstants.AUTO_URI; +import static com.android.SdkConstants.UNIT_DIP; +import static com.android.SdkConstants.UNIT_DP; +import static com.android.SdkConstants.UNIT_IN; +import static com.android.SdkConstants.UNIT_MM; +import static com.android.SdkConstants.UNIT_PT; +import static com.android.SdkConstants.UNIT_PX; +import static com.android.SdkConstants.UNIT_SP; import static com.android.SdkConstants.VALUE_FILL_PARENT; import static com.android.SdkConstants.VALUE_MATCH_PARENT; import static com.android.SdkConstants.VIEW_FRAGMENT; @@ -78,7 +85,7 @@ public class UiElementPullParser extends BasePullParser { * Number of pixels to pad views with in exploded-rendering mode. */ private static final String DEFAULT_PADDING_VALUE = - ExplodedRenderingHelper.PADDING_VALUE + "px"; //$NON-NLS-1$ + ExplodedRenderingHelper.PADDING_VALUE + UNIT_PX; /** * Number of pixels to pad exploded individual views with. (This is HALF the width of the @@ -542,13 +549,13 @@ public class UiElementPullParser extends BasePullParser { private static final int COMPLEX_UNIT_MM = 5; private final static DimensionEntry[] sDimensions = new DimensionEntry[] { - new DimensionEntry("px", COMPLEX_UNIT_PX), - new DimensionEntry("dip", COMPLEX_UNIT_DIP), - new DimensionEntry("dp", COMPLEX_UNIT_DIP), - new DimensionEntry("sp", COMPLEX_UNIT_SP), - new DimensionEntry("pt", COMPLEX_UNIT_PT), - new DimensionEntry("in", COMPLEX_UNIT_IN), - new DimensionEntry("mm", COMPLEX_UNIT_MM), + new DimensionEntry(UNIT_PX, COMPLEX_UNIT_PX), + new DimensionEntry(UNIT_DIP, COMPLEX_UNIT_DIP), + new DimensionEntry(UNIT_DP, COMPLEX_UNIT_DIP), + new DimensionEntry(UNIT_SP, COMPLEX_UNIT_SP), + new DimensionEntry(UNIT_PT, COMPLEX_UNIT_PT), + new DimensionEntry(UNIT_IN, COMPLEX_UNIT_IN), + new DimensionEntry(UNIT_MM, COMPLEX_UNIT_MM), }; /** @@ -564,7 +571,7 @@ public class UiElementPullParser extends BasePullParser { padding += sIntOut[0]; } - return padding + "px"; //$NON-NLS-1$ + return padding + UNIT_PX; } /** diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java index ca229a4..6df6929 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java @@ -265,7 +265,7 @@ public final class CustomViewDescriptorService { if (parser == null) { parser = new AttrsXmlParser( file.getFile().getOsLocation(), - AdtPlugin.getDefault()); + AdtPlugin.getDefault(), 20); parser.preload(); mParserCache.put(file, parser); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java index 0178173..c55d0d8 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java @@ -20,7 +20,7 @@ import static com.android.SdkConstants.DOT_BMP; import static com.android.SdkConstants.DOT_GIF; import static com.android.SdkConstants.DOT_JPG; import static com.android.SdkConstants.DOT_PNG; -import static com.android.ide.eclipse.adt.AdtUtils.endsWithIgnoreCase; +import static com.android.utils.SdkUtils.endsWithIgnoreCase; import com.android.annotations.NonNull; import com.android.annotations.Nullable; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/ResourceValueCompleter.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/ResourceValueCompleter.java index f6b80d7..081ec80 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/ResourceValueCompleter.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/ResourceValueCompleter.java @@ -24,7 +24,6 @@ import static com.android.SdkConstants.PREFIX_THEME_REF; import com.android.ide.common.resources.ResourceItem; import com.android.ide.common.resources.ResourceRepository; -import com.android.ide.eclipse.adt.AdtUtils; import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor; import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; @@ -32,6 +31,7 @@ import com.android.ide.eclipse.adt.internal.editors.uimodel.UiResourceAttributeN import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; import com.android.resources.ResourceType; +import com.android.utils.SdkUtils; import org.eclipse.core.resources.IProject; import org.eclipse.jface.fieldassist.ContentProposal; @@ -172,7 +172,7 @@ class ResourceValueCompleter implements IContentProposalProvider { prefix.length() <= nameStart ? "" : prefix.substring(nameStart); for (ResourceItem item : repository.getResourceItemsOfType(type)) { String name = item.getName(); - if (AdtUtils.startsWithIgnoreCase(name, namePrefix)) { + if (SdkUtils.startsWithIgnoreCase(name, namePrefix)) { results.add(base + name); } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/ValueCompleter.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/ValueCompleter.java index 132855d..5559349 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/ValueCompleter.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/ValueCompleter.java @@ -35,9 +35,9 @@ import com.android.annotations.NonNull; import com.android.annotations.Nullable; import com.android.ide.common.api.IAttributeInfo; import com.android.ide.common.api.IAttributeInfo.Format; -import com.android.ide.eclipse.adt.AdtUtils; import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor; import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; +import com.android.utils.SdkUtils; import org.eclipse.jface.fieldassist.ContentProposal; import org.eclipse.jface.fieldassist.IContentProposal; @@ -147,7 +147,7 @@ abstract class ValueCompleter implements IContentProposalProvider { } for (String value : values) { - if (AdtUtils.startsWithIgnoreCase(value, prefix)) { + if (SdkUtils.startsWithIgnoreCase(value, prefix)) { if (prepend != null && prepend.contains(value)) { continue; } @@ -165,13 +165,13 @@ abstract class ValueCompleter implements IContentProposalProvider { String[] values = info.getEnumValues(); if (values != null) { for (String value : values) { - if (AdtUtils.startsWithIgnoreCase(value, prefix)) { + if (SdkUtils.startsWithIgnoreCase(value, prefix)) { proposals.add(new ContentProposal(value)); } } for (String value : values) { - if (!AdtUtils.startsWithIgnoreCase(value, prefix)) { + if (!SdkUtils.startsWithIgnoreCase(value, prefix)) { proposals.add(new ContentProposal(value)); } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/ApplicationToggle.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/ApplicationToggle.java index 16519fe..159f089 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/ApplicationToggle.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/ApplicationToggle.java @@ -17,7 +17,6 @@ package com.android.ide.eclipse.adt.internal.editors.manifest.pages; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AdtUtils; import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils; import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestEditor; import com.android.ide.eclipse.adt.internal.editors.ui.UiElementPart; @@ -25,6 +24,7 @@ import com.android.ide.eclipse.adt.internal.editors.uimodel.IUiUpdateListener; import com.android.ide.eclipse.adt.internal.editors.uimodel.IUiUpdateListener.UiUpdateState; import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; import com.android.ide.eclipse.adt.internal.sdk.Sdk; +import com.android.utils.SdkUtils; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; @@ -220,7 +220,7 @@ final class ApplicationToggle extends UiElementPart { } mUndoXmlParent.insertBefore(mUndoXmlNode, next); if (next == null) { - Text sep = mUndoXmlDocument.createTextNode(AdtUtils.getLineSeparator()); + Text sep = mUndoXmlDocument.createTextNode(SdkUtils.getLineSeparator()); mUndoXmlParent.insertBefore(sep, null); // insert separator before end tag } success = true; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java index b521d78..f905c73 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java @@ -27,7 +27,6 @@ import com.android.annotations.VisibleForTesting; import com.android.ide.common.api.IAttributeInfo.Format; import com.android.ide.common.resources.platform.AttributeInfo; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AdtUtils; import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; @@ -42,6 +41,7 @@ import com.android.ide.eclipse.adt.internal.editors.otherxml.descriptors.OtherXm import com.android.ide.eclipse.adt.internal.editors.uimodel.IUiUpdateListener.UiUpdateState; import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; +import com.android.utils.SdkUtils; import com.android.utils.XmlUtils; import org.eclipse.jface.text.TextUtilities; @@ -1035,7 +1035,7 @@ public class UiElementNode implements IPropertySource { if (document != null) { newLine = TextUtilities.getDefaultLineDelimiter(document); } else { - newLine = AdtUtils.getLineSeparator(); + newLine = SdkUtils.getLineSeparator(); } Text indentNode = doc.createTextNode(newLine + indent); parentXmlNode.insertBefore(indentNode, xmlNextSibling); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/ConvertToDpFix.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/ConvertToDpFix.java index 0ca6aa2..44b676f 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/ConvertToDpFix.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/ConvertToDpFix.java @@ -15,6 +15,7 @@ */ package com.android.ide.eclipse.adt.internal.lint; +import static com.android.SdkConstants.UNIT_PX; import static com.android.SdkConstants.VALUE_N_DP; import com.android.ide.eclipse.adt.AdtPlugin; @@ -63,7 +64,7 @@ final class ConvertToDpFix extends DocumentFix implements IInputValidator { for (int i = 0, n = attributes.getLength(); i < n; i++) { Attr attribute = (Attr) attributes.item(i); String value = attribute.getValue(); - if (value.endsWith("px")) { + if (value.endsWith(UNIT_PX)) { Matcher matcher = pattern.matcher(value); if (matcher.matches()) { String numberString = matcher.group(1); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java index 1937a94..b3303b3 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java @@ -15,9 +15,9 @@ */ package com.android.ide.eclipse.adt.internal.lint; -import static com.android.SdkConstants.FD_NATIVE_LIBS; import static com.android.SdkConstants.DOT_JAR; import static com.android.SdkConstants.DOT_XML; +import static com.android.SdkConstants.FD_NATIVE_LIBS; import static com.android.ide.eclipse.adt.AdtConstants.MARKER_LINT; import static com.android.ide.eclipse.adt.AdtUtils.workspacePathToFile; @@ -50,6 +50,7 @@ import com.android.tools.lint.detector.api.Project; import com.android.tools.lint.detector.api.Severity; import com.android.tools.lint.detector.api.XmlContext; import com.android.utils.Pair; +import com.android.utils.SdkUtils; import com.google.common.collect.Maps; import org.eclipse.core.resources.IFile; @@ -673,7 +674,7 @@ public class EclipseLintClient extends LintClient implements IDomParser { return readPlainFile(f); } - if (AdtUtils.endsWithIgnoreCase(file.getName(), DOT_XML)) { + if (SdkUtils.endsWithIgnoreCase(file.getName(), DOT_XML)) { IStructuredModel model = null; try { IModelManager modelManager = StructuredModelManager.getModelManager(); @@ -813,7 +814,7 @@ public class EclipseLintClient extends LintClient implements IDomParser { File[] jars = libs.listFiles(); if (jars != null) { for (File jar : jars) { - if (AdtUtils.endsWith(jar.getPath(), DOT_JAR)) { + if (SdkUtils.endsWith(jar.getPath(), DOT_JAR)) { libraries.add(jar); } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java index 47aac0c..5d2a2bb 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java @@ -29,6 +29,7 @@ import com.android.tools.lint.client.api.IssueRegistry; import com.android.tools.lint.detector.api.Issue; import com.android.tools.lint.detector.api.Project; import com.android.tools.lint.detector.api.Severity; +import com.android.utils.SdkUtils; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; @@ -258,7 +259,7 @@ public class LintFixGenerator implements IMarkerResolutionGenerator2, IQuickAssi } IFile file = (IFile) resource; boolean isJava = file.getName().endsWith(DOT_JAVA); - boolean isXml = AdtUtils.endsWith(file.getName(), DOT_XML); + boolean isXml = SdkUtils.endsWith(file.getName(), DOT_XML); if (!isJava && !isXml) { return; } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintJob.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintJob.java index 0442f18..2851c47 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintJob.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintJob.java @@ -28,6 +28,7 @@ import com.android.tools.lint.client.api.IssueRegistry; import com.android.tools.lint.client.api.LintDriver; import com.android.tools.lint.detector.api.Issue; import com.android.tools.lint.detector.api.Scope; +import com.android.utils.SdkUtils; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; @@ -93,7 +94,7 @@ final class LintJob extends Job { scope = Scope.ALL; } else { String name = resource.getName(); - if (AdtUtils.endsWithIgnoreCase(name, DOT_XML)) { + if (SdkUtils.endsWithIgnoreCase(name, DOT_XML)) { if (name.equals(SdkConstants.FN_ANDROID_MANIFEST_XML)) { scope = EnumSet.of(Scope.MANIFEST); } else { 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 index 5a4fc75..d613249 100644 --- 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 @@ -15,15 +15,20 @@ */ package com.android.ide.eclipse.adt.internal.resources.manager; +import static com.android.SdkConstants.ANDROID_URI; 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.annotations.NonNull; +import com.android.annotations.Nullable; import com.android.ide.common.resources.ResourceRepository; import com.android.ide.common.resources.ScanningContext; +import com.android.ide.common.resources.platform.AttributeInfo; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.build.AaptParser; +import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; +import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.utils.Pair; import org.eclipse.core.resources.IFolder; @@ -36,6 +41,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -50,20 +56,36 @@ public class IdeScanningContext extends ScanningContext { private IResource mCurrentFile; private List<Pair<IResource, String>> mErrors; private Set<IProject> mFullAaptProjects; + private boolean mValidate; + private Map<String, AttributeInfo> mAttributeMap; + private ResourceRepository mFrameworkResources; /** * Constructs a new {@link IdeScanningContext} * * @param repository the associated {@link ResourceRepository} * @param project the associated project + * @param validate if true, check that the attributes and resources are + * valid and if not request a full AAPT check */ - public IdeScanningContext(ResourceRepository repository, IProject project) { + public IdeScanningContext(@NonNull ResourceRepository repository, @NonNull IProject project, + boolean validate) { super(repository); mProject = project; + mValidate = validate; + + Sdk sdk = Sdk.getCurrent(); + if (sdk != null) { + AndroidTargetData targetData = sdk.getTargetData(project); + if (targetData != null) { + mAttributeMap = targetData.getAttributeMap(); + mFrameworkResources = targetData.getFrameworkResources(); + } + } } @Override - public void addError(String error) { + public void addError(@NonNull String error) { super.addError(error); if (mErrors == null) { @@ -77,7 +99,7 @@ public class IdeScanningContext extends ScanningContext { * * @param resource the resource about to be scanned */ - public void startScanning(IResource resource) { + public void startScanning(@NonNull IResource resource) { assert mCurrentFile == null : mCurrentFile; mCurrentFile = resource; mScannedResources.add(resource); @@ -88,7 +110,7 @@ public class IdeScanningContext extends ScanningContext { * * @param resource the resource that was scanned */ - public void finishScanning(IResource resource) { + public void finishScanning(@NonNull IResource resource) { assert mCurrentFile != null; mCurrentFile = null; } @@ -193,4 +215,20 @@ public class IdeScanningContext extends ScanningContext { public Collection<IProject> getAaptRequestedProjects() { return mFullAaptProjects; } + + @Override + public boolean checkValue(@Nullable String uri, @NonNull String name, @NonNull String value) { + if (!mValidate) { + return true; + } + + if (!needsFullAapt() && mAttributeMap != null && ANDROID_URI.equals(uri)) { + AttributeInfo info = mAttributeMap.get(name); + if (info != null && !info.isValid(value, mRepository, mFrameworkResources)) { + return false; + } + } + + return super.checkValue(uri, name, value); + } } 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 b346dcd..e1a12d7 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 @@ -423,7 +423,7 @@ public final class ResourceManager { if (delta.getResource() instanceof IProject) { IProject project = (IProject) delta.getResource(); IdeScanningContext context = - new IdeScanningContext(getProjectResources(project), project); + new IdeScanningContext(getProjectResources(project), project, true); processDelta(delta, context); @@ -522,7 +522,7 @@ public final class ResourceManager { mMap.put(project, projectResources); } } - IdeScanningContext context = new IdeScanningContext(projectResources, project); + IdeScanningContext context = new IdeScanningContext(projectResources, project, true); if (resourceFolder != null && resourceFolder.exists()) { try { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetData.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetData.java index 59a1236..85ae9fd 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetData.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetData.java @@ -16,9 +16,12 @@ package com.android.ide.eclipse.adt.internal.sdk; +import com.android.annotations.NonNull; +import com.android.annotations.Nullable; import com.android.ide.common.rendering.LayoutLibrary; import com.android.ide.common.rendering.api.LayoutLog; import com.android.ide.common.resources.ResourceRepository; +import com.android.ide.common.resources.platform.AttributeInfo; import com.android.ide.common.sdk.LoadStatus; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.editors.animator.AnimDescriptors; @@ -87,6 +90,7 @@ public class AndroidTargetData { private ResourceRepository mFrameworkResources; private LayoutLibrary mLayoutLibrary; + private Map<String, AttributeInfo> mAttributeMap; private boolean mLayoutBridgeInit = false; @@ -95,6 +99,27 @@ public class AndroidTargetData { } /** + * Sets the associated map from string attribute name to + * {@link AttributeInfo} + * + * @param attributeMap the map + */ + public void setAttributeMap(@NonNull Map<String, AttributeInfo> attributeMap) { + mAttributeMap = attributeMap; + } + + /** + * Returns the associated map from string attribute name to + * {@link AttributeInfo} + * + * @return the map + */ + @Nullable + public Map<String, AttributeInfo> getAttributeMap() { + return mAttributeMap; + } + + /** * Creates an AndroidTargetData object. */ void setExtraData( diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetParser.java index 24eadfa..9a1fd3d 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetParser.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetParser.java @@ -129,15 +129,17 @@ public final class AndroidTargetParser { progress.subTask("Attributes definitions"); AttrsXmlParser attrsXmlParser = new AttrsXmlParser( mAndroidTarget.getPath(IAndroidTarget.ATTRIBUTES), - AdtPlugin.getDefault()); + AdtPlugin.getDefault(), + 1000); attrsXmlParser.preload(); + progress.worked(1); progress.subTask("Manifest definitions"); AttrsXmlParser attrsManifestXmlParser = new AttrsXmlParser( mAndroidTarget.getPath(IAndroidTarget.MANIFEST_ATTRIBUTES), attrsXmlParser, - AdtPlugin.getDefault()); + AdtPlugin.getDefault(), 1100); attrsManifestXmlParser.preload(); progress.worked(1); @@ -290,6 +292,8 @@ public final class AndroidTargetParser { frameworkResources, layoutBridge); + targetData.setAttributeMap(attrsXmlParser.getAttributeMap()); + Sdk.getCurrent().setTargetData(mAndroidTarget, targetData); return Status.OK_STATUS; 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 b1b057b..1a299d9 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 @@ -16,9 +16,9 @@ package com.android.ide.eclipse.adt.internal.sdk; -import static com.android.SdkConstants.FD_RES; import static com.android.SdkConstants.DOT_XML; import static com.android.SdkConstants.EXT_JAR; +import static com.android.SdkConstants.FD_RES; import com.android.SdkConstants; import com.android.annotations.NonNull; @@ -215,6 +215,7 @@ public final class Sdk { * Returns the lock object used to synchronize all operations dealing with SDK, targets and * projects. */ + @NonNull public static final Object getLock() { return LOCK; } @@ -224,6 +225,7 @@ public final class Sdk { * <p/>If the SDK failed to load, it displays an error to the user. * @param sdkLocation the OS path to the SDK. */ + @Nullable public static Sdk loadSdk(String sdkLocation) { synchronized (LOCK) { if (sCurrentSdk != null) { @@ -298,6 +300,7 @@ public final class Sdk { /** * Returns the current {@link Sdk} object. */ + @Nullable public static Sdk getCurrent() { synchronized (LOCK) { return sCurrentSdk; @@ -336,6 +339,7 @@ public final class Sdk { * * @return A file:// URL on the local documentation folder if it exists or null. */ + @Nullable public String getDocumentationBaseUrl() { return mDocBaseUrl; } @@ -367,7 +371,8 @@ public final class Sdk { * @param hash the {@link IAndroidTarget} hash string. * @return The matching {@link IAndroidTarget} or null. */ - public IAndroidTarget getTargetFromHashString(String hash) { + @Nullable + public IAndroidTarget getTargetFromHashString(@NonNull String hash) { return mManager.getTargetFromHashString(hash); } @@ -377,9 +382,9 @@ public final class Sdk { * @param project the project to initialize * @param target the project's target. * @throws IOException if creating the file failed in any way. - * @throws StreamException + * @throws StreamException if processing the project property file fails */ - public void initProject(IProject project, IAndroidTarget target) + public void initProject(@Nullable IProject project, @Nullable IAndroidTarget target) throws IOException, StreamException { if (project == null || target == null) { return; @@ -423,6 +428,7 @@ public final class Sdk { * @param project the request project * @return the ProjectState for the project. */ + @Nullable @SuppressWarnings("deprecation") public static ProjectState getProjectState(IProject project) { if (project == null) { @@ -492,6 +498,7 @@ public final class Sdk { /** * Returns the {@link IAndroidTarget} object associated with the given {@link IProject}. */ + @Nullable public IAndroidTarget getTarget(IProject project) { if (project == null) { return null; @@ -512,6 +519,7 @@ public final class Sdk { * @param state the state representing the project to load. * @return the target that was loaded. */ + @Nullable public IAndroidTarget loadTarget(ProjectState state) { IAndroidTarget target = null; if (state != null) { @@ -537,6 +545,7 @@ public final class Sdk { * If the target is already loaded, nothing happens. * @return The load status if the target data is already loaded. */ + @NonNull public LoadStatus checkAndLoadTargetData(final IAndroidTarget target, IJavaProject project) { boolean loadData = false; @@ -633,6 +642,7 @@ public final class Sdk { /** * Return the {@link AndroidTargetData} for a given {@link IAndroidTarget}. */ + @Nullable public AndroidTargetData getTargetData(IAndroidTarget target) { synchronized (LOCK) { return mTargetDataMap.get(target); @@ -642,6 +652,7 @@ public final class Sdk { /** * Return the {@link AndroidTargetData} for a given {@link IProject}. */ + @Nullable public AndroidTargetData getTargetData(IProject project) { synchronized (LOCK) { IAndroidTarget target = getTarget(project); @@ -665,11 +676,13 @@ public final class Sdk { * Returns the {@link AvdManager}. If the AvdManager failed to parse the AVD folder, this could * be <code>null</code>. */ + @Nullable public AvdManager getAvdManager() { return mAvdManager; } - public static AndroidVersion getDeviceVersion(IDevice device) { + @Nullable + public static AndroidVersion getDeviceVersion(@NonNull IDevice device) { try { Map<String, String> props = device.getProperties(); String apiLevel = props.get(IDevice.PROP_BUILD_API_LEVEL); @@ -684,11 +697,13 @@ public final class Sdk { } } + @NonNull public DeviceManager getDeviceManager() { return mDeviceManager; } /** Returns the devices provided by the SDK, including user created devices */ + @NonNull public List<Device> getDevices() { return mDeviceManager.getDevices(getSdkLocation()); } @@ -699,10 +714,11 @@ public final class Sdk { * @param project the library project. * @return a possibly empty list of ProjectState. */ + @NonNull public static Set<ProjectState> getMainProjectsFor(IProject project) { synchronized (LOCK) { // first get the project directly depending on this. - HashSet<ProjectState> list = new HashSet<ProjectState>(); + Set<ProjectState> list = new HashSet<ProjectState>(); // loop on all project and see if ProjectState.getLibrary returns a non null // project. diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourcePreviewHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourcePreviewHelper.java index d97d176..eeaca0c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourcePreviewHelper.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourcePreviewHelper.java @@ -16,7 +16,7 @@ package com.android.ide.eclipse.adt.internal.ui; import static com.android.SdkConstants.DOT_9PNG; -import static com.android.ide.eclipse.adt.AdtUtils.endsWithIgnoreCase; +import static com.android.utils.SdkUtils.endsWithIgnoreCase; import com.android.ide.common.rendering.api.ResourceValue; import com.android.ide.common.resources.ResourceResolver; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ProjectNamePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ProjectNamePage.java index 31c7225..6bf5e28 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ProjectNamePage.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ProjectNamePage.java @@ -18,8 +18,8 @@ package com.android.ide.eclipse.adt.internal.wizards.newproject; import static com.android.SdkConstants.FN_PROJECT_PROGUARD_FILE; import static com.android.SdkConstants.OS_SDK_TOOLS_LIB_FOLDER; import static com.android.ide.eclipse.adt.AdtUtils.capitalize; -import static com.android.ide.eclipse.adt.AdtUtils.stripWhitespace; import static com.android.ide.eclipse.adt.internal.wizards.newproject.ApplicationInfoPage.ACTIVITY_NAME_SUFFIX; +import static com.android.utils.SdkUtils.stripWhitespace; import com.android.SdkConstants; import com.android.ide.common.xml.ManifestData; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileCreationPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileCreationPage.java index e2d061b..68c7f4c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileCreationPage.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileCreationPage.java @@ -46,6 +46,7 @@ import com.android.ide.eclipse.adt.internal.sdk.Sdk.TargetChangeListener; import com.android.resources.ResourceFolderType; import com.android.sdklib.IAndroidTarget; import com.android.utils.Pair; +import com.android.utils.SdkUtils; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; @@ -648,7 +649,7 @@ class NewXmlFileCreationPage extends WizardPage { if (res.getType() == IResource.FOLDER) { wsFolderPath = res.getProjectRelativePath(); } else if (res.getType() == IResource.FILE) { - if (AdtUtils.endsWithIgnoreCase(res.getName(), DOT_XML)) { + if (SdkUtils.endsWithIgnoreCase(res.getName(), DOT_XML)) { fileName = res.getName(); } wsFolderPath = res.getParent().getProjectRelativePath(); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlAttributeMethod.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlAttributeMethod.java new file mode 100644 index 0000000..21f33b8 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlAttributeMethod.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2012 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.wizards.templates; + +import com.android.utils.XmlUtils; + +import freemarker.template.SimpleScalar; +import freemarker.template.TemplateMethodModel; +import freemarker.template.TemplateModel; +import freemarker.template.TemplateModelException; + +import java.util.List; + +/** + * Method invoked by FreeMarker to escape a string such that it can be used + * as an XML attribute (escaping ', ", & and <). + */ +public class FmEscapeXmlAttributeMethod implements TemplateMethodModel { + @Override + public TemplateModel exec(List args) throws TemplateModelException { + if (args.size() != 1) { + throw new TemplateModelException("Wrong arguments"); + } + String string = args.get(0).toString(); + return new SimpleScalar(XmlUtils.toXmlAttributeValue(string)); + } +}
\ No newline at end of file diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlStringMethod.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlStringMethod.java new file mode 100644 index 0000000..7e5866e --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlStringMethod.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2012 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.wizards.templates; + +import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringRefactoring; + +import freemarker.template.SimpleScalar; +import freemarker.template.TemplateMethodModel; +import freemarker.template.TemplateModel; +import freemarker.template.TemplateModelException; + +import java.util.List; + +/** + * Method invoked by FreeMarker to escape a string such that it can be placed + * as text in a string resource file. + * This is similar to {@link FmEscapeXmlTextMethod}, but in addition to escaping + * < and & it also escapes characters such as quotes necessary for Android + *{@code <string>} elements. + */ +public class FmEscapeXmlStringMethod implements TemplateMethodModel { + @Override + public TemplateModel exec(List args) throws TemplateModelException { + if (args.size() != 1) { + throw new TemplateModelException("Wrong arguments"); + } + String string = args.get(0).toString(); + return new SimpleScalar(ExtractStringRefactoring.escapeString(string)); + } +}
\ No newline at end of file diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlTextMethod.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlTextMethod.java new file mode 100644 index 0000000..55a4bc8 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlTextMethod.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2012 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.wizards.templates; + +import com.android.utils.XmlUtils; + +import freemarker.template.SimpleScalar; +import freemarker.template.TemplateMethodModel; +import freemarker.template.TemplateModel; +import freemarker.template.TemplateModelException; + +import java.util.List; + +/** + * Method invoked by FreeMarker to escape a string such that it can be used + * as XML text (escaping < and &, but not ' and " etc). + */ +public class FmEscapeXmlTextMethod implements TemplateMethodModel { + @Override + public TemplateModel exec(List args) throws TemplateModelException { + if (args.size() != 1) { + throw new TemplateModelException("Wrong arguments"); + } + String string = args.get(0).toString(); + return new SimpleScalar(XmlUtils.toXmlTextValue(string)); + } +}
\ No newline at end of file diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmExtractLettersMethod.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmExtractLettersMethod.java new file mode 100644 index 0000000..09fa81c --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmExtractLettersMethod.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2012 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.wizards.templates; + +import freemarker.template.SimpleScalar; +import freemarker.template.TemplateMethodModel; +import freemarker.template.TemplateModel; +import freemarker.template.TemplateModelException; + +import java.util.List; + +/** + * Method invoked by FreeMarker to extract letters from a string; this will remove + * any whitespace, punctuation and digits. + */ +public class FmExtractLettersMethod implements TemplateMethodModel { + @Override + public TemplateModel exec(List args) throws TemplateModelException { + if (args.size() != 1) { + throw new TemplateModelException("Wrong arguments"); + } + String string = args.get(0).toString(); + StringBuilder sb = new StringBuilder(string.length()); + for (int i = 0, n = string.length(); i < n; i++) { + char c = string.charAt(i); + if (Character.isLetter(c)) { + sb.append(c); + } + } + return new SimpleScalar(sb.toString()); + } +}
\ No newline at end of file diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java index cb45522..53b6c3c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java @@ -42,6 +42,7 @@ import com.android.ide.eclipse.adt.internal.sdk.AdtManifestMergeCallback; import com.android.manifmerger.ManifestMerger; import com.android.manifmerger.MergerLog; import com.android.resources.ResourceFolderType; +import com.android.utils.SdkUtils; import com.google.common.base.Charsets; import com.google.common.collect.Lists; import com.google.common.io.Files; @@ -315,6 +316,10 @@ class TemplateHandler { paramMap.put("activityToLayout", new FmActivityToLayoutMethod()); //$NON-NLS-1$ paramMap.put("layoutToActivity", new FmLayoutToActivityMethod()); //$NON-NLS-1$ paramMap.put("classToResource", new FmClassNameToResourceMethod()); //$NON-NLS-1$ + paramMap.put("escapeXmlAttribute", new FmEscapeXmlStringMethod()); //$NON-NLS-1$ + paramMap.put("escapeXmlText", new FmEscapeXmlStringMethod()); //$NON-NLS-1$ + paramMap.put("escapeXmlString", new FmEscapeXmlStringMethod()); //$NON-NLS-1$ + paramMap.put("extractLetters", new FmExtractLettersMethod()); //$NON-NLS-1$ // This should be handled better: perhaps declared "required packages" as part of the // inputs? (It would be better if we could conditionally disable template based @@ -688,7 +693,7 @@ class TemplateHandler { } else { // Just insert into file along with comment, using the "standard" conflict // syntax that many tools and editors recognize. - String sep = AdtUtils.getLineSeparator(); + String sep = SdkUtils.getLineSeparator(); contents = "<<<<<<< Original" + sep + currentXml + sep diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/resources/platform/AttributeInfoTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/resources/platform/AttributeInfoTest.java new file mode 100644 index 0000000..7ae96a8 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/resources/platform/AttributeInfoTest.java @@ -0,0 +1,371 @@ +/* + * Copyright (C) 2012 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.platform; + +import static com.android.SdkConstants.ANDROID_URI; +import static com.android.SdkConstants.DOT_XML; + +import com.android.annotations.NonNull; +import com.android.ide.common.api.IAttributeInfo.Format; +import com.android.ide.common.resources.ResourceItem; +import com.android.ide.common.resources.ResourceRepository; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities; +import com.android.resources.ResourceType; +import com.android.utils.StdLogger; +import com.google.common.base.Charsets; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; +import com.google.common.io.Files; + +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.EnumSet; +import java.util.Map; +import java.util.Set; + +import junit.framework.TestCase; + +@SuppressWarnings("javadoc") +public class AttributeInfoTest extends TestCase { + public void testSimple() throws Exception { + AttributeInfo info = new AttributeInfo("test", EnumSet.noneOf(Format.class)); + assertTrue(info.isValid("", null, null)); + assertTrue(info.isValid("a b c", null, null)); + assertTrue(info.isValid("@android foo bar", null, null)); + } + + public void testIsValidString() throws Exception { + AttributeInfo info = new AttributeInfo("test", Format.STRING_SET); + assertTrue(info.isValid("", null, null)); + assertTrue(info.isValid("a b c", null, null)); + assertTrue(info.isValid("@android foo bar", null, null)); + } + + public void testIsValidBoolean() throws Exception { + AttributeInfo info = new AttributeInfo("test", Format.BOOLEAN_SET); + assertTrue(info.isValid("true", null, null)); + assertTrue(info.isValid("false", null, null)); + assertFalse(info.isValid("", null, null)); + assertFalse(info.isValid("TRUE", null, null)); + } + + public void testIsValidInteger() throws Exception { + AttributeInfo info = new AttributeInfo("test", Format.INTEGER_SET); + assertTrue(info.isValid("0", null, null)); + assertTrue(info.isValid("1", null, null)); + assertTrue(info.isValid("10", null, null)); + assertTrue(info.isValid("-10", null, null)); + assertTrue(info.isValid(Integer.toString(Integer.MAX_VALUE), null, null)); + + assertFalse(info.isValid("", null, null)); + assertFalse(info.isValid("a", null, null)); + assertFalse(info.isValid("a1", null, null)); + assertFalse(info.isValid("1a", null, null)); + assertFalse(info.isValid("1.0", null, null)); + } + + public void testIsValidFloat() throws Exception { + AttributeInfo info = new AttributeInfo("test", Format.FLOAT_SET); + assertTrue(info.isValid("0", null, null)); + assertTrue(info.isValid("1", null, null)); + assertTrue(info.isValid("10", null, null)); + assertTrue(info.isValid("-10", null, null)); + assertTrue(info.isValid("-10.1234", null, null)); + assertTrue(info.isValid(".1", null, null)); + assertTrue(info.isValid("-.1", null, null)); + assertTrue(info.isValid("1.5e22", null, null)); + assertTrue(info.isValid(Integer.toString(Integer.MAX_VALUE), null, null)); + + assertFalse(info.isValid("", null, null)); + assertFalse(info.isValid(".", null, null)); + assertFalse(info.isValid("-.", null, null)); + assertFalse(info.isValid("a", null, null)); + assertFalse(info.isValid("a1", null, null)); + assertFalse(info.isValid("1a", null, null)); + } + + public void testIsValidDimension() throws Exception { + AttributeInfo info = new AttributeInfo("test", Format.DIMENSION_SET); + assertTrue(info.isValid("0dp", null, null)); + assertTrue(info.isValid("1dp", null, null)); + assertTrue(info.isValid("10dip", null, null)); + assertTrue(info.isValid("-10px", null, null)); + assertTrue(info.isValid("-10.1234mm", null, null)); + assertTrue(info.isValid("14sp", null, null)); + assertTrue(info.isValid("72pt", null, null)); + + assertFalse(info.isValid("", null, null)); + assertFalse(info.isValid("5", null, null)); + assertFalse(info.isValid("50ps", null, null)); + // Since we allow resources even when not specified in format, don't assert + // this: + //assertFalse(info.isValid("@dimen/foo")); + } + + public void testIsValidColor() throws Exception { + AttributeInfo info = new AttributeInfo("test", Format.COLOR_SET); + assertTrue(info.isValid("#fff", null, null)); + assertTrue(info.isValid("#ffffff", null, null)); + assertTrue(info.isValid("#12345678", null, null)); + assertTrue(info.isValid("#abcdef00", null, null)); + + assertFalse(info.isValid("", null, null)); + assertFalse(info.isValid("#fffffffff", null, null)); + assertFalse(info.isValid("red", null, null)); + assertFalse(info.isValid("rgb(1,2,3)", null, null)); + } + + public void testIsValidFraction() throws Exception { + AttributeInfo info = new AttributeInfo("test", EnumSet.<Format>of(Format.FRACTION)); + assertTrue(info.isValid("5%", null, null)); + assertTrue(info.isValid("25%p", null, null)); + + // We don't validate fractions accurately yet + //assertFalse(info.isValid("")); + //assertFalse(info.isValid("50%%")); + //assertFalse(info.isValid("50")); + //assertFalse(info.isValid("-2%")); + } + + public void testIsValidReference() throws Exception { + AttributeInfo info = new AttributeInfo("test", Format.REFERENCE_SET); + assertTrue(info.isValid("@android:string/foo", null, null)); + assertTrue(info.isValid("@string/foo", null, null)); + assertTrue(info.isValid("@dimen/foo", null, null)); + assertTrue(info.isValid("@color/foo", null, null)); + assertTrue(info.isValid("@animator/foo", null, null)); + assertTrue(info.isValid("@anim/foo", null, null)); + assertTrue(info.isValid("?android:attr/textAppearanceMedium", null, null)); + + assertFalse(info.isValid("", null, null)); + assertFalse(info.isValid("foo", null, null)); + assertFalse(info.isValid("3.4", null, null)); + } + + public void testIsValidEnum() throws Exception { + AttributeInfo info = new AttributeInfo("test", Format.ENUM_SET); + info.setEnumValues(new String[] { "wrap_content", "match_parent" }); + assertTrue(info.isValid("wrap_content", null, null)); + assertTrue(info.isValid("match_parent", null, null)); + assertFalse(info.isValid("", null, null)); + assertFalse(info.isValid("other", null, null)); + assertFalse(info.isValid("50", null, null)); + } + + public void testIsValidFlag() throws Exception { + AttributeInfo info = new AttributeInfo("test", Format.FLAG_SET); + info.setFlagValues(new String[] { "left", "top", "right", "bottom" }); + assertTrue(info.isValid("left", null, null)); + assertTrue(info.isValid("top", null, null)); + assertTrue(info.isValid("left|top", null, null)); + + assertFalse(info.isValid("", null, null)); + assertFalse(info.isValid("other", null, null)); + assertFalse(info.isValid("50", null, null)); + } + + public void testCombined1() throws Exception { + AttributeInfo info = new AttributeInfo("test", EnumSet.<Format>of(Format.INTEGER, + Format.REFERENCE)); + assertTrue(info.isValid("1", null, null)); + assertTrue(info.isValid("@dimen/foo", null, null)); + assertFalse(info.isValid("foo", null, null)); + } + + public void testCombined2() throws Exception { + AttributeInfo info = new AttributeInfo("test", EnumSet.<Format>of(Format.COLOR, + Format.REFERENCE)); + assertTrue(info.isValid("#ff00ff00", null, null)); + assertTrue(info.isValid("@color/foo", null, null)); + assertFalse(info.isValid("foo", null, null)); + } + + public void testCombined3() throws Exception { + AttributeInfo info = new AttributeInfo("test", EnumSet.<Format>of(Format.STRING, + Format.REFERENCE)); + assertTrue(info.isValid("test", null, null)); + assertTrue(info.isValid("@color/foo", null, null)); + } + + public void testCombined4() throws Exception { + AttributeInfo info = new AttributeInfo("test", EnumSet.<Format>of(Format.ENUM, + Format.DIMENSION)); + info.setEnumValues(new String[] { "wrap_content", "match_parent" }); + assertTrue(info.isValid("wrap_content", null, null)); + assertTrue(info.isValid("match_parent", null, null)); + assertTrue(info.isValid("50dp", null, null)); + assertFalse(info.isValid("50", null, null)); + assertFalse(info.isValid("test", null, null)); + } + + public void testResourcesExist() throws Exception { + AttributeInfo info = new AttributeInfo("test", Format.REFERENCE_SET); + TestResourceRepository projectResources = new TestResourceRepository(false); + projectResources.addResource(ResourceType.STRING, "mystring"); + projectResources.addResource(ResourceType.DIMEN, "mydimen"); + TestResourceRepository frameworkResources = new TestResourceRepository(true); + frameworkResources.addResource(ResourceType.LAYOUT, "mylayout"); + + assertTrue(info.isValid("@string/mystring", null, null)); + assertTrue(info.isValid("@dimen/mydimen", null, null)); + assertTrue(info.isValid("@android:layout/mylayout", null, null)); + assertTrue(info.isValid("?android:attr/listPreferredItemHeigh", null, null)); + + assertTrue(info.isValid("@string/mystring", projectResources, frameworkResources)); + assertTrue(info.isValid("@dimen/mydimen", projectResources, frameworkResources)); + assertTrue(info.isValid("@android:layout/mylayout", projectResources, frameworkResources)); + + assertFalse(info.isValid("@android:string/mystring", projectResources, + frameworkResources)); + assertFalse(info.isValid("@android:dimen/mydimen", projectResources, frameworkResources)); + assertFalse(info.isValid("@layout/mylayout", projectResources, frameworkResources)); + assertFalse(info.isValid("@layout/foo", projectResources, frameworkResources)); + assertFalse(info.isValid("@anim/foo", projectResources, frameworkResources)); + assertFalse(info.isValid("@android:anim/foo", projectResources, frameworkResources)); + } + + private class TestResourceRepository extends ResourceRepository { + private Multimap<ResourceType, String> mResources = ArrayListMultimap.create(); + + protected TestResourceRepository(boolean isFrameworkRepository) { + super(isFrameworkRepository); + } + + void addResource(ResourceType type, String name) { + mResources.put(type, name); + } + + @Override + @NonNull + protected ResourceItem createResourceItem(@NonNull String name) { + fail("Not used in test"); + return null; + } + + @Override + public boolean hasResourceItem(@NonNull ResourceType type, @NonNull String name) { + Collection<String> names = mResources.get(type); + if (names != null) { + return names.contains(name); + } + + return false; + } + }; + + + public void testIsValid() throws Exception { + // This test loads the full attrs.xml file and then processes a bunch of platform + // resource file and makes sure that they're all considered valid. This helps + // make sure that isValid() closely matches what aapt accepts. + String sdkPath = System.getenv("ADT_SDK_SOURCE_PATH"); + assertNotNull("This test requires ADT_SDK_SOURCE_PATH to be set to point to the" + + "SDK git repository", sdkPath); + File sdk = new File(sdkPath); + assertNotNull("$ADT_SDK_SOURCE_PATH (" + sdk.getPath() + ") is not a directory", + sdk.isDirectory()); + File git = sdk.getParentFile(); + File attrsPath = new File(git, "frameworks" + File.separator + "base" + + File.separator + "core" + File.separator + "res" + File.separator + "res" + + File.separator + "values" + File.separator + "attrs.xml"); + assertTrue(attrsPath.getPath(), attrsPath.exists()); + AttrsXmlParser parser = new AttrsXmlParser(attrsPath.getPath(), + new StdLogger(StdLogger.Level.VERBOSE), 1100); + parser.preload(); + Map<String, AttributeInfo> attributeMap = parser.getAttributeMap(); + assertNotNull(attributeMap); + assertNotNull(attributeMap.get("layout_width")); + Set<String> seen = Sets.newHashSet(); + + checkDir(new File(git, "packages" + File.separator + "apps"), false, attributeMap, seen); + } + + private void checkDir(File dir, boolean isResourceDir, + Map<String, AttributeInfo> map, Set<String> seen) throws IOException { + assertTrue(dir.isDirectory()); + File[] list = dir.listFiles(); + if (list != null) { + for (File file : list) { + if (isResourceDir && file.isFile() && file.getPath().endsWith(DOT_XML)) { + checkXmlFile(file, map, seen); + } else if (file.isDirectory()) { + checkDir(file, isResourceDir || file.getName().equals("res"), map, seen); + } + } + } + } + + private void checkXmlFile(File file, Map<String, AttributeInfo> map, + Set<String> seen) throws IOException { + String xml = Files.toString(file, Charsets.UTF_8); + if (xml != null) { + //Document doc = DomUtilities.parseStructuredDocument(xml); + Document doc = DomUtilities.parseDocument(xml, false); + if (doc != null && doc.getDocumentElement() != null) { + checkElement(file, doc.getDocumentElement(), map, seen); + } + } + } + + private void checkElement(File file, Element element, Map<String, AttributeInfo> map, + Set<String> seen) { + NamedNodeMap attributes = element.getAttributes(); + for (int i = 0, n = attributes.getLength(); i < n; i++) { + Attr attribute = (Attr) attributes.item(i); + + String uri = attribute.getNamespaceURI(); + String name = attribute.getLocalName(); + String value = attribute.getValue(); + if (ANDROID_URI.equals(uri)) { + AttributeInfo info = map.get(name); + if (info == null) { + System.out.println("Warning: Unknown attribute '" + name + "' in " + file); + return; + } + if (!info.isValid(value, null, null)) { + if (name.equals("duration") || name.equals("exitFadeDuration")) { + // Already known + return; + } + String message = "In file " + + file.getPath() + ":\nCould not validate value \"" + value + + "\" for attribute '" + + name + "' where the attribute info has formats " + info.getFormats() + + "\n"; + System.out.println("\n" + message); + fail(message); + } + if ((value.startsWith("@") || value.startsWith("?")) && + !info.getFormats().contains(Format.REFERENCE)) { + // Print out errors in attrs.xml + + if (!seen.contains(name)) { + seen.add(name); + System.out.println("\"" + name + "\" with formats " + info.getFormats() + + " was passed a reference (" + value + ") in file " + file); + } + } + } + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/resources/platform/AttrsXmlParserManifestTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/resources/platform/AttrsXmlParserManifestTest.java index cddd63e..82d6aca 100755 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/resources/platform/AttrsXmlParserManifestTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/resources/platform/AttrsXmlParserManifestTest.java @@ -35,8 +35,8 @@ public class AttrsXmlParserManifestTest extends TestCase { @Override public void setUp() throws Exception { - mFilePath = AdtTestData.getInstance().getTestFilePath(MOCK_DATA_PATH); //$NON-NLS-1$ - mParser = new AttrsXmlParser(mFilePath, new TestLogger()); + mFilePath = AdtTestData.getInstance().getTestFilePath(MOCK_DATA_PATH); + mParser = new AttrsXmlParser(mFilePath, new TestLogger(), 100); } @Override diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/resources/platform/AttrsXmlParserTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/resources/platform/AttrsXmlParserTest.java index e57e5cd..883577b 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/resources/platform/AttrsXmlParserTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/resources/platform/AttrsXmlParserTest.java @@ -36,7 +36,7 @@ public class AttrsXmlParserTest extends TestCase { @Override public void setUp() throws Exception { mFilePath = AdtTestData.getInstance().getTestFilePath(MOCK_DATA_PATH); - mParser = new AttrsXmlParser(mFilePath, new TestLogger()); + mParser = new AttrsXmlParser(mFilePath, new TestLogger(), 100); } @Override diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/AdtUtilsTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/AdtUtilsTest.java index 0d4e02e..c1e94ac 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/AdtUtilsTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/AdtUtilsTest.java @@ -15,75 +15,12 @@ */ package com.android.ide.eclipse.adt; -import java.util.Locale; - import junit.framework.TestCase; +import java.util.Locale; + @SuppressWarnings("javadoc") public class AdtUtilsTest extends TestCase { - public void testEndsWithIgnoreCase() { - assertTrue(AdtUtils.endsWithIgnoreCase("foo", "foo")); - assertTrue(AdtUtils.endsWithIgnoreCase("foo", "Foo")); - assertTrue(AdtUtils.endsWithIgnoreCase("foo", "foo")); - assertTrue(AdtUtils.endsWithIgnoreCase("Barfoo", "foo")); - assertTrue(AdtUtils.endsWithIgnoreCase("BarFoo", "foo")); - assertTrue(AdtUtils.endsWithIgnoreCase("BarFoo", "foO")); - - assertFalse(AdtUtils.endsWithIgnoreCase("foob", "foo")); - assertFalse(AdtUtils.endsWithIgnoreCase("foo", "fo")); - } - - public void testStartsWithIgnoreCase() { - assertTrue(AdtUtils.startsWithIgnoreCase("foo", "foo")); - assertTrue(AdtUtils.startsWithIgnoreCase("foo", "Foo")); - assertTrue(AdtUtils.startsWithIgnoreCase("foo", "foo")); - assertTrue(AdtUtils.startsWithIgnoreCase("barfoo", "bar")); - assertTrue(AdtUtils.startsWithIgnoreCase("BarFoo", "bar")); - assertTrue(AdtUtils.startsWithIgnoreCase("BarFoo", "bAr")); - - assertFalse(AdtUtils.startsWithIgnoreCase("bfoo", "foo")); - assertFalse(AdtUtils.startsWithIgnoreCase("fo", "foo")); - } - - public void testStartsWith() { - assertTrue(AdtUtils.startsWith("foo", 0, "foo")); - assertTrue(AdtUtils.startsWith("foo", 0, "Foo")); - assertTrue(AdtUtils.startsWith("Foo", 0, "foo")); - assertTrue(AdtUtils.startsWith("aFoo", 1, "foo")); - - assertFalse(AdtUtils.startsWith("aFoo", 0, "foo")); - assertFalse(AdtUtils.startsWith("aFoo", 2, "foo")); - } - - public void testEndsWith() { - assertTrue(AdtUtils.endsWith("foo", "foo")); - assertTrue(AdtUtils.endsWith("foobar", "obar")); - assertTrue(AdtUtils.endsWith("foobar", "bar")); - assertTrue(AdtUtils.endsWith("foobar", "ar")); - assertTrue(AdtUtils.endsWith("foobar", "r")); - assertTrue(AdtUtils.endsWith("foobar", "")); - - assertTrue(AdtUtils.endsWith(new StringBuilder("foobar"), "bar")); - assertTrue(AdtUtils.endsWith(new StringBuilder("foobar"), new StringBuffer("obar"))); - assertTrue(AdtUtils.endsWith("foobar", new StringBuffer("obar"))); - - assertFalse(AdtUtils.endsWith("foo", "fo")); - assertFalse(AdtUtils.endsWith("foobar", "Bar")); - assertFalse(AdtUtils.endsWith("foobar", "longfoobar")); - } - - public void testEndsWith2() { - assertTrue(AdtUtils.endsWith("foo", "foo".length(), "foo")); - assertTrue(AdtUtils.endsWith("foo", "fo".length(), "fo")); - assertTrue(AdtUtils.endsWith("foo", "f".length(), "f")); - } - - public void testStripWhitespace() { - assertEquals("foo", AdtUtils.stripWhitespace("foo")); - assertEquals("foobar", AdtUtils.stripWhitespace("foo bar")); - assertEquals("foobar", AdtUtils.stripWhitespace(" foo bar \n\t")); - } - public void testExtractClassName() { assertEquals("Foo", AdtUtils.extractClassName("foo")); assertEquals("Foobar", AdtUtils.extractClassName("foo bar")); diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/resources/manager/IdeScanningContextTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/resources/manager/IdeScanningContextTest.java new file mode 100644 index 0000000..3104c85 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/resources/manager/IdeScanningContextTest.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2012 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 junit.framework.TestCase; + +@SuppressWarnings("javadoc") +public class IdeScanningContextTest extends TestCase { +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/sdk/LayoutParamsParserTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/sdk/LayoutParamsParserTest.java index 9d87e2f..c89dd06 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/sdk/LayoutParamsParserTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/sdk/LayoutParamsParserTest.java @@ -62,7 +62,7 @@ public class LayoutParamsParserTest extends TestCase { super(new MockFrameworkClassLoader(), new AttrsXmlParser( AdtTestData.getInstance().getTestFilePath(MOCK_DATA_PATH), - new TestLogger()).preload()); + new TestLogger(), 100).preload()); mTopViewClass = new ClassWrapper(mock_android.view.View.class); mTopGroupClass = new ClassWrapper(mock_android.view.ViewGroup.class); diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlAttributeMethodTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlAttributeMethodTest.java new file mode 100644 index 0000000..eb1e949 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlAttributeMethodTest.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2012 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.wizards.templates; + +import freemarker.template.SimpleScalar; +import freemarker.template.TemplateModelException; + +import java.util.Collections; +import java.util.List; + +import junit.framework.TestCase; + +@SuppressWarnings("javadoc") +public class FmEscapeXmlAttributeMethodTest extends TestCase { + @SuppressWarnings("rawtypes") + private void check(String s, String expected) throws TemplateModelException { + FmEscapeXmlAttributeMethod method = new FmEscapeXmlAttributeMethod(); + List list = Collections.singletonList(new SimpleScalar(s)); + assertEquals(expected, ((SimpleScalar) method.exec(list)).getAsString()); + } + + public void test1() throws Exception { + check("", ""); + } + + public void test2() throws Exception { + check("foo", "foo"); + } + + public void test3() throws Exception { + check("<\"'>&", "<"'>&"); + } + + public void test4() throws Exception { + check("foo>bar", "foo>bar"); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlStringMethodTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlStringMethodTest.java new file mode 100644 index 0000000..1a4289a --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlStringMethodTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2012 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.wizards.templates; + +import freemarker.template.SimpleScalar; +import freemarker.template.TemplateModelException; + +import java.util.Collections; +import java.util.List; + +import junit.framework.TestCase; + +@SuppressWarnings("javadoc") +public class FmEscapeXmlStringMethodTest extends TestCase { + @SuppressWarnings("rawtypes") + private void check(String s, String expected) throws TemplateModelException { + FmEscapeXmlStringMethod method = new FmEscapeXmlStringMethod(); + List list = Collections.singletonList(new SimpleScalar(s)); + assertEquals(expected, ((SimpleScalar) method.exec(list)).getAsString()); + } + + public void test1() throws Exception { + check("", ""); + } + + public void test2() throws Exception { + check("foo", "foo"); + } + + public void test3() throws Exception { + check(" Foo Bar ", "\" Foo Bar \""); + } + + public void test4() throws Exception { + check("@foo", "\\@foo"); + } + + public void test5() throws Exception { + check("Hello\nWorld", "Hello\\nWorld"); + } + + public void test6() throws Exception { + check("A & B", "A & B"); + } + + public void test7() throws Exception { + check("Foo's Bar", "Foo\\'s Bar"); + } + + public void test8() throws Exception { + check("'\"\\", "\\'\\\"\\\\"); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlTextMethodTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlTextMethodTest.java new file mode 100644 index 0000000..c08b834 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlTextMethodTest.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2012 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.wizards.templates; + +import freemarker.template.SimpleScalar; +import freemarker.template.TemplateModelException; + +import java.util.Collections; +import java.util.List; + +import junit.framework.TestCase; + +@SuppressWarnings("javadoc") +public class FmEscapeXmlTextMethodTest extends TestCase { + @SuppressWarnings("rawtypes") + private void check(String s, String expected) throws TemplateModelException { + FmEscapeXmlTextMethod method = new FmEscapeXmlTextMethod(); + List list = Collections.singletonList(new SimpleScalar(s)); + assertEquals(expected, ((SimpleScalar) method.exec(list)).getAsString()); + } + + public void test1() throws Exception { + check("", ""); + } + + public void test2() throws Exception { + check("foo", "foo"); + } + + public void test3() throws Exception { + check("<\"'>&", "<\"'>&"); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmExtractLettersMethodTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmExtractLettersMethodTest.java new file mode 100644 index 0000000..b1d3cee --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/templates/FmExtractLettersMethodTest.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2012 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.wizards.templates; + +import freemarker.template.SimpleScalar; +import freemarker.template.TemplateModelException; + +import java.util.Collections; +import java.util.List; + +import junit.framework.TestCase; + +@SuppressWarnings("javadoc") +public class FmExtractLettersMethodTest extends TestCase { + @SuppressWarnings("rawtypes") + private void check(String s, String expected) throws TemplateModelException { + FmExtractLettersMethod method = new FmExtractLettersMethod(); + List list = Collections.singletonList(new SimpleScalar(s)); + assertEquals(expected, ((SimpleScalar) method.exec(list)).getAsString()); + } + + public void test1() throws Exception { + check("", ""); + } + + public void test2() throws Exception { + check("foo", "foo"); + } + + public void test3() throws Exception { + check("<\"'>&foo ", "foo"); + } +} diff --git a/files/ant/build.xml b/files/ant/build.xml index a1e9daa..4861c1d 100644 --- a/files/ant/build.xml +++ b/files/ant/build.xml @@ -758,7 +758,7 @@ <jar destfile="${out.library.jar.file}"> <fileset dir="${out.classes.absolute.dir}" includes="**/*.class" - excludes="${project.app.package.path}/R.class ${project.app.package.path}/R$*.class ${project.app.package.path}/Manifest.class ${project.app.package.path}/Manifest$*.class ${project.app.package.path}/BuildConfig.class"/> + excludes="${project.app.package.path}/R.class ${project.app.package.path}/R$*.class ${project.app.package.path}/BuildConfig.class"/> <fileset dir="${source.absolute.dir}" excludes="**/*.java ${android.package.excludes}" /> </jar> </then> diff --git a/find_java/find_java_exe.cpp b/find_java/find_java_exe.cpp index c8e0e49..6b1add9 100644 --- a/find_java/find_java_exe.cpp +++ b/find_java/find_java_exe.cpp @@ -117,12 +117,15 @@ int main(int argc, char* argv[]) { _ASSERT(!javaPath.isEmpty());
if (doShortPath) {
+ PVOID oldWow64Value = disableWow64FsRedirection();
if (!javaPath.toShortPath(&javaPath)) {
+ revertWow64FsRedirection(&oldWow64Value);
fprintf(stderr,
"Failed to convert path to a short DOS path: %s\n",
javaPath.cstr());
return 1;
}
+ revertWow64FsRedirection(&oldWow64Value);
}
if (doVersion) {
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; + } +} diff --git a/templates/activities/BlankActivity/root/res/values/strings.xml.ftl b/templates/activities/BlankActivity/root/res/values/strings.xml.ftl index 6c636d6..4ba950a 100644 --- a/templates/activities/BlankActivity/root/res/values/strings.xml.ftl +++ b/templates/activities/BlankActivity/root/res/values/strings.xml.ftl @@ -1,6 +1,6 @@ <resources> <#if !isNewProject> - <string name="title_${activityToLayout(activityClass)}">${activityTitle}</string> + <string name="title_${activityToLayout(activityClass)}">${escapeXmlString(activityTitle)}</string> </#if> <string name="menu_settings">Settings</string> diff --git a/templates/activities/FullscreenActivity/root/res/values/strings.xml.ftl b/templates/activities/FullscreenActivity/root/res/values/strings.xml.ftl index 53ff7df..5a43acf 100644 --- a/templates/activities/FullscreenActivity/root/res/values/strings.xml.ftl +++ b/templates/activities/FullscreenActivity/root/res/values/strings.xml.ftl @@ -1,7 +1,7 @@ <resources> <#if !isNewProject> - <string name="title_${simpleName}">${activityTitle}</string> + <string name="title_${simpleName}">${escapeXmlString(activityTitle)}</string> </#if> <string name="dummy_button1">Button 1</string> <string name="dummy_button2">Button 2</string> diff --git a/templates/activities/LoginActivity/root/res/values/strings.xml.ftl b/templates/activities/LoginActivity/root/res/values/strings.xml.ftl index c2ad046..18bf85f 100644 --- a/templates/activities/LoginActivity/root/res/values/strings.xml.ftl +++ b/templates/activities/LoginActivity/root/res/values/strings.xml.ftl @@ -1,6 +1,6 @@ <resources> <#if !isNewProject> - <string name="title_${simpleName}">${activityTitle}</string> + <string name="title_${simpleName}">${escapeXmlString(activityTitle)}</string> </#if> <!-- Strings related to login --> diff --git a/templates/activities/MasterDetailFlow/globals.xml.ftl b/templates/activities/MasterDetailFlow/globals.xml.ftl index 519c081..952e278 100644 --- a/templates/activities/MasterDetailFlow/globals.xml.ftl +++ b/templates/activities/MasterDetailFlow/globals.xml.ftl @@ -1,8 +1,8 @@ <?xml version="1.0"?> <globals> <global id="srcOut" value="src/${slashedPackageName(packageName)}" /> - <global id="CollectionName" value="${objectKind}List" /> - <global id="collection_name" value="${objectKind?lower_case}_list" /> - <global id="DetailName" value="${objectKind}Detail" /> - <global id="detail_name" value="${objectKind?lower_case}_detail" /> + <global id="CollectionName" value="${extractLetters(objectKind)}List" /> + <global id="collection_name" value="${extractLetters(objectKind?lower_case)}_list" /> + <global id="DetailName" value="${extractLetters(objectKind)}Detail" /> + <global id="detail_name" value="${extractLetters(objectKind?lower_case)}_detail" /> </globals> diff --git a/templates/activities/MasterDetailFlow/recipe.xml.ftl b/templates/activities/MasterDetailFlow/recipe.xml.ftl index 2c1f057..8b09c84 100644 --- a/templates/activities/MasterDetailFlow/recipe.xml.ftl +++ b/templates/activities/MasterDetailFlow/recipe.xml.ftl @@ -11,7 +11,7 @@ <instantiate from="res/layout/activity_content_list.xml.ftl" to="res/layout/activity_${collection_name}.xml" /> <instantiate from="res/layout/activity_content_twopane.xml.ftl" - to="res/layout/activity_${objectKind?lower_case}_twopane.xml" /> + to="res/layout/activity_${extractLetters(objectKind?lower_case)}_twopane.xml" /> <instantiate from="res/layout/fragment_content_detail.xml.ftl" to="res/layout/fragment_${detail_name}.xml" /> diff --git a/templates/activities/MasterDetailFlow/root/res/values-large/refs.xml.ftl b/templates/activities/MasterDetailFlow/root/res/values-large/refs.xml.ftl index 3008e2e..97215c3 100644 --- a/templates/activities/MasterDetailFlow/root/res/values-large/refs.xml.ftl +++ b/templates/activities/MasterDetailFlow/root/res/values-large/refs.xml.ftl @@ -6,5 +6,5 @@ For more on layout aliases, see: http://developer.android.com/training/multiscreen/screensizes.html#TaskUseAliasFilters --> - <item type="layout" name="activity_${collection_name}">@layout/activity_${objectKind?lower_case}_twopane</item> + <item type="layout" name="activity_${collection_name}">@layout/activity_${extractLetters(objectKind?lower_case)}_twopane</item> </resources> diff --git a/templates/activities/MasterDetailFlow/root/res/values-sw600dp/refs.xml.ftl b/templates/activities/MasterDetailFlow/root/res/values-sw600dp/refs.xml.ftl index c698e6e..d592404 100644 --- a/templates/activities/MasterDetailFlow/root/res/values-sw600dp/refs.xml.ftl +++ b/templates/activities/MasterDetailFlow/root/res/values-sw600dp/refs.xml.ftl @@ -7,5 +7,5 @@ For more on layout aliases, see: http://developer.android.com/training/multiscreen/screensizes.html#TaskUseAliasFilters --> - <item type="layout" name="activity_${collection_name}">@layout/activity_${objectKind?lower_case}_twopane</item> + <item type="layout" name="activity_${collection_name}">@layout/activity_${extractLetters(objectKind?lower_case)}_twopane</item> </resources> diff --git a/templates/activities/MasterDetailFlow/root/res/values/strings.xml.ftl b/templates/activities/MasterDetailFlow/root/res/values/strings.xml.ftl index 8c555ae..ea882bc 100644 --- a/templates/activities/MasterDetailFlow/root/res/values/strings.xml.ftl +++ b/templates/activities/MasterDetailFlow/root/res/values/strings.xml.ftl @@ -1,6 +1,6 @@ <resources> <#if !isNewProject> - <string name="title_${collection_name}">${objectKindPlural}</string> + <string name="title_${collection_name}">${escapeXmlString(objectKindPlural)}</string> </#if> - <string name="title_${detail_name}">${objectKind} Detail</string> + <string name="title_${detail_name}">${escapeXmlString(objectKind)} Detail</string> </resources> diff --git a/templates/activities/SettingsActivity/root/res/values/strings.xml.ftl b/templates/activities/SettingsActivity/root/res/values/strings.xml.ftl index bf881a3..8dc52ac 100644 --- a/templates/activities/SettingsActivity/root/res/values/strings.xml.ftl +++ b/templates/activities/SettingsActivity/root/res/values/strings.xml.ftl @@ -1,6 +1,6 @@ <resources> <#if !isNewProject> - <string name="title_${simpleName}">${activityTitle}</string> + <string name="title_${simpleName}">${escapeXmlString(activityTitle)}</string> </#if> <!-- Strings related to Settings --> diff --git a/templates/docs/index.html b/templates/docs/index.html index 0916157..f8e89eb 100644 --- a/templates/docs/index.html +++ b/templates/docs/index.html @@ -471,6 +471,58 @@ <h4>See also</h4> <p><a href="#toc_underscoretocamelcase"><code>underscoreToCamelCase</code></a></p> +<h3 data-toctitle="escapeXmlAttribute">string <em>escapeXmlAttribute</em>(string)</h3> + +<p>This function escapes a string, such as <code>Android's</code> such that it can be used as an XML attribute value: <code>Android&apos;s</code>. In particular, it will escape ', ", < and &.</p> + +<h4>Arguments</h4> +<dl> + <dt><code>str</code></dt> + <dd>The string to be escaped.</dd> +</dl> + +<h4>See also</h4> +<p><a href="#toc_escapexmltext"><code>escapeXmlText</code></a></p> +<p><a href="#toc_escapexmlstring"><code>escapeXmlString</code></a></p> + +<h3 data-toctitle="escapeXmlText">string <em>escapeXmlText</em>(string)</h3> + +<p>This function escapes a string, such as <code>A & B's</code> such that it can be used as XML text. This means it will escape < and >, but unlike <a href="#toc_escapexmlattribute"><code>escapeXmlAttribute</code></a> it will <b>not</b> escape ' and ". In the preceeding example, it will escape the string to <code>A &amp; B\s</code>. Note that if you plan to use the XML text as the value for a <string> resource value, you should consider using <a href="#toc_escapexmlstring"><code>escapeXmlString</code></a> instead, since it performs additional escapes necessary for string resources.</p> + +<h4>Arguments</h4> +<dl> + <dt><code>str</code></dt> + <dd>The string to escape to proper XML text.</dd> +</dl> + +<h4>See also</h4> +<p><a href="#toc_escapexmlattribute"><code>escapeXmlAttribute</code></a></p> +<p><a href="#toc_escapexmlstring"><code>escapeXmlString</code></a></p> + +<h3 data-toctitle="escapeXmlString">string <em>escapeXmlString</em>(string)</h3> + +<p>This function escapes a string, such as <code>A & B's</code> such that it is suitable to be inserted in a string resource file as XML text, such as <code>A &amp; B\s</code>. In addition to escaping XML characters like < and &, it also performs additional Android specific escapes, such as escaping apostrophes with a backslash, and so on.</p> + +<h4>Arguments</h4> +<dl> + <dt><code>str</code></dt> + <dd>The string, e.g. <code>Activity's Title</code> to escape to a proper resource XML value.</dd> +</dl> + +<h4>See also</h4> +<p><a href="#toc_escapexmlattribute"><code>escapeXmlAttribute</code></a></p> +<p><a href="#toc_escapexmltext"><code>escapeXmlText</code></a></p> + +<h3 data-toctitle="extractLetters">string <em>extractLetters</em>(string)</h3> + +<p>This function extracts all the letters from a string, effectively removing any punctuation and whitespace characters.</p> + +<h4>Arguments</h4> +<dl> + <dt><code>str</code></dt> + <dd>The string to extract letters from</dd> +</dl> + <h3 data-toctitle="classToResource">string <em>classToResource</em>(string)</h3> <p>This function converts an Android class name, such as <code>FooActivity</code> or <code>FooFragment</code>, to a corresponding resource-friendly identifier string, such as <code>foo</code>, stripping the 'Activity' or 'Fragment' suffix. Currently stripped suffixes are listed below.</p> diff --git a/templates/projects/NewAndroidApplication/root/res/values/strings.xml.ftl b/templates/projects/NewAndroidApplication/root/res/values/strings.xml.ftl index 557e5c2..ee03444 100644 --- a/templates/projects/NewAndroidApplication/root/res/values/strings.xml.ftl +++ b/templates/projects/NewAndroidApplication/root/res/values/strings.xml.ftl @@ -1,3 +1,3 @@ <resources> - <string name="app_name">${appTitle}</string> + <string name="app_name">${escapeXmlString(appTitle)}</string> </resources> |