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