blob: 3139451c7513802aeab2cf868d6d6fd63f5edc1a [file] [log] [blame]
/*
* 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.NewProjectWizard.ATTR_PACKAGE_NAME;
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.editors.manifest.ManifestInfo;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
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.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
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 template defining the parameter */
public final TemplateMetadata template;
/** 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 TemplateMetadata template, @NonNull Element parameter) {
this.template = template;
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 TemplateMetadata template,
@NonNull Type type,
@NonNull String id,
@NonNull String initialValue) {
this.template = template;
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 final 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();
}
// Uniqueness
if (project != null && constraints.contains(Constraint.UNIQUE)) {
try {
// Determine the package.
// If there is a package info
IJavaProject p = BaseProjectHelper.getJavaProject(project);
if (p != null) {
String fqcn = newText;
if (fqcn.indexOf('.') == -1) {
String pkg = null;
Parameter parameter = template.getParameter(
ATTR_PACKAGE_NAME);
if (parameter != null && parameter.value != null) {
pkg = parameter.value.toString();
} else {
pkg = ManifestInfo.get(project).getPackage();
}
fqcn = pkg.isEmpty() ? newText : pkg + '.' + newText;
}
IType t = p.findType(fqcn);
if (t != null && t.exists()) {
return String.format("%1$s already exists", newText);
}
}
} catch (CoreException e) {
AdtPlugin.log(e, null);
}
}
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;
}
}