aboutsummaryrefslogtreecommitdiffstats
path: root/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java
diff options
context:
space:
mode:
Diffstat (limited to 'sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java')
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java511
1 files changed, 511 insertions, 0 deletions
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java
new file mode 100644
index 0000000..28227c6
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java
@@ -0,0 +1,511 @@
+/*
+ * 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;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * The SDK manager parses the SDK folder and gives access to the content.
+ * @see PlatformTarget
+ * @see AddOnTarget
+ */
+public final class SdkManager {
+
+ public final static String PROP_VERSION_SDK = "ro.build.version.sdk";
+ public final static String PROP_VERSION_RELEASE = "ro.build.version.release";
+
+ private final static String ADDON_NAME = "name";
+ private final static String ADDON_VENDOR = "vendor";
+ private final static String ADDON_API = "api";
+ private final static String ADDON_DESCRIPTION = "description";
+ private final static String ADDON_LIBRARIES = "libraries";
+ private final static String ADDON_DEFAULT_SKIN = "skin";
+
+ private final static Pattern PATTERN_PROP = Pattern.compile(
+ "^([a-zA-Z0-9._-]+)\\s*=\\s*(.*)\\s*$");
+
+ private final static Pattern PATTERN_LIB_DATA = Pattern.compile(
+ "^([a-zA-Z0-9._-]+\\.jar);(.*)$", Pattern.CASE_INSENSITIVE);
+
+ /** List of items in the platform to check when parsing it. These paths are relative to the
+ * platform root folder. */
+ private final static String[] sPlatformContentList = new String[] {
+ SdkConstants.FN_FRAMEWORK_LIBRARY,
+ SdkConstants.FN_FRAMEWORK_AIDL,
+ SdkConstants.OS_SDK_TOOLS_FOLDER + SdkConstants.FN_AAPT,
+ SdkConstants.OS_SDK_TOOLS_FOLDER + SdkConstants.FN_AIDL,
+ SdkConstants.OS_SDK_TOOLS_FOLDER + SdkConstants.FN_DX,
+ SdkConstants.OS_SDK_TOOLS_LIB_FOLDER + SdkConstants.FN_DX_JAR,
+ };
+
+ /** the location of the SDK */
+ private final String mSdkLocation;
+ private IAndroidTarget[] mTargets;
+
+ /**
+ * Creates an {@link SdkManager} for a given sdk location.
+ * @param sdkLocation the location of the SDK.
+ * @param log the ISdkLog object receiving warning/error from the parsing.
+ * @return the created {@link SdkManager} or null if the location is not valid.
+ */
+ public static SdkManager createManager(String sdkLocation, ISdkLog log) {
+ try {
+ SdkManager manager = new SdkManager(sdkLocation);
+ ArrayList<IAndroidTarget> list = new ArrayList<IAndroidTarget>();
+ manager.loadPlatforms(list, log);
+ manager.loadAddOns(list, log);
+
+ // sort the targets/add-ons
+ Collections.sort(list);
+
+ manager.setTargets(list.toArray(new IAndroidTarget[list.size()]));
+
+ return manager;
+ } catch (IllegalArgumentException e) {
+ if (log != null) {
+ log.error(e, "Error parsing the sdk.");
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the location of the SDK.
+ */
+ public String getLocation() {
+ return mSdkLocation;
+ }
+
+ /**
+ * Returns the targets that are available in the SDK.
+ */
+ public IAndroidTarget[] getTargets() {
+ return mTargets;
+ }
+
+ /**
+ * Returns a target from a hash that was generated by {@link IAndroidTarget#hashString()}.
+ *
+ * @param hash the {@link IAndroidTarget} hash string.
+ * @return The matching {@link IAndroidTarget} or null.
+ */
+ public IAndroidTarget getTargetFromHashString(String hash) {
+ if (hash != null) {
+ for (IAndroidTarget target : mTargets) {
+ if (hash.equals(target.hashString())) {
+ return target;
+ }
+ }
+ }
+
+ return null;
+ }
+
+
+ private SdkManager(String sdkLocation) {
+ mSdkLocation = sdkLocation;
+ }
+
+ private void setTargets(IAndroidTarget[] targets) {
+ mTargets = targets;
+ }
+
+ /**
+ * Loads the Platforms from the SDK.
+ * @param list the list to fill with the platforms.
+ * @param log the ISdkLog object receiving warning/error from the parsing.
+ */
+ private void loadPlatforms(ArrayList<IAndroidTarget> list, ISdkLog log) {
+ File platformFolder = new File(mSdkLocation, SdkConstants.FD_PLATFORMS);
+ if (platformFolder.isDirectory()) {
+ File[] platforms = platformFolder.listFiles();
+
+ for (File platform : platforms) {
+ if (platform.isDirectory()) {
+ PlatformTarget target = loadPlatform(platform, log);
+ if (target != null) {
+ list.add(target);
+ }
+ } else if (log != null) {
+ log.warning("Ignoring platform '%1$s', not a folder.", platform.getName());
+ }
+ }
+
+ return;
+ }
+
+ String message = null;
+ if (platformFolder.exists() == false) {
+ message = "%s is missing.";
+ } else {
+ message = "%s is not a folder.";
+ }
+
+ throw new IllegalArgumentException(String.format(message,
+ platformFolder.getAbsolutePath()));
+ }
+
+ /**
+ * Loads a specific Platform at a given location.
+ * @param platform the location of the platform.
+ * @param log the ISdkLog object receiving warning/error from the parsing.
+ */
+ private PlatformTarget loadPlatform(File platform, ISdkLog log) {
+ File buildProp = new File(platform, SdkConstants.FN_BUILD_PROP);
+
+ if (buildProp.isFile()) {
+ Map<String, String> map = parsePropertyFile(buildProp, log);
+
+ if (map != null) {
+ // look for some specific values in the map.
+ try {
+ String apiNumber = map.get(PROP_VERSION_SDK);
+ String apiName = map.get(PROP_VERSION_RELEASE);
+ if (apiNumber != null && apiName != null) {
+ // api number and name looks valid, perform a few more checks
+ if (checkPlatformContent(platform, log) == false) {
+ return null;
+ }
+ // create the target.
+ PlatformTarget target = new PlatformTarget(
+ platform.getAbsolutePath(),
+ map,
+ Integer.parseInt(apiNumber),
+ apiName);
+
+ // need to parse the skins.
+ String[] skins = parseSkinFolder(target.getPath(IAndroidTarget.SKINS));
+ target.setSkins(skins);
+
+ return target;
+ }
+ } catch (NumberFormatException e) {
+ // looks like apiNumber does not parse to a number.
+ // Ignore this platform.
+ if (log != null) {
+ log.error(null,
+ "Ignoring platform '%1$s': %2$s is not a valid number in %3$s.",
+ platform.getName(), PROP_VERSION_SDK, SdkConstants.FN_BUILD_PROP);
+ }
+ }
+ }
+ } else if (log != null) {
+ log.error(null, "Ignoring platform '%1$s': %2$s is missing.", platform.getName(),
+ SdkConstants.FN_BUILD_PROP);
+ }
+
+ return null;
+ }
+
+ /**
+ * Loads the Add-on from the SDK.
+ * @param list the list to fill with the add-ons.
+ * @param log the ISdkLog object receiving warning/error from the parsing.
+ */
+ private void loadAddOns(ArrayList<IAndroidTarget> list, ISdkLog log) {
+ File addonFolder = new File(mSdkLocation, SdkConstants.FD_ADDONS);
+ if (addonFolder.isDirectory()) {
+ File[] addons = addonFolder.listFiles();
+
+ for (File addon : addons) {
+ // Add-ons have to be folders. Ignore files and no need to warn about them.
+ if (addon.isDirectory()) {
+ AddOnTarget target = loadAddon(addon, list, log);
+ if (target != null) {
+ list.add(target);
+ }
+ }
+ }
+
+ return;
+ }
+
+ String message = null;
+ if (addonFolder.exists() == false) {
+ message = "%s is missing.";
+ } else {
+ message = "%s is not a folder.";
+ }
+
+ throw new IllegalArgumentException(String.format(message,
+ addonFolder.getAbsolutePath()));
+ }
+
+ /**
+ * Loads a specific Add-on at a given location.
+ * @param addon the location of the addon.
+ * @param list
+ * @param log
+ */
+ private AddOnTarget loadAddon(File addon, ArrayList<IAndroidTarget> list, ISdkLog log) {
+ File addOnManifest = new File(addon, SdkConstants.FN_MANIFEST_INI);
+
+ if (addOnManifest.isFile()) {
+ Map<String, String> propertyMap = parsePropertyFile(addOnManifest, log);
+
+ if (propertyMap != null) {
+ // look for some specific values in the map.
+ // we require name, vendor, and api
+ String name = propertyMap.get(ADDON_NAME);
+ if (name == null) {
+ displayAddonManifestError(log, addon.getName(), ADDON_NAME);
+ return null;
+ }
+
+ String vendor = propertyMap.get(ADDON_VENDOR);
+ if (vendor == null) {
+ displayAddonManifestError(log, addon.getName(), ADDON_VENDOR);
+ return null;
+ }
+
+ String api = propertyMap.get(ADDON_API);
+ PlatformTarget baseTarget = null;
+ if (api == null) {
+ displayAddonManifestError(log, addon.getName(), ADDON_API);
+ return null;
+ } else {
+ try {
+ int apiValue = Integer.parseInt(api);
+ for (IAndroidTarget target : list) {
+ if (target.isPlatform() &&
+ target.getApiVersionNumber() == apiValue) {
+ baseTarget = (PlatformTarget)target;
+ break;
+ }
+ }
+
+ if (baseTarget == null) {
+ if (log != null) {
+ log.error(null,
+ "Ignoring add-on '%1$s': Unable to find base platform with API level %2$d",
+ addon.getName(), apiValue);
+ }
+
+ return null;
+ }
+ } catch (NumberFormatException e) {
+ // looks like apiNumber does not parse to a number.
+ // Ignore this add-on.
+ if (log != null) {
+ log.error(null,
+ "Ignoring add-on '%1$s': %2$s is not a valid number in %3$s.",
+ addon.getName(), ADDON_API, SdkConstants.FN_BUILD_PROP);
+ }
+ return null;
+ }
+ }
+
+ // get the optional description
+ String description = propertyMap.get(ADDON_DESCRIPTION);
+
+ // get the optional libraries
+ String librariesValue = propertyMap.get(ADDON_LIBRARIES);
+ Map<String, String[]> libMap = null;
+
+ if (librariesValue != null) {
+ librariesValue = librariesValue.trim();
+ if (librariesValue.length() > 0) {
+ // split in the string into the libraries name
+ String[] libraries = librariesValue.split(";");
+ if (libraries.length > 0) {
+ libMap = new HashMap<String, String[]>();
+ for (String libName : libraries) {
+ libName = libName.trim();
+
+ // get the library data from the properties
+ String libData = propertyMap.get(libName);
+
+ if (libData != null) {
+ // split the jar file from the description
+ Matcher m = PATTERN_LIB_DATA.matcher(libData);
+ if (m.matches()) {
+ libMap.put(libName, new String[] {
+ m.group(1), m.group(2) });
+ } else if (log != null) {
+ log.error(null,
+ "Ignoring library '%1$s', property value has wrong format\n\t%2$s",
+ libName, libData);
+ }
+ } else if (log != null) {
+ log.error(null,
+ "Ignoring library '%1$s', missing property value",
+ libName, libData);
+ }
+ }
+ }
+ }
+ }
+
+ AddOnTarget target = new AddOnTarget(addon.getAbsolutePath(), name, vendor,
+ description, libMap, baseTarget);
+
+ // need to parse the skins.
+ String[] skins = parseSkinFolder(target.getPath(IAndroidTarget.SKINS));
+
+ // get the default skin, or take it from the base platform if needed.
+ String defaultSkin = propertyMap.get(ADDON_DEFAULT_SKIN);
+
+ if (defaultSkin == null) {
+ if (skins.length == 1) {
+ defaultSkin = skins[1];
+ } else {
+ defaultSkin = baseTarget.getDefaultSkin();
+ }
+ }
+
+ target.setSkins(skins, defaultSkin);
+
+ return target;
+ }
+ } else if (log != null) {
+ log.error(null, "Ignoring add-on '%1$s': %2$s is missing.", addon.getName(),
+ SdkConstants.FN_MANIFEST_INI);
+ }
+
+ return null;
+ }
+
+ private void displayAddonManifestError(ISdkLog log, String addonName, String valueName) {
+ if (log != null) {
+ log.error(null, "Ignoring add-on '%1$s': '%2$s' is missing from %3$s.",
+ addonName, valueName, SdkConstants.FN_MANIFEST_INI);
+ }
+ }
+
+ /**
+ * Checks the given platform has all the required files, and returns true if they are all
+ * present.
+ * <p/>This checks the presence of the following files: android.jar, framework.aidl, aapt(.exe),
+ * aidl(.exe), dx(.bat), and dx.jar
+ */
+ private boolean checkPlatformContent(File platform, ISdkLog log) {
+ for (String relativePath : sPlatformContentList) {
+ File f = new File(platform, relativePath);
+ if (f.exists() == false) {
+ log.error(null,
+ "Ignoring platform '%1$s': %2$s is missing.",
+ platform.getName(), relativePath);
+ return false;
+ }
+
+ }
+ return true;
+ }
+
+
+ /**
+ * Parses a property file and returns
+ * @param buildProp the property file to parse
+ * @param log the ISdkLog object receiving warning/error from the parsing.
+ * @return the map of (key,value) pairs, or null if the parsing failed.
+ */
+ public static Map<String, String> parsePropertyFile(File buildProp, ISdkLog log) {
+ FileInputStream fis = null;
+ BufferedReader reader = null;
+ try {
+ fis = new FileInputStream(buildProp);
+ reader = new BufferedReader(new InputStreamReader(fis));
+
+ 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",
+ buildProp.getAbsolutePath(), 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) {
+ if (log != null) {
+ log.warning("Error parsing '%1$s': %2$s.", buildProp.getAbsolutePath(),
+ e.getMessage());
+ }
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ // pass
+ }
+ }
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (IOException e) {
+ // pass
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Parses the skin folder and builds the skin list.
+ * @param osPath The path of the skin root folder.
+ */
+ private String[] parseSkinFolder(String osPath) {
+ File skinRootFolder = new File(osPath);
+
+ if (skinRootFolder.isDirectory()) {
+ ArrayList<String> skinList = new ArrayList<String>();
+
+ File[] files = skinRootFolder.listFiles();
+
+ for (File skinFolder : files) {
+ if (skinFolder.isDirectory()) {
+ // check for layout file
+ File layout = new File(skinFolder, SdkConstants.FN_SKIN_LAYOUT);
+
+ if (layout.isFile()) {
+ // for now we don't parse the content of the layout and
+ // simply add the directory to the list.
+ skinList.add(skinFolder.getName());
+ }
+ }
+ }
+
+ return skinList.toArray(new String[skinList.size()]);
+ }
+
+ return new String[0];
+ }
+}