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.SdkConstants; 20 import com.android.sdklib.SdkManager; 21 22 import java.io.File; 23 import java.io.FileOutputStream; 24 import java.io.IOException; 25 import java.io.OutputStreamWriter; 26 import java.util.HashMap; 27 import java.util.HashSet; 28 import java.util.Map; 29 import java.util.Set; 30 import java.util.Map.Entry; 31 32 /** 33 * Class to load and save project properties for both ADT and Ant-based build. 34 * 35 */ 36 public final class ProjectProperties { 37 /** The property name for the project target */ 38 public final static String PROPERTY_TARGET = "target"; 39 40 public final static String PROPERTY_LIBRARY = "android.library"; 41 public final static String PROPERTY_LIB_REF = "android.library.reference."; 42 43 public final static String PROPERTY_SDK = "sdk.dir"; 44 // LEGACY - compatibility with 1.6 and before 45 public final static String PROPERTY_SDK_LEGACY = "sdk-location"; 46 47 public final static String PROPERTY_APP_PACKAGE = "application.package"; 48 // LEGACY - compatibility with 1.6 and before 49 public final static String PROPERTY_APP_PACKAGE_LEGACY = "application-package"; 50 51 public final static String PROPERTY_SPLIT_BY_DENSITY = "split.density"; 52 53 public final static String PROPERTY_TESTED_PROJECT = "tested.project.dir"; 54 55 public final static String PROPERTY_BUILD_SOURCE_DIR = "source.dir"; 56 57 public static enum PropertyType { 58 BUILD("build.properties", BUILD_HEADER), 59 DEFAULT(SdkConstants.FN_DEFAULT_PROPERTIES, DEFAULT_HEADER), 60 LOCAL("local.properties", LOCAL_HEADER); 61 62 private final String mFilename; 63 private final String mHeader; 64 65 PropertyType(String filename, String header) { 66 mFilename = filename; 67 mHeader = header; 68 } 69 70 public String getFilename() { 71 return mFilename; 72 } 73 } 74 75 private final static String LOCAL_HEADER = 76 // 1-------10--------20--------30--------40--------50--------60--------70--------80 77 "# This file is automatically generated by Android Tools.\n" + 78 "# Do not modify this file -- YOUR CHANGES WILL BE ERASED!\n" + 79 "# \n" + 80 "# This file must *NOT* be checked in Version Control Systems,\n" + 81 "# as it contains information specific to your local configuration.\n" + 82 "\n"; 83 84 private final static String DEFAULT_HEADER = 85 // 1-------10--------20--------30--------40--------50--------60--------70--------80 86 "# This file is automatically generated by Android Tools.\n" + 87 "# Do not modify this file -- YOUR CHANGES WILL BE ERASED!\n" + 88 "# \n" + 89 "# This file must be checked in Version Control Systems.\n" + 90 "# \n" + 91 "# To customize properties used by the Ant build system use,\n" + 92 "# \"build.properties\", and override values to adapt the script to your\n" + 93 "# project structure.\n" + 94 "\n"; 95 96 private final static String BUILD_HEADER = 97 // 1-------10--------20--------30--------40--------50--------60--------70--------80 98 "# This file is used to override default values used by the Ant build system.\n" + 99 "# \n" + 100 "# This file must be checked in Version Control Systems, as it is\n" + 101 "# integral to the build system of your project.\n" + 102 "\n" + 103 "# This file is only used by the Ant script.\n" + 104 "\n" + 105 "# You can use this to override default values such as\n" + 106 "# 'source.dir' for the location of your java source folder and\n" + 107 "# 'out.dir' for the location of your output folder.\n" + 108 "\n" + 109 "# You can also use it define how the release builds are signed by declaring\n" + 110 "# the following properties:\n" + 111 "# 'key.store' for the location of your keystore and\n" + 112 "# 'key.alias' for the name of the key to use.\n" + 113 "# The password will be asked during the build when you use the 'release' target.\n" + 114 "\n"; 115 116 private final static Map<String, String> COMMENT_MAP = new HashMap<String, String>(); 117 static { 118 // 1-------10--------20--------30--------40--------50--------60--------70--------80 119 COMMENT_MAP.put(PROPERTY_TARGET, 120 "# Project target.\n"); 121 COMMENT_MAP.put(PROPERTY_SPLIT_BY_DENSITY, 122 "# Indicates whether an apk should be generated for each density.\n"); 123 COMMENT_MAP.put(PROPERTY_SDK, 124 "# location of the SDK. This is only used by Ant\n" + 125 "# For customization when using a Version Control System, please read the\n" + 126 "# header note.\n"); 127 COMMENT_MAP.put(PROPERTY_APP_PACKAGE, 128 "# The name of your application package as defined in the manifest.\n" + 129 "# Used by the 'uninstall' rule.\n"); 130 } 131 132 private final String mProjectFolderOsPath; 133 private final Map<String, String> mProperties; 134 private final PropertyType mType; 135 136 /** 137 * Loads a project properties file and return a {@link ProjectProperties} object 138 * containing the properties 139 * 140 * @param projectFolderOsPath the project folder. 141 * @param type One the possible {@link PropertyType}s. 142 */ 143 public static ProjectProperties load(String projectFolderOsPath, PropertyType type) { 144 File projectFolder = new File(projectFolderOsPath); 145 if (projectFolder.isDirectory()) { 146 File defaultFile = new File(projectFolder, type.mFilename); 147 if (defaultFile.isFile()) { 148 Map<String, String> map = SdkManager.parsePropertyFile(defaultFile, null /* log */); 149 if (map != null) { 150 return new ProjectProperties(projectFolderOsPath, map, type); 151 } 152 } 153 } 154 return null; 155 } 156 157 /** 158 * Merges all properties from the given file into the current properties. 159 * <p/> 160 * This emulates the Ant behavior: existing properties are <em>not</em> overriden. 161 * Only new undefined properties become defined. 162 * <p/> 163 * Typical usage: 164 * <ul> 165 * <li>Create a ProjectProperties with {@link PropertyType#BUILD} 166 * <li>Merge in values using {@link PropertyType#DEFAULT} 167 * <li>The result is that this contains all the properties from default plus those 168 * overridden by the build.properties file. 169 * </ul> 170 * 171 * @param type One the possible {@link PropertyType}s. 172 * @return this object, for chaining. 173 */ 174 public synchronized ProjectProperties merge(PropertyType type) { 175 File projectFolder = new File(mProjectFolderOsPath); 176 if (projectFolder.isDirectory()) { 177 File defaultFile = new File(projectFolder, type.mFilename); 178 if (defaultFile.isFile()) { 179 Map<String, String> map = SdkManager.parsePropertyFile(defaultFile, null /* log */); 180 if (map != null) { 181 for(Entry<String, String> entry : map.entrySet()) { 182 String key = entry.getKey(); 183 String value = entry.getValue(); 184 if (!mProperties.containsKey(key) && value != null) { 185 mProperties.put(key, value); 186 } 187 } 188 } 189 } 190 } 191 return this; 192 } 193 194 /** 195 * Creates a new project properties object, with no properties. 196 * <p/>The file is not created until {@link #save()} is called. 197 * @param projectFolderOsPath the project folder. 198 * @param type 199 */ 200 public static ProjectProperties create(String projectFolderOsPath, PropertyType type) { 201 // create and return a ProjectProperties with an empty map. 202 return new ProjectProperties(projectFolderOsPath, new HashMap<String, String>(), type); 203 } 204 205 /** 206 * Returns the type of the property file. 207 * 208 * @see PropertyType 209 */ 210 public PropertyType getType() { 211 return mType; 212 } 213 214 /** 215 * Sets a new properties. If a property with the same name already exists, it is replaced. 216 * @param name the name of the property. 217 * @param value the value of the property. 218 */ 219 public synchronized void setProperty(String name, String value) { 220 mProperties.put(name, value); 221 } 222 223 /** 224 * Returns the value of a property. 225 * @param name the name of the property. 226 * @return the property value or null if the property is not set. 227 */ 228 public synchronized String getProperty(String name) { 229 return mProperties.get(name); 230 } 231 232 /** 233 * Removes a property and returns its previous value (or null if the property did not exist). 234 * @param name the name of the property to remove. 235 */ 236 public synchronized String removeProperty(String name) { 237 return mProperties.remove(name); 238 } 239 240 /** 241 * Returns a set of the property keys. Unlike {@link Map#keySet()} this is not a view of the 242 * map keys. Modifying the returned {@link Set} will not impact the underlying {@link Map}. 243 */ 244 public synchronized Set<String> keySet() { 245 return new HashSet<String>(mProperties.keySet()); 246 } 247 248 /** 249 * Reloads the properties from the underlying file. 250 */ 251 public synchronized void reload() { 252 File projectFolder = new File(mProjectFolderOsPath); 253 if (projectFolder.isDirectory()) { 254 File defaultFile = new File(projectFolder, mType.mFilename); 255 if (defaultFile.isFile()) { 256 Map<String, String> map = SdkManager.parsePropertyFile(defaultFile, null /* log */); 257 if (map != null) { 258 mProperties.clear(); 259 mProperties.putAll(map); 260 } 261 } 262 } 263 } 264 265 /** 266 * Saves the property file, using UTF-8 encoding. 267 * @throws IOException 268 */ 269 public synchronized void save() throws IOException { 270 File toSave = new File(mProjectFolderOsPath, mType.mFilename); 271 272 OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(toSave), 273 SdkConstants.INI_CHARSET); 274 275 // write the header 276 writer.write(mType.mHeader); 277 278 // write the properties. 279 for (Entry<String, String> entry : mProperties.entrySet()) { 280 String comment = COMMENT_MAP.get(entry.getKey()); 281 if (comment != null) { 282 writer.write(comment); 283 } 284 String value = entry.getValue(); 285 if (value != null) { 286 value = value.replaceAll("\\\\", "\\\\\\\\"); 287 writer.write(String.format("%s=%s\n", entry.getKey(), value)); 288 } 289 } 290 291 // close the file to flush 292 writer.close(); 293 } 294 295 /** 296 * Private constructor. 297 * <p/> 298 * Use {@link #load(String, PropertyType)} or {@link #create(String, PropertyType)} 299 * to instantiate. 300 */ 301 private ProjectProperties(String projectFolderOsPath, Map<String, String> map, 302 PropertyType type) { 303 mProjectFolderOsPath = projectFolderOsPath; 304 mProperties = map; 305 mType = type; 306 } 307 } 308