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