/* | |
* 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.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.Element; | |
import org.w3c.dom.NamedNodeMap; | |
import org.w3c.dom.Node; | |
import org.xml.sax.InputSource; | |
import org.xml.sax.SAXException; | |
import java.io.ByteArrayInputStream; | |
import java.io.FileNotFoundException; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.net.URL; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.HashMap; | |
import java.util.regex.Pattern; | |
import javax.net.ssl.SSLKeyException; | |
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; | |
/** | |
* An sdk-repository source, i.e. a download site. | |
* It may be a full repository or an add-on only repository. | |
* A repository describes one or {@link Package}s available for download. | |
*/ | |
public class RepoSource implements IDescription { | |
private String mUrl; | |
private final boolean mUserSource; | |
private Package[] mPackages; | |
private String mDescription; | |
private String mFetchError; | |
/** | |
* Constructs a new source for the given repository URL. | |
* @param url The source URL. Cannot be null. If the URL ends with a /, the default | |
* repository.xml filename will be appended automatically. | |
* @param userSource True if this a user source (add-ons & packages only.) | |
*/ | |
public RepoSource(String url, boolean userSource) { | |
// if the URL ends with a /, it must be "directory" resource, | |
// in which case we automatically add the default file that will | |
// looked for. This way it will be obvious to the user which | |
// resource we are actually trying to fetch. | |
if (url.endsWith("/")) { //$NON-NLS-1$ | |
url += SdkRepository.URL_DEFAULT_XML_FILE; | |
} | |
mUrl = url; | |
mUserSource = userSource; | |
setDefaultDescription(); | |
} | |
/** | |
* Two repo source are equal if they have the same userSource flag and the same URL. | |
*/ | |
@Override | |
public boolean equals(Object obj) { | |
if (obj instanceof RepoSource) { | |
RepoSource rs = (RepoSource) obj; | |
return rs.isUserSource() == this.isUserSource() && rs.getUrl().equals(this.getUrl()); | |
} | |
return false; | |
} | |
@Override | |
public int hashCode() { | |
return mUrl.hashCode() ^ Boolean.valueOf(mUserSource).hashCode(); | |
} | |
/** Returns true if this is a user source. We only load addon and extra packages | |
* from a user source and ignore the rest. */ | |
public boolean isUserSource() { | |
return mUserSource; | |
} | |
/** Returns the URL of the repository.xml file for this source. */ | |
public String getUrl() { | |
return mUrl; | |
} | |
/** | |
* Returns the list of known packages found by the last call to load(). | |
* 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 load() is called. | |
*/ | |
public void clearPackages() { | |
mPackages = null; | |
} | |
public String getShortDescription() { | |
return mUrl; | |
} | |
public String getLongDescription() { | |
return mDescription == null ? "" : mDescription; //$NON-NLS-1$ | |
} | |
/** | |
* Returns the last fetch error description. | |
* If there was no error, returns null. | |
*/ | |
public String getFetchError() { | |
return mFetchError; | |
} | |
/** | |
* Tries to fetch the repository index for the given URL. | |
*/ | |
public void load(ITaskMonitor monitor, boolean forceHttp) { | |
monitor.setProgressMax(4); | |
setDefaultDescription(); | |
String url = mUrl; | |
if (forceHttp) { | |
url = url.replaceAll("https://", "http://"); //$NON-NLS-1$ //$NON-NLS-2$ | |
} | |
monitor.setDescription("Fetching %1$s", url); | |
monitor.incProgress(1); | |
mFetchError = null; | |
String[] validationError = new String[] { null }; | |
Exception[] exception = new Exception[] { null }; | |
ByteArrayInputStream xml = fetchUrl(url, exception); | |
Document validatedDoc = null; | |
boolean usingAlternateXml = false; | |
String validatedUri = null; | |
if (xml != null) { | |
monitor.setDescription("Validate XML"); | |
String uri = validateXml(xml, url, validationError); | |
if (uri != null) { | |
validatedDoc = getDocument(xml, monitor); | |
validatedUri = uri; | |
} else { | |
validatedDoc = findAlternateToolsXml(xml); | |
if (validatedDoc != null) { | |
validationError[0] = null; // remove error from XML validation | |
validatedUri = SdkRepository.NS_SDK_REPOSITORY; | |
usingAlternateXml = true; | |
} | |
} | |
} | |
// If we failed the first time and the URL doesn't explicitly end with | |
// our filename, make another tentative after changing the URL. | |
if (validatedDoc == null && !url.endsWith(SdkRepository.URL_DEFAULT_XML_FILE)) { | |
if (!url.endsWith("/")) { //$NON-NLS-1$ | |
url += "/"; //$NON-NLS-1$ | |
} | |
url += SdkRepository.URL_DEFAULT_XML_FILE; | |
xml = fetchUrl(url, exception); | |
if (xml != null) { | |
String uri = validateXml(xml, url, validationError); | |
if (uri != null) { | |
validatedDoc = getDocument(xml, monitor); | |
validatedUri = uri; | |
} else { | |
validatedDoc = findAlternateToolsXml(xml); | |
if (validatedDoc != null) { | |
validationError[0] = null; // remove error from XML validation | |
validatedUri = SdkRepository.NS_SDK_REPOSITORY; | |
usingAlternateXml = true; | |
} | |
} | |
} | |
if (validatedDoc != null) { | |
// If the second tentative succeeded, indicate it in the console | |
// with the URL that worked. | |
monitor.setResult("Repository found at %1$s", url); | |
// Keep the modified URL | |
mUrl = url; | |
} | |
} | |
// If any exception was handled during the URL fetch, display it now. | |
if (exception[0] != null) { | |
mFetchError = "Failed to fetch URL"; | |
String reason = null; | |
if (exception[0] instanceof FileNotFoundException) { | |
// FNF has no useful getMessage, so we need to special handle it. | |
reason = "File not found"; | |
mFetchError += ": " + reason; | |
} else if (exception[0] instanceof SSLKeyException) { | |
// That's a common error and we have a pref for it. | |
reason = "HTTPS SSL error. You might want to force download through HTTP in the settings."; | |
mFetchError += ": HTTPS SSL error"; | |
} else if (exception[0].getMessage() != null) { | |
reason = exception[0].getMessage(); | |
} else { | |
// We don't know what's wrong. Let's give the exception class at least. | |
reason = String.format("Unknown (%1$s)", exception[0].getClass().getName()); | |
} | |
monitor.setResult("Failed to fetch URL %1$s, reason: %2$s", url, reason); | |
} | |
if(validationError[0] != null) { | |
monitor.setResult("%s", validationError[0]); //$NON-NLS-1$ | |
} | |
// Stop here if we failed to validate the XML. We don't want to load it. | |
if (validatedDoc == null) { | |
return; | |
} | |
if (usingAlternateXml) { | |
String info = "This repository requires a more recent version of the Tools. Please update."; | |
mFetchError = mFetchError == null ? info : mFetchError + ". " + info; | |
mDescription = "This repository requires a more recent version of the Tools.\nYou must update it before you can see other new packages."; | |
} | |
monitor.incProgress(1); | |
if (xml != null) { | |
monitor.setDescription("Parse XML"); | |
monitor.incProgress(1); | |
parsePackages(validatedDoc, validatedUri, monitor); | |
if (mPackages == null || mPackages.length == 0) { | |
mDescription += "\nNo packages found."; | |
} else if (mPackages.length == 1) { | |
mDescription += "\nOne package found."; | |
} else { | |
mDescription += String.format("\n%1$d packages found.", mPackages.length); | |
} | |
} | |
// done | |
monitor.incProgress(1); | |
} | |
private void setDefaultDescription() { | |
if (mUserSource) { | |
mDescription = String.format("Add-on Source: %1$s", mUrl); | |
} else { | |
mDescription = String.format("SDK Source: %1$s", mUrl); | |
} | |
} | |
/** | |
* 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 | |
* Java set Proxy: http://java.sun.com/docs/books/tutorial/networking/urls/_setProxy.html | |
*/ | |
private ByteArrayInputStream fetchUrl(String urlString, Exception[] outException) { | |
URL url; | |
try { | |
url = new URL(urlString); | |
InputStream is = null; | |
int inc = 65536; | |
int curr = 0; | |
byte[] result = new byte[inc]; | |
try { | |
is = url.openStream(); | |
int n; | |
while ((n = is.read(result, curr, result.length - curr)) != -1) { | |
curr += n; | |
if (curr == result.length) { | |
byte[] temp = new byte[curr + inc]; | |
System.arraycopy(result, 0, temp, 0, curr); | |
result = temp; | |
} | |
} | |
return new ByteArrayInputStream(result, 0, curr); | |
} finally { | |
if (is != null) { | |
try { | |
is.close(); | |
} catch (IOException e) { | |
// pass | |
} | |
} | |
} | |
} catch (Exception e) { | |
outException[0] = e; | |
} | |
return null; | |
} | |
/** | |
* Validates this XML against one of the possible SDK Repository schema, starting | |
* by the most recent one. | |
* If the XML was correctly validated, returns the schema that worked. | |
* If no schema validated the XML, returns null. | |
*/ | |
private String validateXml(ByteArrayInputStream xml, String url, String[] outError) { | |
String lastError = null; | |
String extraError = null; | |
for (int version = SdkRepository.NS_LATEST_VERSION; version >= 1; version--) { | |
try { | |
Validator validator = getValidator(version); | |
if (validator == null) { | |
lastError = "XML verification failed for %1$s.\nNo suitable XML Schema Validator could be found in your Java environment. Please consider updating your version of Java."; | |
continue; | |
} | |
xml.reset(); | |
// Validation throws a bunch of possible Exceptions on failure. | |
validator.validate(new StreamSource(xml)); | |
return SdkRepository.getSchemaUri(version); | |
} catch (Exception e) { | |
lastError = "XML verification failed for %1$s.\nError: %2$s"; | |
extraError = e.getMessage(); | |
if (extraError == null) { | |
extraError = e.getClass().getName(); | |
} | |
} | |
} | |
if (lastError != null) { | |
outError[0] = String.format(lastError, url, extraError); | |
} | |
return null; | |
} | |
/** | |
* The purpose of this method is to support forward evolution of our schema. | |
* <p/> | |
* At this point, we know that xml does not point to any schema that this version of | |
* the tool know how to process, so it's not one of the possible 1..N versions of our | |
* XSD schema. | |
* <p/> | |
* We thus try to interpret the byte stream as a possible XML stream. It may not be | |
* one at all in the first place. If it looks anything line an XML schema, we try to | |
* find its <tool> elements. If we find any, we recreate a suitable document | |
* that conforms to what we expect from our XSD schema with only those elements. | |
* To be valid, the <tool> element must have at least one <archive> | |
* compatible with this platform. | |
* | |
* If we don't find anything suitable, we drop the whole thing. | |
* | |
* @param xml The input XML stream. Can be null. | |
* @return Either a new XML document conforming to our schema with at least one <tool> | |
* element or null. | |
*/ | |
protected Document findAlternateToolsXml(InputStream xml) { | |
// Note: protected for unit-test access | |
if (xml == null) { | |
return null; | |
} | |
// Reset the stream if it supports that operation. | |
// At runtime we use a ByteArrayInputStream which can be reset; however for unit tests | |
// we use a FileInputStream that doesn't support resetting and is read-once. | |
try { | |
xml.reset(); | |
} catch (IOException e1) { | |
// ignore if not supported | |
} | |
// Get an XML document | |
Document oldDoc = null; | |
Document newDoc = null; | |
try { | |
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); | |
factory.setIgnoringComments(false); | |
factory.setValidating(false); | |
// Parse the old document using a non namespace aware builder | |
factory.setNamespaceAware(false); | |
DocumentBuilder builder = factory.newDocumentBuilder(); | |
oldDoc = builder.parse(xml); | |
// Prepare a new document using a namespace aware builder | |
factory.setNamespaceAware(true); | |
builder = factory.newDocumentBuilder(); | |
newDoc = builder.newDocument(); | |
} catch (Exception e) { | |
// Failed to get builder factor | |
// Failed to create XML document builder | |
// Failed to parse XML document | |
// Failed to read XML document | |
} | |
if (oldDoc == null || newDoc == null) { | |
return null; | |
} | |
// Check the root element is an xsd-schema with at least the following properties: | |
// <sdk:sdk-repository | |
// xmlns:sdk="http://schemas.android.com/sdk/android/repository/$N"> | |
// | |
// Note that we don't have namespace support enabled, we just do it manually. | |
Pattern nsPattern = Pattern.compile(SdkRepository.NS_SDK_REPOSITORY_PATTERN); | |
Node oldRoot = null; | |
String prefix = null; | |
for (Node child = oldDoc.getFirstChild(); child != null; child = child.getNextSibling()) { | |
if (child.getNodeType() == Node.ELEMENT_NODE) { | |
prefix = null; | |
String name = child.getNodeName(); | |
int pos = name.indexOf(':'); | |
if (pos > 0 && pos < name.length() - 1) { | |
prefix = name.substring(0, pos); | |
name = name.substring(pos + 1); | |
} | |
if (SdkRepository.NODE_SDK_REPOSITORY.equals(name)) { | |
NamedNodeMap attrs = child.getAttributes(); | |
String xmlns = "xmlns"; //$NON-NLS-1$ | |
if (prefix != null) { | |
xmlns += ":" + prefix; //$NON-NLS-1$ | |
} | |
Node attr = attrs.getNamedItem(xmlns); | |
if (attr != null) { | |
String uri = attr.getNodeValue(); | |
if (uri != null && nsPattern.matcher(uri).matches()) { | |
oldRoot = child; | |
break; | |
} | |
} | |
} | |
} | |
} | |
// we must have found the root node, and it must have an XML namespace prefix. | |
if (oldRoot == null || prefix == null || prefix.length() == 0) { | |
return null; | |
} | |
final String ns = SdkRepository.NS_SDK_REPOSITORY; | |
Element newRoot = newDoc.createElementNS(ns, SdkRepository.NODE_SDK_REPOSITORY); | |
newRoot.setPrefix(prefix); | |
newDoc.appendChild(newRoot); | |
int numTool = 0; | |
// Find an inner <tool> node and extract its required parameters | |
Node tool = null; | |
while ((tool = findChild(oldRoot, tool, prefix, SdkRepository.NODE_TOOL)) != null) { | |
// To be valid, the tool element must have: | |
// - a <revision> element with a number | |
// - an optional <uses-license> node, which we'll skip right now. | |
// (if we add it later, we must find the license declaration element too) | |
// - an <archives> element with one or more <archive> elements inside | |
// - one of the <archive> elements must have an "os" and "arch" attributes | |
// compatible with the current platform. Only keep the first such element found. | |
// - the <archive> element must contain a <size>, a <checksum> and a <url>. | |
try { | |
Node revision = findChild(tool, null, prefix, SdkRepository.NODE_REVISION); | |
Node archives = findChild(tool, null, prefix, SdkRepository.NODE_ARCHIVES); | |
if (revision == null || archives == null) { | |
continue; | |
} | |
int rev = 0; | |
try { | |
String content = revision.getTextContent(); | |
content = content.trim(); | |
rev = Integer.parseInt(content); | |
if (rev < 1) { | |
continue; | |
} | |
} catch (NumberFormatException ignore) { | |
continue; | |
} | |
Element newTool = newDoc.createElementNS(ns, SdkRepository.NODE_TOOL); | |
newTool.setPrefix(prefix); | |
appendChild(newTool, ns, prefix, | |
SdkRepository.NODE_REVISION, Integer.toString(rev)); | |
Element newArchives = appendChild(newTool, ns, prefix, | |
SdkRepository.NODE_ARCHIVES, null); | |
int numArchives = 0; | |
Node archive = null; | |
while ((archive = findChild(archives, | |
archive, | |
prefix, | |
SdkRepository.NODE_ARCHIVE)) != null) { | |
try { | |
Os os = (Os) XmlParserUtils.getEnumAttribute(archive, | |
SdkRepository.ATTR_OS, | |
Os.values(), | |
null /*default*/); | |
Arch arch = (Arch) XmlParserUtils.getEnumAttribute(archive, | |
SdkRepository.ATTR_ARCH, | |
Arch.values(), | |
Arch.ANY); | |
if (os == null || !os.isCompatible() || | |
arch == null || !arch.isCompatible()) { | |
continue; | |
} | |
Node node = findChild(archive, null, prefix, SdkRepository.NODE_URL); | |
String url = node == null ? null : node.getTextContent().trim(); | |
if (url == null || url.length() == 0) { | |
continue; | |
} | |
node = findChild(archive, null, prefix, SdkRepository.NODE_SIZE); | |
long size = 0; | |
try { | |
size = Long.parseLong(node.getTextContent()); | |
} catch (Exception e) { | |
// pass | |
} | |
if (size < 1) { | |
continue; | |
} | |
node = findChild(archive, null, prefix, SdkRepository.NODE_CHECKSUM); | |
// double check that the checksum element contains a type=sha1 attribute | |
if (node == null) { | |
continue; | |
} | |
NamedNodeMap attrs = node.getAttributes(); | |
Node typeNode = attrs.getNamedItem(SdkRepository.ATTR_TYPE); | |
if (typeNode == null || | |
!SdkRepository.ATTR_TYPE.equals(typeNode.getNodeName()) || | |
!SdkRepository.SHA1_TYPE.equals(typeNode.getNodeValue())) { | |
continue; | |
} | |
String sha1 = node == null ? null : node.getTextContent().trim(); | |
if (sha1 == null || sha1.length() != SdkRepository.SHA1_CHECKSUM_LEN) { | |
continue; | |
} | |
// Use that archive for the new tool element | |
Element ar = appendChild(newArchives, ns, prefix, | |
SdkRepository.NODE_ARCHIVE, null); | |
ar.setAttributeNS(ns, SdkRepository.ATTR_OS, os.getXmlName()); | |
ar.setAttributeNS(ns, SdkRepository.ATTR_ARCH, arch.getXmlName()); | |
appendChild(ar, ns, prefix, SdkRepository.NODE_URL, url); | |
appendChild(ar, ns, prefix, SdkRepository.NODE_SIZE, Long.toString(size)); | |
Element cs = appendChild(ar, ns, prefix, SdkRepository.NODE_CHECKSUM, sha1); | |
cs.setAttributeNS(ns, SdkRepository.ATTR_TYPE, SdkRepository.SHA1_TYPE); | |
numArchives++; | |
} catch (Exception ignore1) { | |
// pass | |
} | |
} // while <archive> | |
if (numArchives > 0) { | |
newRoot.appendChild(newTool); | |
numTool++; | |
} | |
} catch (Exception ignore2) { | |
// pass | |
} | |
} // while <tool> | |
return numTool > 0 ? newDoc : null; | |
} | |
/** | |
* Helper method used by {@link #findAlternateToolsXml(InputStream)} to find a given | |
* element child in a root XML node. | |
*/ | |
private Node findChild(Node rootNode, Node after, String prefix, String nodeName) { | |
nodeName = prefix + ":" + nodeName; | |
Node child = after == null ? rootNode.getFirstChild() : after.getNextSibling(); | |
for(; child != null; child = child.getNextSibling()) { | |
if (child.getNodeType() == Node.ELEMENT_NODE && nodeName.equals(child.getNodeName())) { | |
return child; | |
} | |
} | |
return null; | |
} | |
/** | |
* Helper method used by {@link #findAlternateToolsXml(InputStream)} to create a new | |
* XML element into a parent element. | |
*/ | |
private Element appendChild(Element rootNode, String namespaceUri, | |
String prefix, String nodeName, | |
String nodeValue) { | |
Element node = rootNode.getOwnerDocument().createElementNS(namespaceUri, nodeName); | |
node.setPrefix(prefix); | |
if (nodeValue != null) { | |
node.setTextContent(nodeValue); | |
} | |
rootNode.appendChild(node); | |
return node; | |
} | |
/** | |
* Helper method that returns a validator for our XSD, or null if the current Java | |
* implementation can't process XSD schemas. | |
* | |
* @param version The version of the XML Schema. | |
* See {@link SdkRepository#getXsdStream(int)} | |
*/ | |
private Validator getValidator(int version) throws SAXException { | |
InputStream xsdStream = SdkRepository.getXsdStream(version); | |
SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); | |
if (factory == null) { | |
return null; | |
} | |
// 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 == null ? null : schema.newValidator(); | |
return validator; | |
} | |
/** | |
* Parse all packages defined in the SDK Repository XML and creates | |
* a new mPackages array with them. | |
*/ | |
protected boolean parsePackages(Document doc, String nsUri, ITaskMonitor monitor) { | |
// protected for unit-test acces | |
assert doc != null; | |
Node root = getFirstChild(doc, nsUri, SdkRepository.NODE_SDK_REPOSITORY); | |
if (root != null) { | |
ArrayList<Package> packages = new ArrayList<Package>(); | |
// Parse license definitions | |
HashMap<String, String> licenses = new HashMap<String, String>(); | |
for (Node child = root.getFirstChild(); | |
child != null; | |
child = child.getNextSibling()) { | |
if (child.getNodeType() == Node.ELEMENT_NODE && | |
nsUri.equals(child.getNamespaceURI()) && | |
child.getLocalName().equals(SdkRepository.NODE_LICENSE)) { | |
Node id = child.getAttributes().getNamedItem(SdkRepository.ATTR_ID); | |
if (id != null) { | |
licenses.put(id.getNodeValue(), child.getTextContent()); | |
} | |
} | |
} | |
// Parse packages | |
for (Node child = root.getFirstChild(); | |
child != null; | |
child = child.getNextSibling()) { | |
if (child.getNodeType() == Node.ELEMENT_NODE && | |
nsUri.equals(child.getNamespaceURI())) { | |
String name = child.getLocalName(); | |
Package p = null; | |
try { | |
// We can load addon and extra packages from all sources, either | |
// internal or user sources. | |
if (SdkRepository.NODE_ADD_ON.equals(name)) { | |
p = new AddonPackage(this, child, licenses); | |
} else if (SdkRepository.NODE_EXTRA.equals(name)) { | |
p = new ExtraPackage(this, child, licenses); | |
} else if (!mUserSource) { | |
// We only load platform, doc and tool packages from internal | |
// sources, never from user sources. | |
if (SdkRepository.NODE_PLATFORM.equals(name)) { | |
p = new PlatformPackage(this, child, licenses); | |
} else if (SdkRepository.NODE_DOC.equals(name)) { | |
p = new DocPackage(this, child, licenses); | |
} else if (SdkRepository.NODE_TOOL.equals(name)) { | |
p = new ToolPackage(this, child, licenses); | |
} | |
} | |
if (p != null) { | |
packages.add(p); | |
monitor.setDescription("Found %1$s", p.getShortDescription()); | |
} | |
} catch (Exception e) { | |
// Ignore invalid packages | |
} | |
} | |
} | |
mPackages = packages.toArray(new Package[packages.size()]); | |
// Order the packages. | |
Arrays.sort(mPackages, null); | |
return true; | |
} | |
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 nsUri, String xmlLocalName) { | |
for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) { | |
if (child.getNodeType() == Node.ELEMENT_NODE && | |
nsUri.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. | |
* | |
* On error, returns null and prints a (hopefully) useful message on the monitor. | |
*/ | |
private Document getDocument(ByteArrayInputStream xml, ITaskMonitor monitor) { | |
try { | |
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); | |
factory.setIgnoringComments(true); | |
factory.setNamespaceAware(true); | |
DocumentBuilder builder = factory.newDocumentBuilder(); | |
xml.reset(); | |
Document doc = builder.parse(new InputSource(xml)); | |
return doc; | |
} catch (ParserConfigurationException e) { | |
monitor.setResult("Failed to create XML document builder"); | |
} catch (SAXException e) { | |
monitor.setResult("Failed to parse XML document"); | |
} catch (IOException e) { | |
monitor.setResult("Failed to read XML document"); | |
} | |
return null; | |
} | |
} |