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 }