• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.sdklib.internal.repository;
18 
19 import com.android.sdklib.internal.repository.Archive.Arch;
20 import com.android.sdklib.internal.repository.Archive.Os;
21 import com.android.sdklib.repository.SdkRepository;
22 
23 import org.w3c.dom.Document;
24 import org.w3c.dom.Element;
25 import org.w3c.dom.NamedNodeMap;
26 import org.w3c.dom.Node;
27 import org.xml.sax.InputSource;
28 import org.xml.sax.SAXException;
29 
30 import java.io.ByteArrayInputStream;
31 import java.io.FileNotFoundException;
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.net.URL;
35 import java.util.ArrayList;
36 import java.util.HashMap;
37 import java.util.regex.Pattern;
38 
39 import javax.net.ssl.SSLKeyException;
40 import javax.xml.XMLConstants;
41 import javax.xml.parsers.DocumentBuilder;
42 import javax.xml.parsers.DocumentBuilderFactory;
43 import javax.xml.parsers.ParserConfigurationException;
44 import javax.xml.transform.stream.StreamSource;
45 import javax.xml.validation.Schema;
46 import javax.xml.validation.SchemaFactory;
47 import javax.xml.validation.Validator;
48 
49 /**
50  * An sdk-repository source, i.e. a download site.
51  * It may be a full repository or an add-on only repository.
52  * A repository describes one or {@link Package}s available for download.
53  */
54 public class RepoSource implements IDescription {
55 
56     private String mUrl;
57     private final boolean mUserSource;
58 
59     private Package[] mPackages;
60     private String mDescription;
61     private String mFetchError;
62 
63     /**
64      * Constructs a new source for the given repository URL.
65      * @param url The source URL. Cannot be null. If the URL ends with a /, the default
66      *            repository.xml filename will be appended automatically.
67      * @param userSource True if this a user source (add-ons & packages only.)
68      */
RepoSource(String url, boolean userSource)69     public RepoSource(String url, boolean userSource) {
70 
71         // if the URL ends with a /, it must be "directory" resource,
72         // in which case we automatically add the default file that will
73         // looked for. This way it will be obvious to the user which
74         // resource we are actually trying to fetch.
75         if (url.endsWith("/")) {  //$NON-NLS-1$
76             url += SdkRepository.URL_DEFAULT_XML_FILE;
77         }
78 
79         mUrl = url;
80         mUserSource = userSource;
81         setDefaultDescription();
82     }
83 
84     /**
85      * Two repo source are equal if they have the same userSource flag and the same URL.
86      */
87     @Override
equals(Object obj)88     public boolean equals(Object obj) {
89         if (obj instanceof RepoSource) {
90             RepoSource rs = (RepoSource) obj;
91             return  rs.isUserSource() == this.isUserSource() && rs.getUrl().equals(this.getUrl());
92         }
93         return false;
94     }
95 
96     @Override
hashCode()97     public int hashCode() {
98         return mUrl.hashCode() ^ Boolean.valueOf(mUserSource).hashCode();
99     }
100 
101     /** Returns true if this is a user source. We only load addon and extra packages
102      * from a user source and ignore the rest. */
isUserSource()103     public boolean isUserSource() {
104         return mUserSource;
105     }
106 
107     /** Returns the URL of the repository.xml file for this source. */
getUrl()108     public String getUrl() {
109         return mUrl;
110     }
111 
112     /**
113      * Returns the list of known packages found by the last call to load().
114      * This is null when the source hasn't been loaded yet.
115      */
getPackages()116     public Package[] getPackages() {
117         return mPackages;
118     }
119 
120     /**
121      * Clear the internal packages list. After this call, {@link #getPackages()} will return
122      * null till load() is called.
123      */
clearPackages()124     public void clearPackages() {
125         mPackages = null;
126     }
127 
getShortDescription()128     public String getShortDescription() {
129         return mUrl;
130     }
131 
getLongDescription()132     public String getLongDescription() {
133         return mDescription == null ? "" : mDescription;  //$NON-NLS-1$
134     }
135 
136     /**
137      * Returns the last fetch error description.
138      * If there was no error, returns null.
139      */
getFetchError()140     public String getFetchError() {
141         return mFetchError;
142     }
143 
144     /**
145      * Tries to fetch the repository index for the given URL.
146      */
load(ITaskMonitor monitor, boolean forceHttp)147     public void load(ITaskMonitor monitor, boolean forceHttp) {
148 
149         monitor.setProgressMax(4);
150 
151         setDefaultDescription();
152 
153         String url = mUrl;
154         if (forceHttp) {
155             url = url.replaceAll("https://", "http://");  //$NON-NLS-1$ //$NON-NLS-2$
156         }
157 
158         monitor.setDescription("Fetching %1$s", url);
159         monitor.incProgress(1);
160 
161         mFetchError = null;
162         Exception[] exception = new Exception[] { null };
163         ByteArrayInputStream xml = fetchUrl(url, exception);
164         Document validatedDoc = null;
165         String validatedUri = null;
166         if (xml != null) {
167             monitor.setDescription("Validate XML");
168             String uri = validateXml(xml, url, monitor);
169             if (uri != null) {
170                 validatedDoc = getDocument(xml, monitor);
171                 validatedUri = uri;
172             } else {
173                 validatedDoc = findAlternateToolsXml(xml);
174                 validatedUri = SdkRepository.NS_SDK_REPOSITORY;
175             }
176         }
177 
178         // If we failed the first time and the URL doesn't explicitly end with
179         // our filename, make another tentative after changing the URL.
180         if (validatedDoc == null && !url.endsWith(SdkRepository.URL_DEFAULT_XML_FILE)) {
181             if (!url.endsWith("/")) {       //$NON-NLS-1$
182                 url += "/";                 //$NON-NLS-1$
183             }
184             url += SdkRepository.URL_DEFAULT_XML_FILE;
185 
186             xml = fetchUrl(url, exception);
187             if (xml != null) {
188                 String uri = validateXml(xml, url, monitor);
189                 if (uri != null) {
190                     validatedDoc = getDocument(xml, monitor);
191                     validatedUri = uri;
192                 } else {
193                     validatedDoc = findAlternateToolsXml(xml);
194                     validatedUri = SdkRepository.NS_SDK_REPOSITORY;
195                 }
196             }
197 
198             if (validatedDoc != null) {
199                 // If the second tentative succeeded, indicate it in the console
200                 // with the URL that worked.
201                 monitor.setResult("Repository found at %1$s", url);
202 
203                 // Keep the modified URL
204                 mUrl = url;
205             }
206         }
207 
208         // If any exception was handled during the URL fetch, display it now.
209         if (exception[0] != null) {
210             mFetchError = "Failed to fetch URL";
211 
212             String reason = null;
213             if (exception[0] instanceof FileNotFoundException) {
214                 // FNF has no useful getMessage, so we need to special handle it.
215                 reason = "File not found";
216                 mFetchError += ": " + reason;
217             } else if (exception[0] instanceof SSLKeyException) {
218                 // That's a common error and we have a pref for it.
219                 reason = "HTTPS SSL error. You might want to force download through HTTP in the settings.";
220                 mFetchError += ": HTTPS SSL error";
221             } else if (exception[0].getMessage() != null) {
222                 reason = exception[0].getMessage();
223             } else {
224                 // We don't know what's wrong. Let's give the exception class at least.
225                 reason = String.format("Unknown (%1$s)", exception[0].getClass().getName());
226             }
227 
228             monitor.setResult("Failed to fetch URL %1$s, reason: %2$s", url, reason);
229         }
230 
231         // Stop here if we failed to validate the XML. We don't want to load it.
232         if (validatedDoc == null) {
233             return;
234         }
235 
236         monitor.incProgress(1);
237 
238         if (xml != null) {
239             monitor.setDescription("Parse XML");
240             monitor.incProgress(1);
241             parsePackages(validatedDoc, validatedUri, monitor);
242             if (mPackages == null || mPackages.length == 0) {
243                 mDescription += "\nNo packages found.";
244             } else if (mPackages.length == 1) {
245                 mDescription += "\nOne package found.";
246             } else {
247                 mDescription += String.format("\n%1$d packages found.", mPackages.length);
248             }
249         }
250 
251         // done
252         monitor.incProgress(1);
253     }
254 
setDefaultDescription()255     private void setDefaultDescription() {
256         if (mUserSource) {
257             mDescription = String.format("Add-on Source: %1$s", mUrl);
258         } else {
259             mDescription = String.format("SDK Source: %1$s", mUrl);
260         }
261     }
262 
263     /**
264      * Fetches the document at the given URL and returns it as a string.
265      * Returns null if anything wrong happens and write errors to the monitor.
266      *
267      * References:
268      * Java URL Connection: http://java.sun.com/docs/books/tutorial/networking/urls/readingWriting.html
269      * Java URL Reader: http://java.sun.com/docs/books/tutorial/networking/urls/readingURL.html
270      * Java set Proxy: http://java.sun.com/docs/books/tutorial/networking/urls/_setProxy.html
271      */
fetchUrl(String urlString, Exception[] outException)272     private ByteArrayInputStream fetchUrl(String urlString, Exception[] outException) {
273         URL url;
274         try {
275             url = new URL(urlString);
276 
277             InputStream is = null;
278 
279             int inc = 65536;
280             int curr = 0;
281             byte[] result = new byte[inc];
282 
283             try {
284                 is = url.openStream();
285 
286                 int n;
287                 while ((n = is.read(result, curr, result.length - curr)) != -1) {
288                     curr += n;
289                     if (curr == result.length) {
290                         byte[] temp = new byte[curr + inc];
291                         System.arraycopy(result, 0, temp, 0, curr);
292                         result = temp;
293                     }
294                 }
295 
296                 return new ByteArrayInputStream(result, 0, curr);
297 
298             } finally {
299                 if (is != null) {
300                     try {
301                         is.close();
302                     } catch (IOException e) {
303                         // pass
304                     }
305                 }
306             }
307 
308         } catch (Exception e) {
309             outException[0] = e;
310         }
311 
312         return null;
313     }
314 
315     /**
316      * Validates this XML against one of the possible SDK Repository schema, starting
317      * by the most recent one.
318      * If the XML was correctly validated, returns the schema that worked.
319      * If no schema validated the XML, returns null.
320      */
validateXml(ByteArrayInputStream xml, String url, ITaskMonitor monitor)321     private String validateXml(ByteArrayInputStream xml, String url, ITaskMonitor monitor) {
322 
323         String lastError = null;
324         String extraError = null;
325         for (int version = SdkRepository.NS_LATEST_VERSION; version >= 1; version--) {
326             try {
327                 Validator validator = getValidator(version);
328 
329                 if (validator == null) {
330                     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.";
331                     continue;
332                 }
333 
334                 xml.reset();
335                 // Validation throws a bunch of possible Exceptions on failure.
336                 validator.validate(new StreamSource(xml));
337                 return SdkRepository.getSchemaUri(version);
338 
339             } catch (Exception e) {
340                 lastError = "XML verification failed for %1$s.\nError: %2$s";
341                 extraError = e.getMessage();
342                 if (extraError == null) {
343                     extraError = e.getClass().getName();
344                 }
345             }
346         }
347 
348         if (lastError != null) {
349             monitor.setResult(lastError, url, extraError);
350         }
351         return null;
352     }
353 
354     /**
355      * The purpose of this method is to support forward evolution of our schema.
356      * <p/>
357      * At this point, we know that xml does not point to any schema that this version of
358      * the tool know how to process, so it's not one of the possible 1..N versions of our
359      * XSD schema.
360      * <p/>
361      * We thus try to interpret the byte stream as a possible XML stream. It may not be
362      * one at all in the first place. If it looks anything line an XML schema, we try to
363      * find its &lt;tool&gt; elements. If we find any, we recreate a suitable document
364      * that conforms to what we expect from our XSD schema with only those elements.
365      * To be valid, the &lt;tool&gt; element must have at least one &lt;archive&gt;
366      * compatible with this platform.
367      *
368      * If we don't find anything suitable, we drop the whole thing.
369      *
370      * @param xml The input XML stream. Can be null.
371      * @return Either a new XML document conforming to our schema with at least one &lt;tool&gt;
372      *         element or null.
373      */
findAlternateToolsXml(InputStream xml)374     protected Document findAlternateToolsXml(InputStream xml) {
375         // Note: protected for unit-test access
376 
377         if (xml == null) {
378             return null;
379         }
380 
381         // Reset the stream if it supports that operation.
382         // At runtime we use a ByteArrayInputStream which can be reset; however for unit tests
383         // we use a FileInputStream that doesn't support resetting and is read-once.
384         try {
385             xml.reset();
386         } catch (IOException e1) {
387             // ignore if not supported
388         }
389 
390         // Get an XML document
391 
392         Document oldDoc = null;
393         Document newDoc = null;
394         try {
395             DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
396             factory.setIgnoringComments(false);
397             factory.setValidating(false);
398 
399             // Parse the old document using a non namespace aware builder
400             factory.setNamespaceAware(false);
401             DocumentBuilder builder = factory.newDocumentBuilder();
402             oldDoc = builder.parse(xml);
403 
404             // Prepare a new document using a namespace aware builder
405             factory.setNamespaceAware(true);
406             builder = factory.newDocumentBuilder();
407             newDoc = builder.newDocument();
408 
409         } catch (Exception e) {
410             // Failed to get builder factor
411             // Failed to create XML document builder
412             // Failed to parse XML document
413             // Failed to read XML document
414         }
415 
416         if (oldDoc == null || newDoc == null) {
417             return null;
418         }
419 
420 
421         // Check the root element is an xsd-schema with at least the following properties:
422         // <sdk:sdk-repository
423         //    xmlns:sdk="http://schemas.android.com/sdk/android/repository/$N">
424         //
425         // Note that we don't have namespace support enabled, we just do it manually.
426 
427         Pattern nsPattern = Pattern.compile(SdkRepository.NS_SDK_REPOSITORY_PATTERN);
428 
429         Node oldRoot = null;
430         String prefix = null;
431         for (Node child = oldDoc.getFirstChild(); child != null; child = child.getNextSibling()) {
432             if (child.getNodeType() == Node.ELEMENT_NODE) {
433                 prefix = null;
434                 String name = child.getNodeName();
435                 int pos = name.indexOf(':');
436                 if (pos > 0 && pos < name.length() - 1) {
437                     prefix = name.substring(0, pos);
438                     name = name.substring(pos + 1);
439                 }
440                 if (SdkRepository.NODE_SDK_REPOSITORY.equals(name)) {
441                     NamedNodeMap attrs = child.getAttributes();
442                     String xmlns = "xmlns";                                         //$NON-NLS-1$
443                     if (prefix != null) {
444                         xmlns += ":" + prefix;                                      //$NON-NLS-1$
445                     }
446                     Node attr = attrs.getNamedItem(xmlns);
447                     if (attr != null) {
448                         String uri = attr.getNodeValue();
449                         if (uri != null && nsPattern.matcher(uri).matches()) {
450                             oldRoot = child;
451                             break;
452                         }
453                     }
454                 }
455             }
456         }
457 
458         // we must have found the root node, and it must have an XML namespace prefix.
459         if (oldRoot == null || prefix == null || prefix.length() == 0) {
460             return null;
461         }
462 
463         final String ns = SdkRepository.NS_SDK_REPOSITORY;
464         Element newRoot = newDoc.createElementNS(ns, SdkRepository.NODE_SDK_REPOSITORY);
465         newRoot.setPrefix(prefix);
466         newDoc.appendChild(newRoot);
467         int numTool = 0;
468 
469         // Find an inner <tool> node and extract its required parameters
470 
471         Node tool = null;
472         while ((tool = findChild(oldRoot, tool, prefix, SdkRepository.NODE_TOOL)) != null) {
473             // To be valid, the tool element must have:
474             // - a <revision> element with a number
475             // - an optional <uses-license> node, which we'll skip right now.
476             //   (if we add it later, we must find the license declaration element too)
477             // - an <archives> element with one or more <archive> elements inside
478             // - one of the <archive> elements must have an "os" and "arch" attributes
479             //   compatible with the current platform. Only keep the first such element found.
480             // - the <archive> element must contain a <size>, a <checksum> and a <url>.
481 
482             try {
483                 Node revision = findChild(tool, null, prefix, SdkRepository.NODE_REVISION);
484                 Node archives = findChild(tool, null, prefix, SdkRepository.NODE_ARCHIVES);
485 
486                 if (revision == null || archives == null) {
487                     continue;
488                 }
489 
490                 int rev = 0;
491                 try {
492                     String content = revision.getTextContent();
493                     content = content.trim();
494                     rev = Integer.parseInt(content);
495                     if (rev < 1) {
496                         continue;
497                     }
498                 } catch (NumberFormatException ignore) {
499                     continue;
500                 }
501 
502                 Element newTool = newDoc.createElementNS(ns, SdkRepository.NODE_TOOL);
503                 newTool.setPrefix(prefix);
504                 appendChild(newTool, ns, prefix,
505                         SdkRepository.NODE_REVISION, Integer.toString(rev));
506                 Element newArchives = appendChild(newTool, ns, prefix,
507                                                   SdkRepository.NODE_ARCHIVES, null);
508                 int numArchives = 0;
509 
510                 Node archive = null;
511                 while ((archive = findChild(archives,
512                                             archive,
513                                             prefix,
514                                             SdkRepository.NODE_ARCHIVE)) != null) {
515                     try {
516                         Os os = (Os) XmlParserUtils.getEnumAttribute(archive,
517                                 SdkRepository.ATTR_OS,
518                                 Os.values(),
519                                 null /*default*/);
520                         Arch arch = (Arch) XmlParserUtils.getEnumAttribute(archive,
521                                 SdkRepository.ATTR_ARCH,
522                                 Arch.values(),
523                                 Arch.ANY);
524                         if (os == null || !os.isCompatible() ||
525                                 arch == null || !arch.isCompatible()) {
526                             continue;
527                         }
528 
529                         Node node = findChild(archive, null, prefix, SdkRepository.NODE_URL);
530                         String url = node == null ? null : node.getTextContent().trim();
531                         if (url == null || url.length() == 0) {
532                             continue;
533                         }
534 
535                         node = findChild(archive, null, prefix, SdkRepository.NODE_SIZE);
536                         long size = 0;
537                         try {
538                             size = Long.parseLong(node.getTextContent());
539                         } catch (Exception e) {
540                             // pass
541                         }
542                         if (size < 1) {
543                             continue;
544                         }
545 
546                         node = findChild(archive, null, prefix, SdkRepository.NODE_CHECKSUM);
547                         // double check that the checksum element contains a type=sha1 attribute
548                         if (node == null) {
549                             continue;
550                         }
551                         NamedNodeMap attrs = node.getAttributes();
552                         Node typeNode = attrs.getNamedItem(SdkRepository.ATTR_TYPE);
553                         if (typeNode == null ||
554                                 !SdkRepository.ATTR_TYPE.equals(typeNode.getNodeName()) ||
555                                 !SdkRepository.SHA1_TYPE.equals(typeNode.getNodeValue())) {
556                             continue;
557                         }
558                         String sha1 = node == null ? null : node.getTextContent().trim();
559                         if (sha1 == null || sha1.length() != SdkRepository.SHA1_CHECKSUM_LEN) {
560                             continue;
561                         }
562 
563                         // Use that archive for the new tool element
564                         Element ar = appendChild(newArchives, ns, prefix,
565                                                  SdkRepository.NODE_ARCHIVE, null);
566                         ar.setAttributeNS(ns, SdkRepository.ATTR_OS, os.getXmlName());
567                         ar.setAttributeNS(ns, SdkRepository.ATTR_ARCH, arch.getXmlName());
568 
569                         appendChild(ar, ns, prefix, SdkRepository.NODE_URL, url);
570                         appendChild(ar, ns, prefix, SdkRepository.NODE_SIZE, Long.toString(size));
571                         Element cs = appendChild(ar, ns, prefix, SdkRepository.NODE_CHECKSUM, sha1);
572                         cs.setAttributeNS(ns, SdkRepository.ATTR_TYPE, SdkRepository.SHA1_TYPE);
573 
574                         numArchives++;
575 
576                     } catch (Exception ignore1) {
577                         // pass
578                     }
579                 } // while <archive>
580 
581                 if (numArchives > 0) {
582                     newRoot.appendChild(newTool);
583                     numTool++;
584                 }
585             } catch (Exception ignore2) {
586                 // pass
587             }
588         } // while <tool>
589 
590         return numTool > 0 ? newDoc : null;
591     }
592 
593     /**
594      * Helper method used by {@link #findAlternateToolsXml(InputStream)} to find a given
595      * element child in a root XML node.
596      */
findChild(Node rootNode, Node after, String prefix, String nodeName)597     private Node findChild(Node rootNode, Node after, String prefix, String nodeName) {
598         nodeName = prefix + ":" + nodeName;
599         Node child = after == null ? rootNode.getFirstChild() : after.getNextSibling();
600         for(; child != null; child = child.getNextSibling()) {
601             if (child.getNodeType() == Node.ELEMENT_NODE && nodeName.equals(child.getNodeName())) {
602                 return child;
603             }
604         }
605         return null;
606     }
607 
608     /**
609      * Helper method used by {@link #findAlternateToolsXml(InputStream)} to create a new
610      * XML element into a parent element.
611      */
appendChild(Element rootNode, String namespaceUri, String prefix, String nodeName, String nodeValue)612     private Element appendChild(Element rootNode, String namespaceUri,
613             String prefix, String nodeName,
614             String nodeValue) {
615         Element node = rootNode.getOwnerDocument().createElementNS(namespaceUri, nodeName);
616         node.setPrefix(prefix);
617         if (nodeValue != null) {
618             node.setTextContent(nodeValue);
619         }
620         rootNode.appendChild(node);
621         return node;
622     }
623 
624 
625     /**
626      * Helper method that returns a validator for our XSD, or null if the current Java
627      * implementation can't process XSD schemas.
628      *
629      * @param version The version of the XML Schema.
630      *        See {@link SdkRepository#getXsdStream(int)}
631      */
getValidator(int version)632     private Validator getValidator(int version) throws SAXException {
633         InputStream xsdStream = SdkRepository.getXsdStream(version);
634         SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
635 
636         if (factory == null) {
637             return null;
638         }
639 
640         // This may throw a SAX Exception if the schema itself is not a valid XSD
641         Schema schema = factory.newSchema(new StreamSource(xsdStream));
642 
643         Validator validator = schema == null ? null : schema.newValidator();
644 
645         return validator;
646     }
647 
648 
649     /**
650      * Parse all packages defined in the SDK Repository XML and creates
651      * a new mPackages array with them.
652      */
parsePackages(Document doc, String nsUri, ITaskMonitor monitor)653     protected boolean parsePackages(Document doc, String nsUri, ITaskMonitor monitor) {
654         // protected for unit-test acces
655 
656         assert doc != null;
657 
658         Node root = getFirstChild(doc, nsUri, SdkRepository.NODE_SDK_REPOSITORY);
659         if (root != null) {
660 
661             ArrayList<Package> packages = new ArrayList<Package>();
662 
663             // Parse license definitions
664             HashMap<String, String> licenses = new HashMap<String, String>();
665             for (Node child = root.getFirstChild();
666                  child != null;
667                  child = child.getNextSibling()) {
668                 if (child.getNodeType() == Node.ELEMENT_NODE &&
669                         nsUri.equals(child.getNamespaceURI()) &&
670                         child.getLocalName().equals(SdkRepository.NODE_LICENSE)) {
671                     Node id = child.getAttributes().getNamedItem(SdkRepository.ATTR_ID);
672                     if (id != null) {
673                         licenses.put(id.getNodeValue(), child.getTextContent());
674                     }
675                 }
676             }
677 
678             // Parse packages
679             for (Node child = root.getFirstChild();
680                  child != null;
681                  child = child.getNextSibling()) {
682                 if (child.getNodeType() == Node.ELEMENT_NODE &&
683                         nsUri.equals(child.getNamespaceURI())) {
684                     String name = child.getLocalName();
685                     Package p = null;
686 
687                     try {
688                         // We can load addon and extra packages from all sources, either
689                         // internal or user sources.
690                         if (SdkRepository.NODE_ADD_ON.equals(name)) {
691                             p = new AddonPackage(this, child, licenses);
692 
693                         } else if (SdkRepository.NODE_EXTRA.equals(name)) {
694                             p = new ExtraPackage(this, child, licenses);
695 
696                         } else if (!mUserSource) {
697                             // We only load platform, doc and tool packages from internal
698                             // sources, never from user sources.
699                             if (SdkRepository.NODE_PLATFORM.equals(name)) {
700                                 p = new PlatformPackage(this, child, licenses);
701                             } else if (SdkRepository.NODE_DOC.equals(name)) {
702                                 p = new DocPackage(this, child, licenses);
703                             } else if (SdkRepository.NODE_TOOL.equals(name)) {
704                                 p = new ToolPackage(this, child, licenses);
705                             }
706                         }
707 
708                         if (p != null) {
709                             packages.add(p);
710                             monitor.setDescription("Found %1$s", p.getShortDescription());
711                         }
712                     } catch (Exception e) {
713                         // Ignore invalid packages
714                     }
715                 }
716             }
717 
718             mPackages = packages.toArray(new Package[packages.size()]);
719 
720             return true;
721         }
722 
723         return false;
724     }
725 
726     /**
727      * Returns the first child element with the given XML local name.
728      * If xmlLocalName is null, returns the very first child element.
729      */
getFirstChild(Node node, String nsUri, String xmlLocalName)730     private Node getFirstChild(Node node, String nsUri, String xmlLocalName) {
731 
732         for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
733             if (child.getNodeType() == Node.ELEMENT_NODE &&
734                     nsUri.equals(child.getNamespaceURI())) {
735                 if (xmlLocalName == null || child.getLocalName().equals(xmlLocalName)) {
736                     return child;
737                 }
738             }
739         }
740 
741         return null;
742     }
743 
744     /**
745      * Takes an XML document as a string as parameter and returns a DOM for it.
746      *
747      * On error, returns null and prints a (hopefully) useful message on the monitor.
748      */
getDocument(ByteArrayInputStream xml, ITaskMonitor monitor)749     private Document getDocument(ByteArrayInputStream xml, ITaskMonitor monitor) {
750         try {
751             DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
752             factory.setIgnoringComments(true);
753             factory.setNamespaceAware(true);
754 
755             DocumentBuilder builder = factory.newDocumentBuilder();
756             xml.reset();
757             Document doc = builder.parse(new InputSource(xml));
758 
759             return doc;
760         } catch (ParserConfigurationException e) {
761             monitor.setResult("Failed to create XML document builder");
762 
763         } catch (SAXException e) {
764             monitor.setResult("Failed to parse XML document");
765 
766         } catch (IOException e) {
767             monitor.setResult("Failed to read XML document");
768         }
769 
770         return null;
771     }
772 }
773