blob: 8d067f267d00efbef83e3f515d207796a0147a1e [file] [log] [blame]
/*
* Copyright (C) 2009 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.repository;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.ISdkLog;
import com.android.sdklib.SdkConstants;
import com.android.sdklib.SdkManager;
import com.android.sdklib.internal.repository.Archive.Arch;
import com.android.sdklib.internal.repository.Archive.Os;
import com.android.sdklib.repository.SdkRepository;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
/**
* Scans a local SDK to find which packages are currently installed.
*/
public class LocalSdkParser {
private static final String SOURCE_XML = "source.xml"; //$NON-NLS-1$ // TODO move to global constants
private Package[] mPackages;
public LocalSdkParser() {
// TODO Auto-generated constructor stub
}
/**
* Returns the packages found by the last call to {@link #parseSdk(String)}.
*/
public Package[] getPackages() {
return mPackages;
}
/**
* Clear the internal packages list. After this call, {@link #getPackages()} will return
* null till {@link #parseSdk(String)} is called.
*/
public void clearPackages() {
mPackages = null;
}
/**
* Scan the give SDK to find all the packages already installed at this location.
* <p/>
* Store the packages internally. You can use {@link #getPackages()} to retrieve them
* at any time later.
*
* @param osSdkRoot The path to the SDK folder.
* @return The packages found. Can be retrieved later using {@link #getPackages()}.
*/
public Package[] parseSdk(String osSdkRoot) {
ArrayList<Package> packages = new ArrayList<Package>();
Package pkg = scanDoc(new File(osSdkRoot, SdkConstants.FD_DOCS));
if (pkg != null) {
packages.add(pkg);
}
pkg = scanTools(new File(osSdkRoot, SdkConstants.FD_TOOLS));
if (pkg != null) {
packages.add(pkg);
}
// for platforms and add-ons, rely on the SdkManager parser
SdkManager sdkman = SdkManager.createManager(osSdkRoot, new ISdkLog() {
// A dummy sdk logger that doesn't log anything.
public void error(Throwable t, String errorFormat, Object... args) {
// pass
}
public void printf(String msgFormat, Object... args) {
// pass
}
public void warning(String warningFormat, Object... args) {
// pass
}
});
for(IAndroidTarget target : sdkman.getTargets()) {
pkg = null;
if (target.isPlatform()) {
pkg = parseXml(new File(target.getLocation(), SOURCE_XML),
SdkRepository.NODE_PLATFORM);
if (pkg == null) {
pkg = new PlatformPackage(target);
}
} else {
pkg = parseXml(new File(target.getLocation(), SOURCE_XML),
SdkRepository.NODE_ADD_ON);
if (pkg == null) {
pkg = new AddonPackage(target);
}
}
if (pkg != null) {
packages.add(pkg);
}
}
mPackages = packages.toArray(new Package[packages.size()]);
return mPackages;
}
/**
* Try to find a tools package at the given location.
* Returns null if not found.
*/
private Package scanTools(File toolFolder) {
// Can we find a source.xml?
Package pkg = parseXml(new File(toolFolder, SOURCE_XML), SdkRepository.NODE_TOOL);
// We're not going to check that all tools are present. At the very least
// we should expect to find adb, android and an emulator adapted to the current OS.
Set<String> names = new HashSet<String>();
for (File file : toolFolder.listFiles()) {
names.add(file.getName());
}
if (!names.contains(SdkConstants.FN_ADB) ||
!names.contains(SdkConstants.androidCmdName()) ||
!names.contains(SdkConstants.FN_EMULATOR)) {
return null;
}
// if we don't have the package info, make one up
if (pkg == null) {
pkg = new ToolPackage(
null, //source
0, //revision
null, //license
"Tools", //description
null, //descUrl
Os.getCurrentOs(), //archiveOs
Arch.getCurrentArch(), //archiveArch
"", //archiveUrl //$NON-NLS-1$
0, //archiveSize
null //archiveChecksum
);
}
return pkg;
}
/**
* Try to find a docs package at the given location.
* Returns null if not found.
*/
private Package scanDoc(File docFolder) {
// Can we find a source.xml?
Package pkg = parseXml(new File(docFolder, SOURCE_XML), SdkRepository.NODE_DOC);
// To start with, a doc folder should have an "index.html" to be acceptable.
String html = readFile(new File(docFolder, "index.html"));
if (html != null) {
// Try to find something that looks like this line:
// <a href="./sdk/1.5_r1/index.html">
// We should find one or more of these and we want the highest version
// and release numbers. Note that unfortunately that doesn't give us
// the api-level we care about for the doc package.
String found = null;
Pattern re = Pattern.compile(
"<a\\s+href=\"./sdk/(\\d\\.\\d_r\\d)/index.html\">",
Pattern.DOTALL);
Matcher m = re.matcher(html);
while(m.find()) {
String v = m.group(1);
if (found == null || v.compareTo(found) == 1) {
found = v;
}
}
if (found == null) {
// That doesn't look like a doc folder.
return null;
}
// We found the line, so it seems like an SDK doc.
// Create a pkg if we don't have one yet.
if (pkg == null) {
String url = null;
try {
url = docFolder.toURI().toURL().toString();
} catch (MalformedURLException e) {
// ignore
}
pkg = new DocPackage(
null, //source
0, //apiLevel
0, //revision
null, //license
String.format("Documentation for %1$s", found), //description
null, //descUrl
Os.getCurrentOs(), //archiveOs
Arch.getCurrentArch(), //archiveArch
url, //archiveUrl
0, //archiveSize
null //archiveChecksum
);
}
}
return pkg;
}
/**
* Parses the given XML file for the specific element filter.
* The element must one of the package type local names: doc, tool, platform or addon.
* Returns null if no such package was found.
*/
private Package parseXml(File sourceXmlFile, String elementFilter) {
String xml = readFile(sourceXmlFile);
if (xml != null) {
if (validateXml(xml)) {
return parsePackages(xml, elementFilter);
}
}
return null;
}
/**
* Parses the given XML to find the specific element filter.
* The element must one of the package type local names: doc, tool, platform or addon.
* Returns null if no such package was found.
*/
private Package parsePackages(String xml, String elementFilter) {
try {
Document doc = getDocument(xml);
Node root = getFirstChild(doc, SdkRepository.NODE_SDK_REPOSITORY);
if (root != null) {
for (Node child = root.getFirstChild();
child != null;
child = child.getNextSibling()) {
if (child.getNodeType() == Node.ELEMENT_NODE &&
SdkRepository.NS_SDK_REPOSITORY.equals(child.getNamespaceURI()) &&
elementFilter.equals(child.getLocalName())) {
String name = child.getLocalName();
Package p = null;
try {
if (SdkRepository.NODE_ADD_ON.equals(name)) {
return new AddonPackage(null /*source*/, child);
} else if (SdkRepository.NODE_PLATFORM.equals(name)) {
return new PlatformPackage(null /*source*/, child);
} else if (SdkRepository.NODE_DOC.equals(name)) {
return new DocPackage(null /*source*/, child);
} else if (SdkRepository.NODE_TOOL.equals(name)) {
return new ToolPackage(null /*source*/, child);
}
} catch (Exception e) {
// Ignore invalid packages
}
}
}
}
} catch (Exception e) {
// ignore
}
return null;
}
/**
* Reads a file as a string.
* Returns null if the file could not be read.
*/
private String readFile(File sourceXmlFile) {
FileReader fr = null;
try {
fr = new FileReader(sourceXmlFile);
BufferedReader br = new BufferedReader(fr);
StringBuilder dest = new StringBuilder();
char[] buf = new char[65536];
int n;
while ((n = br.read(buf)) > 0) {
if (n > 0) {
dest.append(buf, 0, n);
}
}
return dest.toString();
} catch (IOException e) {
// ignore
} finally {
if (fr != null) {
try {
fr.close();
} catch (IOException e) {
// ignore
}
}
}
return null;
}
/**
* Validates this XML against the SDK Repository schema.
* Returns true if the XML was correctly validated.
*/
private boolean validateXml(String xml) {
try {
Validator validator = getValidator();
validator.validate(new StreamSource(new StringReader(xml)));
return true;
} catch (SAXException e) {
// ignore
} catch (IOException e) {
// ignore
}
return false;
}
/**
* Helper method that returns a validator for our XSD
*/
private Validator getValidator() throws SAXException {
InputStream xsdStream = SdkRepository.getXsdStream();
SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
// This may throw a SAX Exception if the schema itself is not a valid XSD
Schema schema = factory.newSchema(new StreamSource(xsdStream));
Validator validator = schema.newValidator();
return validator;
}
/**
* Returns the first child element with the given XML local name.
* If xmlLocalName is null, returns the very first child element.
*/
private Node getFirstChild(Node node, String xmlLocalName) {
for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
if (child.getNodeType() == Node.ELEMENT_NODE &&
SdkRepository.NS_SDK_REPOSITORY.equals(child.getNamespaceURI())) {
if (xmlLocalName == null || child.getLocalName().equals(xmlLocalName)) {
return child;
}
}
}
return null;
}
/**
* Takes an XML document as a string as parameter and returns a DOM for it.
*/
private Document getDocument(String xml)
throws ParserConfigurationException, SAXException, IOException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setIgnoringComments(true);
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new InputSource(new StringReader(xml)));
return doc;
}
}