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