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