Home | History | Annotate | Download | only in templates
      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 
     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 
    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 
    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 
    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 
    274     List<Element> getOptions() {
    275         if (element != null) {
    276             return DomUtilities.getChildren(element);
    277         } else {
    278             return Collections.emptyList();
    279         }
    280     }
    281 
    282     @Nullable
    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 }