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