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 
TemplateMetadata(@onNull Document document)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 
isSupported()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
getTitle()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
getDescription()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 
getMinSdk()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 
getMinBuildApi()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 
getRevision()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 
getFormFactor()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 
getCategory()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
getIconState(IProject project)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 
updateIconName(List<Parameter> parameters, StringEvaluator evaluator)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 
getRgb(@onNull Element element, @NonNull String name)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
getAttributeOrNull(@onNull Element element, @NonNull String name)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
getThumbnailPath()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      */
getDependencies()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
getParameters()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
getParameter(@onNull String id)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 */
getDefaultTemplateIcon()465     static Image getDefaultTemplateIcon() {
466         return IconFactory.getInstance().getIcon("default_template"); //$NON-NLS-1$
467     }
468 }
469