1 /* 2 * Copyright (C) 2010 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.internal.project.ProjectProperties.PropertyType; 21 import com.android.sdklib.io.IAbstractFile; 22 import com.android.sdklib.io.IAbstractFolder; 23 import com.android.sdklib.io.StreamException; 24 25 import java.io.BufferedReader; 26 import java.io.ByteArrayOutputStream; 27 import java.io.IOException; 28 import java.io.InputStreamReader; 29 import java.io.OutputStream; 30 import java.io.OutputStreamWriter; 31 import java.util.HashMap; 32 import java.util.HashSet; 33 import java.util.Map; 34 import java.util.Map.Entry; 35 import java.util.regex.Matcher; 36 37 /** 38 * A modifyable and saveable copy of a {@link ProjectProperties}. 39 * <p/>This copy gives access to modification method such as {@link #setProperty(String, String)} 40 * and {@link #removeProperty(String)}. 41 * 42 * To get access to an instance, use {@link ProjectProperties#makeWorkingCopy()} or 43 * {@link ProjectProperties#create(IAbstractFolder, PropertyType)}. 44 */ 45 @SuppressWarnings("deprecation") 46 public class ProjectPropertiesWorkingCopy extends ProjectProperties { 47 48 private final static Map<String, String> COMMENT_MAP = new HashMap<String, String>(); 49 static { 50 // 1-------10--------20--------30--------40--------50--------60--------70--------80 51 COMMENT_MAP.put(PROPERTY_TARGET, 52 "# Project target.\n"); 53 COMMENT_MAP.put(PROPERTY_SPLIT_BY_DENSITY, 54 "# Indicates whether an apk should be generated for each density.\n"); 55 COMMENT_MAP.put(PROPERTY_SDK, 56 "# location of the SDK. This is only used by Ant\n" + 57 "# For customization when using a Version Control System, please read the\n" + 58 "# header note.\n"); 59 COMMENT_MAP.put(PROPERTY_APP_PACKAGE, 60 "# The name of your application package as defined in the manifest.\n" + 61 "# Used by the 'uninstall' rule.\n"); 62 COMMENT_MAP.put(PROPERTY_PACKAGE, 63 "# Package of the application being exported\n"); 64 COMMENT_MAP.put(PROPERTY_VERSIONCODE, 65 "# Major version code\n"); 66 COMMENT_MAP.put(PROPERTY_PROJECTS, 67 "# List of the Android projects being used for the export.\n" + 68 "# The list is made of paths that are relative to this project,\n" + 69 "# using forward-slash (/) as separator, and are separated by colons (:).\n"); 70 } 71 72 73 /** 74 * Sets a new properties. If a property with the same name already exists, it is replaced. 75 * @param name the name of the property. 76 * @param value the value of the property. 77 */ 78 public synchronized void setProperty(String name, String value) { 79 mProperties.put(name, value); 80 } 81 82 /** 83 * Removes a property and returns its previous value (or null if the property did not exist). 84 * @param name the name of the property to remove. 85 */ 86 public synchronized String removeProperty(String name) { 87 return mProperties.remove(name); 88 } 89 90 /** 91 * Merges all properties from the given file into the current properties. 92 * <p/> 93 * This emulates the Ant behavior: existing properties are <em>not</em> overridden. 94 * Only new undefined properties become defined. 95 * <p/> 96 * Typical usage: 97 * <ul> 98 * <li>Create a ProjectProperties with {@link PropertyType#BUILD} 99 * <li>Merge in values using {@link PropertyType#DEFAULT} 100 * <li>The result is that this contains all the properties from default plus those 101 * overridden by the build.properties file. 102 * </ul> 103 * 104 * @param type One the possible {@link PropertyType}s. 105 * @return this object, for chaining. 106 */ 107 public synchronized ProjectPropertiesWorkingCopy merge(PropertyType type) { 108 if (mProjectFolder.exists() && mType != type) { 109 IAbstractFile propFile = mProjectFolder.getFile(type.getFilename()); 110 if (propFile.exists()) { 111 Map<String, String> map = parsePropertyFile(propFile, null /* log */); 112 if (map != null) { 113 for (Entry<String, String> entry : map.entrySet()) { 114 String key = entry.getKey(); 115 String value = entry.getValue(); 116 if (!mProperties.containsKey(key) && value != null) { 117 mProperties.put(key, value); 118 } 119 } 120 } 121 } 122 } 123 return this; 124 } 125 126 127 /** 128 * Saves the property file, using UTF-8 encoding. 129 * @throws IOException 130 * @throws StreamException 131 */ 132 public synchronized void save() throws IOException, StreamException { 133 IAbstractFile toSave = mProjectFolder.getFile(mType.getFilename()); 134 135 // write the whole file in a byte array before dumping it in the file. This 136 // This is so that if the file already existing 137 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 138 OutputStreamWriter writer = new OutputStreamWriter(baos, SdkConstants.INI_CHARSET); 139 140 if (toSave.exists()) { 141 BufferedReader reader = new BufferedReader(new InputStreamReader(toSave.getContents(), 142 SdkConstants.INI_CHARSET)); 143 144 // since we're reading the existing file and replacing values with new ones, or skipping 145 // removed values, we need to record what properties have been visited, so that 146 // we can figure later what new properties need to be added at the end of the file. 147 HashSet<String> visitedProps = new HashSet<String>(); 148 149 String line = null; 150 while ((line = reader.readLine()) != null) { 151 // check if this is a line containing a property. 152 if (line.length() > 0 && line.charAt(0) != '#') { 153 154 Matcher m = PATTERN_PROP.matcher(line); 155 if (m.matches()) { 156 String key = m.group(1); 157 String value = m.group(2); 158 159 // record the prop 160 visitedProps.add(key); 161 162 // check if the property still exists. 163 if (mProperties.containsKey(key)) { 164 // put the new value. 165 value = mProperties.get(key); 166 } else { 167 // property doesn't exist. Check if it's a known property. 168 // if it's a known one, we'll remove it, otherwise, leave it untouched. 169 if (mType.isKnownProperty(key)) { 170 value = null; 171 } 172 } 173 174 // if the value is still valid, write it down. 175 if (value != null) { 176 writeValue(writer, key, value, false /*addComment*/); 177 } 178 } else { 179 // the line was wrong, let's just ignore it so that it's removed from the 180 // file. 181 } 182 } else { 183 // non-property line: just write the line in the output as-is. 184 writer.append(line).append('\n'); 185 } 186 } 187 188 // now add the new properties. 189 for (Entry<String, String> entry : mProperties.entrySet()) { 190 if (visitedProps.contains(entry.getKey()) == false) { 191 String value = entry.getValue(); 192 if (value != null) { 193 writeValue(writer, entry.getKey(), value, true /*addComment*/); 194 } 195 } 196 } 197 198 } else { 199 // new file, just write it all 200 // write the header 201 writer.write(mType.getHeader()); 202 203 // write the properties. 204 for (Entry<String, String> entry : mProperties.entrySet()) { 205 String value = entry.getValue(); 206 if (value != null) { 207 writeValue(writer, entry.getKey(), value, true /*addComment*/); 208 } 209 } 210 } 211 212 writer.flush(); 213 214 // now put the content in the file. 215 OutputStream filestream = toSave.getOutputStream(); 216 filestream.write(baos.toByteArray()); 217 filestream.flush(); 218 } 219 220 private void writeValue(OutputStreamWriter writer, String key, String value, 221 boolean addComment) throws IOException { 222 if (addComment) { 223 String comment = COMMENT_MAP.get(key); 224 if (comment != null) { 225 writer.write(comment); 226 } 227 } 228 229 value = value.replaceAll("\\\\", "\\\\\\\\"); 230 writer.write(String.format("%s=%s\n", key, value)); 231 } 232 233 /** 234 * Private constructor. 235 * <p/> 236 * Use {@link #load(String, PropertyType)} or {@link #create(String, PropertyType)} 237 * to instantiate. 238 */ 239 ProjectPropertiesWorkingCopy(IAbstractFolder projectFolder, Map<String, String> map, 240 PropertyType type) { 241 super(projectFolder, map, type); 242 } 243 244 } 245