• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
COMMENT_MAP.put(PROPERTY_TARGET, "# Project target.\\n")49         COMMENT_MAP.put(PROPERTY_TARGET,
50                 "# Project target.\n");
COMMENT_MAP.put(PROPERTY_SPLIT_BY_DENSITY, "# Indicates whether an apk should be generated for each density.\\n")51         COMMENT_MAP.put(PROPERTY_SPLIT_BY_DENSITY,
52                 "# 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")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");
COMMENT_MAP.put(PROPERTY_PACKAGE, "# Package of the application being exported\\n")57         COMMENT_MAP.put(PROPERTY_PACKAGE,
58                 "# Package of the application being exported\n");
COMMENT_MAP.put(PROPERTY_VERSIONCODE, "# Major version code\\n")59         COMMENT_MAP.put(PROPERTY_VERSIONCODE,
60                 "# Major version code\n");
COMMENT_MAP.put(PROPERTY_PROJECTS, "# List of the Android projects being used for the export.\\n" + "# The list is made of paths that are relative to this project,\\n" + "# using forward-slash (/) as separator, and are separated by colons (:).\\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      */
setProperty(String name, String value)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      */
removeProperty(String name)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#ANT}
94      * <li>Merge in values using {@code PropertyType#PROJECT}
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      */
merge(PropertyType type)102     public synchronized ProjectPropertiesWorkingCopy merge(PropertyType type) {
103         if (mProjectFolder.exists() && mType != type) {
104             IAbstractFile propFile = mProjectFolder.getFile(type.getFilename());
105             if (propFile.exists()) {
106                 Map<String, String> map = parsePropertyFile(propFile, null /* log */);
107                 if (map != null) {
108                     for (Entry<String, String> entry : map.entrySet()) {
109                         String key = entry.getKey();
110                         String value = entry.getValue();
111                         if (!mProperties.containsKey(key) && value != null) {
112                             mProperties.put(key, value);
113                         }
114                     }
115                 }
116             }
117         }
118         return this;
119     }
120 
121 
122     /**
123      * Saves the property file, using UTF-8 encoding.
124      * @throws IOException
125      * @throws StreamException
126      */
save()127     public synchronized void save() throws IOException, StreamException {
128         IAbstractFile toSave = mProjectFolder.getFile(mType.getFilename());
129 
130         // write the whole file in a byte array before dumping it in the file. This
131         // This is so that if the file already existing
132         ByteArrayOutputStream baos = new ByteArrayOutputStream();
133         OutputStreamWriter writer = new OutputStreamWriter(baos, SdkConstants.INI_CHARSET);
134 
135         if (toSave.exists()) {
136             BufferedReader reader = new BufferedReader(new InputStreamReader(toSave.getContents(),
137                     SdkConstants.INI_CHARSET));
138 
139             // since we're reading the existing file and replacing values with new ones, or skipping
140             // removed values, we need to record what properties have been visited, so that
141             // we can figure later what new properties need to be added at the end of the file.
142             HashSet<String> visitedProps = new HashSet<String>();
143 
144             String line = null;
145             while ((line = reader.readLine()) != null) {
146                 // check if this is a line containing a property.
147                 if (line.length() > 0 && line.charAt(0) != '#') {
148 
149                     Matcher m = PATTERN_PROP.matcher(line);
150                     if (m.matches()) {
151                         String key = m.group(1);
152                         String value = m.group(2);
153 
154                         // record the prop
155                         visitedProps.add(key);
156 
157                         // check if this property must be removed.
158                         if (mType.isRemovedProperty(key)) {
159                             value = null;
160                         } else if (mProperties.containsKey(key)) { // if the property still exists.
161                             // put the new value.
162                             value = mProperties.get(key);
163                         } else {
164                             // property doesn't exist. Check if it's a known property.
165                             // if it's a known one, we'll remove it, otherwise, leave it untouched.
166                             if (mType.isKnownProperty(key)) {
167                                 value = null;
168                             }
169                         }
170 
171                         // if the value is still valid, write it down.
172                         if (value != null) {
173                             writeValue(writer, key, value, false /*addComment*/);
174                         }
175                     } else  {
176                         // the line was wrong, let's just ignore it so that it's removed from the
177                         // file.
178                     }
179                 } else {
180                     // non-property line: just write the line in the output as-is.
181                     writer.append(line).append('\n');
182                 }
183             }
184 
185             // now add the new properties.
186             for (Entry<String, String> entry : mProperties.entrySet()) {
187                 if (visitedProps.contains(entry.getKey()) == false) {
188                     String value = entry.getValue();
189                     if (value != null) {
190                         writeValue(writer, entry.getKey(), value, true /*addComment*/);
191                     }
192                 }
193             }
194 
195         } else {
196             // new file, just write it all
197 
198             // write the header (can be null, for example for PropertyType.LEGACY_BUILD)
199             if (mType.getHeader() != null) {
200                 writer.write(mType.getHeader());
201             }
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 
writeValue(OutputStreamWriter writer, String key, String value, boolean addComment)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         writer.write(String.format("%s=%s\n", key, escape(value)));
230     }
231 
232     /**
233      * Private constructor.
234      * <p/>
235      * Use {@link #load(String, PropertyType)} or {@link #create(String, PropertyType)}
236      * to instantiate.
237      */
ProjectPropertiesWorkingCopy(IAbstractFolder projectFolder, Map<String, String> map, PropertyType type)238     ProjectPropertiesWorkingCopy(IAbstractFolder projectFolder, Map<String, String> map,
239             PropertyType type) {
240         super(projectFolder, map, type);
241     }
242 
243 }
244