1 /* 2 * Copyright (C) 2011 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.eclipse.adt.internal.resources; 18 19 import static com.android.AndroidConstants.FD_RES_VALUES; 20 import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI; 21 import static com.android.ide.common.resources.ResourceResolver.PREFIX_ANDROID_STYLE; 22 import static com.android.ide.common.resources.ResourceResolver.PREFIX_STYLE; 23 import static com.android.ide.eclipse.adt.AdtConstants.ANDROID_PKG; 24 import static com.android.ide.eclipse.adt.AdtConstants.DOT_XML; 25 import static com.android.ide.eclipse.adt.AdtConstants.EXT_XML; 26 import static com.android.ide.eclipse.adt.AdtConstants.WS_SEP; 27 import static com.android.sdklib.SdkConstants.FD_RESOURCES; 28 29 import com.android.ide.common.rendering.api.ResourceValue; 30 import com.android.ide.common.resources.ResourceDeltaKind; 31 import com.android.ide.common.resources.ResourceResolver; 32 import com.android.ide.common.resources.configuration.CountryCodeQualifier; 33 import com.android.ide.common.resources.configuration.DensityQualifier; 34 import com.android.ide.common.resources.configuration.FolderConfiguration; 35 import com.android.ide.common.resources.configuration.KeyboardStateQualifier; 36 import com.android.ide.common.resources.configuration.LanguageQualifier; 37 import com.android.ide.common.resources.configuration.NavigationMethodQualifier; 38 import com.android.ide.common.resources.configuration.NavigationStateQualifier; 39 import com.android.ide.common.resources.configuration.NetworkCodeQualifier; 40 import com.android.ide.common.resources.configuration.NightModeQualifier; 41 import com.android.ide.common.resources.configuration.RegionQualifier; 42 import com.android.ide.common.resources.configuration.ResourceQualifier; 43 import com.android.ide.common.resources.configuration.ScreenDimensionQualifier; 44 import com.android.ide.common.resources.configuration.ScreenHeightQualifier; 45 import com.android.ide.common.resources.configuration.ScreenOrientationQualifier; 46 import com.android.ide.common.resources.configuration.ScreenRatioQualifier; 47 import com.android.ide.common.resources.configuration.ScreenSizeQualifier; 48 import com.android.ide.common.resources.configuration.ScreenWidthQualifier; 49 import com.android.ide.common.resources.configuration.SmallestScreenWidthQualifier; 50 import com.android.ide.common.resources.configuration.TextInputMethodQualifier; 51 import com.android.ide.common.resources.configuration.TouchScreenQualifier; 52 import com.android.ide.common.resources.configuration.UiModeQualifier; 53 import com.android.ide.common.resources.configuration.VersionQualifier; 54 import com.android.ide.eclipse.adt.AdtPlugin; 55 import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; 56 import com.android.ide.eclipse.adt.internal.editors.Hyperlinks; 57 import com.android.ide.eclipse.adt.internal.editors.IconFactory; 58 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageUtils; 59 import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.VisualRefactoring; 60 import com.android.ide.eclipse.adt.internal.editors.values.descriptors.ValuesDescriptors; 61 import com.android.ide.eclipse.adt.internal.wizards.newxmlfile.NewXmlFileWizard; 62 import com.android.resources.FolderTypeRelationship; 63 import com.android.resources.ResourceFolderType; 64 import com.android.resources.ResourceType; 65 import com.android.util.Pair; 66 67 import org.eclipse.core.resources.IFile; 68 import org.eclipse.core.resources.IProject; 69 import org.eclipse.core.resources.IResource; 70 import org.eclipse.core.resources.IResourceDelta; 71 import org.eclipse.core.runtime.CoreException; 72 import org.eclipse.core.runtime.IPath; 73 import org.eclipse.core.runtime.Path; 74 import org.eclipse.jface.text.IRegion; 75 import org.eclipse.jface.text.Region; 76 import org.eclipse.swt.graphics.Image; 77 import org.eclipse.swt.graphics.RGB; 78 import org.eclipse.wst.sse.core.StructuredModelManager; 79 import org.eclipse.wst.sse.core.internal.provisional.IModelManager; 80 import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; 81 import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; 82 import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; 83 import org.eclipse.wst.xml.core.internal.document.ElementImpl; 84 import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel; 85 import org.w3c.dom.Attr; 86 import org.w3c.dom.Document; 87 import org.w3c.dom.Element; 88 import org.w3c.dom.NamedNodeMap; 89 import org.w3c.dom.Node; 90 import org.w3c.dom.NodeList; 91 import org.w3c.dom.Text; 92 import org.xml.sax.InputSource; 93 94 import java.io.BufferedInputStream; 95 import java.io.ByteArrayInputStream; 96 import java.io.File; 97 import java.io.FileInputStream; 98 import java.io.IOException; 99 import java.io.InputStream; 100 import java.io.UnsupportedEncodingException; 101 import java.util.HashMap; 102 import java.util.List; 103 import java.util.Map; 104 import java.util.Set; 105 106 import javax.xml.parsers.DocumentBuilder; 107 import javax.xml.parsers.DocumentBuilderFactory; 108 109 /** 110 * Helper class to deal with SWT specifics for the resources. 111 */ 112 @SuppressWarnings("restriction") // XML model 113 public class ResourceHelper { 114 115 private static final String TAG_ITEM = "item"; //$NON-NLS-1$ 116 private static final String ATTR_COLOR = "color"; //$NON-NLS-1$ 117 118 private final static Map<Class<?>, Image> sIconMap = new HashMap<Class<?>, Image>( 119 FolderConfiguration.getQualifierCount()); 120 121 static { 122 IconFactory factory = IconFactory.getInstance(); sIconMap.put(CountryCodeQualifier.class, factory.getIcon("mcc"))123 sIconMap.put(CountryCodeQualifier.class, factory.getIcon("mcc")); //$NON-NLS-1$ sIconMap.put(NetworkCodeQualifier.class, factory.getIcon("mnc"))124 sIconMap.put(NetworkCodeQualifier.class, factory.getIcon("mnc")); //$NON-NLS-1$ sIconMap.put(LanguageQualifier.class, factory.getIcon("language"))125 sIconMap.put(LanguageQualifier.class, factory.getIcon("language")); //$NON-NLS-1$ sIconMap.put(RegionQualifier.class, factory.getIcon("region"))126 sIconMap.put(RegionQualifier.class, factory.getIcon("region")); //$NON-NLS-1$ sIconMap.put(ScreenSizeQualifier.class, factory.getIcon("size"))127 sIconMap.put(ScreenSizeQualifier.class, factory.getIcon("size")); //$NON-NLS-1$ sIconMap.put(ScreenRatioQualifier.class, factory.getIcon("ratio"))128 sIconMap.put(ScreenRatioQualifier.class, factory.getIcon("ratio")); //$NON-NLS-1$ sIconMap.put(ScreenOrientationQualifier.class, factory.getIcon("orientation"))129 sIconMap.put(ScreenOrientationQualifier.class, factory.getIcon("orientation")); //$NON-NLS-1$ sIconMap.put(UiModeQualifier.class, factory.getIcon("dockmode"))130 sIconMap.put(UiModeQualifier.class, factory.getIcon("dockmode")); //$NON-NLS-1$ sIconMap.put(NightModeQualifier.class, factory.getIcon("nightmode"))131 sIconMap.put(NightModeQualifier.class, factory.getIcon("nightmode")); //$NON-NLS-1$ sIconMap.put(DensityQualifier.class, factory.getIcon("dpi"))132 sIconMap.put(DensityQualifier.class, factory.getIcon("dpi")); //$NON-NLS-1$ sIconMap.put(TouchScreenQualifier.class, factory.getIcon("touch"))133 sIconMap.put(TouchScreenQualifier.class, factory.getIcon("touch")); //$NON-NLS-1$ sIconMap.put(KeyboardStateQualifier.class, factory.getIcon("keyboard"))134 sIconMap.put(KeyboardStateQualifier.class, factory.getIcon("keyboard")); //$NON-NLS-1$ sIconMap.put(TextInputMethodQualifier.class, factory.getIcon("text_input"))135 sIconMap.put(TextInputMethodQualifier.class, factory.getIcon("text_input")); //$NON-NLS-1$ sIconMap.put(NavigationStateQualifier.class, factory.getIcon("navpad"))136 sIconMap.put(NavigationStateQualifier.class, factory.getIcon("navpad")); //$NON-NLS-1$ sIconMap.put(NavigationMethodQualifier.class, factory.getIcon("navpad"))137 sIconMap.put(NavigationMethodQualifier.class, factory.getIcon("navpad")); //$NON-NLS-1$ sIconMap.put(ScreenDimensionQualifier.class, factory.getIcon("dimension"))138 sIconMap.put(ScreenDimensionQualifier.class, factory.getIcon("dimension")); //$NON-NLS-1$ sIconMap.put(VersionQualifier.class, factory.getIcon("version"))139 sIconMap.put(VersionQualifier.class, factory.getIcon("version")); //$NON-NLS-1$ sIconMap.put(ScreenWidthQualifier.class, factory.getIcon("width"))140 sIconMap.put(ScreenWidthQualifier.class, factory.getIcon("width")); //$NON-NLS-1$ sIconMap.put(ScreenHeightQualifier.class, factory.getIcon("height"))141 sIconMap.put(ScreenHeightQualifier.class, factory.getIcon("height")); //$NON-NLS-1$ sIconMap.put(SmallestScreenWidthQualifier.class,factory.getIcon("swidth"))142 sIconMap.put(SmallestScreenWidthQualifier.class,factory.getIcon("swidth")); //$NON-NLS-1$ 143 } 144 145 /** 146 * Returns the icon for the qualifier. 147 */ getIcon(Class<? extends ResourceQualifier> theClass)148 public static Image getIcon(Class<? extends ResourceQualifier> theClass) { 149 return sIconMap.get(theClass); 150 } 151 152 /** 153 * Returns a {@link ResourceDeltaKind} from an {@link IResourceDelta} value. 154 * @param kind a {@link IResourceDelta} integer constant. 155 * @return a matching {@link ResourceDeltaKind} or null. 156 * 157 * @see IResourceDelta#ADDED 158 * @see IResourceDelta#REMOVED 159 * @see IResourceDelta#CHANGED 160 */ getResourceDeltaKind(int kind)161 public static ResourceDeltaKind getResourceDeltaKind(int kind) { 162 switch (kind) { 163 case IResourceDelta.ADDED: 164 return ResourceDeltaKind.ADDED; 165 case IResourceDelta.REMOVED: 166 return ResourceDeltaKind.REMOVED; 167 case IResourceDelta.CHANGED: 168 return ResourceDeltaKind.CHANGED; 169 } 170 171 return null; 172 } 173 174 /** 175 * Return the resource type of the given url, and the resource name 176 * 177 * @param url the resource url to be parsed 178 * @return a pair of the resource type and the resource name 179 */ parseResource(String url)180 public static Pair<ResourceType,String> parseResource(String url) { 181 if (!url.startsWith("@")) { //$NON-NLS-1$ 182 return null; 183 } 184 int typeEnd = url.indexOf('/', 1); 185 if (typeEnd == -1) { 186 return null; 187 } 188 int nameBegin = typeEnd + 1; 189 190 // Skip @ and @+ 191 int typeBegin = url.startsWith("@+") ? 2 : 1; //$NON-NLS-1$ 192 193 int colon = url.lastIndexOf(':', typeEnd); 194 if (colon != -1) { 195 typeBegin = colon + 1; 196 } 197 String typeName = url.substring(typeBegin, typeEnd); 198 ResourceType type = ResourceType.getEnum(typeName); 199 if (type == null) { 200 return null; 201 } 202 String name = url.substring(nameBegin); 203 204 return Pair.of(type, name); 205 } 206 207 /** 208 * Is this a resource that can be defined in any file within the "values" folder? 209 * <p> 210 * Some resource types can be defined <b>both</b> as a separate XML file as well 211 * as defined within a value XML file. This method will return true for these types 212 * as well. In other words, a ResourceType can return true for both 213 * {@link #isValueBasedResourceType} and {@link #isFileBasedResourceType}. 214 * 215 * @param type the resource type to check 216 * @return true if the given resource type can be represented as a value under the 217 * values/ folder 218 */ isValueBasedResourceType(ResourceType type)219 public static boolean isValueBasedResourceType(ResourceType type) { 220 List<ResourceFolderType> folderTypes = FolderTypeRelationship.getRelatedFolders(type); 221 for (ResourceFolderType folderType : folderTypes) { 222 if (folderType == ResourceFolderType.VALUES) { 223 return true; 224 } 225 } 226 227 return false; 228 } 229 230 /** 231 * Is this a resource that is defined in a file named by the resource plus the XML 232 * extension? 233 * <p> 234 * Some resource types can be defined <b>both</b> as a separate XML file as well as 235 * defined within a value XML file along with other properties. This method will 236 * return true for these resource types as well. In other words, a ResourceType can 237 * return true for both {@link #isValueBasedResourceType} and 238 * {@link #isFileBasedResourceType}. 239 * 240 * @param type the resource type to check 241 * @return true if the given resource type is stored in a file named by the resource 242 */ isFileBasedResourceType(ResourceType type)243 public static boolean isFileBasedResourceType(ResourceType type) { 244 List<ResourceFolderType> folderTypes = FolderTypeRelationship.getRelatedFolders(type); 245 for (ResourceFolderType folderType : folderTypes) { 246 if (folderType != ResourceFolderType.VALUES) { 247 248 if (type == ResourceType.ID) { 249 // The folder types for ID is not only VALUES but also 250 // LAYOUT and MENU. However, unlike resources, they are only defined 251 // inline there so for the purposes of isFileBasedResourceType 252 // (where the intent is to figure out files that are uniquely identified 253 // by a resource's name) this method should return false anyway. 254 return false; 255 } 256 257 return true; 258 } 259 } 260 261 return false; 262 } 263 264 /** 265 * Returns true if this class can create the given resource 266 * 267 * @param resource the resource to be created 268 * @return true if the {@link #createResource} method can create this resource 269 */ canCreateResource(String resource)270 public static boolean canCreateResource(String resource) { 271 // Cannot create framework resources 272 if (resource.startsWith('@' + ANDROID_PKG + ':')) { 273 return false; 274 } 275 276 Pair<ResourceType,String> parsed = parseResource(resource); 277 if (parsed != null) { 278 ResourceType type = parsed.getFirst(); 279 String name = parsed.getSecond(); 280 281 // Make sure the name is valid 282 ResourceNameValidator validator = 283 ResourceNameValidator.create(false, (Set<String>) null /* existing */, type); 284 if (validator.isValid(name) != null) { 285 return false; 286 } 287 288 return canCreateResourceType(type); 289 } 290 291 return false; 292 } 293 294 /** 295 * Returns true if this class can create resources of the given resource 296 * type 297 * 298 * @param type the type of resource to be created 299 * @return true if the {@link #createResource} method can create resources 300 * of this type (provided the name parameter is also valid) 301 */ canCreateResourceType(ResourceType type)302 public static boolean canCreateResourceType(ResourceType type) { 303 // We can create all value types 304 if (isValueBasedResourceType(type)) { 305 return true; 306 } 307 308 // We can create -some- file-based types - those supported by the New XML wizard: 309 for (ResourceFolderType folderType : FolderTypeRelationship.getRelatedFolders(type)) { 310 if (NewXmlFileWizard.canCreateXmlFile(folderType)) { 311 return true; 312 } 313 } 314 315 return false; 316 } 317 318 /** Creates a file-based resource, like a layout. Used by {@link #createResource} */ createFileResource(IProject project, ResourceType type, String name)319 private static Pair<IFile,IRegion> createFileResource(IProject project, ResourceType type, 320 String name) { 321 322 ResourceFolderType folderType = null; 323 for (ResourceFolderType f : FolderTypeRelationship.getRelatedFolders(type)) { 324 if (NewXmlFileWizard.canCreateXmlFile(f)) { 325 folderType = f; 326 break; 327 } 328 } 329 if (folderType == null) { 330 return null; 331 } 332 333 // Find "dimens.xml" file in res/values/ (or corresponding name for other 334 // value types) 335 IPath projectPath = new Path(FD_RESOURCES + WS_SEP + folderType.getName() + WS_SEP 336 + name + '.' + EXT_XML); 337 IFile file = project.getFile(projectPath); 338 return NewXmlFileWizard.createXmlFile(project, file, folderType); 339 } 340 341 /** 342 * Creates a resource of a given type, name and (if applicable) value 343 * 344 * @param project the project to contain the resource 345 * @param type the type of resource 346 * @param name the name of the resource 347 * @param value the value of the resource, if it is a value-type resource 348 * @return a pair of the file containing the resource and a region where the value 349 * appears 350 */ createResource(IProject project, ResourceType type, String name, String value)351 public static Pair<IFile,IRegion> createResource(IProject project, ResourceType type, 352 String name, String value) { 353 if (!isValueBasedResourceType(type)) { 354 return createFileResource(project, type, name); 355 } 356 357 // Find "dimens.xml" file in res/values/ (or corresponding name for other 358 // value types) 359 String typeName = type.getName(); 360 String fileName = typeName + 's'; 361 String projectPath = FD_RESOURCES + WS_SEP + FD_RES_VALUES + WS_SEP 362 + fileName + '.' + EXT_XML; 363 Object editRequester = project; 364 IResource member = project.findMember(projectPath); 365 String tagName = Hyperlinks.getTagName(type); 366 boolean createEmptyTag = type == ResourceType.ID; 367 if (member != null) { 368 if (member instanceof IFile) { 369 IFile file = (IFile) member; 370 // File exists: Must add item to the XML 371 IModelManager manager = StructuredModelManager.getModelManager(); 372 IStructuredModel model = null; 373 try { 374 model = manager.getExistingModelForEdit(file); 375 if (model == null) { 376 model = manager.getModelForEdit(file); 377 } 378 if (model instanceof IDOMModel) { 379 model.beginRecording(editRequester, String.format("Add %1$s", 380 type.getDisplayName())); 381 IDOMModel domModel = (IDOMModel) model; 382 Document document = domModel.getDocument(); 383 Element root = document.getDocumentElement(); 384 IStructuredDocument structuredDocument = model.getStructuredDocument(); 385 Node lastElement = null; 386 NodeList childNodes = root.getChildNodes(); 387 String indent = null; 388 for (int i = childNodes.getLength() - 1; i >= 0; i--) { 389 Node node = childNodes.item(i); 390 if (node.getNodeType() == Node.ELEMENT_NODE) { 391 lastElement = node; 392 indent = AndroidXmlEditor.getIndent(structuredDocument, node); 393 break; 394 } 395 } 396 if (indent == null || indent.length() == 0) { 397 indent = " "; //$NON-NLS-1$ 398 } 399 Node nextChild = lastElement != null ? lastElement.getNextSibling() : null; 400 Text indentNode = document.createTextNode('\n' + indent); 401 root.insertBefore(indentNode, nextChild); 402 Element element = document.createElement(tagName); 403 if (createEmptyTag) { 404 if (element instanceof ElementImpl) { 405 ElementImpl elementImpl = (ElementImpl) element; 406 elementImpl.setEmptyTag(true); 407 } 408 } 409 element.setAttribute(ValuesDescriptors.NAME_ATTR, name); 410 if (!tagName.equals(typeName)) { 411 element.setAttribute(ValuesDescriptors.TYPE_ATTR, typeName); 412 } 413 root.insertBefore(element, nextChild); 414 IRegion region = null; 415 416 if (createEmptyTag) { 417 IndexedRegion domRegion = VisualRefactoring.getRegion(element); 418 int endOffset = domRegion.getEndOffset(); 419 region = new Region(endOffset, 0); 420 } else { 421 Node valueNode = document.createTextNode(value); 422 element.appendChild(valueNode); 423 424 IndexedRegion domRegion = VisualRefactoring.getRegion(valueNode); 425 int startOffset = domRegion.getStartOffset(); 426 int length = domRegion.getLength(); 427 region = new Region(startOffset, length); 428 } 429 model.save(); 430 return Pair.of(file, region); 431 } 432 } catch (Exception e) { 433 AdtPlugin.log(e, "Cannot access XML value model"); 434 } finally { 435 if (model != null) { 436 model.endRecording(editRequester); 437 model.releaseFromEdit(); 438 } 439 } 440 } 441 442 return null; 443 } else { 444 // No such file exists: just create it 445 String prolog = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; //$NON-NLS-1$ 446 StringBuilder sb = new StringBuilder(prolog); 447 448 String root = ValuesDescriptors.ROOT_ELEMENT; 449 sb.append('<').append(root).append('>').append('\n'); 450 sb.append(" "); //$NON-NLS-1$ 451 sb.append('<'); 452 sb.append(tagName); 453 sb.append(" name=\""); //$NON-NLS-1$ 454 sb.append(name); 455 sb.append('"'); 456 if (!tagName.equals(typeName)) { 457 sb.append(" type=\""); //$NON-NLS-1$ 458 sb.append(typeName); 459 sb.append('"'); 460 } 461 int start, end; 462 if (createEmptyTag) { 463 sb.append("/>"); //$NON-NLS-1$ 464 start = sb.length(); 465 end = sb.length(); 466 } else { 467 sb.append('>'); 468 start = sb.length(); 469 sb.append(value); 470 end = sb.length(); 471 sb.append('<').append('/'); 472 sb.append(tagName); 473 sb.append('>'); 474 } 475 sb.append('\n').append('<').append('/').append(root).append('>').append('\n'); 476 String result = sb.toString(); 477 // TODO: Pretty print string (wait until that CL is integrated) 478 String error = null; 479 try { 480 byte[] buf = result.getBytes("UTF8"); //$NON-NLS-1$ 481 InputStream stream = new ByteArrayInputStream(buf); 482 IFile file = project.getFile(new Path(projectPath)); 483 file.create(stream, true /*force*/, null /*progress*/); 484 IRegion region = new Region(start, end - start); 485 return Pair.of(file, region); 486 } catch (UnsupportedEncodingException e) { 487 error = e.getMessage(); 488 } catch (CoreException e) { 489 error = e.getMessage(); 490 } 491 492 error = String.format("Failed to generate %1$s: %2$s", name, error); 493 AdtPlugin.displayError("New Android XML File", error); 494 } 495 return null; 496 } 497 498 /** 499 * Returns the theme name to be shown for theme styles, e.g. for "@style/Theme" it 500 * returns "Theme" 501 * 502 * @param style a theme style string 503 * @return the user visible theme name 504 */ styleToTheme(String style)505 public static String styleToTheme(String style) { 506 if (style.startsWith(PREFIX_STYLE)) { 507 style = style.substring(PREFIX_STYLE.length()); 508 } else if (style.startsWith(PREFIX_ANDROID_STYLE)) { 509 style = style.substring(PREFIX_ANDROID_STYLE.length()); 510 } 511 return style; 512 } 513 514 /** 515 * Returns the layout resource name for the given layout file, e.g. for 516 * /res/layout/foo.xml returns foo. 517 * 518 * @param layoutFile the layout file whose name we want to look up 519 * @return the layout name 520 */ getLayoutName(IFile layoutFile)521 public static String getLayoutName(IFile layoutFile) { 522 String layoutName = layoutFile.getName(); 523 int dotIndex = layoutName.indexOf('.'); 524 if (dotIndex != -1) { 525 layoutName = layoutName.substring(0, dotIndex); 526 } 527 return layoutName; 528 } 529 530 /** 531 * Tries to resolve the given resource value to an actual RGB color. For state lists 532 * it will pick the simplest/fallback color. 533 * 534 * @param resources the resource resolver to use to follow color references 535 * @param color the color to resolve 536 * @return the corresponding {@link RGB} color, or null 537 */ resolveColor(ResourceResolver resources, ResourceValue color)538 public static RGB resolveColor(ResourceResolver resources, ResourceValue color) { 539 color = resources.resolveResValue(color); 540 if (color == null) { 541 return null; 542 } 543 String value = color.getValue(); 544 545 while (value != null) { 546 if (value.startsWith("#")) { //$NON-NLS-1$ 547 try { 548 int rgba = ImageUtils.getColor(value); 549 // Drop alpha channel 550 return ImageUtils.intToRgb(rgba); 551 } catch (NumberFormatException nfe) { 552 // Pass 553 } 554 return null; 555 } 556 if (value.startsWith("@")) { //$NON-NLS-1$ 557 boolean isFramework = color.isFramework(); 558 color = resources.findResValue(value, isFramework); 559 if (color != null) { 560 value = color.getValue(); 561 } else { 562 break; 563 } 564 } else { 565 File file = new File(value); 566 if (file.exists() && file.getName().endsWith(DOT_XML)) { 567 // Parse 568 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 569 BufferedInputStream bis = null; 570 try { 571 bis = new BufferedInputStream(new FileInputStream(file)); 572 InputSource is = new InputSource(bis); 573 factory.setNamespaceAware(true); 574 factory.setValidating(false); 575 DocumentBuilder builder = factory.newDocumentBuilder(); 576 Document document = builder.parse(is); 577 NodeList items = document.getElementsByTagName(TAG_ITEM); 578 579 value = findColorValue(items); 580 continue; 581 } catch (Exception e) { 582 AdtPlugin.log(e, "Failed parsing color file %1$s", file.getName()); 583 } finally { 584 if (bis != null) { 585 try { 586 bis.close(); 587 } catch (IOException e) { 588 // Nothing useful can be done here 589 } 590 } 591 } 592 } 593 594 return null; 595 } 596 } 597 598 return null; 599 } 600 601 /** 602 * Searches a color XML file for the color definition element that does not 603 * have an associated state and returns its color 604 */ findColorValue(NodeList items)605 private static String findColorValue(NodeList items) { 606 for (int i = 0, n = items.getLength(); i < n; i++) { 607 // Find non-state color definition 608 Node item = items.item(i); 609 boolean hasState = false; 610 if (item.getNodeType() == Node.ELEMENT_NODE) { 611 Element element = (Element) item; 612 if (element.hasAttributeNS(ANDROID_URI, ATTR_COLOR)) { 613 NamedNodeMap attributes = element.getAttributes(); 614 for (int j = 0, m = attributes.getLength(); j < m; j++) { 615 Attr attribute = (Attr) attributes.item(j); 616 if (attribute.getLocalName().startsWith("state_")) { //$NON-NLS-1$ 617 hasState = true; 618 break; 619 } 620 } 621 622 if (!hasState) { 623 return element.getAttributeNS(ANDROID_URI, ATTR_COLOR); 624 } 625 } 626 } 627 } 628 629 return null; 630 } 631 } 632