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 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 
    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 
    131         public String getFilename() {
    132             return mFilename;
    133         }
    134 
    135         public String getHeader() {
    136             return mHeader;
    137         }
    138 
    139         /**
    140          * Returns whether a given property is known for the property type.
    141          */
    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          */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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
    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      */
    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      */
    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      */
    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      */
    439     protected ProjectProperties(IAbstractFolder projectFolder, Map<String, String> map,
    440             PropertyType type) {
    441         mProjectFolder = projectFolder;
    442         mProperties = map;
    443         mType = type;
    444     }
    445 
    446     private static String unescape(String value) {
    447         return value.replaceAll("\\\\\\\\", "\\\\");
    448     }
    449 
    450     protected static String escape(String value) {
    451         return value.replaceAll("\\\\", "\\\\\\\\");
    452     }
    453 }
    454