All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.android.sdklib.internal.repository.sources.SdkRepoSource Maven / Gradle / Ivy

There is a newer version: 25.3.0
Show newest version
/*
 * 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.sources;

import com.android.annotations.Nullable;
import com.android.sdklib.internal.repository.archives.ArchFilter;
import com.android.sdklib.internal.repository.packages.Package;
import com.android.sdklib.internal.repository.packages.PackageParserUtils;
import com.android.sdklib.repository.RepoConstants;
import com.android.sdklib.repository.SdkRepoConstants;

import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.Text;
import org.xml.sax.ErrorHandler;

import java.io.IOException;
import java.io.InputStream;
import java.util.regex.Pattern;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;


/**
 * An sdk-repository source, i.e. a download site.
 * A repository describes one or more {@link Package}s available for download.
 */
public class SdkRepoSource extends SdkSource {

    /**
     * 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 uiName The UI-visible name of the source. Can be null.
     */
    public SdkRepoSource(String url, String uiName) {
        super(url, uiName);
    }

    /**
     * Returns true if this is an addon source.
     * We only load addons and extras from these sources.
     */
    @Override
    public boolean isAddonSource() {
        return false;
    }

    /**
     * Returns true if this is a system-image source.
     * We only load system-images from these sources.
     */
    @Override
    public boolean isSysImgSource() {
        return false;
    }

    private static String[] sDefaults = null; // lazily allocated in getDefaultXmlFileUrls

    @Override
    protected String[] getDefaultXmlFileUrls() {
        if (sDefaults == null) {
            String[] values = new String[SdkRepoConstants.NS_LATEST_VERSION
                                         - SdkRepoConstants.NS_SERVER_MIN_VERSION
                                         + 2];
            int k = 0;
            for (int i  = SdkRepoConstants.NS_LATEST_VERSION;
                     i >= SdkRepoConstants.NS_SERVER_MIN_VERSION;
                     i--) {
                values[k++] = String.format(SdkRepoConstants.URL_FILENAME_PATTERN, i);
            }
            values[k++] = SdkRepoConstants.URL_DEFAULT_FILENAME;
            assert k == values.length;
            sDefaults = values;
        }

        return sDefaults;
    }

    @Override
    protected int getNsLatestVersion() {
        return SdkRepoConstants.NS_LATEST_VERSION;
    }

    @Override
    protected String getNsUri() {
        return SdkRepoConstants.NS_URI;
    }

    @Override
    protected String getNsPattern() {
        return SdkRepoConstants.NS_PATTERN;
    }

    @Override
    protected String getSchemaUri(int version) {
        return SdkRepoConstants.getSchemaUri(version);
    }

    @Override
    protected String getRootElementName() {
        return SdkRepoConstants.NODE_SDK_REPOSITORY;
    }

    @Override
    protected InputStream getXsdStream(int version) {
        return SdkRepoConstants.getXsdStream(version);
    }

