• 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.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
COMMENT_MAP.put(PROPERTY_TARGET, "# Project target.\\n")51         COMMENT_MAP.put(PROPERTY_TARGET,
52                 "# Project target.\n");
COMMENT_MAP.put(PROPERTY_SPLIT_BY_DENSITY, "# Indicates whether an apk should be generated for each density.\\n")53         COMMENT_MAP.put(PROPERTY_SPLIT_BY_DENSITY,
54                 "# 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")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");
COMMENT_MAP.put(PROPERTY_APP_PACKAGE, "# The name of your application package as defined in the manifest.\\n" + "# Used by the 'uninstall' rule.\\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");
COMMENT_MAP.put(PROPERTY_PACKAGE, "# Package of the application being exported\\n")62         COMMENT_MAP.put(PROPERTY_PACKAGE,
63                 "# Package of the application being exported\n");
COMMENT_MAP.put(PROPERTY_VERSIONCODE, "# Major version code\\n")64         COMMENT_MAP.put(PROPERTY_VERSIONCODE,
65                 "# 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")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      */
setProperty(String name, String value)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      */
removeProperty(String name)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      */
merge(PropertyType type)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      */
save()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 
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         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      */
ProjectPropertiesWorkingCopy(IAbstractFolder projectFolder, Map<String, String> map, PropertyType type)239     ProjectPropertiesWorkingCopy(IAbstractFolder projectFolder, Map<String, String> map,
240             PropertyType type) {
241         super(projectFolder, map, type);
242     }
243 
244 }
245