Home | History | Annotate | Download | only in platform
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ide.common.resources.platform;
     18 
     19 import static com.android.SdkConstants.ANDROID_PREFIX;
     20 import static com.android.SdkConstants.ANDROID_THEME_PREFIX;
     21 import static com.android.SdkConstants.ID_PREFIX;
     22 import static com.android.SdkConstants.NEW_ID_PREFIX;
     23 import static com.android.SdkConstants.PREFIX_THEME_REF;
     24 import static com.android.SdkConstants.VALUE_FALSE;
     25 import static com.android.SdkConstants.VALUE_TRUE;
     26 import static com.android.ide.common.api.IAttributeInfo.Format.BOOLEAN;
     27 import static com.android.ide.common.api.IAttributeInfo.Format.COLOR;
     28 import static com.android.ide.common.api.IAttributeInfo.Format.DIMENSION;
     29 import static com.android.ide.common.api.IAttributeInfo.Format.ENUM;
     30 import static com.android.ide.common.api.IAttributeInfo.Format.FLAG;
     31 import static com.android.ide.common.api.IAttributeInfo.Format.FLOAT;
     32 import static com.android.ide.common.api.IAttributeInfo.Format.FRACTION;
     33 import static com.android.ide.common.api.IAttributeInfo.Format.INTEGER;
     34 import static com.android.ide.common.api.IAttributeInfo.Format.STRING;
     35 
     36 import com.android.annotations.NonNull;
     37 import com.android.annotations.Nullable;
     38 import com.android.ide.common.api.IAttributeInfo;
     39 import com.android.ide.common.resources.ResourceRepository;
     40 import com.android.resources.ResourceType;
     41 import com.google.common.base.Splitter;
     42 
     43 import java.util.EnumSet;
     44 import java.util.regex.Pattern;
     45 
     46 
     47 /**
     48  * Information about an attribute as gathered from the attrs.xml file where
     49  * the attribute was declared. This must include a format (string, reference, float, etc.),
     50  * possible flag or enum values, whether it's deprecated and its javadoc.
     51  */
     52 public class AttributeInfo implements IAttributeInfo {
     53     /** XML Name of the attribute */
     54     private String mName;
     55 
     56     /** Formats of the attribute. Cannot be null. Should have at least one format. */
     57     private EnumSet<Format> mFormats;
     58     /** Values for enum. null for other types. */
     59     private String[] mEnumValues;
     60     /** Values for flag. null for other types. */
     61     private String[] mFlagValues;
     62     /** Short javadoc (i.e. the first sentence). */
     63     private String mJavaDoc;
     64     /** Documentation for deprecated attributes. Null if not deprecated. */
     65     private String mDeprecatedDoc;
     66     /** The source class defining this attribute */
     67     private String mDefinedBy;
     68 
     69     /**
     70      * @param name The XML Name of the attribute
     71      * @param formats The formats of the attribute. Cannot be null.
     72      *                Should have at least one format.
     73      */
     74     public AttributeInfo(@NonNull String name, @NonNull EnumSet<Format> formats) {
     75         mName = name;
     76         mFormats = formats;
     77     }
     78 
     79     /**
     80      * @param name The XML Name of the attribute
     81      * @param formats The formats of the attribute. Cannot be null.
     82      *                Should have at least one format.
     83      * @param javadoc Short javadoc (i.e. the first sentence).
     84      */
     85     public AttributeInfo(@NonNull String name, @NonNull EnumSet<Format> formats, String javadoc) {
     86         mName = name;
     87         mFormats = formats;
     88         mJavaDoc = javadoc;
     89     }
     90 
     91     public AttributeInfo(AttributeInfo info) {
     92         mName = info.mName;
     93         mFormats = info.mFormats;
     94         mEnumValues = info.mEnumValues;
     95         mFlagValues = info.mFlagValues;
     96         mJavaDoc = info.mJavaDoc;
     97         mDeprecatedDoc = info.mDeprecatedDoc;
     98     }
     99 
    100     /**
    101      * Sets the XML Name of the attribute
    102      *
    103      * @param name the new name to assign
    104      */
    105     public void setName(String name) {
    106         mName = name;
    107     }
    108 
    109     /** Returns the XML Name of the attribute */
    110     @Override
    111     public @NonNull String getName() {
    112         return mName;
    113     }
    114     /** Returns the formats of the attribute. Cannot be null.
    115      *  Should have at least one format. */
    116     @Override
    117     public @NonNull EnumSet<Format> getFormats() {
    118         return mFormats;
    119     }
    120     /** Returns the values for enums. null for other types. */
    121     @Override
    122     public String[] getEnumValues() {
    123         return mEnumValues;
    124     }
    125     /** Returns the values for flags. null for other types. */
    126     @Override
    127     public String[] getFlagValues() {
    128         return mFlagValues;
    129     }
    130     /** Returns a short javadoc, .i.e. the first sentence. */
    131     @Override
    132     public @NonNull String getJavaDoc() {
    133         return mJavaDoc;
    134     }
    135     /** Returns the documentation for deprecated attributes. Null if not deprecated. */
    136     @Override
    137     public String getDeprecatedDoc() {
    138         return mDeprecatedDoc;
    139     }
    140 
    141     /** Sets the values for enums. null for other types. */
    142     public AttributeInfo setEnumValues(String[] values) {
    143         mEnumValues = values;
    144         return this;
    145     }
    146 
    147     /** Sets the values for flags. null for other types. */
    148     public AttributeInfo setFlagValues(String[] values) {
    149         mFlagValues = values;
    150         return this;
    151     }
    152 
    153     /** Sets a short javadoc, .i.e. the first sentence. */
    154     public void setJavaDoc(String javaDoc) {
    155         mJavaDoc = javaDoc;
    156     }
    157 
    158     /** Sets the documentation for deprecated attributes. Null if not deprecated. */
    159     public void setDeprecatedDoc(String deprecatedDoc) {
    160         mDeprecatedDoc = deprecatedDoc;
    161     }
    162 
    163     /**
    164      * Sets the name of the class (fully qualified class name) which defined
    165      * this attribute
    166      *
    167      * @param definedBy the name of the class (fully qualified class name) which
    168      *            defined this attribute
    169      */
    170     public void setDefinedBy(String definedBy) {
    171         mDefinedBy = definedBy;
    172     }
    173 
    174     /**
    175      * Returns the name of the class (fully qualified class name) which defined
    176      * this attribute
    177      *
    178      * @return the name of the class (fully qualified class name) which defined
    179      *         this attribute
    180      */
    181     @Override
    182     public @NonNull String getDefinedBy() {
    183         return mDefinedBy;
    184     }
    185 
    186     private final static Pattern INTEGER_PATTERN = Pattern.compile("-?[0-9]+"); //$NON-NLS-1$
    187     private final static Pattern FLOAT_PATTERN =
    188             Pattern.compile("-?[0-9]?(\\.[0-9]+)?"); //$NON-NLS-1$
    189     private final static Pattern DIMENSION_PATTERN =
    190             Pattern.compile("-?[0-9]+(\\.[0-9]+)?(dp|dip|sp|px|pt|in|mm)"); //$NON-NLS-1$
    191 
    192     /**
    193      * Checks the given value and returns true only if it is a valid XML value
    194      * for this attribute.
    195      *
    196      * @param value the XML value to check
    197      * @param projectResources project resources to validate resource URLs with,
    198      *            if any
    199      * @param frameworkResources framework resources to validate resource URLs
    200      *            with, if any
    201      * @return true if the value is valid, false otherwise
    202      */
    203     public boolean isValid(
    204             @NonNull String value,
    205             @Nullable ResourceRepository projectResources,
    206             @Nullable ResourceRepository frameworkResources) {
    207 
    208         if (mFormats.contains(STRING) || mFormats.isEmpty()) {
    209             // Anything is allowed
    210             return true;
    211         }
    212 
    213         // All other formats require a nonempty string
    214         if (value.isEmpty()) {
    215             // Except for flags
    216             if (mFormats.contains(FLAG)) {
    217                 return true;
    218             }
    219 
    220             return false;
    221         }
    222         char first = value.charAt(0);
    223 
    224         // There are many attributes which are incorrectly marked in the attrs.xml
    225         // file, such as "duration", "minHeight", etc. These are marked as only
    226         // accepting "integer", but also appear to accept "reference". Therefore,
    227         // in these cases, be more lenient. (This happens for theme references too,
    228         // such as ?android:attr/listPreferredItemHeight)
    229         if ((first == '@' || first == '?') /* && mFormats.contains(REFERENCE)*/) {
    230             if (value.equals("@null")) {
    231                 return true;
    232             }
    233 
    234             if (value.startsWith(NEW_ID_PREFIX) || value.startsWith(ID_PREFIX)) {
    235                 // These are handled in the IdGeneratingResourceFile; we shouldn't
    236                 // complain about not finding ids in the repository yet since they may
    237                 // not yet have been defined (@+id's can be defined in the same layout,
    238                 // later on.)
    239                 return true;
    240             }
    241 
    242             if (value.startsWith(ANDROID_PREFIX) || value.startsWith(ANDROID_THEME_PREFIX)) {
    243                 if (frameworkResources != null) {
    244                     return frameworkResources.hasResourceItem(value);
    245                 }
    246             } else if (projectResources != null) {
    247                 return projectResources.hasResourceItem(value);
    248             }
    249 
    250             // Validate resource string
    251             String url = value;
    252             int typeEnd = url.indexOf('/', 1);
    253             if (typeEnd != -1) {
    254                 int typeBegin = url.startsWith("@+") ? 2 : 1; //$NON-NLS-1$
    255                 int colon = url.lastIndexOf(':', typeEnd);
    256                 if (colon != -1) {
    257                     typeBegin = colon + 1;
    258                 }
    259                 String typeName = url.substring(typeBegin, typeEnd);
    260                 ResourceType type = ResourceType.getEnum(typeName);
    261                 if (type != null) {
    262                     // TODO: Validate that the name portion conforms to the rules
    263                     // (is an identifier but not a keyword, etc.)
    264                     // Also validate that the prefix before the colon is either
    265                     // not there or is "android"
    266 
    267                     //int nameBegin = typeEnd + 1;
    268                     //String name = url.substring(nameBegin);
    269                     return true;
    270                 }
    271             } else if (value.startsWith(PREFIX_THEME_REF)) {
    272                 if (projectResources != null) {
    273                     return projectResources.hasResourceItem(ResourceType.ATTR,
    274                             value.substring(PREFIX_THEME_REF.length()));
    275                 } else {
    276                     // Until proven otherwise
    277                     return true;
    278                 }
    279             }
    280         }
    281 
    282         if (mFormats.contains(ENUM) && mEnumValues != null) {
    283             for (String e : mEnumValues) {
    284                 if (value.equals(e)) {
    285                     return true;
    286                 }
    287             }
    288         }
    289 
    290         if (mFormats.contains(FLAG) && mFlagValues != null) {
    291             for (String v : Splitter.on('|').split(value)) {
    292                 for (String e : mFlagValues) {
    293                     if (v.equals(e)) {
    294                         return true;
    295                     }
    296                 }
    297             }
    298         }
    299 
    300         if (mFormats.contains(DIMENSION)) {
    301             if (DIMENSION_PATTERN.matcher(value).matches()) {
    302                 return true;
    303             }
    304         }
    305 
    306         if (mFormats.contains(BOOLEAN)) {
    307             if (value.equalsIgnoreCase(VALUE_TRUE) || value.equalsIgnoreCase(VALUE_FALSE)) {
    308                 return true;
    309             }
    310         }
    311 
    312         if (mFormats.contains(FLOAT)) {
    313             if (Character.isDigit(first) || first == '-' || first == '.') {
    314                 if (FLOAT_PATTERN.matcher(value).matches()) {
    315                     return true;
    316                 }
    317                 // AAPT accepts more general floats, such as ".1",
    318                 try {
    319                     Float.parseFloat(value);
    320                     return true;
    321                 } catch (NumberFormatException nufe) {
    322                     // Not a float
    323                 }
    324             }
    325         }
    326 
    327         if (mFormats.contains(INTEGER)) {
    328             if (Character.isDigit(first) || first == '-') {
    329                 if (INTEGER_PATTERN.matcher(value).matches()) {
    330                     return true;
    331                 }
    332             }
    333         }
    334 
    335         if (mFormats.contains(COLOR)) {
    336             if (first == '#' && value.length() <= 9) { // Only allowed 32 bit ARGB
    337                 try {
    338                     // Use Long.parseLong rather than Integer.parseInt to not overflow on
    339                     // 32 big hex values like "ff191919"
    340                     Long.parseLong(value.substring(1), 16);
    341                     return true;
    342                 } catch (NumberFormatException nufe) {
    343                     // Not a valid color number
    344                 }
    345             }
    346         }
    347 
    348         if (mFormats.contains(FRACTION)) {
    349             // should end with % or %p
    350             return true;
    351         }
    352 
    353         return false;
    354     }
    355 }
    356