    /**
     * The purpose of this method is to support forward evolution of our schema.
     * 

* At this point, we know that xml does not point to any schema that this version of * the tool knows how to process, so it's not one of the possible 1..N versions of our * XSD schema. *

* 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> and the <platform-tools> 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> and the <platform-tools> elements must have at * least one <archive> compatible with this platform. *

* Starting the sdk-repository schema v3, <tools> has a <min-platform-tools-rev> * node, so technically the corresponding XML schema will be usable only if there's a * <platform-tools> with the request revision number. We don't enforce that here, as * this is done at install time. *

* 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> * and <platform-tools> element or null. * @throws IOException if InputStream.reset() fails * @null Can return null on failure. */ @Override protected Document findAlternateToolsXml(@Nullable InputStream xml) throws IOException { return findAlternateToolsXml(xml, null /*errorHandler*/); } /** * An alternate version of {@link #findAlternateToolsXml(InputStream)} that allows * the caller to specify the XML error handler. The default from the underlying Java * XML Xerces parser will dump to stdout/stderr, which is not convenient during unit tests. * * @param xml The input XML stream. Can be null. * @param errorHandler An optional XML error handler. If null, the default will be used. * @return Either a new XML document conforming to our schema with at least one <tool> * and <platform-tools> element or null. * @throws IOException if InputStream.reset() fails * @null Can return null on failure. * @see #findAlternateToolsXml(InputStream) findAlternateToolsXml() provides more details. */ protected Document findAlternateToolsXml( @Nullable InputStream xml, @Nullable ErrorHandler errorHandler) throws IOException { if (xml == null) { return null; } // Reset the stream if it supports that operation. assert xml.markSupported(); xml.reset(); // 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(); if (errorHandler != null) { builder.setErrorHandler(errorHandler); } 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 XML with at least the following properties: // // // Note that we don't have namespace support enabled, we just do it manually. Pattern nsPattern = Pattern.compile(getNsPattern()); 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 (SdkRepoConstants.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 = getNsUri(); Element newRoot = newDoc.createElementNS(ns, getRootElementName()); newRoot.setPrefix(prefix); newDoc.appendChild(newRoot); int numTool = 0; // Find any inner or nodes and extract their required parameters String[] elementNames = { SdkRepoConstants.NODE_TOOL, SdkRepoConstants.NODE_PLATFORM_TOOL, SdkRepoConstants.NODE_LICENSE }; Element element = null; while ((element = findChild(oldRoot, element, prefix, elementNames)) != null) { boolean isElementValid = false; String name = element.getLocalName(); if (name == null) { name = element.getNodeName(); int pos = name.indexOf(':'); if (pos > 0 && pos < name.length() - 1) { name = name.substring(pos + 1); } } // To be valid, the tool or platform-tool element must have: // - a element with a number // - a element with a number for a element // - an element with one or more elements inside // - one of the elements must have an "os" and "arch" attributes // compatible with the current platform. Only keep the first such element found. // - the element must contain a , a and a . // - none of the above for a license element if (SdkRepoConstants.NODE_LICENSE.equals(name)) { isElementValid = true; } else { try { Node revision = findChild(element, null, prefix, RepoConstants.NODE_REVISION); Node archives = findChild(element, null, prefix, RepoConstants.NODE_ARCHIVES); if (revision == null || archives == null) { continue; } // check revision contains a number try { String content = revision.getTextContent(); content = content.trim(); int rev = Integer.parseInt(content); if (rev < 1) { continue; } } catch (NumberFormatException ignore) { continue; } if (SdkRepoConstants.NODE_TOOL.equals(name)) { Node minPTRev = findChild(element, null, prefix, RepoConstants.NODE_MIN_PLATFORM_TOOLS_REV); if (minPTRev == null) { continue; } // check min-platform-tools-rev contains a number try { String content = minPTRev.getTextContent(); content = content.trim(); int rev = Integer.parseInt(content); if (rev < 1) { continue; } } catch (NumberFormatException ignore) { continue; } } Node archive = null; while ((archive = findChild(archives, archive, prefix, RepoConstants.NODE_ARCHIVE)) != null) { try { ArchFilter af = PackageParserUtils.parseArchFilter(archive); if (af == null || !af.isCompatibleWith(ArchFilter.getCurrent())) { continue; } Node node = findChild(archive, null, prefix, RepoConstants.NODE_URL); String url = node == null ? null : node.getTextContent().trim(); if (url == null || url.length() == 0) { continue; } node = findChild(archive, null, prefix, RepoConstants.NODE_SIZE); long size = 0; try { size = Long.parseLong(node.getTextContent()); } catch (Exception e) { // pass } if (size < 1) { continue; } node = findChild(archive, null, prefix, RepoConstants.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(RepoConstants.ATTR_TYPE); if (typeNode == null || !RepoConstants.ATTR_TYPE.equals(typeNode.getNodeName()) || !RepoConstants.SHA1_TYPE.equals(typeNode.getNodeValue())) { continue; } String sha1 = node == null ? null : node.getTextContent().trim(); if (sha1 == null || sha1.length() != RepoConstants.SHA1_CHECKSUM_LEN) { continue; } isElementValid = true; } catch (Exception ignore1) { // For debugging it is useful to re-throw the exception. // For end-users, not so much. It would be nice to make it // happen automatically during unit tests. if (System.getenv("TESTING") != null || System.getProperty("THROW_DEEP_EXCEPTION_DURING_TESTING") != null) { throw new RuntimeException(ignore1); } } } // while } catch (Exception ignore2) { // For debugging it is useful to re-throw the exception. // For end-users, not so much. It would be nice to make it // happen automatically during unit tests. if (System.getenv("TESTING") != null || System.getProperty("THROW_DEEP_EXCEPTION_DURING_TESTING") != null) { throw new RuntimeException(ignore2); } } } if (isElementValid) { duplicateNode(newRoot, element, SdkRepoConstants.NS_URI, prefix); numTool++; } } // while return numTool > 0 ? newDoc : null; } /** * Helper method used by {@link #findAlternateToolsXml(InputStream)} to find a given * element child in a root XML node. */ private Element findChild(Node rootNode, Node after, String prefix, String[] nodeNames) { for (int i = 0; i < nodeNames.length; i++) { if (nodeNames[i].indexOf(':') < 0) { nodeNames[i] = prefix + ":" + nodeNames[i]; } } Node child = after == null ? rootNode.getFirstChild() : after.getNextSibling(); for(; child != null; child = child.getNextSibling()) { if (child.getNodeType() != Node.ELEMENT_NODE) { continue; } for (String nodeName : nodeNames) { if (nodeName.equals(child.getNodeName())) { return (Element) child; } } } return 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) { return findChild(rootNode, after, prefix, new String[] { nodeName }); } /** * Helper method used by {@link #findAlternateToolsXml(InputStream)} to duplicate a node * and attach it to the given root in the new document. */ private Element duplicateNode(Element newRootNode, Element oldNode, String namespaceUri, String prefix) { // The implementation here is more or less equivalent to // // newRoot.appendChild(newDoc.importNode(oldNode, deep=true)) // // except we can't just use importNode() since we need to deal with the fact // that the old document is not namespace-aware yet the new one is. Document newDoc = newRootNode.getOwnerDocument(); Element newNode = null; String nodeName = oldNode.getNodeName(); int pos = nodeName.indexOf(':'); if (pos > 0 && pos < nodeName.length() - 1) { nodeName = nodeName.substring(pos + 1); newNode = newDoc.createElementNS(namespaceUri, nodeName); newNode.setPrefix(prefix); } else { newNode = newDoc.createElement(nodeName); } newRootNode.appendChild(newNode); // Merge in all the attributes NamedNodeMap attrs = oldNode.getAttributes(); for (int i = 0; i < attrs.getLength(); i++) { Attr attr = (Attr) attrs.item(i); Attr newAttr = null; String attrName = attr.getNodeName(); pos = attrName.indexOf(':'); if (pos > 0 && pos < attrName.length() - 1) { attrName = attrName.substring(pos + 1); newAttr = newDoc.createAttributeNS(namespaceUri, attrName); newAttr.setPrefix(prefix); } else { newAttr = newDoc.createAttribute(attrName); } newAttr.setNodeValue(attr.getNodeValue()); if (pos > 0) { newNode.getAttributes().setNamedItemNS(newAttr); } else { newNode.getAttributes().setNamedItem(newAttr); } } // Merge all child elements and texts for (Node child = oldNode.getFirstChild(); child != null; child = child.getNextSibling()) { if (child.getNodeType() == Node.ELEMENT_NODE) { duplicateNode(newNode, (Element) child, namespaceUri, prefix); } else if (child.getNodeType() == Node.TEXT_NODE) { Text newText = newDoc.createTextNode(child.getNodeValue()); newNode.appendChild(newText); } } return newNode; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy