package com.android.sdklib.internal.repository;\r
\r
import com.android.sdklib.IAndroidTarget;\r
+import com.android.sdklib.ISdkLog;\r
import com.android.sdklib.SdkConstants;\r
import com.android.sdklib.SdkManager;\r
import com.android.sdklib.internal.repository.Archive.Arch;\r
import com.android.sdklib.internal.repository.Archive.Os;\r
-import com.android.sdklib.repository.SdkRepository;\r
\r
-import org.w3c.dom.Document;\r
-import org.w3c.dom.Node;\r
-import org.xml.sax.InputSource;\r
-import org.xml.sax.SAXException;\r
-\r
-import java.io.BufferedReader;\r
import java.io.File;\r
-import java.io.FileReader;\r
+import java.io.FileInputStream;\r
import java.io.IOException;\r
-import java.io.InputStream;\r
-import java.io.StringReader;\r
import java.util.ArrayList;\r
-import java.util.HashMap;\r
import java.util.HashSet;\r
+import java.util.Properties;\r
import java.util.Set;\r
-import java.util.regex.Matcher;\r
-import java.util.regex.Pattern;\r
-\r
-import javax.xml.XMLConstants;\r
-import javax.xml.parsers.DocumentBuilder;\r
-import javax.xml.parsers.DocumentBuilderFactory;\r
-import javax.xml.parsers.ParserConfigurationException;\r
-import javax.xml.transform.stream.StreamSource;\r
-import javax.xml.validation.Schema;\r
-import javax.xml.validation.SchemaFactory;\r
-import javax.xml.validation.Validator;\r
\r
/**\r
* Scans a local SDK to find which packages are currently installed.\r
*/\r
public class LocalSdkParser {\r
\r
- private static final String SOURCE_XML = "source.xml"; //$NON-NLS-1$ // TODO move to global constants\r
+ static final String SOURCE_PROPERTIES = "source.properties"; //$NON-NLS-1$\r
private Package[] mPackages;\r
\r
public LocalSdkParser() {\r
}\r
\r
/**\r
- * Returns the packages found by the last call to {@link #parseSdk(String, SdkManager)}.\r
+ * Returns the packages found by the last call to {@link #parseSdk(String, SdkManager, ISdkLog)}.\r
*/\r
public Package[] getPackages() {\r
return mPackages;\r
\r
/**\r
* Clear the internal packages list. After this call, {@link #getPackages()} will return\r
- * null till {@link #parseSdk(String, SdkManager)} is called.\r
+ * null till {@link #parseSdk(String, SdkManager, ISdkLog)} is called.\r
*/\r
public void clearPackages() {\r
mPackages = null;\r
*\r
* @param osSdkRoot The path to the SDK folder.\r
* @param sdkManager An existing SDK manager to list current platforms and addons.\r
+ * @param log An SDK logger object.\r
* @return The packages found. Can be retrieved later using {@link #getPackages()}.\r
*/\r
- public Package[] parseSdk(String osSdkRoot, SdkManager sdkManager) {\r
+ public Package[] parseSdk(String osSdkRoot, SdkManager sdkManager, ISdkLog log) {\r
ArrayList<Package> packages = new ArrayList<Package>();\r
+ HashSet<File> visited = new HashSet<File>();\r
\r
- Package pkg = scanDoc(new File(osSdkRoot, SdkConstants.FD_DOCS));\r
+ File dir = new File(osSdkRoot, SdkConstants.FD_DOCS);\r
+ Package pkg = scanDoc(dir, log);\r
if (pkg != null) {\r
packages.add(pkg);\r
+ visited.add(dir);\r
}\r
\r
- pkg = scanTools(new File(osSdkRoot, SdkConstants.FD_TOOLS));\r
+ dir = new File(osSdkRoot, SdkConstants.FD_TOOLS);\r
+ pkg = scanTools(dir, log);\r
if (pkg != null) {\r
packages.add(pkg);\r
+ visited.add(dir);\r
}\r
\r
// for platforms and add-ons, rely on the SdkManager parser\r
for(IAndroidTarget target : sdkManager.getTargets()) {\r
- pkg = null;\r
-\r
- if (target.isPlatform()) {\r
- pkg = parseXml(new File(target.getLocation(), SOURCE_XML),\r
- SdkRepository.NODE_PLATFORM);\r
- if (pkg == null) {\r
- pkg = new PlatformPackage(target);\r
- }\r
\r
- } else {\r
- pkg = parseXml(new File(target.getLocation(), SOURCE_XML),\r
- SdkRepository.NODE_ADD_ON);\r
+ Properties props = parseProperties(new File(target.getLocation(), SOURCE_PROPERTIES));\r
\r
- if (pkg == null) {\r
- pkg = new AddonPackage(target);\r
+ try {\r
+ if (target.isPlatform()) {\r
+ pkg = new PlatformPackage(target, props);\r
+ } else {\r
+ pkg = new AddonPackage(target, props);\r
}\r
+ } catch (Exception e) {\r
+ log.error(e, null);\r
}\r
\r
if (pkg != null) {\r
packages.add(pkg);\r
+ visited.add(new File(target.getLocation()));\r
}\r
}\r
\r
+ scanExtra(osSdkRoot, visited, packages, log);\r
+\r
mPackages = packages.toArray(new Package[packages.size()]);\r
return mPackages;\r
}\r
\r
/**\r
+ * Find any other directory what we haven't successfully visited and\r
+ * assume they contain extra packages.\r
+ * @param log\r
+ */\r
+ private void scanExtra(String osSdkRoot,\r
+ HashSet<File> visited,\r
+ ArrayList<Package> packages,\r
+ ISdkLog log) {\r
+ File root = new File(osSdkRoot);\r
+ for (File dir : root.listFiles()) {\r
+ if (dir.isDirectory() && !visited.contains(dir)) {\r
+\r
+ Properties props = parseProperties(new File(dir, SOURCE_PROPERTIES));\r
+ if (props != null) {\r
+ try {\r
+ ExtraPackage pkg = new ExtraPackage(\r
+ null, //source\r
+ props, //properties\r
+ dir.getName(), //path\r
+ 0, //revision\r
+ null, //license\r
+ "Tools", //description\r
+ null, //descUrl\r
+ Os.getCurrentOs(), //archiveOs\r
+ Arch.getCurrentArch(), //archiveArch\r
+ dir.getPath() //archiveOsPath\r
+ );\r
+\r
+ // We only accept this as an extra package if it has a valid local path.\r
+ if (pkg.isPathValid()) {\r
+ packages.add(pkg);\r
+ }\r
+ } catch (Exception e) {\r
+ log.error(e, null);\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
* Try to find a tools package at the given location.\r
* Returns null if not found.\r
*/\r
- private Package scanTools(File toolFolder) {\r
- // Can we find a source.xml?\r
- Package pkg = parseXml(new File(toolFolder, SOURCE_XML), SdkRepository.NODE_TOOL);\r
+ private Package scanTools(File toolFolder, ISdkLog log) {\r
+ // Can we find some properties?\r
+ Properties props = parseProperties(new File(toolFolder, SOURCE_PROPERTIES));\r
\r
// We're not going to check that all tools are present. At the very least\r
// we should expect to find adb, android and an emulator adapted to the current OS.\r
return null;\r
}\r
\r
- // if we don't have the package info, make one up\r
- if (pkg == null) {\r
- pkg = new ToolPackage(\r
+ // Create are package. use the properties if we found any.\r
+ try {\r
+ ToolPackage pkg = new ToolPackage(\r
null, //source\r
+ props, //properties\r
0, //revision\r
null, //license\r
"Tools", //description\r
Arch.getCurrentArch(), //archiveArch\r
toolFolder.getPath() //archiveOsPath\r
);\r
- }\r
\r
- return pkg;\r
+ return pkg;\r
+ } catch (Exception e) {\r
+ log.error(e, null);\r
+ }\r
+ return null;\r
}\r
\r
/**\r
* Try to find a docs package at the given location.\r
* Returns null if not found.\r
*/\r
- private Package scanDoc(File docFolder) {\r
- // Can we find a source.xml?\r
- Package pkg = parseXml(new File(docFolder, SOURCE_XML), SdkRepository.NODE_DOC);\r
+ private Package scanDoc(File docFolder, ISdkLog log) {\r
+ // Can we find some properties?\r
+ Properties props = parseProperties(new File(docFolder, SOURCE_PROPERTIES));\r
\r
// To start with, a doc folder should have an "index.html" to be acceptable.\r
- String html = readFile(new File(docFolder, "index.html"));\r
- if (html != null) {\r
- // Try to find something that looks like this line:\r
- // <a href="./sdk/1.5_r1/index.html">\r
- // We should find one or more of these and we want the highest version\r
- // and release numbers. Note that unfortunately that doesn't give us\r
- // the api-level we care about for the doc package.\r
-\r
- String found = null;\r
- Pattern re = Pattern.compile(\r
- "<a\\s+href=\"./sdk/(\\d\\.\\d_r\\d)/index.html\">",\r
- Pattern.DOTALL);\r
- Matcher m = re.matcher(html);\r
- while(m.find()) {\r
- String v = m.group(1);\r
- if (found == null || v.compareTo(found) == 1) {\r
- found = v;\r
- }\r
- }\r
-\r
- if (found == null) {\r
- // That doesn't look like a doc folder.\r
- return null;\r
- }\r
-\r
- // We found the line, so it seems like an SDK doc.\r
- // Create a pkg if we don't have one yet.\r
-\r
- if (pkg == null) {\r
- pkg = new DocPackage(\r
+ // We don't actually check the content of the file.\r
+ if (new File(docFolder, "index.html").isFile()) {\r
+ try {\r
+ DocPackage pkg = new DocPackage(\r
null, //source\r
+ props, //properties\r
0, //apiLevel\r
0, //revision\r
null, //license\r
- String.format("Documentation for %1$s", found), //description\r
+ null, //description\r
null, //descUrl\r
Os.getCurrentOs(), //archiveOs\r
Arch.getCurrentArch(), //archiveArch\r
docFolder.getPath() //archiveOsPath\r
);\r
- }\r
- }\r
\r
- return pkg;\r
- }\r
-\r
- /**\r
- * Parses the given XML file for the specific element filter.\r
- * The element must one of the package type local names: doc, tool, platform or addon.\r
- * Returns null if no such package was found.\r
- */\r
- private Package parseXml(File sourceXmlFile, String elementFilter) {\r
-\r
- String xml = readFile(sourceXmlFile);\r
- if (xml != null) {\r
- if (validateXml(xml)) {\r
- return parsePackages(xml, elementFilter);\r
+ return pkg;\r
+ } catch (Exception e) {\r
+ log.error(e, null);\r
}\r
}\r
\r
}\r
\r
/**\r
- * Parses the given XML to find the specific element filter.\r
- * The element must one of the package type local names: doc, tool, platform or addon.\r
- * Returns null if no such package was found.\r
+ * Parses the given file as properties file if it exists.\r
+ * Returns null if the file does not exist, cannot be parsed or has no properties.\r
*/\r
- private Package parsePackages(String xml, String elementFilter) {\r
-\r
+ private Properties parseProperties(File propsFile) {\r
+ FileInputStream fis = null;\r
try {\r
- Document doc = getDocument(xml);\r
-\r
- Node root = getFirstChild(doc, SdkRepository.NODE_SDK_REPOSITORY);\r
- if (root != null) {\r
-\r
- // Parse license definitions\r
- HashMap<String, String> licenses = new HashMap<String, String>();\r
- for (Node child = root.getFirstChild();\r
- child != null;\r
- child = child.getNextSibling()) {\r
- if (child.getNodeType() == Node.ELEMENT_NODE &&\r
- SdkRepository.NS_SDK_REPOSITORY.equals(child.getNamespaceURI()) &&\r
- child.getLocalName().equals(SdkRepository.NODE_LICENSE)) {\r
- Node id = child.getAttributes().getNamedItem(SdkRepository.ATTR_ID);\r
- if (id != null) {\r
- licenses.put(id.getNodeValue(), child.getTextContent());\r
- }\r
- }\r
- }\r
-\r
- // Parse packages\r
- for (Node child = root.getFirstChild();\r
- child != null;\r
- child = child.getNextSibling()) {\r
- if (child.getNodeType() == Node.ELEMENT_NODE &&\r
- SdkRepository.NS_SDK_REPOSITORY.equals(child.getNamespaceURI()) &&\r
- elementFilter.equals(child.getLocalName())) {\r
- String name = child.getLocalName();\r
-\r
- try {\r
- if (SdkRepository.NODE_ADD_ON.equals(name)) {\r
- return new AddonPackage(null /*source*/, child, licenses);\r
-\r
- } else if (SdkRepository.NODE_PLATFORM.equals(name)) {\r
- return new PlatformPackage(null /*source*/, child, licenses);\r
-\r
- } else if (SdkRepository.NODE_DOC.equals(name)) {\r
- return new DocPackage(null /*source*/, child, licenses);\r
-\r
- } else if (SdkRepository.NODE_TOOL.equals(name)) {\r
- return new ToolPackage(null /*source*/, child, licenses);\r
- }\r
- } catch (Exception e) {\r
- // Ignore invalid packages\r
- }\r
- }\r
- }\r
- }\r
-\r
- } catch (Exception e) {\r
- // ignore\r
- }\r
+ if (propsFile.exists()) {\r
+ fis = new FileInputStream(propsFile);\r
\r
- return null;\r
- }\r
+ Properties props = new Properties();\r
+ props.load(fis);\r
\r
- /**\r
- * Reads a file as a string.\r
- * Returns null if the file could not be read.\r
- */\r
- private String readFile(File sourceXmlFile) {\r
- FileReader fr = null;\r
- try {\r
- fr = new FileReader(sourceXmlFile);\r
- BufferedReader br = new BufferedReader(fr);\r
- StringBuilder dest = new StringBuilder();\r
- char[] buf = new char[65536];\r
- int n;\r
- while ((n = br.read(buf)) > 0) {\r
- if (n > 0) {\r
- dest.append(buf, 0, n);\r
+ // To be valid, there must be at least one property in it.\r
+ if (props.size() > 0) {\r
+ return props;\r
}\r
}\r
- return dest.toString();\r
\r
} catch (IOException e) {\r
- // ignore\r
-\r
+ e.printStackTrace();\r
} finally {\r
- if (fr != null) {\r
+ if (fis != null) {\r
try {\r
- fr.close();\r
+ fis.close();\r
} catch (IOException e) {\r
- // ignore\r
- }\r
- }\r
- }\r
-\r
- return null;\r
- }\r
-\r
- /**\r
- * Validates this XML against the SDK Repository schema.\r
- * Returns true if the XML was correctly validated.\r
- */\r
- private boolean validateXml(String xml) {\r
-\r
- try {\r
- Validator validator = getValidator();\r
- validator.validate(new StreamSource(new StringReader(xml)));\r
- return true;\r
-\r
- } catch (SAXException e) {\r
- // ignore\r
-\r
- } catch (IOException e) {\r
- // ignore\r
- }\r
-\r
- return false;\r
- }\r
-\r
- /**\r
- * Helper method that returns a validator for our XSD\r
- */\r
- private Validator getValidator() throws SAXException {\r
- InputStream xsdStream = SdkRepository.getXsdStream();\r
- SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);\r
-\r
- // This may throw a SAX Exception if the schema itself is not a valid XSD\r
- Schema schema = factory.newSchema(new StreamSource(xsdStream));\r
-\r
- Validator validator = schema.newValidator();\r
-\r
- return validator;\r
- }\r
-\r
- /**\r
- * Returns the first child element with the given XML local name.\r
- * If xmlLocalName is null, returns the very first child element.\r
- */\r
- private Node getFirstChild(Node node, String xmlLocalName) {\r
-\r
- for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {\r
- if (child.getNodeType() == Node.ELEMENT_NODE &&\r
- SdkRepository.NS_SDK_REPOSITORY.equals(child.getNamespaceURI())) {\r
- if (xmlLocalName == null || child.getLocalName().equals(xmlLocalName)) {\r
- return child;\r
}\r
}\r
}\r
-\r
return null;\r
}\r
-\r
- /**\r
- * Takes an XML document as a string as parameter and returns a DOM for it.\r
- */\r
- private Document getDocument(String xml)\r
- throws ParserConfigurationException, SAXException, IOException {\r
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\r
- factory.setIgnoringComments(true);\r
- factory.setNamespaceAware(true);\r
-\r
- DocumentBuilder builder = factory.newDocumentBuilder();\r
- Document doc = builder.parse(new InputSource(new StringReader(xml)));\r
-\r
- return doc;\r
- }\r
}\r
import java.io.File;\r
import java.util.ArrayList;\r
import java.util.Map;\r
+import java.util.Properties;\r
\r
/**\r
* A {@link Package} is the base class for "something" that can be downloaded from\r
*/\r
public abstract class Package implements IDescription {\r
\r
+ private static final String PROP_REVISION = "Pkg.Revision"; //$NON-NLS-1$\r
+ private static final String PROP_LICENSE = "Pkg.License"; //$NON-NLS-1$\r
+ private static final String PROP_DESC = "Pkg.Desc"; //$NON-NLS-1$\r
+ private static final String PROP_DESC_URL = "Pkg.DescUrl"; //$NON-NLS-1$\r
+ private static final String PROP_SOURCE_URL = "Pkg.SourceUrl"; //$NON-NLS-1$\r
+ private static final String PROP_USER_SOURCE = "Pkg.UserSrc"; //$NON-NLS-1$\r
private final int mRevision;\r
private final String mLicense;\r
private final String mDescription;\r
* Manually create a new package with one archive and the given attributes.\r
* This is used to create packages from local directories in which case there must be\r
* one archive which URL is the actual target location.\r
+ *\r
+ * Properties from props are used first when possible, e.g. if props is non null.\r
*/\r
- public Package(RepoSource source,\r
+ public Package(\r
+ RepoSource source,\r
+ Properties props,\r
int revision,\r
String license,\r
String description,\r
Os archiveOs,\r
Arch archiveArch,\r
String archiveOsPath) {\r
+\r
+ mRevision = Integer.parseInt(getProperty(props, PROP_REVISION, Integer.toString(revision)));\r
+ mLicense = getProperty(props, PROP_LICENSE, license);\r
+ mDescription = getProperty(props, PROP_DESC, description);\r
+ mDescUrl = getProperty(props, PROP_DESC_URL, descUrl);\r
+\r
+ // If source is null and we can find a source URL in the properties, generate\r
+ // a dummy source just to store the URL. This allows us to easily remember where\r
+ // a package comes from.\r
+ String srcUrl = getProperty(props, PROP_SOURCE_URL, null);\r
+ if (props != null && source == null && srcUrl != null) {\r
+ boolean isUser = Boolean.parseBoolean(props.getProperty(PROP_USER_SOURCE,\r
+ Boolean.TRUE.toString()));\r
+ source = new RepoSource(srcUrl, isUser);\r
+ }\r
mSource = source;\r
- mRevision = revision;\r
- mLicense = license;\r
- mDescription = description;\r
- mDescUrl = descUrl;\r
+\r
mArchives = new Archive[1];\r
mArchives[0] = new Archive(this,\r
+ props,\r
archiveOs,\r
archiveArch,\r
archiveOsPath);\r
}\r
\r
/**\r
+ * Utility method that returns a property from a {@link Properties} object.\r
+ * Returns the default value if props is null or if the property is not defined.\r
+ */\r
+ protected String getProperty(Properties props, String propKey, String defaultValue) {\r
+ if (props == null) {\r
+ return defaultValue;\r
+ }\r
+ return props.getProperty(propKey, defaultValue);\r
+ }\r
+\r
+ /**\r
+ * Save the properties of the current packages in the given {@link Properties} object.\r
+ * These properties will later be give the constructor that takes a {@link Properties} object.\r
+ */\r
+ void saveProperties(Properties props) {\r
+ props.setProperty(PROP_REVISION, Integer.toString(mRevision));\r
+ props.setProperty(PROP_LICENSE, mLicense);\r
+ props.setProperty(PROP_DESC, mDescription);\r
+ props.setProperty(PROP_DESC_URL, mDescUrl);\r
+\r
+ if (mSource != null) {\r
+ props.setProperty(PROP_SOURCE_URL, mSource.getUrl());\r
+ props.setProperty(PROP_USER_SOURCE, Boolean.toString(mSource.isUserSource()));\r
+ }\r
+ }\r
+\r
+ /**\r
* Parses the uses-licence node of this package, if any, and returns the license\r
* definition if there's one. Returns null if there's no uses-license element or no\r
* license of this name defined.\r