Home | History | Annotate | Download | only in repository
      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      */
     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      */
    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)
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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
    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      */
    439     public abstract String getListDescription();
    440 
    441     /**
    442      * Returns a short description for an {@link IDescription}.
    443      * Can be empty but not null.
    444      */
    445     public abstract String getShortDescription();
    446 
    447     /**
    448      * Returns a long description for an {@link IDescription}.
    449      * Can be empty but not null.
    450      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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
    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
    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