1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 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.sdklib.internal.project; 18 19 import com.android.io.FolderWrapper; 20 import com.android.io.IAbstractFile; 21 import com.android.io.IAbstractFolder; 22 import com.android.io.StreamException; 23 import com.android.sdklib.ISdkLog; 24 import com.android.sdklib.SdkConstants; 25 26 import java.io.BufferedReader; 27 import java.io.FileNotFoundException; 28 import java.io.IOException; 29 import java.io.InputStreamReader; 30 import java.util.Arrays; 31 import java.util.Collections; 32 import java.util.HashMap; 33 import java.util.HashSet; 34 import java.util.Map; 35 import java.util.Set; 36 import java.util.regex.Matcher; 37 import java.util.regex.Pattern; 38 39 /** 40 * Class representing project properties for both ADT and Ant-based build. 41 * <p/>The class is associated to a {@link PropertyType} that indicate which of the project 42 * property file is represented. 43 * <p/>To load an existing file, use {@link #load(IAbstractFolder, PropertyType)}. 44 * <p/>The class is meant to be always in sync (or at least not newer) than the file it represents. 45 * Once created, it can only be updated through {@link #reload()} 46 * 47 * <p/>The make modification or make new file, use a {@link ProjectPropertiesWorkingCopy} instance, 48 * either through {@link #create(IAbstractFolder, PropertyType)} or through 49 * {@link #makeWorkingCopy()}. 50 * 51 */ 52 public class ProjectProperties { 53 protected final static Pattern PATTERN_PROP = Pattern.compile( 54 "^([a-zA-Z0-9._-]+)\\s*=\\s*(.*)\\s*$"); 55 56 /** The property name for the project target */ 57 public final static String PROPERTY_TARGET = "target"; 58 59 public final static String PROPERTY_LIBRARY = "android.library"; 60 public final static String PROPERTY_LIB_REF = "android.library.reference."; 61 private final static String PROPERTY_LIB_REF_REGEX = "android.library.reference.\\d+"; 62 63 public final static String PROPERTY_PROGUARD_CONFIG = "proguard.config"; 64 public final static String PROPERTY_RULES_PATH = "layoutrules.jars"; 65 66 public final static String PROPERTY_SDK = "sdk.dir"; 67 // LEGACY - Kept so that we can actually remove it from local.properties. 68 private final static String PROPERTY_SDK_LEGACY = "sdk-location"; 69 70 public final static String PROPERTY_SPLIT_BY_DENSITY = "split.density"; 71 public final static String PROPERTY_SPLIT_BY_ABI = "split.abi"; 72 public final static String PROPERTY_SPLIT_BY_LOCALE = "split.locale"; 73 74 public final static String PROPERTY_TESTED_PROJECT = "tested.project.dir"; 75 76 public final static String PROPERTY_BUILD_SOURCE_DIR = "source.dir"; 77 public final static String PROPERTY_BUILD_OUT_DIR = "out.dir"; 78 79 public final static String PROPERTY_PACKAGE = "package"; 80 public final static String PROPERTY_VERSIONCODE = "versionCode"; 81 public final static String PROPERTY_PROJECTS = "projects"; 82 public final static String PROPERTY_KEY_STORE = "key.store"; 83 public final static String PROPERTY_KEY_ALIAS = "key.alias"; 84 85 public static enum PropertyType { 86 ANT(SdkConstants.FN_ANT_PROPERTIES, BUILD_HEADER, new String[] { 87 PROPERTY_BUILD_SOURCE_DIR, PROPERTY_BUILD_OUT_DIR 88 }, null), 89 PROJECT(SdkConstants.FN_PROJECT_PROPERTIES, DEFAULT_HEADER, new String[] { 90 PROPERTY_TARGET, PROPERTY_LIBRARY, PROPERTY_LIB_REF_REGEX, 91 PROPERTY_KEY_STORE, PROPERTY_KEY_ALIAS, PROPERTY_PROGUARD_CONFIG, 92 PROPERTY_RULES_PATH 93 }, null), 94 LOCAL(SdkConstants.FN_LOCAL_PROPERTIES, LOCAL_HEADER, new String[] { 95 PROPERTY_SDK 96 }, 97 new String[] { PROPERTY_SDK_LEGACY }), 98 @Deprecated 99 LEGACY_DEFAULT("default.properties", null, null, null), 100 @Deprecated 101 LEGACY_BUILD("build.properties", null, null, null); 102 103 104 private final String mFilename; 105 private final String mHeader; 106 private final Set<String> mKnownProps; 107 private final Set<String> mRemovedProps; 108 109 PropertyType(String filename, String header, String[] validProps, String[] removedProps) { 110 mFilename = filename; 111 mHeader = header; 112 HashSet<String> s = new HashSet<String>(); 113 if (validProps != null) { 114 s.addAll(Arrays.asList(validProps)); 115 } 116 mKnownProps = Collections.unmodifiableSet(s); 117 118 s = new HashSet<String>(); 119 if (removedProps != null) { 120 s.addAll(Arrays.asList(removedProps)); 121 } 122 mRemovedProps = Collections.unmodifiableSet(s); 123 124 } 125 126 public String getFilename() { 127 return mFilename; 128 } 129 130 public String getHeader() { 131 return mHeader; 132 } 133 134 /** 135 * Returns whether a given property is known for the property type. 136 */ 137 public boolean isKnownProperty(String name) { 138 for (String propRegex : mKnownProps) { 139 if (propRegex.equals(name) || Pattern.matches(propRegex, name)) { 140 return true; 141 } 142 } 143 144 return false; 145 } 146 147 /** 148 * Returns whether a given property should be removed for the property type. 149 */ 150 public boolean isRemovedProperty(String name) { 151 for (String propRegex : mRemovedProps) { 152 if (propRegex.equals(name) || Pattern.matches(propRegex, name)) { 153 return true; 154 } 155 } 156 157 return false; 158 } 159 } 160 161 private final static String LOCAL_HEADER = 162 // 1-------10--------20--------30--------40--------50--------60--------70--------80 163 "# This file is automatically generated by Android Tools.\n" + 164 "# Do not modify this file -- YOUR CHANGES WILL BE ERASED!\n" + 165 "#\n" + 166 "# This file must *NOT* be checked in Version Control Systems,\n" + 167 "# as it contains information specific to your local configuration.\n" + 168 "\n"; 169 170 private final static String DEFAULT_HEADER = 171 // 1-------10--------20--------30--------40--------50--------60--------70--------80 172 "# This file is automatically generated by Android Tools.\n" + 173 "# Do not modify this file -- YOUR CHANGES WILL BE ERASED!\n" + 174 "#\n" + 175 "# This file must be checked in Version Control Systems.\n" + 176 "#\n" + 177 "# To customize properties used by the Ant build system use,\n" + 178 "# \"ant.properties\", and override values to adapt the script to your\n" + 179 "# project structure.\n" + 180 "\n"; 181 182 private final static String BUILD_HEADER = 183 // 1-------10--------20--------30--------40--------50--------60--------70--------80 184 "# This file is used to override default values used by the Ant build system.\n" + 185 "#\n" + 186 "# This file must be checked in Version Control Systems, as it is\n" + 187 "# integral to the build system of your project.\n" + 188 "\n" + 189 "# This file is only used by the Ant script.\n" + 190 "\n" + 191 "# You can use this to override default values such as\n" + 192 "# 'source.dir' for the location of your java source folder and\n" + 193 "# 'out.dir' for the location of your output folder.\n" + 194 "\n" + 195 "# You can also use it define how the release builds are signed by declaring\n" + 196 "# the following properties:\n" + 197 "# 'key.store' for the location of your keystore and\n" + 198 "# 'key.alias' for the name of the key to use.\n" + 199 "# The password will be asked during the build when you use the 'release' target.\n" + 200 "\n"; 201 202 protected final IAbstractFolder mProjectFolder; 203 protected final Map<String, String> mProperties; 204 protected final PropertyType mType; 205 206 /** 207 * Loads a project properties file and return a {@link ProjectProperties} object 208 * containing the properties. 209 * 210 * @param projectFolderOsPath the project folder. 211 * @param type One the possible {@link PropertyType}s. 212 */ 213 public static ProjectProperties load(String projectFolderOsPath, PropertyType type) { 214 IAbstractFolder wrapper = new FolderWrapper(projectFolderOsPath); 215 return load(wrapper, type); 216 } 217 218 /** 219 * Loads a project properties file and return a {@link ProjectProperties} object 220 * containing the properties. 221 * 222 * @param projectFolder the project folder. 223 * @param type One the possible {@link PropertyType}s. 224 */ 225 public static ProjectProperties load(IAbstractFolder projectFolder, PropertyType type) { 226 if (projectFolder.exists()) { 227 IAbstractFile propFile = projectFolder.getFile(type.mFilename); 228 if (propFile.exists()) { 229 Map<String, String> map = parsePropertyFile(propFile, null /* log */); 230 if (map != null) { 231 return new ProjectProperties(projectFolder, map, type); 232 } 233 } 234 } 235 return null; 236 } 237 238 /** 239 * Deletes a project properties file. 240 * 241 * @param projectFolder the project folder. 242 * @param type One the possible {@link PropertyType}s. 243 * @return true if success. 244 */ 245 public static boolean delete(IAbstractFolder projectFolder, PropertyType type) { 246 if (projectFolder.exists()) { 247 IAbstractFile propFile = projectFolder.getFile(type.mFilename); 248 if (propFile.exists()) { 249 return propFile.delete(); 250 } 251 } 252 253 return false; 254 } 255 256 /** 257 * Deletes a project properties file. 258 * 259 * @param projectFolderOsPath the project folder. 260 * @param type One the possible {@link PropertyType}s. 261 * @return true if success. 262 */ 263 public static boolean delete(String projectFolderOsPath, PropertyType type) { 264 IAbstractFolder wrapper = new FolderWrapper(projectFolderOsPath); 265 return delete(wrapper, type); 266 } 267 268 269 /** 270 * Creates a new project properties object, with no properties. 271 * <p/>The file is not created until {@link ProjectPropertiesWorkingCopy#save()} is called. 272 * @param projectFolderOsPath the project folder. 273 * @param type the type of property file to create 274 */ 275 public static ProjectPropertiesWorkingCopy create(String projectFolderOsPath, 276 PropertyType type) { 277 // create and return a ProjectProperties with an empty map. 278 IAbstractFolder folder = new FolderWrapper(projectFolderOsPath); 279 return create(folder, type); 280 } 281 282 /** 283 * Creates a new project properties object, with no properties. 284 * <p/>The file is not created until {@link ProjectPropertiesWorkingCopy#save()} is called. 285 * @param projectFolder the project folder. 286 * @param type the type of property file to create 287 */ 288 public static ProjectPropertiesWorkingCopy create(IAbstractFolder projectFolder, 289 PropertyType type) { 290 // create and return a ProjectProperties with an empty map. 291 return new ProjectPropertiesWorkingCopy(projectFolder, new HashMap<String, String>(), type); 292 } 293 294 /** 295 * Creates and returns a copy of the current properties as a 296 * {@link ProjectPropertiesWorkingCopy} that can be modified and saved. 297 * @return a new instance of {@link ProjectPropertiesWorkingCopy} 298 */ 299 public ProjectPropertiesWorkingCopy makeWorkingCopy() { 300 return makeWorkingCopy(mType); 301 } 302 303 /** 304 * Creates and returns a copy of the current properties as a 305 * {@link ProjectPropertiesWorkingCopy} that can be modified and saved. This also allows 306 * converting to a new type, by specifying a different {@link PropertyType}. 307 * 308 * @param type the {@link PropertyType} of the prop file to save. 309 * 310 * @return a new instance of {@link ProjectPropertiesWorkingCopy} 311 */ 312 public ProjectPropertiesWorkingCopy makeWorkingCopy(PropertyType type) { 313 // copy the current properties in a new map 314 HashMap<String, String> propList = new HashMap<String, String>(); 315 propList.putAll(mProperties); 316 317 return new ProjectPropertiesWorkingCopy(mProjectFolder, propList, type); 318 } 319 320 /** 321 * Returns the type of the property file. 322 * 323 * @see PropertyType 324 */ 325 public PropertyType getType() { 326 return mType; 327 } 328 329 /** 330 * Returns the value of a property. 331 * @param name the name of the property. 332 * @return the property value or null if the property is not set. 333 */ 334 public synchronized String getProperty(String name) { 335 return mProperties.get(name); 336 } 337 338 /** 339 * Returns a set of the property keys. Unlike {@link Map#keySet()} this is not a view of the 340 * map keys. Modifying the returned {@link Set} will not impact the underlying {@link Map}. 341 */ 342 public synchronized Set<String> keySet() { 343 return new HashSet<String>(mProperties.keySet()); 344 } 345 346 /** 347 * Reloads the properties from the underlying file. 348 */ 349 public synchronized void reload() { 350 if (mProjectFolder.exists()) { 351 IAbstractFile propFile = mProjectFolder.getFile(mType.mFilename); 352 if (propFile.exists()) { 353 Map<String, String> map = parsePropertyFile(propFile, null /* log */); 354 if (map != null) { 355 mProperties.clear(); 356 mProperties.putAll(map); 357 } 358 } 359 } 360 } 361 362 /** 363 * Parses a property file (using UTF-8 encoding) and returns a map of the content. 364 * <p/>If the file is not present, null is returned with no error messages sent to the log. 365 * 366 * @param propFile the property file to parse 367 * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null. 368 * @return the map of (key,value) pairs, or null if the parsing failed. 369 */ 370 public static Map<String, String> parsePropertyFile(IAbstractFile propFile, ISdkLog log) { 371 BufferedReader reader = null; 372 try { 373 reader = new BufferedReader(new InputStreamReader(propFile.getContents(), 374 SdkConstants.INI_CHARSET)); 375 376 String line = null; 377 Map<String, String> map = new HashMap<String, String>(); 378 while ((line = reader.readLine()) != null) { 379 if (line.length() > 0 && line.charAt(0) != '#') { 380 381 Matcher m = PATTERN_PROP.matcher(line); 382 if (m.matches()) { 383 map.put(m.group(1), m.group(2)); 384 } else { 385 log.warning("Error parsing '%1$s': \"%2$s\" is not a valid syntax", 386 propFile.getOsLocation(), 387 line); 388 return null; 389 } 390 } 391 } 392 393 return map; 394 } catch (FileNotFoundException e) { 395 // this should not happen since we usually test the file existence before 396 // calling the method. 397 // Return null below. 398 } catch (IOException e) { 399 log.warning("Error parsing '%1$s': %2$s.", 400 propFile.getOsLocation(), 401 e.getMessage()); 402 } catch (StreamException e) { 403 log.warning("Error parsing '%1$s': %2$s.", 404 propFile.getOsLocation(), 405 e.getMessage()); 406 } finally { 407 if (reader != null) { 408 try { 409 reader.close(); 410 } catch (IOException e) { 411 // pass 412 } 413 } 414 } 415 416 return null; 417 } 418 419 420 /** 421 * Private constructor. 422 * <p/> 423 * Use {@link #load(String, PropertyType)} or {@link #create(String, PropertyType)} 424 * to instantiate. 425 */ 426 protected ProjectProperties(IAbstractFolder projectFolder, Map<String, String> map, 427 PropertyType type) { 428 mProjectFolder = projectFolder; 429 mProperties = map; 430 mType = type; 431 } 432 } 433