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