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