/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.sdklib.internal.project;
import com.android.io.FolderWrapper;
import com.android.io.IAbstractFile;
import com.android.io.IAbstractFolder;
import com.android.io.StreamException;
import com.android.sdklib.ISdkLog;
import com.android.sdklib.SdkConstants;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Class representing project properties for both ADT and Ant-based build.
* <p/>The class is associated to a {@link PropertyType} that indicate which of the project
* property file is represented.
* <p/>To load an existing file, use {@link #load(IAbstractFolder, PropertyType)}.
* <p/>The class is meant to be always in sync (or at least not newer) than the file it represents.
* Once created, it can only be updated through {@link #reload()}
*
* <p/>The make modification or make new file, use a {@link ProjectPropertiesWorkingCopy} instance,
* either through {@link #create(IAbstractFolder, PropertyType)} or through
* {@link #makeWorkingCopy()}.
*
*/
public class ProjectProperties {
protected final static Pattern PATTERN_PROP = Pattern.compile(
"^([a-zA-Z0-9._-]+)\\s*=\\s*(.*)\\s*$");
/** The property name for the project target */
public final static String PROPERTY_TARGET = "target";
public final static String PROPERTY_LIBRARY = "android.library";
public final static String PROPERTY_LIB_REF = "android.library.reference.";
private final static String PROPERTY_LIB_REF_REGEX = "android.library.reference.\\d+";
public final static String PROPERTY_PROGUARD_CONFIG = "proguard.config";
public final static String PROPERTY_RULES_PATH = "layoutrules.jars";
public final static String PROPERTY_SDK = "sdk.dir";
// LEGACY - Kept so that we can actually remove it from local.properties.
private final static String PROPERTY_SDK_LEGACY = "sdk-location";
public final static String PROPERTY_SPLIT_BY_DENSITY = "split.density";
public final static String PROPERTY_SPLIT_BY_ABI = "split.abi";
public final static String PROPERTY_SPLIT_BY_LOCALE = "split.locale";
public final static String PROPERTY_TESTED_PROJECT = "tested.project.dir";
public final static String PROPERTY_BUILD_SOURCE_DIR = "source.dir";
public final static String PROPERTY_BUILD_OUT_DIR = "out.dir";
public final static String PROPERTY_PACKAGE = "package";
public final static String PROPERTY_VERSIONCODE = "versionCode";
public final static String PROPERTY_PROJECTS = "projects";
public final static String PROPERTY_KEY_STORE = "key.store";
public final static String PROPERTY_KEY_ALIAS = "key.alias";
public static enum PropertyType {
BUILD(SdkConstants.FN_BUILD_PROPERTIES, BUILD_HEADER, new String[] {
PROPERTY_BUILD_SOURCE_DIR, PROPERTY_BUILD_OUT_DIR
}, null),
DEFAULT(SdkConstants.FN_DEFAULT_PROPERTIES, DEFAULT_HEADER, new String[] {
PROPERTY_TARGET, PROPERTY_LIBRARY, PROPERTY_LIB_REF_REGEX,
PROPERTY_KEY_STORE, PROPERTY_KEY_ALIAS, PROPERTY_PROGUARD_CONFIG,
PROPERTY_RULES_PATH
}, null),
LOCAL(SdkConstants.FN_LOCAL_PROPERTIES, LOCAL_HEADER, new String[] {
PROPERTY_SDK
},
new String[] { PROPERTY_SDK_LEGACY }),
EXPORT(SdkConstants.FN_EXPORT_PROPERTIES, EXPORT_HEADER, new String[] {
PROPERTY_PACKAGE, PROPERTY_VERSIONCODE, PROPERTY_PROJECTS,
PROPERTY_KEY_STORE, PROPERTY_KEY_ALIAS
}, null);
private final String mFilename;
private final String mHeader;
private final Set<String> mKnownProps;
private final Set<String> mRemovedProps;
PropertyType(String filename, String header, String[] validProps, String[] removedProps) {
mFilename = filename;
mHeader = header;
HashSet<String> s = new HashSet<String>();
if (validProps != null) {
s.addAll(Arrays.asList(validProps));
}
mKnownProps = Collections.unmodifiableSet(s);
s = new HashSet<String>();
if (removedProps != null) {
s.addAll(Arrays.asList(removedProps));
}
mRemovedProps = Collections.unmodifiableSet(s);
}
public String getFilename() {
return mFilename;
}
public String getHeader() {
return mHeader;
}
/**
* Returns whether a given property is known for the property type.
*/
public boolean isKnownProperty(String name) {
for (String propRegex : mKnownProps) {
if (propRegex.equals(name) || Pattern.matches(propRegex, name)) {
return true;
}
}
return false;
}
/**
* Returns whether a given property should be removed for the property type.
*/
public boolean isRemovedProperty(String name) {
for (String propRegex : mRemovedProps) {
if (propRegex.equals(name) || Pattern.matches(propRegex, name)) {
return true;
}
}
return false;
}
}
private final static String LOCAL_HEADER =
// 1-------10--------20--------30--------40--------50--------60--------70--------80
"# This file is automatically generated by Android Tools.\n" +
"# Do not modify this file -- YOUR CHANGES WILL BE ERASED!\n" +
"#\n" +
"# This file must *NOT* be checked in Version Control Systems,\n" +
"# as it contains information specific to your local configuration.\n" +
"\n";
private final static String DEFAULT_HEADER =
// 1-------10--------20--------30--------40--------50--------60--------70--------80
"# This file is automatically generated by Android Tools.\n" +
"# Do not modify this file -- YOUR CHANGES WILL BE ERASED!\n" +
"#\n" +
"# This file must be checked in Version Control Systems.\n" +
"#\n" +
"# To customize properties used by the Ant build system use,\n" +
"# \"build.properties\", and override values to adapt the script to your\n" +
"# project structure.\n" +
"\n";
private final static String BUILD_HEADER =
// 1-------10--------20--------30--------40--------50--------60--------70--------80
"# This file is used to override default values used by the Ant build system.\n" +
"#\n" +
"# This file must be checked in Version Control Systems, as it is\n" +
"# integral to the build system of your project.\n" +
"\n" +
"# This file is only used by the Ant script.\n" +
"\n" +
"# You can use this to override default values such as\n" +
"# 'source.dir' for the location of your java source folder and\n" +
"# 'out.dir' for the location of your output folder.\n" +
"\n" +
"# You can also use it define how the release builds are signed by declaring\n" +
"# the following properties:\n" +
"# 'key.store' for the location of your keystore and\n" +
"# 'key.alias' for the name of the key to use.\n" +
"# The password will be asked during the build when you use the 'release' target.\n" +
"\n";
private final static String EXPORT_HEADER =
// 1-------10--------20--------30--------40--------50--------60--------70--------80
"# Export properties\n" +
"#\n" +
"# This file must be checked in Version Control Systems.\n" +
"\n" +
"# The main content for this file is:\n" +
"# - package name for the application being export\n" +
"# - list of the projects being export\n" +
"# - version code for the application\n" +
"\n" +
"# You can also use it define how the release builds are signed by declaring\n" +
"# the following properties:\n" +
"# 'key.store' for the location of your keystore and\n" +
"# 'key.alias' for the name of the key alias to use.\n" +
"# The password will be asked during the build when you use the 'release' target.\n" +
"\n";
protected final IAbstractFolder mProjectFolder;
protected final Map<String, String> mProperties;
protected final PropertyType mType;
/**
* Loads a project properties file and return a {@link ProjectProperties} object
* containing the properties
*
* @param projectFolderOsPath the project folder.
* @param type One the possible {@link PropertyType}s.
*/
public static ProjectProperties load(String projectFolderOsPath, PropertyType type) {
IAbstractFolder wrapper = new FolderWrapper(projectFolderOsPath);
return load(wrapper, type);
}
/**
* Loads a project properties file and return a {@link ProjectProperties} object
* containing the properties
*
* @param projectFolder the project folder.
* @param type One the possible {@link PropertyType}s.
*/
public static ProjectProperties load(IAbstractFolder projectFolder, PropertyType type) {
if (projectFolder.exists()) {
IAbstractFile propFile = projectFolder.getFile(type.mFilename);
if (propFile.exists()) {
Map<String, String> map = parsePropertyFile(propFile, null /* log */);
if (map != null) {
return new ProjectProperties(projectFolder, map, type);
}
}
}
return null;
}
/**
* Creates a new project properties object, with no properties.
* <p/>The file is not created until {@link ProjectPropertiesWorkingCopy#save()} is called.
* @param projectFolderOsPath the project folder.
* @param type the type of property file to create
*/
public static ProjectPropertiesWorkingCopy create(String projectFolderOsPath,
PropertyType type) {
// create and return a ProjectProperties with an empty map.
IAbstractFolder folder = new FolderWrapper(projectFolderOsPath);
return create(folder, type);
}
/**
* Creates a new project properties object, with no properties.
* <p/>The file is not created until {@link ProjectPropertiesWorkingCopy#save()} is called.
* @param projectFolder the project folder.
* @param type the type of property file to create
*/
public static ProjectPropertiesWorkingCopy create(IAbstractFolder projectFolder,
PropertyType type) {
// create and return a ProjectProperties with an empty map.
return new ProjectPropertiesWorkingCopy(projectFolder, new HashMap<String, String>(), type);
}
/**
* Creates and returns a copy of the current properties as a
* {@link ProjectPropertiesWorkingCopy} that can be modified and saved.
* @return a new instance of {@link ProjectPropertiesWorkingCopy}
*/
public ProjectPropertiesWorkingCopy makeWorkingCopy() {
// copy the current properties in a new map
HashMap<String, String> propList = new HashMap<String, String>();
propList.putAll(mProperties);
return new ProjectPropertiesWorkingCopy(mProjectFolder, propList, mType);
}
/**
* Returns the type of the property file.
*
* @see PropertyType
*/
public PropertyType getType() {
return mType;
}
/**
* Returns the value of a property.
* @param name the name of the property.
* @return the property value or null if the property is not set.
*/
public synchronized String getProperty(String name) {
return mProperties.get(name);
}
/**
* Returns a set of the property keys. Unlike {@link Map#keySet()} this is not a view of the
* map keys. Modifying the returned {@link Set} will not impact the underlying {@link Map}.
*/
public synchronized Set<String> keySet() {
return new HashSet<String>(mProperties.keySet());
}
/**
* Reloads the properties from the underlying file.
*/
public synchronized void reload() {
if (mProjectFolder.exists()) {
IAbstractFile propFile = mProjectFolder.getFile(mType.mFilename);
if (propFile.exists()) {
Map<String, String> map = parsePropertyFile(propFile, null /* log */);
if (map != null) {
mProperties.clear();
mProperties.putAll(map);
}
}
}
}
/**
* Parses a property file (using UTF-8 encoding) and returns a map of the content.
* <p/>If the file is not present, null is returned with no error messages sent to the log.
*
* @param propFile the property file to parse
* @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null.
* @return the map of (key,value) pairs, or null if the parsing failed.
*/
public static Map<String, String> parsePropertyFile(IAbstractFile propFile, ISdkLog log) {
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(propFile.getContents(),
SdkConstants.INI_CHARSET));
String line = null;
Map<String, String> map = new HashMap<String, String>();
while ((line = reader.readLine()) != null) {
if (line.length() > 0 && line.charAt(0) != '#') {
Matcher m = PATTERN_PROP.matcher(line);
if (m.matches()) {
map.put(m.group(1), m.group(2));
} else {
log.warning("Error parsing '%1$s': \"%2$s\" is not a valid syntax",
propFile.getOsLocation(),
line);
return null;
}
}
}
return map;
} catch (FileNotFoundException e) {
// this should not happen since we usually test the file existence before
// calling the method.
// Return null below.
} catch (IOException e) {
log.warning("Error parsing '%1$s': %2$s.",
propFile.getOsLocation(),
e.getMessage());
} catch (StreamException e) {
log.warning("Error parsing '%1$s': %2$s.",
propFile.getOsLocation(),
e.getMessage());
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
// pass
}
}
}
return null;
}
/**
* Private constructor.
* <p/>
* Use {@link #load(String, PropertyType)} or {@link #create(String, PropertyType)}
* to instantiate.
*/
protected ProjectProperties(IAbstractFolder projectFolder, Map<String, String> map,
PropertyType type) {
mProjectFolder = projectFolder;
mProperties = map;
mType = type;
}
}