1 /* 2 * Copyright (C) 2012 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 package com.android.ide.eclipse.adt.internal.wizards.templates; 17 18 import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectWizard.ATTR_MIN_API; 19 import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectWizard.ATTR_MIN_BUILD_API; 20 import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectWizard.ATTR_REVISION; 21 import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_BACKGROUND; 22 import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_CLIPART_NAME; 23 import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_DESCRIPTION; 24 import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_FOREGROUND; 25 import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_FORMAT; 26 import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_NAME; 27 import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_PADDING; 28 import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_SHAPE; 29 import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_SOURCE_TYPE; 30 import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_TEXT; 31 import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_TRIM; 32 import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_TYPE; 33 import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_VALUE; 34 import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.CURRENT_FORMAT; 35 import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.TAG_DEPENDENCY; 36 import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.TAG_ICONS; 37 import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.TAG_PARAMETER; 38 import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.TAG_THUMB; 39 import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.TAG_FORMFACTOR; 40 import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.TAG_CATEGORY; 41 42 import com.android.annotations.NonNull; 43 import com.android.annotations.Nullable; 44 import com.android.assetstudiolib.GraphicGenerator; 45 import com.android.ide.eclipse.adt.AdtPlugin; 46 import com.android.ide.eclipse.adt.internal.assetstudio.AssetType; 47 import com.android.ide.eclipse.adt.internal.assetstudio.CreateAssetSetWizardState; 48 import com.android.ide.eclipse.adt.internal.assetstudio.CreateAssetSetWizardState.SourceType; 49 import com.android.ide.eclipse.adt.internal.editors.IconFactory; 50 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageUtils; 51 import com.android.utils.Pair; 52 import com.google.common.collect.Lists; 53 54 import org.eclipse.core.resources.IProject; 55 import org.eclipse.swt.graphics.Image; 56 import org.eclipse.swt.graphics.RGB; 57 import org.w3c.dom.Attr; 58 import org.w3c.dom.Document; 59 import org.w3c.dom.Element; 60 import org.w3c.dom.NamedNodeMap; 61 import org.w3c.dom.Node; 62 import org.w3c.dom.NodeList; 63 64 import java.util.ArrayList; 65 import java.util.Collections; 66 import java.util.HashMap; 67 import java.util.List; 68 import java.util.Locale; 69 import java.util.Map; 70 71 /** An ADT template along with metadata */ 72 class TemplateMetadata { 73 private final Document mDocument; 74 private final List<Parameter> mParameters; 75 private final Map<String, Parameter> mParameterMap; 76 private List<Pair<String, Integer>> mDependencies; 77 private Integer mMinApi; 78 private Integer mMinBuildApi; 79 private Integer mRevision; 80 private boolean mNoIcons; 81 private CreateAssetSetWizardState mIconState; 82 private String mFormFactor; 83 private String mCategory; 84 85 TemplateMetadata(@NonNull Document document) { 86 mDocument = document; 87 88 NodeList parameters = mDocument.getElementsByTagName(TAG_PARAMETER); 89 mParameters = new ArrayList<Parameter>(parameters.getLength()); 90 mParameterMap = new HashMap<String, Parameter>(parameters.getLength()); 91 for (int index = 0, max = parameters.getLength(); index < max; index++) { 92 Element element = (Element) parameters.item(index); 93 Parameter parameter = new Parameter(this, element); 94 mParameters.add(parameter); 95 if (parameter.id != null) { 96 mParameterMap.put(parameter.id, parameter); 97 } 98 } 99 } 100 101 boolean isSupported() { 102 String versionString = mDocument.getDocumentElement().getAttribute(ATTR_FORMAT); 103 if (versionString != null && !versionString.isEmpty()) { 104 try { 105 int version = Integer.parseInt(versionString); 106 return version <= CURRENT_FORMAT; 107 } catch (NumberFormatException nufe) { 108 return false; 109 } 110 } 111 112 // Older templates without version specified: supported 113 return true; 114 } 115 116 @Nullable 117 String getTitle() { 118 String name = mDocument.getDocumentElement().getAttribute(ATTR_NAME); 119 if (name != null && !name.isEmpty()) { 120 return name; 121 } 122 123 return null; 124 } 125 126 @Nullable 127 String getDescription() { 128 String description = mDocument.getDocumentElement().getAttribute(ATTR_DESCRIPTION); 129 if (description != null && !description.isEmpty()) { 130 return description; 131 } 132 133 return null; 134 } 135 136 int getMinSdk() { 137 if (mMinApi == null) { 138 mMinApi = 1; 139 String api = mDocument.getDocumentElement().getAttribute(ATTR_MIN_API); 140 if (api != null && !api.isEmpty()) { 141 try { 142 mMinApi = Integer.parseInt(api); 143 } catch (NumberFormatException nufe) { 144 // Templates aren't allowed to contain codenames, should always be an integer 145 AdtPlugin.log(nufe, null); 146 mMinApi = 1; 147 } 148 } 149 } 150 151 return mMinApi.intValue(); 152 } 153 154 int getMinBuildApi() { 155 if (mMinBuildApi == null) { 156 mMinBuildApi = 1; 157 String api = mDocument.getDocumentElement().getAttribute(ATTR_MIN_BUILD_API); 158 if (api != null && !api.isEmpty()) { 159 try { 160 mMinBuildApi = Integer.parseInt(api); 161 } catch (NumberFormatException nufe) { 162 // Templates aren't allowed to contain codenames, should always be an integer 163 AdtPlugin.log(nufe, null); 164 mMinBuildApi = 1; 165 } 166 } 167 } 168 169 return mMinBuildApi.intValue(); 170 } 171 172 public int getRevision() { 173 if (mRevision == null) { 174 mRevision = 1; 175 String revision = mDocument.getDocumentElement().getAttribute(ATTR_REVISION); 176 if (revision != null && !revision.isEmpty()) { 177 try { 178 mRevision = Integer.parseInt(revision); 179 } catch (NumberFormatException nufe) { 180 AdtPlugin.log(nufe, null); 181 mRevision = 1; 182 } 183 } 184 } 185 186 return mRevision.intValue(); 187 } 188 189 public String getFormFactor() { 190 if (mFormFactor == null) { 191 mFormFactor = "Mobile"; 192 193 NodeList formfactorDeclarations = mDocument.getElementsByTagName(TAG_FORMFACTOR); 194 if (formfactorDeclarations.getLength() > 0) { 195 Element element = (Element) formfactorDeclarations.item(0); 196 String formFactor = element.getAttribute(ATTR_VALUE); 197 if (formFactor != null && !formFactor.isEmpty()) { 198 mFormFactor = formFactor; 199 } 200 } 201 } 202 return mFormFactor; 203 } 204 205 public String getCategory() { 206 if (mCategory == null) { 207 mCategory = ""; 208 NodeList categories = mDocument.getElementsByTagName(TAG_CATEGORY); 209 if (categories.getLength() > 0) { 210 Element element = (Element) categories.item(0); 211 String category = element.getAttribute(ATTR_VALUE); 212 if (category != null && !category.isEmpty()) { 213 mCategory = category; 214 } 215 } 216 } 217 return mCategory; 218 } 219 220 /** 221 * Returns a suitable icon wizard state instance if this wizard requests 222 * icons to be created, and null otherwise 223 * 224 * @return icon wizard state or null 225 */ 226 @Nullable 227 public CreateAssetSetWizardState getIconState(IProject project) { 228 if (mIconState == null && !mNoIcons) { 229 NodeList icons = mDocument.getElementsByTagName(TAG_ICONS); 230 if (icons.getLength() < 1) { 231 mNoIcons = true; 232 return null; 233 } 234 Element icon = (Element) icons.item(0); 235 236 mIconState = new CreateAssetSetWizardState(); 237 mIconState.project = project; 238 239 String typeString = getAttributeOrNull(icon, ATTR_TYPE); 240 if (typeString != null) { 241 typeString = typeString.toUpperCase(Locale.US); 242 boolean found = false; 243 for (AssetType type : AssetType.values()) { 244 if (typeString.equals(type.name())) { 245 mIconState.type = type; 246 found = true; 247 break; 248 } 249 } 250 if (!found) { 251 AdtPlugin.log(null, "Unknown asset type %1$s", typeString); 252 } 253 } 254 255 mIconState.outputName = getAttributeOrNull(icon, ATTR_NAME); 256 if (mIconState.outputName != null) { 257 // Register parameter such that if it is referencing other values, it gets 258 // updated when other values are edited 259 Parameter outputParameter = new Parameter(this, 260 Parameter.Type.STRING, "_iconname", mIconState.outputName); //$NON-NLS-1$ 261 getParameters().add(outputParameter); 262 } 263 264 RGB background = getRgb(icon, ATTR_BACKGROUND); 265 if (background != null) { 266 mIconState.background = background; 267 } 268 RGB foreground = getRgb(icon, ATTR_FOREGROUND); 269 if (foreground != null) { 270 mIconState.foreground = foreground; 271 } 272 String shapeString = getAttributeOrNull(icon, ATTR_SHAPE); 273 if (shapeString != null) { 274 shapeString = shapeString.toUpperCase(Locale.US); 275 boolean found = false; 276 for (GraphicGenerator.Shape shape : GraphicGenerator.Shape.values()) { 277 if (shapeString.equals(shape.name())) { 278 mIconState.shape = shape; 279 found = true; 280 break; 281 } 282 } 283 if (!found) { 284 AdtPlugin.log(null, "Unknown shape %1$s", shapeString); 285 } 286 } 287 String trimString = getAttributeOrNull(icon, ATTR_TRIM); 288 if (trimString != null) { 289 mIconState.trim = Boolean.valueOf(trimString); 290 } 291 String paddingString = getAttributeOrNull(icon, ATTR_PADDING); 292 if (paddingString != null) { 293 mIconState.padding = Integer.parseInt(paddingString); 294 } 295 String sourceTypeString = getAttributeOrNull(icon, ATTR_SOURCE_TYPE); 296 if (sourceTypeString != null) { 297 sourceTypeString = sourceTypeString.toUpperCase(Locale.US); 298 boolean found = false; 299 for (SourceType type : SourceType.values()) { 300 if (sourceTypeString.equals(type.name())) { 301 mIconState.sourceType = type; 302 found = true; 303 break; 304 } 305 } 306 if (!found) { 307 AdtPlugin.log(null, "Unknown source type %1$s", sourceTypeString); 308 } 309 } 310 mIconState.clipartName = getAttributeOrNull(icon, ATTR_CLIPART_NAME); 311 312 String textString = getAttributeOrNull(icon, ATTR_TEXT); 313 if (textString != null) { 314 mIconState.text = textString; 315 } 316 } 317 318 return mIconState; 319 } 320 321 void updateIconName(List<Parameter> parameters, StringEvaluator evaluator) { 322 if (mIconState != null) { 323 NodeList icons = mDocument.getElementsByTagName(TAG_ICONS); 324 if (icons.getLength() < 1) { 325 return; 326 } 327 Element icon = (Element) icons.item(0); 328 String name = getAttributeOrNull(icon, ATTR_NAME); 329 if (name != null) { 330 mIconState.outputName = evaluator.evaluate(name, parameters); 331 } 332 } 333 } 334 335 private static RGB getRgb(@NonNull Element element, @NonNull String name) { 336 String colorString = getAttributeOrNull(element, name); 337 if (colorString != null) { 338 int rgb = ImageUtils.getColor(colorString.trim()); 339 return ImageUtils.intToRgb(rgb); 340 } 341 342 return null; 343 } 344 345 @Nullable 346 private static String getAttributeOrNull(@NonNull Element element, @NonNull String name) { 347 String value = element.getAttribute(name); 348 if (value != null && value.isEmpty()) { 349 return null; 350 } 351 return value; 352 } 353 354 @Nullable 355 String getThumbnailPath() { 356 // Apply selector logic. Pick the thumb first thumb that satisfies the largest number 357 // of conditions. 358 NodeList thumbs = mDocument.getElementsByTagName(TAG_THUMB); 359 if (thumbs.getLength() == 0) { 360 return null; 361 } 362 363 364 int bestMatchCount = 0; 365 Element bestMatch = null; 366 367 for (int i = 0, n = thumbs.getLength(); i < n; i++) { 368 Element thumb = (Element) thumbs.item(i); 369 370 NamedNodeMap attributes = thumb.getAttributes(); 371 if (bestMatch == null && attributes.getLength() == 0) { 372 bestMatch = thumb; 373 } else if (attributes.getLength() <= bestMatchCount) { 374 // Already have a match with this number of attributes, no point checking 375 continue; 376 } else { 377 boolean match = true; 378 for (int j = 0, max = attributes.getLength(); j < max; j++) { 379 Attr attribute = (Attr) attributes.item(j); 380 Parameter parameter = mParameterMap.get(attribute.getName()); 381 if (parameter == null) { 382 AdtPlugin.log(null, "Unexpected parameter in template thumbnail: %1$s", 383 attribute.getName()); 384 continue; 385 } 386 String thumbNailValue = attribute.getValue(); 387 String editedValue = parameter.value != null ? parameter.value.toString() : ""; 388 if (!thumbNailValue.equals(editedValue)) { 389 match = false; 390 break; 391 } 392 } 393 if (match) { 394 bestMatch = thumb; 395 bestMatchCount = attributes.getLength(); 396 } 397 } 398 } 399 400 if (bestMatch != null) { 401 NodeList children = bestMatch.getChildNodes(); 402 for (int i = 0, n = children.getLength(); i < n; i++) { 403 Node child = children.item(i); 404 if (child.getNodeType() == Node.TEXT_NODE) { 405 return child.getNodeValue().trim(); 406 } 407 } 408 } 409 410 return null; 411 } 412 413 /** 414 * Returns the dependencies (as a list of pairs of names and revisions) 415 * required by this template 416 */ 417 List<Pair<String, Integer>> getDependencies() { 418 if (mDependencies == null) { 419 NodeList elements = mDocument.getElementsByTagName(TAG_DEPENDENCY); 420 if (elements.getLength() == 0) { 421 return Collections.emptyList(); 422 } 423 424 List<Pair<String, Integer>> dependencies = Lists.newArrayList(); 425 for (int i = 0, n = elements.getLength(); i < n; i++) { 426 Element element = (Element) elements.item(i); 427 String name = element.getAttribute(ATTR_NAME); 428 int revision = -1; 429 String revisionString = element.getAttribute(ATTR_REVISION); 430 if (!revisionString.isEmpty()) { 431 revision = Integer.parseInt(revisionString); 432 } 433 dependencies.add(Pair.of(name, revision)); 434 } 435 mDependencies = dependencies; 436 } 437 438 return mDependencies; 439 } 440 441 /** Returns the list of available parameters */ 442 @NonNull 443 List<Parameter> getParameters() { 444 return mParameters; 445 } 446 447 /** 448 * Returns the parameter of the given id, or null if not found 449 * 450 * @param id the id of the target parameter 451 * @return the corresponding parameter, or null if not found 452 */ 453 @Nullable 454 public Parameter getParameter(@NonNull String id) { 455 for (Parameter parameter : mParameters) { 456 if (id.equals(parameter.id)) { 457 return parameter; 458 } 459 } 460 461 return null; 462 } 463 464 /** Returns a default icon for templates */ 465 static Image getDefaultTemplateIcon() { 466 return IconFactory.getInstance().getIcon("default_template"); //$NON-NLS-1$ 467 } 468 } 469