1 /* 2 * Copyright (C) 2010 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 17 package com.android.ide.eclipse.adt.internal.resources; 18 19 import static com.android.SdkConstants.DOT_XML; 20 21 import com.android.annotations.NonNull; 22 import com.android.annotations.Nullable; 23 import com.android.ide.common.resources.ResourceItem; 24 import com.android.ide.eclipse.adt.AdtPlugin; 25 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageUtils; 26 import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources; 27 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; 28 import com.android.resources.ResourceFolderType; 29 import com.android.resources.ResourceType; 30 31 import org.eclipse.core.resources.IProject; 32 import org.eclipse.core.runtime.IStatus; 33 import org.eclipse.jdt.core.JavaConventions; 34 import org.eclipse.jface.dialogs.IInputValidator; 35 36 import java.util.Collection; 37 import java.util.HashSet; 38 import java.util.Set; 39 40 /** 41 * Validator which ensures that new Android resource names are valid. 42 */ 43 public class ResourceNameValidator implements IInputValidator { 44 /** Set of existing names to check for conflicts with */ 45 private Set<String> mExisting; 46 47 /** If true, the validated name must be unique */ 48 private boolean mUnique = true; 49 50 /** If true, the validated name must exist */ 51 private boolean mExist; 52 53 /** 54 * True if the resource name being considered is a "file" based resource (where the 55 * resource name is the actual file name, rather than just a value attribute inside an 56 * XML file name of arbitrary name 57 */ 58 private boolean mIsFileType; 59 60 /** 61 * True if the resource type can point to image resources 62 */ 63 private boolean mIsImageType; 64 65 /** If true, allow .xml as a name suffix */ 66 private boolean mAllowXmlExtension; 67 68 private ResourceNameValidator(boolean allowXmlExtension, Set<String> existing, 69 boolean isFileType, boolean isImageType) { 70 mAllowXmlExtension = allowXmlExtension; 71 mExisting = existing; 72 mIsFileType = isFileType; 73 mIsImageType = isImageType; 74 } 75 76 /** 77 * Makes the resource name validator require that names are unique. 78 * 79 * @return this, for construction chaining 80 */ 81 public ResourceNameValidator unique() { 82 mUnique = true; 83 mExist = false; 84 85 return this; 86 } 87 88 /** 89 * Makes the resource name validator require that names already exist 90 * 91 * @return this, for construction chaining 92 */ 93 public ResourceNameValidator exist() { 94 mExist = true; 95 mUnique = false; 96 97 return this; 98 } 99 100 @Override 101 public String isValid(String newText) { 102 // IValidator has the same interface as SWT's IInputValidator 103 try { 104 if (newText == null || newText.trim().length() == 0) { 105 return "Enter a new name"; 106 } 107 108 if (mAllowXmlExtension && newText.endsWith(DOT_XML)) { 109 newText = newText.substring(0, newText.length() - DOT_XML.length()); 110 } 111 112 if (mAllowXmlExtension && mIsImageType 113 && ImageUtils.hasImageExtension(newText)) { 114 newText = newText.substring(0, newText.lastIndexOf('.')); 115 } 116 117 if (!mIsFileType) { 118 newText = newText.replace('.', '_'); 119 } 120 121 if (newText.indexOf('.') != -1 && !newText.endsWith(DOT_XML)) { 122 if (mIsImageType) { 123 return "The filename must end with .xml or .png"; 124 } else { 125 return "The filename must end with .xml"; 126 } 127 } 128 129 // Resource names must be valid Java identifiers, since they will 130 // be represented as Java identifiers in the R file: 131 if (!Character.isJavaIdentifierStart(newText.charAt(0))) { 132 return "The resource name must begin with a character"; 133 } 134 for (int i = 1, n = newText.length(); i < n; i++) { 135 char c = newText.charAt(i); 136 if (!Character.isJavaIdentifierPart(c)) { 137 return String.format("'%1$c' is not a valid resource name character", c); 138 } 139 } 140 141 if (mIsFileType) { 142 char first = newText.charAt(0); 143 if (!(first >= 'a' && first <= 'z')) { 144 return String.format( 145 "File-based resource names must start with a lowercase letter."); 146 } 147 148 // AAPT only allows lowercase+digits+_: 149 // "%s: Invalid file name: must contain only [a-z0-9_.]"," 150 for (int i = 0, n = newText.length(); i < n; i++) { 151 char c = newText.charAt(i); 152 if (!((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_')) { 153 return String.format( 154 "File-based resource names must contain only lowercase a-z, 0-9, or _."); 155 } 156 } 157 } 158 159 String level = "1.5"; //$NON-NLS-1$ 160 IStatus validIdentifier = JavaConventions.validateIdentifier(newText, level, level); 161 if (!validIdentifier.isOK()) { 162 return String.format("%1$s is not a valid name (reserved Java keyword)", newText); 163 } 164 165 166 if (mExisting != null && (mUnique || mExist)) { 167 boolean exists = mExisting.contains(newText); 168 if (mUnique && exists) { 169 return String.format("%1$s already exists", newText); 170 } else if (mExist && !exists) { 171 return String.format("%1$s does not exist", newText); 172 } 173 } 174 175 return null; 176 } catch (Exception e) { 177 AdtPlugin.log(e, "Validation failed: %s", e.toString()); 178 return ""; //$NON-NLS-1$ 179 } 180 } 181 182 /** 183 * Creates a new {@link ResourceNameValidator} 184 * 185 * @param allowXmlExtension if true, allow .xml to be entered as a suffix for the 186 * resource name 187 * @param type the resource type of the resource name being validated 188 * @return a new {@link ResourceNameValidator} 189 */ 190 public static ResourceNameValidator create(boolean allowXmlExtension, 191 ResourceFolderType type) { 192 boolean isFileType = type != ResourceFolderType.VALUES; 193 return new ResourceNameValidator(allowXmlExtension, null, isFileType, 194 type == ResourceFolderType.DRAWABLE); 195 } 196 197 /** 198 * Creates a new {@link ResourceNameValidator} 199 * 200 * @param allowXmlExtension if true, allow .xml to be entered as a suffix for the 201 * resource name 202 * @param existing An optional set of names that already exist (and therefore will not 203 * be considered valid if entered as the new name) 204 * @param type the resource type of the resource name being validated 205 * @return a new {@link ResourceNameValidator} 206 */ 207 public static ResourceNameValidator create(boolean allowXmlExtension, Set<String> existing, 208 ResourceType type) { 209 boolean isFileType = ResourceHelper.isFileBasedResourceType(type); 210 return new ResourceNameValidator(allowXmlExtension, existing, isFileType, 211 type == ResourceType.DRAWABLE).unique(); 212 } 213 214 /** 215 * Creates a new {@link ResourceNameValidator}. By default, the name will need to be 216 * unique in the project. 217 * 218 * @param allowXmlExtension if true, allow .xml to be entered as a suffix for the 219 * resource name 220 * @param project the project to validate new resource names for 221 * @param type the resource type of the resource name being validated 222 * @return a new {@link ResourceNameValidator} 223 */ 224 public static ResourceNameValidator create(boolean allowXmlExtension, 225 @Nullable IProject project, 226 @NonNull ResourceType type) { 227 Set<String> existing = null; 228 if (project != null) { 229 existing = new HashSet<String>(); 230 ResourceManager manager = ResourceManager.getInstance(); 231 ProjectResources projectResources = manager.getProjectResources(project); 232 Collection<ResourceItem> items = projectResources.getResourceItemsOfType(type); 233 for (ResourceItem item : items) { 234 existing.add(item.getName()); 235 } 236 } 237 238 boolean isFileType = ResourceHelper.isFileBasedResourceType(type); 239 return new ResourceNameValidator(allowXmlExtension, existing, isFileType, 240 type == ResourceType.DRAWABLE); 241 } 242 } 243