Home | History | Annotate | Download | only in project
      1 /*
      2  * Copyright (C) 2010 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.SdkConstants;
     20 import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
     21 import com.android.sdklib.io.IAbstractFile;
     22 import com.android.sdklib.io.IAbstractFolder;
     23 import com.android.sdklib.io.StreamException;
     24 
     25 import java.io.BufferedReader;
     26 import java.io.ByteArrayOutputStream;
     27 import java.io.IOException;
     28 import java.io.InputStreamReader;
     29 import java.io.OutputStream;
     30 import java.io.OutputStreamWriter;
     31 import java.util.HashMap;
     32 import java.util.HashSet;
     33 import java.util.Map;
     34 import java.util.Map.Entry;
     35 import java.util.regex.Matcher;
     36 
     37 /**
     38  * A modifyable and saveable copy of a {@link ProjectProperties}.
     39  * <p/>This copy gives access to modification method such as {@link #setProperty(String, String)}
     40  * and {@link #removeProperty(String)}.
     41  *
     42  * To get access to an instance, use {@link ProjectProperties#makeWorkingCopy()} or
     43  * {@link ProjectProperties#create(IAbstractFolder, PropertyType)}.
     44  */
     45 @SuppressWarnings("deprecation")
     46 public class ProjectPropertiesWorkingCopy extends ProjectProperties {
     47 
     48     private final static Map<String, String> COMMENT_MAP = new HashMap<String, String>();
     49     static {
     50 //               1-------10--------20--------30--------40--------50--------60--------70--------80
     51         COMMENT_MAP.put(PROPERTY_TARGET,
     52                 "# Project target.\n");
     53         COMMENT_MAP.put(PROPERTY_SPLIT_BY_DENSITY,
     54                 "# Indicates whether an apk should be generated for each density.\n");
     55         COMMENT_MAP.put(PROPERTY_SDK,
     56                 "# location of the SDK. This is only used by Ant\n" +
     57                 "# For customization when using a Version Control System, please read the\n" +
     58                 "# header note.\n");
     59         COMMENT_MAP.put(PROPERTY_APP_PACKAGE,
     60                 "# The name of your application package as defined in the manifest.\n" +
     61                 "# Used by the 'uninstall' rule.\n");
     62         COMMENT_MAP.put(PROPERTY_PACKAGE,
     63                 "# Package of the application being exported\n");
     64         COMMENT_MAP.put(PROPERTY_VERSIONCODE,
     65                 "# Major version code\n");
     66         COMMENT_MAP.put(PROPERTY_PROJECTS,
     67                 "# List of the Android projects being used for the export.\n" +
     68                 "# The list is made of paths that are relative to this project,\n" +
     69                 "# using forward-slash (/) as separator, and are separated by colons (:).\n");
     70     }
     71 
     72 
     73     /**
     74      * Sets a new properties. If a property with the same name already exists, it is replaced.
     75      * @param name the name of the property.
     76      * @param value the value of the property.
     77      */
     78     public synchronized void setProperty(String name, String value) {
     79         mProperties.put(name, value);
     80     }
     81 
     82     /**
     83      * Removes a property and returns its previous value (or null if the property did not exist).
     84      * @param name the name of the property to remove.
     85      */
     86     public synchronized String removeProperty(String name) {
     87         return mProperties.remove(name);
     88     }
     89 
     90     /**
     91      * Merges all properties from the given file into the current properties.
     92      * <p/>
     93      * This emulates the Ant behavior: existing properties are <em>not</em> overridden.
     94      * Only new undefined properties become defined.
     95      * <p/>
     96      * Typical usage:
     97      * <ul>
     98      * <li>Create a ProjectProperties with {@link PropertyType#BUILD}
     99      * <li>Merge in values using {@link PropertyType#DEFAULT}
    100      * <li>The result is that this contains all the properties from default plus those
    101      *     overridden by the build.properties file.
    102      * </ul>
    103      *
    104      * @param type One the possible {@link PropertyType}s.
    105      * @return this object, for chaining.
    106      */
    107     public synchronized ProjectPropertiesWorkingCopy merge(PropertyType type) {
    108         if (mProjectFolder.exists() && mType != type) {
    109             IAbstractFile propFile = mProjectFolder.getFile(type.getFilename());
    110             if (propFile.exists()) {
    111                 Map<String, String> map = parsePropertyFile(propFile, null /* log */);
    112                 if (map != null) {
    113                     for (Entry<String, String> entry : map.entrySet()) {
    114                         String key = entry.getKey();
    115                         String value = entry.getValue();
    116                         if (!mProperties.containsKey(key) && value != null) {
    117                             mProperties.put(key, value);
    118                         }
    119                     }
    120                 }
    121             }
    122         }
    123         return this;
    124     }
    125 
    126 
    127     /**
    128      * Saves the property file, using UTF-8 encoding.
    129      * @throws IOException
    130      * @throws StreamException
    131      */
    132     public synchronized void save() throws IOException, StreamException {
    133         IAbstractFile toSave = mProjectFolder.getFile(mType.getFilename());
    134 
    135         // write the whole file in a byte array before dumping it in the file. This
    136         // This is so that if the file already existing
    137         ByteArrayOutputStream baos = new ByteArrayOutputStream();
    138         OutputStreamWriter writer = new OutputStreamWriter(baos, SdkConstants.INI_CHARSET);
    139 
    140         if (toSave.exists()) {
    141             BufferedReader reader = new BufferedReader(new InputStreamReader(toSave.getContents(),
    142                     SdkConstants.INI_CHARSET));
    143 
    144             // since we're reading the existing file and replacing values with new ones, or skipping
    145             // removed values, we need to record what properties have been visited, so that
    146             // we can figure later what new properties need to be added at the end of the file.
    147             HashSet<String> visitedProps = new HashSet<String>();
    148 
    149             String line = null;
    150             while ((line = reader.readLine()) != null) {
    151                 // check if this is a line containing a property.
    152                 if (line.length() > 0 && line.charAt(0) != '#') {
    153 
    154                     Matcher m = PATTERN_PROP.matcher(line);
    155                     if (m.matches()) {
    156                         String key = m.group(1);
    157                         String value = m.group(2);
    158 
    159                         // record the prop
    160                         visitedProps.add(key);
    161 
    162                         // check if the property still exists.
    163                         if (mProperties.containsKey(key)) {
    164                             // put the new value.
    165                             value = mProperties.get(key);
    166                         } else {
    167                             // property doesn't exist. Check if it's a known property.
    168                             // if it's a known one, we'll remove it, otherwise, leave it untouched.
    169                             if (mType.isKnownProperty(key)) {
    170                                 value = null;
    171                             }
    172                         }
    173 
    174                         // if the value is still valid, write it down.
    175                         if (value != null) {
    176                             writeValue(writer, key, value, false /*addComment*/);
    177                         }
    178                     } else  {
    179                         // the line was wrong, let's just ignore it so that it's removed from the
    180                         // file.
    181                     }
    182                 } else {
    183                     // non-property line: just write the line in the output as-is.
    184                     writer.append(line).append('\n');
    185                 }
    186             }
    187 
    188             // now add the new properties.
    189             for (Entry<String, String> entry : mProperties.entrySet()) {
    190                 if (visitedProps.contains(entry.getKey()) == false) {
    191                     String value = entry.getValue();
    192                     if (value != null) {
    193                         writeValue(writer, entry.getKey(), value, true /*addComment*/);
    194                     }
    195                 }
    196             }
    197 
    198         } else {
    199             // new file, just write it all
    200             // write the header
    201             writer.write(mType.getHeader());
    202 
    203             // write the properties.
    204             for (Entry<String, String> entry : mProperties.entrySet()) {
    205                 String value = entry.getValue();
    206                 if (value != null) {
    207                     writeValue(writer, entry.getKey(), value, true /*addComment*/);
    208                 }
    209             }
    210         }
    211 
    212         writer.flush();
    213 
    214         // now put the content in the file.
    215         OutputStream filestream = toSave.getOutputStream();
    216         filestream.write(baos.toByteArray());
    217         filestream.flush();
    218     }
    219 
    220     private void writeValue(OutputStreamWriter writer, String key, String value,
    221             boolean addComment) throws IOException {
    222         if (addComment) {
    223             String comment = COMMENT_MAP.get(key);
    224             if (comment != null) {
    225                 writer.write(comment);
    226             }
    227         }
    228 
    229         value = value.replaceAll("\\\\", "\\\\\\\\");
    230         writer.write(String.format("%s=%s\n", key, value));
    231     }
    232 
    233     /**
    234      * Private constructor.
    235      * <p/>
    236      * Use {@link #load(String, PropertyType)} or {@link #create(String, PropertyType)}
    237      * to instantiate.
    238      */
    239     ProjectPropertiesWorkingCopy(IAbstractFolder projectFolder, Map<String, String> map,
    240             PropertyType type) {
    241         super(projectFolder, map, type);
    242     }
    243 
    244 }
    245