• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 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.FolderWrapper;
20 import com.android.io.IAbstractFile;
21 import com.android.io.IAbstractFolder;
22 import com.android.io.StreamException;
23 import com.android.sdklib.ISdkLog;
24 import com.android.sdklib.SdkConstants;
25 
26 import java.io.BufferedReader;
27 import java.io.FileNotFoundException;
28 import java.io.IOException;
29 import java.io.InputStreamReader;
30 import java.util.Arrays;
31 import java.util.Collections;
32 import java.util.HashMap;
33 import java.util.HashSet;
34 import java.util.Map;
35 import java.util.Set;
36 import java.util.regex.Matcher;
37 import java.util.regex.Pattern;
38 
39 /**
40  * Class representing project properties for both ADT and Ant-based build.
41  * <p/>The class is associated to a {@link PropertyType} that indicate which of the project
42  * property file is represented.
43  * <p/>To load an existing file, use {@link #load(IAbstractFolder, PropertyType)}.
44  * <p/>The class is meant to be always in sync (or at least not newer) than the file it represents.
45  * Once created, it can only be updated through {@link #reload()}
46  *
47  * <p/>The make modification or make new file, use a {@link ProjectPropertiesWorkingCopy} instance,
48  * either through {@link #create(IAbstractFolder, PropertyType)} or through
49  * {@link #makeWorkingCopy()}.
50  *
51  */
52 public class ProjectProperties {
53     protected final static Pattern PATTERN_PROP = Pattern.compile(
54     "^([a-zA-Z0-9._-]+)\\s*=\\s*(.*)\\s*$");
55 
56     /** The property name for the project target */
57     public final static String PROPERTY_TARGET = "target";
58 
59     public final static String PROPERTY_LIBRARY = "android.library";
60     public final static String PROPERTY_LIB_REF = "android.library.reference.";
61     private final static String PROPERTY_LIB_REF_REGEX = "android.library.reference.\\d+";
62 
63     public final static String PROPERTY_PROGUARD_CONFIG = "proguard.config";
64     public final static String PROPERTY_RULES_PATH = "layoutrules.jars";
65 
66     public final static String PROPERTY_SDK = "sdk.dir";
67     // LEGACY - Kept so that we can actually remove it from local.properties.
68     private final static String PROPERTY_SDK_LEGACY = "sdk-location";
69 
70     public final static String PROPERTY_SPLIT_BY_DENSITY = "split.density";
71     public final static String PROPERTY_SPLIT_BY_ABI = "split.abi";
72     public final static String PROPERTY_SPLIT_BY_LOCALE = "split.locale";
73 
74     public final static String PROPERTY_TESTED_PROJECT = "tested.project.dir";
75 
76     public final static String PROPERTY_BUILD_SOURCE_DIR = "source.dir";
77     public final static String PROPERTY_BUILD_OUT_DIR = "out.dir";
78 
79     public final static String PROPERTY_PACKAGE = "package";
80     public final static String PROPERTY_VERSIONCODE = "versionCode";
81     public final static String PROPERTY_PROJECTS = "projects";
82     public final static String PROPERTY_KEY_STORE = "key.store";
83     public final static String PROPERTY_KEY_ALIAS = "key.alias";
84 
85     public static enum PropertyType {
86         ANT(SdkConstants.FN_ANT_PROPERTIES, BUILD_HEADER, new String[] {
87                 PROPERTY_BUILD_SOURCE_DIR, PROPERTY_BUILD_OUT_DIR
88             }, null),
89         PROJECT(SdkConstants.FN_PROJECT_PROPERTIES, DEFAULT_HEADER, new String[] {
90                 PROPERTY_TARGET, PROPERTY_LIBRARY, PROPERTY_LIB_REF_REGEX,
91                 PROPERTY_KEY_STORE, PROPERTY_KEY_ALIAS, PROPERTY_PROGUARD_CONFIG,
92                 PROPERTY_RULES_PATH
93             }, null),
94         LOCAL(SdkConstants.FN_LOCAL_PROPERTIES, LOCAL_HEADER, new String[] {
95                 PROPERTY_SDK
96             },
97             new String[] { PROPERTY_SDK_LEGACY }),
98         @Deprecated
99         LEGACY_DEFAULT("default.properties", null, null, null),
100         @Deprecated
101         LEGACY_BUILD("build.properties", null, null, null);
102 
103 
104         private final String mFilename;
105         private final String mHeader;
106         private final Set<String> mKnownProps;
107         private final Set<String> mRemovedProps;
108 
PropertyType(String filename, String header, String[] validProps, String[] removedProps)109         PropertyType(String filename, String header, String[] validProps, String[] removedProps) {
110             mFilename = filename;
111             mHeader = header;
112             HashSet<String> s = new HashSet<String>();
113             if (validProps != null) {
114                 s.addAll(Arrays.asList(validProps));
115             }
116             mKnownProps = Collections.unmodifiableSet(s);
117 
118             s = new HashSet<String>();
119             if (removedProps != null) {
120                 s.addAll(Arrays.asList(removedProps));
121             }
122             mRemovedProps = Collections.unmodifiableSet(s);
123 
124         }
125 
getFilename()126         public String getFilename() {
127             return mFilename;
128         }
129 
getHeader()130         public String getHeader() {
131             return mHeader;
132         }
133 
134         /**
135          * Returns whether a given property is known for the property type.
136          */
isKnownProperty(String name)137         public boolean isKnownProperty(String name) {
138             for (String propRegex : mKnownProps) {
139                 if (propRegex.equals(name) || Pattern.matches(propRegex, name)) {
140                     return true;
141                 }
142             }
143 
144             return false;
145         }
146 
147         /**
148          * Returns whether a given property should be removed for the property type.
149          */
isRemovedProperty(String name)150         public boolean isRemovedProperty(String name) {
151             for (String propRegex : mRemovedProps) {
152                 if (propRegex.equals(name) || Pattern.matches(propRegex, name)) {
153                     return true;
154                 }
155             }
156 
157             return false;
158         }
159     }
160 
161     private final static String LOCAL_HEADER =
162 //           1-------10--------20--------30--------40--------50--------60--------70--------80
163             "# This file is automatically generated by Android Tools.\n" +
164             "# Do not modify this file -- YOUR CHANGES WILL BE ERASED!\n" +
165             "#\n" +
166             "# This file must *NOT* be checked in Version Control Systems,\n" +
167             "# as it contains information specific to your local configuration.\n" +
168             "\n";
169 
170     private final static String DEFAULT_HEADER =
171 //          1-------10--------20--------30--------40--------50--------60--------70--------80
172            "# This file is automatically generated by Android Tools.\n" +
173            "# Do not modify this file -- YOUR CHANGES WILL BE ERASED!\n" +
174            "#\n" +
175            "# This file must be checked in Version Control Systems.\n" +
176            "#\n" +
177            "# To customize properties used by the Ant build system use,\n" +
178            "# \"ant.properties\", and override values to adapt the script to your\n" +
179            "# project structure.\n" +
180            "\n";
181 
182     private final static String BUILD_HEADER =
183 //          1-------10--------20--------30--------40--------50--------60--------70--------80
184            "# This file is used to override default values used by the Ant build system.\n" +
185            "#\n" +
186            "# This file must be checked in Version Control Systems, as it is\n" +
187            "# integral to the build system of your project.\n" +
188            "\n" +
189            "# This file is only used by the Ant script.\n" +
190            "\n" +
191            "# You can use this to override default values such as\n" +
192            "#  'source.dir' for the location of your java source folder and\n" +
193            "#  'out.dir' for the location of your output folder.\n" +
194            "\n" +
195            "# You can also use it define how the release builds are signed by declaring\n" +
196            "# the following properties:\n" +
197            "#  'key.store' for the location of your keystore and\n" +
198            "#  'key.alias' for the name of the key to use.\n" +
199            "# The password will be asked during the build when you use the 'release' target.\n" +
200            "\n";
201 
202     protected final IAbstractFolder mProjectFolder;
203     protected final Map<String, String> mProperties;
204     protected final PropertyType mType;
205 
206     /**
207      * Loads a project properties file and return a {@link ProjectProperties} object
208      * containing the properties.
209      *
210      * @param projectFolderOsPath the project folder.
211      * @param type One the possible {@link PropertyType}s.
212      */
load(String projectFolderOsPath, PropertyType type)213     public static ProjectProperties load(String projectFolderOsPath, PropertyType type) {
214         IAbstractFolder wrapper = new FolderWrapper(projectFolderOsPath);
215         return load(wrapper, type);
216     }
217 
218     /**
219      * Loads a project properties file and return a {@link ProjectProperties} object
220      * containing the properties.
221      *
222      * @param projectFolder the project folder.
223      * @param type One the possible {@link PropertyType}s.
224      */
load(IAbstractFolder projectFolder, PropertyType type)225     public static ProjectProperties load(IAbstractFolder projectFolder, PropertyType type) {
226         if (projectFolder.exists()) {
227             IAbstractFile propFile = projectFolder.getFile(type.mFilename);
228             if (propFile.exists()) {
229                 Map<String, String> map = parsePropertyFile(propFile, null /* log */);
230                 if (map != null) {
231                     return new ProjectProperties(projectFolder, map, type);
232                 }
233             }
234         }
235         return null;
236     }
237 
238     /**
239      * Deletes a project properties file.
240      *
241      * @param projectFolder the project folder.
242      * @param type One the possible {@link PropertyType}s.
243      * @return true if success.
244      */
delete(IAbstractFolder projectFolder, PropertyType type)245     public static boolean delete(IAbstractFolder projectFolder, PropertyType type) {
246         if (projectFolder.exists()) {
247             IAbstractFile propFile = projectFolder.getFile(type.mFilename);
248             if (propFile.exists()) {
249                 return propFile.delete();
250             }
251         }
252 
253         return false;
254     }
255 
256     /**
257      * Deletes a project properties file.
258      *
259      * @param projectFolderOsPath the project folder.
260      * @param type One the possible {@link PropertyType}s.
261      * @return true if success.
262      */
delete(String projectFolderOsPath, PropertyType type)263     public static boolean delete(String projectFolderOsPath, PropertyType type) {
264         IAbstractFolder wrapper = new FolderWrapper(projectFolderOsPath);
265         return delete(wrapper, type);
266     }
267 
268 
269     /**
270      * Creates a new project properties object, with no properties.
271      * <p/>The file is not created until {@link ProjectPropertiesWorkingCopy#save()} is called.
272      * @param projectFolderOsPath the project folder.
273      * @param type the type of property file to create
274      */
create(String projectFolderOsPath, PropertyType type)275     public static ProjectPropertiesWorkingCopy create(String projectFolderOsPath,
276             PropertyType type) {
277         // create and return a ProjectProperties with an empty map.
278         IAbstractFolder folder = new FolderWrapper(projectFolderOsPath);
279         return create(folder, type);
280     }
281 
282     /**
283      * Creates a new project properties object, with no properties.
284      * <p/>The file is not created until {@link ProjectPropertiesWorkingCopy#save()} is called.
285      * @param projectFolder the project folder.
286      * @param type the type of property file to create
287      */
create(IAbstractFolder projectFolder, PropertyType type)288     public static ProjectPropertiesWorkingCopy create(IAbstractFolder projectFolder,
289             PropertyType type) {
290         // create and return a ProjectProperties with an empty map.
291         return new ProjectPropertiesWorkingCopy(projectFolder, new HashMap<String, String>(), type);
292     }
293 
294     /**
295      * Creates and returns a copy of the current properties as a
296      * {@link ProjectPropertiesWorkingCopy} that can be modified and saved.
297      * @return a new instance of {@link ProjectPropertiesWorkingCopy}
298      */
makeWorkingCopy()299     public ProjectPropertiesWorkingCopy makeWorkingCopy() {
300         return makeWorkingCopy(mType);
301     }
302 
303     /**
304      * Creates and returns a copy of the current properties as a
305      * {@link ProjectPropertiesWorkingCopy} that can be modified and saved. This also allows
306      * converting to a new type, by specifying a different {@link PropertyType}.
307      *
308      * @param type the {@link PropertyType} of the prop file to save.
309      *
310      * @return a new instance of {@link ProjectPropertiesWorkingCopy}
311      */
makeWorkingCopy(PropertyType type)312     public ProjectPropertiesWorkingCopy makeWorkingCopy(PropertyType type) {
313         // copy the current properties in a new map
314         HashMap<String, String> propList = new HashMap<String, String>();
315         propList.putAll(mProperties);
316 
317         return new ProjectPropertiesWorkingCopy(mProjectFolder, propList, type);
318     }
319 
320     /**
321      * Returns the type of the property file.
322      *
323      * @see PropertyType
324      */
getType()325     public PropertyType getType() {
326         return mType;
327     }
328 
329     /**
330      * Returns the value of a property.
331      * @param name the name of the property.
332      * @return the property value or null if the property is not set.
333      */
getProperty(String name)334     public synchronized String getProperty(String name) {
335         return mProperties.get(name);
336     }
337 
338     /**
339      * Returns a set of the property keys. Unlike {@link Map#keySet()} this is not a view of the
340      * map keys. Modifying the returned {@link Set} will not impact the underlying {@link Map}.
341      */
keySet()342     public synchronized Set<String> keySet() {
343         return new HashSet<String>(mProperties.keySet());
344     }
345 
346     /**
347      * Reloads the properties from the underlying file.
348      */
reload()349     public synchronized void reload() {
350         if (mProjectFolder.exists()) {
351             IAbstractFile propFile = mProjectFolder.getFile(mType.mFilename);
352             if (propFile.exists()) {
353                 Map<String, String> map = parsePropertyFile(propFile, null /* log */);
354                 if (map != null) {
355                     mProperties.clear();
356                     mProperties.putAll(map);
357                 }
358             }
359         }
360     }
361 
362     /**
363      * Parses a property file (using UTF-8 encoding) and returns a map of the content.
364      * <p/>If the file is not present, null is returned with no error messages sent to the log.
365      *
366      * @param propFile the property file to parse
367      * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null.
368      * @return the map of (key,value) pairs, or null if the parsing failed.
369      */
parsePropertyFile(IAbstractFile propFile, ISdkLog log)370     public static Map<String, String> parsePropertyFile(IAbstractFile propFile, ISdkLog log) {
371         BufferedReader reader = null;
372         try {
373             reader = new BufferedReader(new InputStreamReader(propFile.getContents(),
374                     SdkConstants.INI_CHARSET));
375 
376             String line = null;
377             Map<String, String> map = new HashMap<String, String>();
378             while ((line = reader.readLine()) != null) {
379                 if (line.length() > 0 && line.charAt(0) != '#') {
380 
381                     Matcher m = PATTERN_PROP.matcher(line);
382                     if (m.matches()) {
383                         map.put(m.group(1), m.group(2));
384                     } else {
385                         log.warning("Error parsing '%1$s': \"%2$s\" is not a valid syntax",
386                                 propFile.getOsLocation(),
387                                 line);
388                         return null;
389                     }
390                 }
391             }
392 
393             return map;
394         } catch (FileNotFoundException e) {
395             // this should not happen since we usually test the file existence before
396             // calling the method.
397             // Return null below.
398         } catch (IOException e) {
399             log.warning("Error parsing '%1$s': %2$s.",
400                     propFile.getOsLocation(),
401                     e.getMessage());
402         } catch (StreamException e) {
403             log.warning("Error parsing '%1$s': %2$s.",
404                     propFile.getOsLocation(),
405                     e.getMessage());
406         } finally {
407             if (reader != null) {
408                 try {
409                     reader.close();
410                 } catch (IOException e) {
411                     // pass
412                 }
413             }
414         }
415 
416         return null;
417     }
418 
419 
420     /**
421      * Private constructor.
422      * <p/>
423      * Use {@link #load(String, PropertyType)} or {@link #create(String, PropertyType)}
424      * to instantiate.
425      */
ProjectProperties(IAbstractFolder projectFolder, Map<String, String> map, PropertyType type)426     protected ProjectProperties(IAbstractFolder projectFolder, Map<String, String> map,
427             PropertyType type) {
428         mProjectFolder = projectFolder;
429         mProperties = map;
430         mType = type;
431     }
432 }
433