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