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 */ AttributeInfo(@onNull String name, @NonNull EnumSet<Format> formats)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 */ AttributeInfo(@onNull String name, @NonNull EnumSet<Format> formats, String javadoc)85 public AttributeInfo(@NonNull String name, @NonNull EnumSet<Format> formats, String javadoc) { 86 mName = name; 87 mFormats = formats; 88 mJavaDoc = javadoc; 89 } 90 AttributeInfo(AttributeInfo info)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 */ setName(String name)105 public void setName(String name) { 106 mName = name; 107 } 108 109 /** Returns the XML Name of the attribute */ 110 @Override getName()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 getFormats()117 public @NonNull EnumSet<Format> getFormats() { 118 return mFormats; 119 } 120 /** Returns the values for enums. null for other types. */ 121 @Override getEnumValues()122 public String[] getEnumValues() { 123 return mEnumValues; 124 } 125 /** Returns the values for flags. null for other types. */ 126 @Override getFlagValues()127 public String[] getFlagValues() { 128 return mFlagValues; 129 } 130 /** Returns a short javadoc, .i.e. the first sentence. */ 131 @Override getJavaDoc()132 public @NonNull String getJavaDoc() { 133 return mJavaDoc; 134 } 135 /** Returns the documentation for deprecated attributes. Null if not deprecated. */ 136 @Override getDeprecatedDoc()137 public String getDeprecatedDoc() { 138 return mDeprecatedDoc; 139 } 140 141 /** Sets the values for enums. null for other types. */ setEnumValues(String[] values)142 public AttributeInfo setEnumValues(String[] values) { 143 mEnumValues = values; 144 return this; 145 } 146 147 /** Sets the values for flags. null for other types. */ setFlagValues(String[] values)148 public AttributeInfo setFlagValues(String[] values) { 149 mFlagValues = values; 150 return this; 151 } 152 153 /** Sets a short javadoc, .i.e. the first sentence. */ setJavaDoc(String javaDoc)154 public void setJavaDoc(String javaDoc) { 155 mJavaDoc = javaDoc; 156 } 157 158 /** Sets the documentation for deprecated attributes. Null if not deprecated. */ setDeprecatedDoc(String deprecatedDoc)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 */ setDefinedBy(String definedBy)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 getDefinedBy()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 */ isValid( @onNull String value, @Nullable ResourceRepository projectResources, @Nullable ResourceRepository frameworkResources)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