• 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.annotations.VisibleForTesting;
20 import com.android.annotations.VisibleForTesting.Visibility;
21 import com.android.sdklib.AndroidVersion;
22 import com.android.sdklib.SdkManager;
23 import com.android.sdklib.internal.repository.Archive.Arch;
24 import com.android.sdklib.internal.repository.Archive.Os;
25 import com.android.sdklib.repository.PkgProps;
26 import com.android.sdklib.repository.SdkRepoConstants;
27 
28 import org.w3c.dom.Node;
29 
30 import java.io.File;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.Map;
34 import java.util.Properties;
35 
36 /**
37  * A {@link Package} is the base class for "something" that can be downloaded from
38  * the SDK repository.
39  * <p/>
40  * A package has some attributes (revision, description) and a list of archives
41  * which represent the downloadable bits.
42  * <p/>
43  * Packages are contained by a {@link SdkSource} (a download site).
44  * <p/>
45  * Derived classes must implement the {@link IDescription} methods.
46  */
47 public abstract class Package implements IDescription, Comparable<Package> {
48 
49     private final int mRevision;
50     private final String mObsolete;
51     private final String mLicense;
52     private final String mDescription;
53     private final String mDescUrl;
54     private final String mReleaseNote;
55     private final String mReleaseUrl;
56     private final Archive[] mArchives;
57     private final SdkSource mSource;
58 
59     /**
60      * Enum for the result of {@link Package#canBeUpdatedBy(Package)}. This used so that we can
61      * differentiate between a package that is totally incompatible, and one that is the same item
62      * but just not an update.
63      * @see #canBeUpdatedBy(Package)
64      */
65     public static enum UpdateInfo {
66         /** Means that the 2 packages are not the same thing */
67         INCOMPATIBLE,
68         /** Means that the 2 packages are the same thing but one does not upgrade the other.
69          *  </p>
70          *  TODO: this name is confusing. We need to dig deeper. */
71         NOT_UPDATE,
72         /** Means that the 2 packages are the same thing, and one is the upgrade of the other */
73         UPDATE;
74     }
75 
76     /**
77      * Creates a new package from the attributes and elements of the given XML node.
78      * This constructor should throw an exception if the package cannot be created.
79      *
80      * @param source The {@link SdkSource} where this is loaded from.
81      * @param packageNode The XML element being parsed.
82      * @param nsUri The namespace URI of the originating XML document, to be able to deal with
83      *          parameters that vary according to the originating XML schema.
84      * @param licenses The licenses loaded from the XML originating document.
85      */
Package(SdkSource source, Node packageNode, String nsUri, Map<String,String> licenses)86     Package(SdkSource source, Node packageNode, String nsUri, Map<String,String> licenses) {
87         mSource = source;
88         mRevision    = XmlParserUtils.getXmlInt   (packageNode, SdkRepoConstants.NODE_REVISION, 0);
89         mDescription = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_DESCRIPTION);
90         mDescUrl     = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_DESC_URL);
91         mReleaseNote = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_RELEASE_NOTE);
92         mReleaseUrl  = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_RELEASE_URL);
93         mObsolete    = XmlParserUtils.getOptionalXmlString(
94                                                    packageNode, SdkRepoConstants.NODE_OBSOLETE);
95 
96         mLicense  = parseLicense(packageNode, licenses);
97         mArchives = parseArchives(XmlParserUtils.getFirstChild(
98                                   packageNode, SdkRepoConstants.NODE_ARCHIVES));
99     }
100 
101     /**
102      * Manually create a new package with one archive and the given attributes.
103      * This is used to create packages from local directories in which case there must be
104      * one archive which URL is the actual target location.
105      * <p/>
106      * Properties from props are used first when possible, e.g. if props is non null.
107      * <p/>
108      * By design, this creates a package with one and only one archive.
109      */
Package( SdkSource source, Properties props, int revision, String license, String description, String descUrl, Os archiveOs, Arch archiveArch, String archiveOsPath)110     public Package(
111             SdkSource source,
112             Properties props,
113             int revision,
114             String license,
115             String description,
116             String descUrl,
117             Os archiveOs,
118             Arch archiveArch,
119             String archiveOsPath) {
120 
121         if (description == null) {
122             description = "";
123         }
124         if (descUrl == null) {
125             descUrl = "";
126         }
127 
128         mRevision = Integer.parseInt(
129                        getProperty(props, PkgProps.PKG_REVISION, Integer.toString(revision)));
130         mLicense     = getProperty(props, PkgProps.PKG_LICENSE,      license);
131         mDescription = getProperty(props, PkgProps.PKG_DESC,         description);
132         mDescUrl     = getProperty(props, PkgProps.PKG_DESC_URL,     descUrl);
133         mReleaseNote = getProperty(props, PkgProps.PKG_RELEASE_NOTE, "");
134         mReleaseUrl  = getProperty(props, PkgProps.PKG_RELEASE_URL,  "");
135         mObsolete    = getProperty(props, PkgProps.PKG_OBSOLETE,     null);
136 
137         // If source is null and we can find a source URL in the properties, generate
138         // a dummy source just to store the URL. This allows us to easily remember where
139         // a package comes from.
140         String srcUrl = getProperty(props, PkgProps.PKG_SOURCE_URL, null);
141         if (props != null && source == null && srcUrl != null) {
142             if (this instanceof AddonPackage) {
143                 source = new SdkAddonSource(srcUrl, null /*uiName*/);
144             } else {
145                 source = new SdkRepoSource(srcUrl, null /*uiName*/);
146             }
147         }
148         mSource = source;
149 
150         assert archiveOsPath != null;
151         mArchives = initializeArchives(props, archiveOs, archiveArch, archiveOsPath);
152     }
153 
154     /**
155      * Called by the constructor to get the initial {@link #mArchives} array.
156      * <p/>
157      * This is invoked by the local-package constructor and allows mock testing
158      * classes to override the archives created.
159      * This is an <em>implementation</em> details and clients must <em>not</em>
160      * rely on this.
161      *
162      * @return Always return a non-null array. The array may be empty.
163      */
164     @VisibleForTesting(visibility=Visibility.PRIVATE)
initializeArchives( Properties props, Os archiveOs, Arch archiveArch, String archiveOsPath)165     protected Archive[] initializeArchives(
166             Properties props,
167             Os archiveOs,
168             Arch archiveArch,
169             String archiveOsPath) {
170         return new Archive[] {
171                 new Archive(this,
172                     props,
173                     archiveOs,
174                     archiveArch,
175                     archiveOsPath) };
176     }
177 
178     /**
179      * Utility method that returns a property from a {@link Properties} object.
180      * Returns the default value if props is null or if the property is not defined.
181      *
182      * @param props The {@link Properties} to search into.
183      *   If null, the default value is returned.
184      * @param propKey The name of the property. Must not be null.
185      * @param defaultValue The default value to return if {@code props} is null or if the
186      *   key is not found. Can be null.
187      * @return The string value of the given key in the properties, or null if the key
188      *   isn't found or if {@code props} is null.
189      */
getProperty(Properties props, String propKey, String defaultValue)190     static String getProperty(Properties props, String propKey, String defaultValue) {
191         if (props == null) {
192             return defaultValue;
193         }
194         return props.getProperty(propKey, defaultValue);
195     }
196 
197     /**
198      * Save the properties of the current packages in the given {@link Properties} object.
199      * These properties will later be give the constructor that takes a {@link Properties} object.
200      */
saveProperties(Properties props)201     void saveProperties(Properties props) {
202         props.setProperty(PkgProps.PKG_REVISION, Integer.toString(mRevision));
203         if (mLicense != null && mLicense.length() > 0) {
204             props.setProperty(PkgProps.PKG_LICENSE, mLicense);
205         }
206 
207         if (mDescription != null && mDescription.length() > 0) {
208             props.setProperty(PkgProps.PKG_DESC, mDescription);
209         }
210         if (mDescUrl != null && mDescUrl.length() > 0) {
211             props.setProperty(PkgProps.PKG_DESC_URL, mDescUrl);
212         }
213 
214         if (mReleaseNote != null && mReleaseNote.length() > 0) {
215             props.setProperty(PkgProps.PKG_RELEASE_NOTE, mReleaseNote);
216         }
217         if (mReleaseUrl != null && mReleaseUrl.length() > 0) {
218             props.setProperty(PkgProps.PKG_RELEASE_URL, mReleaseUrl);
219         }
220         if (mObsolete != null) {
221             props.setProperty(PkgProps.PKG_OBSOLETE, mObsolete);
222         }
223 
224         if (mSource != null) {
225             props.setProperty(PkgProps.PKG_SOURCE_URL,  mSource.getUrl());
226         }
227     }
228 
229     /**
230      * Parses the uses-licence node of this package, if any, and returns the license
231      * definition if there's one. Returns null if there's no uses-license element or no
232      * license of this name defined.
233      */
parseLicense(Node packageNode, Map<String, String> licenses)234     private String parseLicense(Node packageNode, Map<String, String> licenses) {
235         Node usesLicense = XmlParserUtils.getFirstChild(
236                                             packageNode, SdkRepoConstants.NODE_USES_LICENSE);
237         if (usesLicense != null) {
238             Node ref = usesLicense.getAttributes().getNamedItem(SdkRepoConstants.ATTR_REF);
239             if (ref != null) {
240                 String licenseRef = ref.getNodeValue();
241                 return licenses.get(licenseRef);
242             }
243         }
244         return null;
245     }
246 
247     /**
248      * Parses an XML node to process the <archives> element.
249      * Always return a non-null array. The array may be empty.
250      */
parseArchives(Node archivesNode)251     private Archive[] parseArchives(Node archivesNode) {
252         ArrayList<Archive> archives = new ArrayList<Archive>();
253 
254         if (archivesNode != null) {
255             String nsUri = archivesNode.getNamespaceURI();
256             for(Node child = archivesNode.getFirstChild();
257                      child != null;
258                      child = child.getNextSibling()) {
259 
260                 if (child.getNodeType() == Node.ELEMENT_NODE &&
261                         nsUri.equals(child.getNamespaceURI()) &&
262                         SdkRepoConstants.NODE_ARCHIVE.equals(child.getLocalName())) {
263                     archives.add(parseArchive(child));
264                 }
265             }
266         }
267 
268         return archives.toArray(new Archive[archives.size()]);
269     }
270 
271     /**
272      * Parses one <archive> element from an <archives> container.
273      */
parseArchive(Node archiveNode)274     private Archive parseArchive(Node archiveNode) {
275         Archive a = new Archive(
276                     this,
277                     (Os)   XmlParserUtils.getEnumAttribute(archiveNode, SdkRepoConstants.ATTR_OS,
278                             Os.values(), null),
279                     (Arch) XmlParserUtils.getEnumAttribute(archiveNode, SdkRepoConstants.ATTR_ARCH,
280                             Arch.values(), Arch.ANY),
281                     XmlParserUtils.getXmlString(archiveNode, SdkRepoConstants.NODE_URL),
282                     XmlParserUtils.getXmlLong  (archiveNode, SdkRepoConstants.NODE_SIZE, 0),
283                     XmlParserUtils.getXmlString(archiveNode, SdkRepoConstants.NODE_CHECKSUM)
284                 );
285 
286         return a;
287     }
288 
289     /**
290      * Returns the source that created (and owns) this package. Can be null.
291      */
getParentSource()292     public SdkSource getParentSource() {
293         return mSource;
294     }
295 
296     /**
297      * Returns true if the package is deemed obsolete, that is it contains an
298      * actual <code>&lt;obsolete&gt;</code> element.
299      */
isObsolete()300     public boolean isObsolete() {
301         return mObsolete != null;
302     }
303 
304     /**
305      * Returns the revision, an int > 0, for all packages (platform, add-on, tool, doc).
306      * Can be 0 if this is a local package of unknown revision.
307      */
getRevision()308     public int getRevision() {
309         return mRevision;
310     }
311 
312     /**
313      * Returns the optional description for all packages (platform, add-on, tool, doc) or
314      * for a lib. It is null if the element has not been specified in the repository XML.
315      */
getLicense()316     public String getLicense() {
317         return mLicense;
318     }
319 
320     /**
321      * Returns the optional description for all packages (platform, add-on, tool, doc) or
322      * for a lib. Can be empty but not null.
323      */
getDescription()324     public String getDescription() {
325         return mDescription;
326     }
327 
328     /**
329      * Returns the optional description URL for all packages (platform, add-on, tool, doc).
330      * Can be empty but not null.
331      */
getDescUrl()332     public String getDescUrl() {
333         return mDescUrl;
334     }
335 
336     /**
337      * Returns the optional release note for all packages (platform, add-on, tool, doc) or
338      * for a lib. Can be empty but not null.
339      */
getReleaseNote()340     public String getReleaseNote() {
341         return mReleaseNote;
342     }
343 
344     /**
345      * Returns the optional release note URL for all packages (platform, add-on, tool, doc).
346      * Can be empty but not null.
347      */
getReleaseNoteUrl()348     public String getReleaseNoteUrl() {
349         return mReleaseUrl;
350     }
351 
352     /**
353      * Returns the archives defined in this package.
354      * Can be an empty array but not null.
355      */
getArchives()356     public Archive[] getArchives() {
357         return mArchives;
358     }
359 
360     /**
361      * Returns true if this package contains the exact given archive.
362      * Important: This compares object references, not object equality.
363      */
hasArchive(Archive archive)364     public boolean hasArchive(Archive archive) {
365         for (Archive a : mArchives) {
366             if (a == archive) {
367                 return true;
368             }
369         }
370         return false;
371     }
372 
373     /**
374      * Returns whether the {@link Package} has at least one {@link Archive} compatible with
375      * the host platform.
376      */
hasCompatibleArchive()377     public boolean hasCompatibleArchive() {
378         for (Archive archive : mArchives) {
379             if (archive.isCompatible()) {
380                 return true;
381             }
382         }
383 
384         return false;
385     }
386 
387     /**
388      * Returns a short, reasonably unique string identifier that can be used
389      * to identify this package when installing from the command-line interface.
390      * {@code 'android list sdk'} will show these IDs and then in turn they can
391      * be provided to {@code 'android update sdk --no-ui --filter'} to select
392      * some specific packages.
393      * <p/>
394      * The identifiers must have the following properties: <br/>
395      * - They must contain only simple alphanumeric characters. <br/>
396      * - Commas, whitespace and any special character that could be obviously problematic
397      *   to a shell interface should be avoided (so dash/underscore are OK, but things
398      *   like colon, pipe or dollar should be avoided.) <br/>
399      * - The name must be consistent across calls and reasonably unique for the package
400      *   type. Collisions can occur but should be rare. <br/>
401      * - Different package types should have a clearly different name pattern. <br/>
402      * - The revision number should not be included, as this would prevent updates
403      *   from being automated (which is the whole point.) <br/>
404      * - It must remain reasonably human readable. <br/>
405      * - If no such id can exist (for example for a local package that cannot be installed)
406      *   then an empty string should be returned. Don't return null.
407      * <p/>
408      * Important: This is <em>not</em> a strong unique identifier for the package.
409      * If you need a strong unique identifier, you should use {@link #comparisonKey()}
410      * and the {@link Comparable} interface.
411      */
installId()412     public abstract String installId();
413 
414     /**
415      * Returns the short description of the source, if not null.
416      * Otherwise returns the default Object toString result.
417      * <p/>
418      * This is mostly helpful for debugging.
419      * For UI display, use the {@link IDescription} interface.
420      */
421     @Override
toString()422     public String toString() {
423         String s = getShortDescription();
424         if (s != null) {
425             return s;
426         }
427         return super.toString();
428     }
429 
430     /**
431      * Returns a description of this package that is suitable for a list display.
432      * Should not be empty. Must never be null.
433      * <p/>
434      * Note that this is the "base" name for the package
435      * with no specific revision nor API mentionned.
436      * In contrast, {@link #getShortDescription()} should be used if you want more details
437      * such as the package revision number or the API, if applicable.
438      */
getListDescription()439     public abstract String getListDescription();
440 
441     /**
442      * Returns a short description for an {@link IDescription}.
443      * Can be empty but not null.
444      */
getShortDescription()445     public abstract String getShortDescription();
446 
447     /**
448      * Returns a long description for an {@link IDescription}.
449      * Can be empty but not null.
450      */
getLongDescription()451     public String getLongDescription() {
452         StringBuilder sb = new StringBuilder();
453 
454         String s = getDescription();
455         if (s != null) {
456             sb.append(s);
457         }
458         if (sb.length() > 0) {
459             sb.append("\n");
460         }
461 
462         sb.append(String.format("Revision %1$d%2$s",
463                 getRevision(),
464                 isObsolete() ? " (Obsolete)" : ""));
465 
466         s = getDescUrl();
467         if (s != null && s.length() > 0) {
468             sb.append(String.format("\n\nMore information at %1$s", s));
469         }
470 
471         s = getReleaseNote();
472         if (s != null && s.length() > 0) {
473             sb.append("\n\nRelease note:\n").append(s);
474         }
475 
476         s = getReleaseNoteUrl();
477         if (s != null && s.length() > 0) {
478             sb.append("\nRelease note URL: ").append(s);
479         }
480 
481         return sb.toString();
482     }
483 
484     /**
485      * A package is local (that is 'installed locally') if it contains a single
486      * archive that is local. If not local, it's a remote package, only available
487      * on a remote source for download and installation.
488      */
isLocal()489     public boolean isLocal() {
490         return mArchives.length == 1 && mArchives[0].isLocal();
491     }
492 
493     /**
494      * Computes a potential installation folder if an archive of this package were
495      * to be installed right away in the given SDK root.
496      * <p/>
497      * Some types of packages install in a fix location, for example docs and tools.
498      * In this case the returned folder may already exist with a different archive installed
499      * at the desired location. <br/>
500      * For other packages types, such as add-on or platform, the folder name is only partially
501      * relevant to determine the content and thus a real check will be done to provide an
502      * existing or new folder depending on the current content of the SDK.
503      * <p/>
504      * Note that the installer *will* create all directories returned here just before
505      * installation so this method must not attempt to create them.
506      *
507      * @param osSdkRoot The OS path of the SDK root folder.
508      * @param sdkManager An existing SDK manager to list current platforms and addons.
509      * @return A new {@link File} corresponding to the directory to use to install this package.
510      */
getInstallFolder(String osSdkRoot, SdkManager sdkManager)511     public abstract File getInstallFolder(String osSdkRoot, SdkManager sdkManager);
512 
513     /**
514      * Hook called right before an archive is installed. The archive has already
515      * been downloaded successfully and will be installed in the directory specified by
516      * <var>installFolder</var> when this call returns.
517      * <p/>
518      * The hook lets the package decide if installation of this specific archive should
519      * be continue. The installer will still install the remaining packages if possible.
520      * <p/>
521      * The base implementation always return true.
522      * <p/>
523      * Note that the installer *will* create all directories specified by
524      * {@link #getInstallFolder} just before installation, so they must not be
525      * created here. This is also called before the previous install dir is removed
526      * so the previous content is still there during upgrade.
527      *
528      * @param archive The archive that will be installed
529      * @param monitor The {@link ITaskMonitor} to display errors.
530      * @param osSdkRoot The OS path of the SDK root folder.
531      * @param installFolder The folder where the archive will be installed. Note that this
532      *                      is <em>not</em> the folder where the archive was temporary
533      *                      unzipped. The installFolder, if it exists, contains the old
534      *                      archive that will soon be replaced by the new one.
535      * @return True if installing this archive shall continue, false if it should be skipped.
536      */
preInstallHook(Archive archive, ITaskMonitor monitor, String osSdkRoot, File installFolder)537     public boolean preInstallHook(Archive archive, ITaskMonitor monitor,
538             String osSdkRoot, File installFolder) {
539         // Nothing to do in base class.
540         return true;
541     }
542 
543     /**
544      * Hook called right after an archive has been installed.
545      *
546      * @param archive The archive that has been installed.
547      * @param monitor The {@link ITaskMonitor} to display errors.
548      * @param installFolder The folder where the archive was successfully installed.
549      *                      Null if the installation failed, in case the archive needs to
550      *                      do some cleanup after <code>preInstallHook</code>.
551      */
postInstallHook(Archive archive, ITaskMonitor monitor, File installFolder)552     public void postInstallHook(Archive archive, ITaskMonitor monitor, File installFolder) {
553         // Nothing to do in base class.
554     }
555 
556     /**
557      * Returns whether the give package represents the same item as the current package.
558      * <p/>
559      * Two packages are considered the same if they represent the same thing, except for the
560      * revision number.
561      * @param pkg the package to compare
562      * @return true if the item
563      */
sameItemAs(Package pkg)564     public abstract boolean sameItemAs(Package pkg);
565 
566     /**
567      * Computes whether the given package is a suitable update for the current package.
568      * <p/>
569      * An update is just that: a new package that supersedes the current one. If the new
570      * package does not represent the same item or if it has the same or lower revision as the
571      * current one, it's not an update.
572      *
573      * @param replacementPackage The potential replacement package.
574      * @return One of the {@link UpdateInfo} values.
575      *
576      * @see #sameItemAs(Package)
577      */
canBeUpdatedBy(Package replacementPackage)578     public UpdateInfo canBeUpdatedBy(Package replacementPackage) {
579         if (replacementPackage == null) {
580             return UpdateInfo.INCOMPATIBLE;
581         }
582 
583         // check they are the same item.
584         if (sameItemAs(replacementPackage) == false) {
585             return UpdateInfo.INCOMPATIBLE;
586         }
587 
588         // check revision number
589         if (replacementPackage.getRevision() > this.getRevision()) {
590             return UpdateInfo.UPDATE;
591         }
592 
593         // not an upgrade but not incompatible either.
594         return UpdateInfo.NOT_UPDATE;
595     }
596 
597     /**
598      * Returns an ordering like this: <br/>
599      * - Tools <br/>
600      * - Platform-Tools <br/>
601      * - Docs. <br/>
602      * - Platform n preview <br/>
603      * - Platform n <br/>
604      * - Platform n-1 <br/>
605      * - Samples packages <br/>
606      * - Add-on based on n preview <br/>
607      * - Add-on based on n <br/>
608      * - Add-on based on n-1 <br/>
609      * - Extra packages <br/>
610      * <p/>
611      * Important: this must NOT be used to compare if two packages are the same thing.
612      * This is achieved by {@link #sameItemAs(Package)} or {@link #canBeUpdatedBy(Package)}.
613      * <p/>
614      * This {@link #compareTo(Package)} method is purely an implementation detail to
615      * perform the right ordering of the packages in the list of available or installed packages.
616      * <p/>
617      * <em>Important</em>: Derived classes should consider overriding {@link #comparisonKey()}
618      * instead of this method.
619      */
compareTo(Package other)620     public int compareTo(Package other) {
621         String s1 = this.comparisonKey();
622         String s2 = other.comparisonKey();
623 
624         return s1.compareTo(s2);
625     }
626 
627     /**
628      * Computes a comparison key for each package used by {@link #compareTo(Package)}.
629      * The key is a string.
630      * The base package class return a string that encodes the package type,
631      * the revision number and the platform version, if applicable, in the form:
632      * <pre>
633      *      t:N|v:NNNN.P|r:NNNN|
634      * </pre>
635      * All fields must start by a "letter colon" prefix and end with a vertical pipe (|, ASCII 124).
636      * <p/>
637      * The string format <em>may</em> change between releases and clients should not
638      * store them outside of the session or expect them to be consistent between
639      * different releases. They are purely an internal implementation details of the
640      * {@link #compareTo(Package)} method.
641      * <p/>
642      * Derived classes should get the string from the super class and then append
643      * or <em>insert</em> their own |-separated content.
644      * For example an extra vendor name & path can be inserted before the revision
645      * number, since it has more sorting weight.
646      */
comparisonKey()647     protected String comparisonKey() {
648 
649         StringBuilder sb = new StringBuilder();
650 
651         sb.append("t:");                                                        //$NON-NLS-1$
652         if (this instanceof ToolPackage) {
653             sb.append(0);
654         } else if (this instanceof PlatformToolPackage) {
655             sb.append(1);
656         } else if (this instanceof DocPackage) {
657             sb.append(2);
658         } else if (this instanceof PlatformPackage) {
659             sb.append(3);
660         } else if (this instanceof SamplePackage) {
661             sb.append(4);
662         } else if (this instanceof SystemImagePackage) {
663             sb.append(5);
664         } else if (this instanceof AddonPackage) {
665             sb.append(6);
666         } else {
667             // extras and everything else
668             sb.append(9);
669         }
670         sb.append("|v:");                                                       //$NON-NLS-1$
671 
672 
673         // We insert the package version here because it is more important
674         // than the revision number. We want package version to be sorted
675         // top-down, so we'll use 10k-api as the sorting key. The day we
676         // get reach 10k APIs, we'll need to revisit this.
677 
678         if (this instanceof IPackageVersion) {
679             AndroidVersion v = ((IPackageVersion) this).getVersion();
680 
681             sb.append(String.format("%1$04d.%2$d",                              //$NON-NLS-1$
682                     10000 - v.getApiLevel(),
683                     v.isPreview() ? 1 : 0
684                     ));
685         }
686         sb.append("|r:");                                                       //$NON-NLS-1$
687 
688 
689         // Append revision number
690 
691         sb.append(String.format("%1$04d", getRevision()));                      //$NON-NLS-1$
692         sb.append('|');
693 
694         return sb.toString();
695     }
696 
697     @Override
hashCode()698     public int hashCode() {
699         final int prime = 31;
700         int result = 1;
701         result = prime * result + Arrays.hashCode(mArchives);
702         result = prime * result + ((mObsolete == null) ? 0 : mObsolete.hashCode());
703         result = prime * result + mRevision;
704         result = prime * result + ((mSource == null) ? 0 : mSource.hashCode());
705         return result;
706     }
707 
708     @Override
equals(Object obj)709     public boolean equals(Object obj) {
710         if (this == obj) {
711             return true;
712         }
713         if (obj == null) {
714             return false;
715         }
716         if (!(obj instanceof Package)) {
717             return false;
718         }
719         Package other = (Package) obj;
720         if (!Arrays.equals(mArchives, other.mArchives)) {
721             return false;
722         }
723         if (mObsolete == null) {
724             if (other.mObsolete != null) {
725                 return false;
726             }
727         } else if (!mObsolete.equals(other.mObsolete)) {
728             return false;
729         }
730         if (mRevision != other.mRevision) {
731             return false;
732         }
733         if (mSource == null) {
734             if (other.mSource != null) {
735                 return false;
736             }
737         } else if (!mSource.equals(other.mSource)) {
738             return false;
739         }
740         return true;
741     }
742 }
743