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