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.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 
    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 
    126         public String getFilename() {
    127             return mFilename;
    128         }
    129 
    130         public String getHeader() {
    131             return mHeader;
    132         }
    133 
    134         /**
    135          * Returns whether a given property is known for the property type.
    136          */
    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          */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    426     protected ProjectProperties(IAbstractFolder projectFolder, Map<String, String> map,
    427             PropertyType type) {
    428         mProjectFolder = projectFolder;
    429         mProperties = map;
    430         mType = type;
    431     }
    432 }
    433