• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_PACKAGE_NAME;
19 import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_CONSTRAINTS;
20 import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_DEFAULT;
21 import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_HELP;
22 import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_ID;
23 import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_NAME;
24 import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_SUGGEST;
25 
26 import com.android.annotations.NonNull;
27 import com.android.annotations.Nullable;
28 import com.android.ide.eclipse.adt.AdtPlugin;
29 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
30 import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
31 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
32 import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator;
33 import com.android.ide.eclipse.adt.internal.wizards.newproject.ApplicationInfoPage;
34 import com.android.resources.ResourceFolderType;
35 import com.android.resources.ResourceType;
36 import com.google.common.base.Splitter;
37 
38 import org.eclipse.core.resources.IProject;
39 import org.eclipse.core.runtime.CoreException;
40 import org.eclipse.core.runtime.IStatus;
41 import org.eclipse.jdt.core.IJavaProject;
42 import org.eclipse.jdt.core.IType;
43 import org.eclipse.jface.dialogs.IInputValidator;
44 import org.eclipse.jface.fieldassist.ControlDecoration;
45 import org.eclipse.swt.widgets.Control;
46 import org.w3c.dom.Element;
47 
48 import java.util.Collections;
49 import java.util.EnumSet;
50 import java.util.List;
51 import java.util.Locale;
52 
53 /**
54  * A template parameter editable and edited by the user.
55  * <p>
56  * Note that this class encapsulates not just the metadata provided by the
57  * template, but the actual editing operation of that template in the wizard: it
58  * also captures current values, a reference to the editing widget (such that
59  * related widgets can be updated when one value depends on another etc)
60  */
61 class Parameter {
62     enum Type {
63         STRING,
64         BOOLEAN,
65         ENUM,
66         SEPARATOR;
67         // TODO: Numbers?
68 
get(String name)69         public static Type get(String name) {
70             try {
71                 return Type.valueOf(name.toUpperCase(Locale.US));
72             } catch (IllegalArgumentException e) {
73                 AdtPlugin.printErrorToConsole("Unexpected template type '" + name + "'");
74                 AdtPlugin.printErrorToConsole("Expected one of :");
75                 for (Type s : Type.values()) {
76                     AdtPlugin.printErrorToConsole("  " + s.name().toLowerCase(Locale.US));
77                 }
78             }
79 
80             return STRING;
81         }
82     }
83 
84     /**
85      * Constraints that can be applied to a parameter which helps the UI add a
86      * validator etc for user input. These are typically combined into a set
87      * of constraints via an EnumSet.
88      */
89     enum Constraint {
90         /**
91          * This value must be unique. This constraint usually only makes sense
92          * when other constraints are specified, such as {@link #LAYOUT}, which
93          * means that the parameter should designate a name that does not
94          * represent an existing layout resource name
95          */
96         UNIQUE,
97 
98         /**
99          * This value must already exist. This constraint usually only makes sense
100          * when other constraints are specified, such as {@link #LAYOUT}, which
101          * means that the parameter should designate a name that already exists as
102          * a resource name.
103          */
104         EXISTS,
105 
106         /** The associated value must not be empty */
107         NONEMPTY,
108 
109         /** The associated value is allowed to be empty */
110         EMPTY,
111 
112         /** The associated value should represent a fully qualified activity class name */
113         ACTIVITY,
114 
115         /** The associated value should represent an API level */
116         APILEVEL,
117 
118         /** The associated value should represent a valid class name */
119         CLASS,
120 
121         /** The associated value should represent a valid package name */
122         PACKAGE,
123 
124         /** The associated value should represent a valid layout resource name */
125         LAYOUT,
126 
127         /** The associated value should represent a valid drawable resource name */
128         DRAWABLE,
129 
130         /** The associated value should represent a valid id resource name */
131         ID,
132 
133         /** The associated value should represent a valid string resource name */
134         STRING;
135 
get(String name)136         public static Constraint get(String name) {
137             try {
138                 return Constraint.valueOf(name.toUpperCase(Locale.US));
139             } catch (IllegalArgumentException e) {
140                 AdtPlugin.printErrorToConsole("Unexpected template constraint '" + name + "'");
141                 if (name.indexOf(',') != -1) {
142                     AdtPlugin.printErrorToConsole("Use | to separate constraints");
143                 } else {
144                     AdtPlugin.printErrorToConsole("Expected one of :");
145                     for (Constraint s : Constraint.values()) {
146                         AdtPlugin.printErrorToConsole("  " + s.name().toLowerCase(Locale.US));
147                     }
148                 }
149             }
150 
151             return NONEMPTY;
152         }
153     }
154 
155     /** The template defining the parameter */
156     public final TemplateMetadata template;
157 
158     /** The type of parameter */
159     @NonNull
160     public final Type type;
161 
162     /** The unique id of the parameter (not displayed to the user) */
163     @Nullable
164     public final String id;
165 
166     /** The display name for this parameter */
167     @Nullable
168     public final String name;
169 
170     /**
171      * The initial value for this parameter (see also {@link #suggest} for more
172      * dynamic defaults
173      */
174     @Nullable
175     public final String initial;
176 
177     /**
178      * A template expression using other template parameters for producing a
179      * default value based on other edited parameters, if possible.
180      */
181     @Nullable
182     public final String suggest;
183 
184     /** Help for the parameter, if any */
185     @Nullable
186     public final String help;
187 
188     /** The currently edited value */
189     @Nullable
190     public Object value;
191 
192     /** The control showing this value */
193     @Nullable
194     public Control control;
195 
196     /** The decoration associated with the control */
197     @Nullable
198     public ControlDecoration decoration;
199 
200     /** Whether the parameter has been edited */
201     public boolean edited;
202 
203     /** The element defining this parameter */
204     @NonNull
205     public final Element element;
206 
207     /** The constraints applicable for this parameter */
208     @NonNull
209     public final EnumSet<Constraint> constraints;
210 
211     /** The validator, if any, for this field */
212     private IInputValidator mValidator;
213 
214     /** True if this field has no validator */
215     private boolean mNoValidator;
216 
217     /** Project associated with this validator */
218     private IProject mValidatorProject;
219 
Parameter(@onNull TemplateMetadata template, @NonNull Element parameter)220     Parameter(@NonNull TemplateMetadata template, @NonNull Element parameter) {
221         this.template = template;
222         element = parameter;
223 
224         String typeName = parameter.getAttribute(TemplateHandler.ATTR_TYPE);
225         assert typeName != null && !typeName.isEmpty() : TemplateHandler.ATTR_TYPE;
226         type = Type.get(typeName);
227 
228         id = parameter.getAttribute(ATTR_ID);
229         initial = parameter.getAttribute(ATTR_DEFAULT);
230         suggest = parameter.getAttribute(ATTR_SUGGEST);
231         name = parameter.getAttribute(ATTR_NAME);
232         help = parameter.getAttribute(ATTR_HELP);
233         String constraintString = parameter.getAttribute(ATTR_CONSTRAINTS);
234         if (constraintString != null && !constraintString.isEmpty()) {
235             EnumSet<Constraint> constraintSet = null;
236             for (String s : Splitter.on('|').omitEmptyStrings().split(constraintString)) {
237                 Constraint constraint = Constraint.get(s);
238                 if (constraintSet == null) {
239                     constraintSet = EnumSet.of(constraint);
240                 } else {
241                     constraintSet = EnumSet.copyOf(constraintSet);
242                     constraintSet.add(constraint);
243                 }
244             }
245             constraints = constraintSet;
246         } else {
247             constraints = EnumSet.noneOf(Constraint.class);
248         }
249 
250         if (initial != null && !initial.isEmpty() && type == Type.BOOLEAN) {
251             value = Boolean.valueOf(initial);
252         } else {
253             value = initial;
254         }
255     }
256 
Parameter( @onNull TemplateMetadata template, @NonNull Type type, @NonNull String id, @NonNull String initialValue)257     Parameter(
258             @NonNull TemplateMetadata template,
259             @NonNull Type type,
260             @NonNull String id,
261             @NonNull String initialValue) {
262         this.template = template;
263         this.type = type;
264         this.id = id;
265         this.value = initialValue;
266         element = null;
267         initial = null;
268         suggest = null;
269         name = id;
270         help = null;
271         constraints = EnumSet.noneOf(Constraint.class);
272     }
273 
getOptions()274     List<Element> getOptions() {
275         if (element != null) {
276             return DomUtilities.getChildren(element);
277         } else {
278             return Collections.emptyList();
279         }
280     }
281 
282     @Nullable
getValidator(@ullable final IProject project)283     public IInputValidator getValidator(@Nullable final IProject project) {
284         if (mNoValidator) {
285             return null;
286         }
287 
288         if (project != mValidatorProject) {
289             // Force update of validators if the project changes, since the validators
290             // are often tied to project metadata (for example, the resource name validators
291             // which look for name conflicts)
292             mValidator = null;
293             mValidatorProject = project;
294         }
295 
296         if (mValidator == null) {
297             if (constraints.contains(Constraint.LAYOUT)) {
298                 if (project != null && constraints.contains(Constraint.UNIQUE)) {
299                     mValidator = ResourceNameValidator.create(false, project, ResourceType.LAYOUT);
300                 } else {
301                     mValidator = ResourceNameValidator.create(false, ResourceFolderType.LAYOUT);
302                 }
303                 return mValidator;
304             } else if (constraints.contains(Constraint.STRING)) {
305                 if (project != null && constraints.contains(Constraint.UNIQUE)) {
306                     mValidator = ResourceNameValidator.create(false, project, ResourceType.STRING);
307                 } else {
308                     mValidator = ResourceNameValidator.create(false, ResourceFolderType.VALUES);
309                 }
310                 return mValidator;
311             } else if (constraints.contains(Constraint.ID)) {
312                 if (project != null && constraints.contains(Constraint.UNIQUE)) {
313                     mValidator = ResourceNameValidator.create(false, project, ResourceType.ID);
314                 } else {
315                     mValidator = ResourceNameValidator.create(false, ResourceFolderType.VALUES);
316                 }
317                 return mValidator;
318             } else if (constraints.contains(Constraint.DRAWABLE)) {
319                 if (project != null && constraints.contains(Constraint.UNIQUE)) {
320                     mValidator = ResourceNameValidator.create(false, project,
321                             ResourceType.DRAWABLE);
322                 } else {
323                     mValidator = ResourceNameValidator.create(false, ResourceFolderType.DRAWABLE);
324                 }
325                 return mValidator;
326             } else if (constraints.contains(Constraint.PACKAGE)
327                     || constraints.contains(Constraint.CLASS)
328                     || constraints.contains(Constraint.ACTIVITY)) {
329                 mValidator = new IInputValidator() {
330                     @Override
331                     public String isValid(String newText) {
332                         newText = newText.trim();
333                         if (newText.isEmpty()) {
334                             if (constraints.contains(Constraint.EMPTY)) {
335                                 return null;
336                             } else if (constraints.contains(Constraint.NONEMPTY)) {
337                                 return String.format("Enter a value for %1$s", name);
338                             } else {
339                                 // Compatibility mode: older templates might not specify;
340                                 // in that case, accept empty
341                                 if (!"activityClass".equals(id)) { //$NON-NLS-1$
342                                     return null;
343                                 }
344                             }
345                         }
346                         IStatus status;
347                         if (constraints.contains(Constraint.ACTIVITY)) {
348                             status = ApplicationInfoPage.validateActivity(newText);
349                         } else if (constraints.contains(Constraint.PACKAGE)) {
350                             status = ApplicationInfoPage.validatePackage(newText);
351                         } else {
352                             assert constraints.contains(Constraint.CLASS);
353                             status = ApplicationInfoPage.validateClass(newText);
354                         }
355                         if (status != null && !status.isOK()) {
356                             return status.getMessage();
357                         }
358 
359                         // Uniqueness
360                         if (project != null && constraints.contains(Constraint.UNIQUE)) {
361                             try {
362                                 // Determine the package.
363                                 // If there is a package info
364 
365                                 IJavaProject p = BaseProjectHelper.getJavaProject(project);
366                                 if (p != null) {
367                                     String fqcn = newText;
368                                     if (fqcn.indexOf('.') == -1) {
369                                         String pkg = null;
370                                         Parameter parameter = template.getParameter(
371                                                 ATTR_PACKAGE_NAME);
372                                         if (parameter != null && parameter.value != null) {
373                                             pkg = parameter.value.toString();
374                                         } else {
375                                             pkg = ManifestInfo.get(project).getPackage();
376                                         }
377                                         fqcn = pkg.isEmpty() ? newText : pkg + '.' + newText;
378                                     }
379 
380                                     IType t = p.findType(fqcn);
381                                     if (t != null && t.exists()) {
382                                         return String.format("%1$s already exists", newText);
383                                     }
384                                 }
385                             } catch (CoreException e) {
386                                 AdtPlugin.log(e, null);
387                             }
388                         }
389 
390                         return null;
391                     }
392                 };
393                 return mValidator;
394             } else if (constraints.contains(Constraint.NONEMPTY)) {
395                 mValidator = new IInputValidator() {
396                     @Override
397                     public String isValid(String newText) {
398                         if (newText.trim().isEmpty()) {
399                             return String.format("Enter a value for %1$s", name);
400                         }
401 
402                         return null;
403                     }
404                 };
405                 return mValidator;
406             }
407 
408             // TODO: Handle EXISTS, APILEVEL (which is currently handled manually in the
409             // new project wizard, and never actually input by the user in a templated
410             // wizard)
411 
412             mNoValidator = true;
413         }
414 
415         return mValidator;
416     }
417 }