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