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