/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Eclipse Public License, Version 1.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.eclipse.org/org/documents/epl-v10.php * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.ide.eclipse.adt.internal.wizards.templates; import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_CONSTRAINTS; import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_DEFAULT; import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_HELP; import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_ID; import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_NAME; import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_SUGGEST; import com.android.annotations.NonNull; import com.android.annotations.Nullable; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities; import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator; import com.android.ide.eclipse.adt.internal.wizards.newproject.ApplicationInfoPage; import com.android.resources.ResourceFolderType; import com.android.resources.ResourceType; import com.google.common.base.Splitter; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.IStatus; import org.eclipse.jface.dialogs.IInputValidator; import org.eclipse.jface.fieldassist.ControlDecoration; import org.eclipse.swt.widgets.Control; import org.w3c.dom.Element; import java.util.Collections; import java.util.EnumSet; import java.util.List; import java.util.Locale; /** * A template parameter editable and edited by the user. * <p> * Note that this class encapsulates not just the metadata provided by the * template, but the actual editing operation of that template in the wizard: it * also captures current values, a reference to the editing widget (such that * related widgets can be updated when one value depends on another etc) */ class Parameter { enum Type { STRING, BOOLEAN, ENUM, SEPARATOR; // TODO: Numbers? public static Type get(String name) { try { return Type.valueOf(name.toUpperCase(Locale.US)); } catch (IllegalArgumentException e) { AdtPlugin.printErrorToConsole("Unexpected template type '" + name + "'"); AdtPlugin.printErrorToConsole("Expected one of :"); for (Type s : Type.values()) { AdtPlugin.printErrorToConsole(" " + s.name().toLowerCase(Locale.US)); } } return STRING; } } /** * Constraints that can be applied to a parameter which helps the UI add a * validator etc for user input. These are typically combined into a set * of constraints via an EnumSet. */ enum Constraint { /** * This value must be unique. This constraint usually only makes sense * when other constraints are specified, such as {@link #LAYOUT}, which * means that the parameter should designate a name that does not * represent an existing layout resource name */ UNIQUE, /** * This value must already exist. This constraint usually only makes sense * when other constraints are specified, such as {@link #LAYOUT}, which * means that the parameter should designate a name that already exists as * a resource name. */ EXISTS, /** The associated value must not be empty */ NONEMPTY, /** The associated value is allowed to be empty */ EMPTY, /** The associated value should represent a fully qualified activity class name */ ACTIVITY, /** The associated value should represent an API level */ APILEVEL, /** The associated value should represent a valid class name */ CLASS, /** The associated value should represent a valid package name */ PACKAGE, /** The associated value should represent a valid layout resource name */ LAYOUT, /** The associated value should represent a valid drawable resource name */ DRAWABLE, /** The associated value should represent a valid id resource name */ ID, /** The associated value should represent a valid string resource name */ STRING; public static Constraint get(String name) { try { return Constraint.valueOf(name.toUpperCase(Locale.US)); } catch (IllegalArgumentException e) { AdtPlugin.printErrorToConsole("Unexpected template constraint '" + name + "'"); if (name.indexOf(',') != -1) { AdtPlugin.printErrorToConsole("Use | to separate constraints"); } else { AdtPlugin.printErrorToConsole("Expected one of :"); for (Constraint s : Constraint.values()) { AdtPlugin.printErrorToConsole(" " + s.name().toLowerCase(Locale.US)); } } } return NONEMPTY; } } /** The type of parameter */ @NonNull public final Type type; /** The unique id of the parameter (not displayed to the user) */ @Nullable public final String id; /** The display name for this parameter */ @Nullable public final String name; /** * The initial value for this parameter (see also {@link #suggest} for more * dynamic defaults */ @Nullable public final String initial; /** * A template expression using other template parameters for producing a * default value based on other edited parameters, if possible. */ @Nullable public final String suggest; /** Help for the parameter, if any */ @Nullable public final String help; /** The currently edited value */ @Nullable public Object value; /** The control showing this value */ @Nullable public Control control; /** The decoration associated with the control */ @Nullable public ControlDecoration decoration; /** Whether the parameter has been edited */ public boolean edited; /** The element defining this parameter */ @NonNull public final Element element; /** The constraints applicable for this parameter */ @NonNull public final EnumSet<Constraint> constraints; /** The validator, if any, for this field */ private IInputValidator mValidator; /** True if this field has no validator */ private boolean mNoValidator; /** Project associated with this validator */ private IProject mValidatorProject; Parameter(@NonNull Element parameter) { element = parameter; String typeName = parameter.getAttribute(TemplateHandler.ATTR_TYPE); assert typeName != null && !typeName.isEmpty() : TemplateHandler.ATTR_TYPE; type = Type.get(typeName); id = parameter.getAttribute(ATTR_ID); initial = parameter.getAttribute(ATTR_DEFAULT); suggest = parameter.getAttribute(ATTR_SUGGEST); name = parameter.getAttribute(ATTR_NAME); help = parameter.getAttribute(ATTR_HELP); String constraintString = parameter.getAttribute(ATTR_CONSTRAINTS); if (constraintString != null && !constraintString.isEmpty()) { EnumSet<Constraint> constraintSet = null; for (String s : Splitter.on('|').omitEmptyStrings().split(constraintString)) { Constraint constraint = Constraint.get(s); if (constraintSet == null) { constraintSet = EnumSet.of(constraint); } else { constraintSet = EnumSet.copyOf(constraintSet); constraintSet.add(constraint); } } constraints = constraintSet; } else { constraints = EnumSet.noneOf(Constraint.class); } if (initial != null && !initial.isEmpty() && type == Type.BOOLEAN) { value = Boolean.valueOf(initial); } else { value = initial; } } Parameter(@NonNull Type type, @NonNull String id, @NonNull String initialValue) { this.type = type; this.id = id; this.value = initialValue; element = null; initial = null; suggest = null; name = id; help = null; constraints = EnumSet.noneOf(Constraint.class); } List<Element> getOptions() { if (element != null) { return DomUtilities.getChildren(element); } else { return Collections.emptyList(); } } @Nullable public IInputValidator getValidator(@Nullable IProject project) { if (mNoValidator) { return null; } if (project != mValidatorProject) { // Force update of validators if the project changes, since the validators // are often tied to project metadata (for example, the resource name validators // which look for name conflicts) mValidator = null; mValidatorProject = project; } if (mValidator == null) { if (constraints.contains(Constraint.LAYOUT)) { if (project != null && constraints.contains(Constraint.UNIQUE)) { mValidator = ResourceNameValidator.create(false, project, ResourceType.LAYOUT); } else { mValidator = ResourceNameValidator.create(false, ResourceFolderType.LAYOUT); } return mValidator; } else if (constraints.contains(Constraint.STRING)) { if (project != null && constraints.contains(Constraint.UNIQUE)) { mValidator = ResourceNameValidator.create(false, project, ResourceType.STRING); } else { mValidator = ResourceNameValidator.create(false, ResourceFolderType.VALUES); } return mValidator; } else if (constraints.contains(Constraint.ID)) { if (project != null && constraints.contains(Constraint.UNIQUE)) { mValidator = ResourceNameValidator.create(false, project, ResourceType.ID); } else { mValidator = ResourceNameValidator.create(false, ResourceFolderType.VALUES); } return mValidator; } else if (constraints.contains(Constraint.DRAWABLE)) { if (project != null && constraints.contains(Constraint.UNIQUE)) { mValidator = ResourceNameValidator.create(false, project, ResourceType.DRAWABLE); } else { mValidator = ResourceNameValidator.create(false, ResourceFolderType.DRAWABLE); } return mValidator; } else if (constraints.contains(Constraint.PACKAGE) || constraints.contains(Constraint.CLASS) || constraints.contains(Constraint.ACTIVITY)) { mValidator = new IInputValidator() { @Override public String isValid(String newText) { newText = newText.trim(); if (newText.isEmpty()) { if (constraints.contains(Constraint.EMPTY)) { return null; } else if (constraints.contains(Constraint.NONEMPTY)) { return String.format("Enter a value for %1$s", name); } else { // Compatibility mode: older templates might not specify; // in that case, accept empty if (!"activityClass".equals(id)) { //$NON-NLS-1$ return null; } } } IStatus status; if (constraints.contains(Constraint.ACTIVITY)) { status = ApplicationInfoPage.validateActivity(newText); } else if (constraints.contains(Constraint.PACKAGE)) { status = ApplicationInfoPage.validatePackage(newText); } else { assert constraints.contains(Constraint.CLASS); status = ApplicationInfoPage.validateClass(newText); } if (status != null && !status.isOK()) { return status.getMessage(); } return null; } }; return mValidator; } else if (constraints.contains(Constraint.NONEMPTY)) { mValidator = new IInputValidator() { @Override public String isValid(String newText) { if (newText.trim().isEmpty()) { return String.format("Enter a value for %1$s", name); } return null; } }; return mValidator; } // TODO: Handle EXISTS, APILEVEL (which is currently handled manually in the // new project wizard, and never actually input by the user in a templated // wizard) mNoValidator = true; } return mValidator; } }