Home | History | Annotate | Download | only in project
      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 
    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 
    115         public String getFilename() {
    116             return mFilename;
    117         }
    118 
    119         public String getHeader() {
    120             return mHeader;
    121         }
    122 
    123         /**
    124          * Returns whether a given property is known for the property type.
    125          */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    390     protected ProjectProperties(IAbstractFolder projectFolder, Map<String, String> map,
    391             PropertyType type) {
    392         mProjectFolder = projectFolder;
    393         mProperties = map;
    394         mType = type;
    395     }
    396 }
    397