diff options
Diffstat (limited to 'sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java')
-rw-r--r-- | sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java | 511 |
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]; + } +} |