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><obsolete></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