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