SDK Updater: parse local packages, download and install remote packages.

The install phase is still work in progress.
The local part needs to display descriptions.
Buttons callback are generally not implemented yet.
This commit is contained in:
Raphael
2009-05-29 14:02:50 -07:00
parent 2e46d39eb8
commit 2382dd7514
20 changed files with 1518 additions and 191 deletions

View File

@@ -82,18 +82,26 @@ public final class SdkConstants {
/** dex.jar file */
public static final String FN_DX_JAR = "dx.jar"; //$NON-NLS-1$
/** dx executable */
/** dx executable (with extension for the current OS) */
public final static String FN_DX = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ?
"dx.bat" : "dx"; //$NON-NLS-1$ //$NON-NLS-2$
/** aapt executable */
/** aapt executable (with extension for the current OS) */
public final static String FN_AAPT = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ?
"aapt.exe" : "aapt"; //$NON-NLS-1$ //$NON-NLS-2$
/** aidl executable */
/** aidl executable (with extension for the current OS) */
public final static String FN_AIDL = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ?
"aidl.exe" : "aidl"; //$NON-NLS-1$ //$NON-NLS-2$
/** adb executable (with extension for the current OS) */
public final static String FN_ADB = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ?
"adb.exe" : "adb"; //$NON-NLS-1$ //$NON-NLS-2$
/** emulator executable (with extension for the current OS) */
public final static String FN_EMULATOR = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ?
"emulator.exe" : "emulator"; //$NON-NLS-1$ //$NON-NLS-2$
/* Folder Names for Android Projects . */
/** Resources folder name, i.e. "res". */

View File

@@ -16,10 +16,17 @@
package com.android.sdklib.internal.repository;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.SdkConstants;
import com.android.sdklib.SdkManager;
import com.android.sdklib.IAndroidTarget.IOptionalLibrary;
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.Node;
import java.io.File;
import java.util.ArrayList;
/**
@@ -57,8 +64,8 @@ public class AddonPackage extends Package {
* <p/>
* This constructor should throw an exception if the package cannot be created.
*/
AddonPackage(Node packageNode) {
super(packageNode);
AddonPackage(RepoSource source, Node packageNode) {
super(source, packageNode);
mVendor = getXmlString(packageNode, SdkRepository.NODE_VENDOR);
mName = getXmlString(packageNode, SdkRepository.NODE_NAME);
mApiLevel = getXmlInt (packageNode, SdkRepository.NODE_API_LEVEL, 0);
@@ -66,6 +73,41 @@ public class AddonPackage extends Package {
mLibs = parseLibs(getFirstChild(packageNode, SdkRepository.NODE_LIBS));
}
/**
* Creates a new platform package based on an actual {@link IAndroidTarget} (with
* {@link IAndroidTarget#isPlatform()} false) from the {@link SdkManager}.
* This is used to list local SDK folders.
*/
AddonPackage(IAndroidTarget target) {
super( null, //source
0, //revision
target.getDescription(), //description
null, //descUrl
Os.getCurrentOs(), //archiveOs
Arch.getCurrentArch(), //archiveArch
"", //archiveUrl //$NON-NLS-1$
0, //archiveSize
null //archiveChecksum
);
mApiLevel = target.getApiVersionNumber();
mName = target.getName();
mVendor = target.getVendor();
IOptionalLibrary[] optLibs = target.getOptionalLibraries();
if (optLibs == null || optLibs.length == 0) {
mLibs = new Lib[0];
} else {
mLibs = new Lib[optLibs.length];
for (int i = 0; i < optLibs.length; i++) {
mLibs[i] = new Lib(optLibs[i].getName(), optLibs[i].getDescription());
}
}
}
/**
* Parses a <libs> element.
*/
private Lib[] parseLibs(Node libsNode) {
ArrayList<Lib> libs = new ArrayList<Lib>();
@@ -85,6 +127,9 @@ public class AddonPackage extends Package {
return libs.toArray(new Lib[libs.size()]);
}
/**
* Parses a <lib> element from a <libs> container.
*/
private Lib parseLib(Node libNode) {
return new Lib(getXmlString(libNode, SdkRepository.NODE_NAME),
getXmlString(libNode, SdkRepository.NODE_DESCRIPTION));
@@ -126,4 +171,31 @@ public class AddonPackage extends Package {
getShortDescription(),
super.getLongDescription());
}
/**
* Computes a potential installation folder if an archive of this package were
* to be installed right away in the given SDK root.
* <p/>
* An add-on package is typically installed in SDK/add-ons/"addon-name"-"api-level".
* The name needs to be sanitized to be acceptable as a directory name.
* However if we can find a different directory under SDK/add-ons that already
* has this add-ons installed, we'll use that one.
*
* @param osSdkRoot The OS path of the SDK root folder.
* @return A new {@link File} corresponding to the directory to use to install this package.
*/
@Override
public File getInstallFolder(String osSdkRoot) {
File addons = new File(osSdkRoot, SdkConstants.FD_ADDONS);
String name = String.format("%s-%d", getName(), getApiLevel()); // $NON-NLS-1$
name = name.replaceAll("[^a-zA-Z0-9_-]+", "_"); // $NON-NLS-1$
name = name.replaceAll("_+", "_"); // $NON-NLS-1$
File folder = new File(addons, name);
// TODO find similar existing addon in addons folder
return folder;
}
}

View File

@@ -16,6 +16,9 @@
package com.android.sdklib.internal.repository;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* A {@link Archive} is the base class for "something" that can be downloaded from
@@ -32,23 +35,106 @@ public class Archive implements IDescription {
/** The checksum type. */
public enum ChecksumType {
/** A SHA1 checksum, represented as a 40-hex string. */
SHA1
SHA1("SHA-1"); //$NON-NLS-1$
private final String mAlgorithmName;
/**
* Constructs a {@link ChecksumType} with the algorigth name
* suitable for {@link MessageDigest#getInstance(String)}.
* <p/>
* These names are officially documented at
* http://java.sun.com/javase/6/docs/technotes/guides/security/StandardNames.html#MessageDigest
*/
private ChecksumType(String algorithmName) {
mAlgorithmName = algorithmName;
}
/**
* Returns a new {@link MessageDigest} instance for this checksum type.
* @throws NoSuchAlgorithmException if this algorithm is not available.
*/
public MessageDigest getMessageDigest() throws NoSuchAlgorithmException {
return MessageDigest.getInstance(mAlgorithmName);
}
}
/** The OS that this archive can be downloaded on. */
public enum Os {
ANY,
LINUX,
MACOSX,
WINDOWS
ANY("Any"),
LINUX("Linux"),
MACOSX("MacOS X"),
WINDOWS("Windows");
private final String mUiName;
private Os(String uiName) {
mUiName = uiName;
}
@Override
public String toString() {
return mUiName;
}
/**
* Returns the current OS as one of the {@link Os} enum values or null.
*/
public static Os getCurrentOs() {
String os = System.getProperty("os.name"); //$NON-NLS-1$
if (os.startsWith("Mac OS")) { //$NON-NLS-1$
return Os.MACOSX;
} else if (os.startsWith("Windows")) { //$NON-NLS-1$
return Os.WINDOWS;
} else if (os.startsWith("Linux")) { //$NON-NLS-1$
return Os.LINUX;
}
return null;
}
}
/** The Architecture that this archvie can be downloaded on. */
/** The Architecture that this archive can be downloaded on. */
public enum Arch {
ANY,
PPC,
X86,
X86_64
ANY("Any"),
PPC("PowerPC"),
X86("x86"),
X86_64("x86_64");
private final String mUiName;
private Arch(String uiName) {
mUiName = uiName;
}
@Override
public String toString() {
return mUiName;
}
/**
* Returns the current architecture as one of the {@link Arch} enum values or null.
*/
public static Arch getCurrentArch() {
// Values listed from http://lopica.sourceforge.net/os.html
String arch = System.getProperty("os.arch");
if (arch.equalsIgnoreCase("x86_64") || arch.equalsIgnoreCase("amd64")) {
return Arch.X86_64;
} else if (arch.equalsIgnoreCase("x86")
|| arch.equalsIgnoreCase("i386")
|| arch.equalsIgnoreCase("i686")) {
return Arch.X86;
} else if (arch.equalsIgnoreCase("ppc") || arch.equalsIgnoreCase("PowerPC")) {
return Arch.PPC;
}
return null;
}
}
private final Os mOs;
@@ -57,11 +143,13 @@ public class Archive implements IDescription {
private final long mSize;
private final String mChecksum;
private final ChecksumType mChecksumType = ChecksumType.SHA1;
private final Package mPackage;
/**
* Creates a new archive.
*/
Archive(Os os, Arch arch, String url, long size, String checksum) {
Archive(Package pkg, Os os, Arch arch, String url, long size, String checksum) {
mPackage = pkg;
mOs = os;
mArch = arch;
mUrl = url;
@@ -69,63 +157,117 @@ public class Archive implements IDescription {
mChecksum = checksum;
}
/** Returns the archive size, an int > 0. */
/**
* Returns the package that created and owns this archive.
* It should generally not be null.
*/
public Package getParentPackage() {
return mPackage;
}
/**
* Returns the archive size, an int > 0.
* Size will be 0 if this a local installed folder of unknown size.
*/
public long getSize() {
return mSize;
}
/** Returns the SHA1 archive checksum, as a 40-char hex. */
/**
* Returns the SHA1 archive checksum, as a 40-char hex.
* Can be empty but not null for local installed folders.
*/
public String getChecksum() {
return mChecksum;
}
/** Returns the checksum type, always {@link ChecksumType#SHA1} right now. */
/**
* Returns the checksum type, always {@link ChecksumType#SHA1} right now.
*/
public ChecksumType getChecksumType() {
return mChecksumType;
}
/** Returns the optional description URL for all packages (platform, add-on, tool, doc).
* Can be empty but not null. */
public String getDescUrl() {
/**
* Returns the download archive URL, either absolute or relative to the repository xml.
* For a local installed folder, an URL is frabricated from the folder path.
*/
public String getUrl() {
return mUrl;
}
/** Returns the archive {@link Os} enum. */
/**
* Returns the archive {@link Os} enum.
* Can be null for a local installed folder on an unknown OS.
*/
public Os getOs() {
return mOs;
}
/** Returns the archive {@link Arch} enum. */
/**
* Returns the archive {@link Arch} enum.
* Can be null for a local installed folder on an unknown architecture.
*/
public Arch getArch() {
return mArch;
}
/**
* Generates a short description for this archive.
*/
public String getShortDescription() {
String os = "any OS";
if (mOs != Os.ANY) {
os = capitalize(mOs.toString());
String os;
if (mOs == null) {
os = "unknown OS";
} else if (mOs == Os.ANY) {
os = "any OS";
} else {
os = mOs.toString();
}
String arch = "";
if (mArch != Arch.ANY) {
arch = mArch.toString().toLowerCase();
if (mArch != null && mArch != Arch.ANY) {
arch = mArch.toString();
}
return String.format("Archive for %1$s %2$s", os, arch);
}
private String capitalize(String string) {
if (string.length() > 1) {
return string.substring(0, 1).toUpperCase() + string.substring(1).toLowerCase();
} else {
return string.toUpperCase();
}
}
/**
* Generates a longer description for this archive.
*/
public String getLongDescription() {
return String.format("%1$s\nSize: %2$d MiB\nSHA1: %3$s",
getShortDescription(),
Math.round(getSize() / (1024*1024)),
getChecksum());
}
/**
* Returns true if this archive can be installed on the current platform.
*/
public boolean isCompatible() {
// Check OS
Os os = getOs();
if (os != Os.ANY) {
Os os2 = Os.getCurrentOs();
if (os2 != os) {
return false;
}
}
// Check Arch
Arch arch = getArch();
if (arch != Arch.ANY) {
Arch arch2 = Arch.getCurrentArch();
if (arch2 != arch) {
return false;
}
}
return true;
}
}

View File

@@ -16,10 +16,15 @@
package com.android.sdklib.internal.repository;
import com.android.sdklib.SdkConstants;
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.Node;
import java.io.File;
/**
* Represents a doc XML node in an SDK repository.
*/
@@ -32,12 +37,39 @@ public class DocPackage extends Package {
* <p/>
* This constructor should throw an exception if the package cannot be created.
*/
DocPackage(Node packageNode) {
super(packageNode);
DocPackage(RepoSource source, Node packageNode) {
super(source, packageNode);
mApiLevel = getXmlInt(packageNode, SdkRepository.NODE_API_LEVEL, 0);
}
/** Returns the api-level, an int > 0, for platform, add-on and doc packages. */
/**
* Manually create a new package with one archive and the given attributes.
* This is used to create packages from local directories.
*/
DocPackage(RepoSource source,
int apiLevel,
int revision,
String description,
String descUrl,
Os archiveOs,
Arch archiveArch,
String archiveUrl,
long archiveSize,
String archiveChecksum) {
super(source,
revision,
description,
descUrl,
archiveOs,
archiveArch,
archiveUrl,
archiveSize,
archiveChecksum);
mApiLevel = apiLevel;
}
/** Returns the api-level, an int > 0, for platform, add-on and doc packages.
* Can be 0 if this is a local package of unknown api-level. */
public int getApiLevel() {
return mApiLevel;
}
@@ -45,7 +77,11 @@ public class DocPackage extends Package {
/** Returns a short description for an {@link IDescription}. */
@Override
public String getShortDescription() {
return String.format("Documentation for SDK Android API %1$d", getApiLevel());
if (mApiLevel != 0) {
return String.format("Documentation for Android SDK, API %1$d", mApiLevel);
} else {
return String.format("Documentation for Android SDK");
}
}
/** Returns a long description for an {@link IDescription}. */
@@ -55,4 +91,18 @@ public class DocPackage extends Package {
getShortDescription(),
super.getLongDescription());
}
/**
* Computes a potential installation folder if an archive of this package were
* to be installed right away in the given SDK root.
* <p/>
* A "doc" package should always be located in SDK/docs.
*
* @param osSdkRoot The OS path of the SDK root folder.
* @return A new {@link File} corresponding to the directory to use to install this package.
*/
@Override
public File getInstallFolder(String osSdkRoot) {
return new File(osSdkRoot, SdkConstants.FD_DOCS);
}
}

View File

@@ -34,13 +34,13 @@ public interface ITaskMonitor {
* Sets the description in the current task dialog.
* This method can be invoked from a non-UI thread.
*/
public void setDescription(String description);
public void setDescription(String descriptionFormat, Object...args);
/**
* Sets the result text in the current task dialog.
* This method can be invoked from a non-UI thread.
*/
public void setResult(String result);
public void setResult(String resultFormat, Object...args);
/**
* Sets the max value of the progress bar.

View File

@@ -0,0 +1,415 @@
/*
* 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
"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
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;
}
}

View File

@@ -22,6 +22,7 @@ import com.android.sdklib.repository.SdkRepository;
import org.w3c.dom.Node;
import java.io.File;
import java.util.ArrayList;
/**
@@ -42,13 +43,15 @@ public abstract class Package implements IDescription {
private final String mDescription;
private final String mDescUrl;
private final Archive[] mArchives;
private final RepoSource mSource;
/**
* Creates a new package from the attributes and elements of the given XML node.
* <p/>
* This constructor should throw an exception if the package cannot be created.
*/
Package(Node packageNode) {
Package(RepoSource source, Node packageNode) {
mSource = source;
mRevision = getXmlInt (packageNode, SdkRepository.NODE_REVISION, 0);
mDescription = getXmlString(packageNode, SdkRepository.NODE_DESCRIPTION);
mDescUrl = getXmlString(packageNode, SdkRepository.NODE_DESC_URL);
@@ -56,6 +59,35 @@ public abstract class Package implements IDescription {
mArchives = parseArchives(getFirstChild(packageNode, SdkRepository.NODE_ARCHIVES));
}
/**
* Manually create a new package with one archive and the given attributes.
* This is used to create packages from local directories.
*/
public Package(RepoSource source,
int revision,
String description,
String descUrl,
Os archiveOs,
Arch archiveArch,
String archiveUrl,
long archiveSize,
String archiveChecksum) {
mSource = source;
mRevision = revision;
mDescription = description;
mDescUrl = descUrl;
mArchives = new Archive[1];
mArchives[0] = new Archive(this,
archiveOs,
archiveArch,
archiveUrl,
archiveSize,
archiveChecksum);
}
/**
* Parses an XML node to process the <archives> element.
*/
private Archive[] parseArchives(Node archivesNode) {
ArrayList<Archive> archives = new ArrayList<Archive>();
@@ -75,52 +107,99 @@ public abstract class Package implements IDescription {
return archives.toArray(new Archive[archives.size()]);
}
/**
* Parses one <archive> element from an <archives> container.
*/
private Archive parseArchive(Node archiveNode) {
Archive a = new Archive(
this,
(Os) getEnumAttribute(archiveNode, SdkRepository.ATTR_OS,
Os.values(), null),
(Arch) getEnumAttribute(archiveNode, SdkRepository.ATTR_ARCH,
Arch.values(), Arch.ANY),
getXmlString(archiveNode, SdkRepository.NODE_URL),
getXmlInt(archiveNode, SdkRepository.NODE_SIZE, 0),
getXmlLong(archiveNode, SdkRepository.NODE_SIZE, 0),
getXmlString(archiveNode, SdkRepository.NODE_CHECKSUM)
);
return a;
}
/** Returns the revision, an int > 0, for all packages (platform, add-on, tool, doc). */
/**
* Returns the source that created (and owns) this package. Can be null.
*/
public RepoSource getParentSource() {
return mSource;
}
/**
* Returns the revision, an int > 0, for all packages (platform, add-on, tool, doc).
* Can be 0 if this is a local package of unknown revision.
*/
public int getRevision() {
return mRevision;
}
/** Returns the optional description for all packages (platform, add-on, tool, doc) or
* for a lib. */
/**
* Returns the optional description for all packages (platform, add-on, tool, doc) or
* for a lib. Can be empty but not null.
*/
public String getDescription() {
return mDescription;
}
/** Returns the optional description URL for all packages (platform, add-on, tool, doc).
* Can be empty but not null. */
/**
* Returns the optional description URL for all packages (platform, add-on, tool, doc).
* Can be empty but not null.
*/
public String getDescUrl() {
return mDescUrl;
}
/** Returns the archives defined in this package. Can be an empty array but not null. */
/**
* Returns the archives defined in this package.
* Can be an empty array but not null.
*/
public Archive[] getArchives() {
return mArchives;
}
/** Returns a short description for an {@link IDescription}. */
/**
* Returns a short description for an {@link IDescription}.
* Can be empty but not null.
*/
public abstract String getShortDescription();
/** Returns a long description for an {@link IDescription}. */
/**
* Returns a long description for an {@link IDescription}.
* Can be empty but not null.
*/
public String getLongDescription() {
return String.format("%1$s\nRevision %2$d", getDescription(), getRevision());
}
/**
* Computes a potential installation folder if an archive of this package were
* to be installed right away in the given SDK root.
* <p/>
* Some types of packages install in a fix location, for example docs and tools.
* In this case the returned folder may already exist with a different archive installed
* at the desired location.
* For other packages types, such as add-on or platform, the folder name is only partially
* relevant to determine the content and thus a real check will be done to provide an
* existing or new folder depending on the current content of the SDK.
*
* @param osSdkRoot The OS path of the SDK root folder.
* @return A new {@link File} corresponding to the directory to use to install this package.
*/
public abstract File getInstallFolder(String osSdkRoot);
//---
/**
* Returns the first child element with the given XML local name.
* If xmlLocalName is null, returns the very first child element.
*/
protected static Node getFirstChild(Node node, String xmlLocalName) {
for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
@@ -158,6 +237,19 @@ public abstract class Package implements IDescription {
}
}
/**
* Retrieves the value of that XML element as a long.
* Returns the default value when the element is missing or is not an integer.
*/
protected static long getXmlLong(Node node, String xmlLocalName, long defaultValue) {
String s = getXmlString(node, xmlLocalName);
try {
return Long.parseLong(s);
} catch (NumberFormatException e) {
return defaultValue;
}
}
/**
* Retrieve an attribute which value must match one of the given enums using a
* case-insensitive name match.

View File

@@ -16,10 +16,17 @@
package com.android.sdklib.internal.repository;
import com.android.sdklib.IAndroidTarget;
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.Node;
import java.io.File;
/**
* Represents a platform XML node in an SDK repository.
*/
@@ -33,12 +40,33 @@ public class PlatformPackage extends Package {
* <p/>
* This constructor should throw an exception if the package cannot be created.
*/
PlatformPackage(Node packageNode) {
super(packageNode);
PlatformPackage(RepoSource source, Node packageNode) {
super(source, packageNode);
mVersion = getXmlString(packageNode, SdkRepository.NODE_VERSION);
mApiLevel = getXmlInt (packageNode, SdkRepository.NODE_API_LEVEL, 0);
}
/**
* Creates a new platform package based on an actual {@link IAndroidTarget} (with
* must have {@link IAndroidTarget#isPlatform()} true) from the {@link SdkManager}.
* This is used to list local SDK folders.
*/
PlatformPackage(IAndroidTarget target) {
super( null, //source
0, //revision
target.getDescription(), //description
null, //descUrl
Os.getCurrentOs(), //archiveOs
Arch.getCurrentArch(), //archiveArch
"", //archiveUrl //$NON-NLS-1$
0, //archiveSize
null //archiveChecksum
);
mApiLevel = target.getApiVersionNumber();
mVersion = target.getApiVersionName();
}
/** Returns the version, a string, for platform packages. */
public String getVersion() {
return mVersion;
@@ -64,4 +92,23 @@ public class PlatformPackage extends Package {
getShortDescription(),
super.getLongDescription());
}
/**
* Computes a potential installation folder if an archive of this package were
* to be installed right away in the given SDK root.
* <p/>
* A platform package is typically installed in SDK/platforms/android-"version".
* However if we can find a different directory under SDK/platform that already
* has this platform version installed, we'll use that one.
*
* @param osSdkRoot The OS path of the SDK root folder.
* @return A new {@link File} corresponding to the directory to use to install this package.
*/
@Override
public File getInstallFolder(String osSdkRoot) {
File platforms = new File(osSdkRoot, SdkConstants.FD_PLATFORMS);
File folder = new File(platforms, String.format("android-%s", getVersion())); //$NON-NLS-1$
// TODO find similar existing platform in platforms folder
return folder;
}
}

View File

@@ -68,12 +68,21 @@ public class RepoSource implements IDescription {
}
/**
* Returns the list of known packages. This is null when the source hasn't been loaded yet.
* Returns the list of known packages found by the last call to {@link #load(ITaskFactory)}.
* This is null when the source hasn't been loaded yet.
*/
public Package[] getPackages() {
return mPackages;
}
/**
* Clear the internal packages list. After this call, {@link #getPackages()} will return
* null till {@link #load(ITaskFactory)} is called.
*/
public void clearPackages() {
mPackages = null;
}
public String getShortDescription() {
return mUrl;
}
@@ -93,7 +102,7 @@ public class RepoSource implements IDescription {
setDefaultDescription();
monitor.setDescription(String.format("Fetching %1$s", mUrl));
monitor.setDescription("Fetching %1$s", mUrl);
monitor.incProgress(1);
String xml = fetchUrl(mUrl, monitor);
@@ -136,7 +145,10 @@ public class RepoSource implements IDescription {
}
}
/*
/**
* Fetches the document at the given URL and returns it as a string.
* Returns null if anything wrong happens and write errors to the monitor.
*
* References:
* Java URL Connection: http://java.sun.com/docs/books/tutorial/networking/urls/readingWriting.html
* Java URL Reader: http://java.sun.com/docs/books/tutorial/networking/urls/readingURL.html
@@ -186,6 +198,10 @@ public class RepoSource implements IDescription {
return null;
}
/**
* Validates this XML against the SDK Repository schema.
* Returns true if the XML was correctly validated.
*/
private boolean validateXml(String xml, ITaskMonitor monitor) {
try {
@@ -203,7 +219,9 @@ public class RepoSource implements IDescription {
return false;
}
/** Helper method that returns a validator for our XSD */
/**
* 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);
@@ -217,6 +235,10 @@ public class RepoSource implements IDescription {
}
/**
* Parse all packages defined in the SDK Repository XML and creates
* a new mPackages array with them.
*/
private boolean parsePackages(String xml, ITaskMonitor monitor) {
try {
@@ -237,22 +259,21 @@ public class RepoSource implements IDescription {
try {
if (SdkRepository.NODE_ADD_ON.equals(name)) {
p = new AddonPackage(child);
p = new AddonPackage(this, child);
} else if (!mAddonOnly) {
if (SdkRepository.NODE_PLATFORM.equals(name)) {
p = new PlatformPackage(child);
p = new PlatformPackage(this, child);
} else if (SdkRepository.NODE_DOC.equals(name)) {
p = new DocPackage(child);
p = new DocPackage(this, child);
} else if (SdkRepository.NODE_TOOL.equals(name)) {
p = new ToolPackage(child);
p = new ToolPackage(this, child);
}
}
if (p != null) {
packages.add(p);
monitor.setDescription(
String.format("Found %1$s", p.getShortDescription()));
monitor.setDescription("Found %1$s", p.getShortDescription());
}
} catch (Exception e) {
// Ignore invalid packages
@@ -278,6 +299,10 @@ public class RepoSource implements IDescription {
return false;
}
/**
* 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()) {
@@ -292,6 +317,9 @@ public class RepoSource implements IDescription {
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();

View File

@@ -0,0 +1,47 @@
/*
* 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 java.util.ArrayList;
/**
* A list of sdk-repository sources.
*/
public class RepoSources {
private ArrayList<RepoSource> mSources = new ArrayList<RepoSource>();
private ITaskFactory mTaskFactory;
public RepoSources() {
}
public void setTaskFactory(ITaskFactory taskFactory) {
mTaskFactory = taskFactory;
}
public ITaskFactory getTaskFactory() {
return mTaskFactory;
}
public void add(RepoSource source) {
mSources.add(source);
}
public ArrayList<RepoSource> getSources() {
return mSources;
}
}

View File

@@ -16,8 +16,14 @@
package com.android.sdklib.internal.repository;
import com.android.sdklib.SdkConstants;
import com.android.sdklib.internal.repository.Archive.Arch;
import com.android.sdklib.internal.repository.Archive.Os;
import org.w3c.dom.Node;
import java.io.File;
/**
* Represents a tool XML node in an SDK repository.
*/
@@ -28,8 +34,32 @@ public class ToolPackage extends Package {
* <p/>
* This constructor should throw an exception if the package cannot be created.
*/
ToolPackage(Node packageNode) {
super(packageNode);
ToolPackage(RepoSource source, Node packageNode) {
super(source, packageNode);
}
/**
* Manually create a new package with one archive and the given attributes.
* This is used to create packages from local directories.
*/
ToolPackage(RepoSource source,
int revision,
String description,
String descUrl,
Os archiveOs,
Arch archiveArch,
String archiveUrl,
long archiveSize,
String archiveChecksum) {
super(source,
revision,
description,
descUrl,
archiveOs,
archiveArch,
archiveUrl,
archiveSize,
archiveChecksum);
}
/** Returns a short description for an {@link IDescription}. */
@@ -45,4 +75,18 @@ public class ToolPackage extends Package {
getRevision(),
super.getLongDescription());
}
/**
* Computes a potential installation folder if an archive of this package were
* to be installed right away in the given SDK root.
* <p/>
* A "tool" package should always be located in SDK/tools.
*
* @param osSdkRoot The OS path of the SDK root folder.
* @return A new {@link File} corresponding to the directory to use to install this package.
*/
@Override
public File getInstallFolder(String osSdkRoot) {
return new File(osSdkRoot, SdkConstants.FD_TOOLS);
}
}

View File

@@ -16,6 +16,7 @@
package com.android.sdklib.repository;
import java.io.InputStream;
/**
@@ -23,6 +24,10 @@ import java.io.InputStream;
*/
public class SdkRepository {
/** The URL of the official Google sdk-repository site. */
public static final String URL_GOOGLE_SDK_REPO_SITE =
"https://dl.google.com/android/eclipse/repository/index.xml"; //$NON-NLS-1$
/** The XML namespace of the sdk-repository XML. */
public static final String NS_SDK_REPOSITORY =
"http://schemas.android.com/sdk/android/repository/1"; //$NON-NLS-1$