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.sdklib.AndroidVersion; 20 import com.android.sdklib.SdkManager; 21 import com.android.sdklib.internal.repository.Archive.Arch; 22 import com.android.sdklib.internal.repository.Archive.Os; 23 import com.android.sdklib.repository.SdkRepository; 24 25 import org.w3c.dom.Node; 26 27 import java.io.File; 28 import java.util.ArrayList; 29 import java.util.Map; 30 import java.util.Properties; 31 32 /** 33 * A {@link Package} is the base class for "something" that can be downloaded from 34 * the SDK repository. 35 * <p/> 36 * A package has some attributes (revision, description) and a list of archives 37 * which represent the downloadable bits. 38 * <p/> 39 * Packages are contained by a {@link RepoSource} (a download site). 40 * <p/> 41 * Derived classes must implement the {@link IDescription} methods. 42 */ 43 public abstract class Package implements IDescription, Comparable<Package> { 44 45 public static final String PROP_REVISION = "Pkg.Revision"; //$NON-NLS-1$ 46 public static final String PROP_LICENSE = "Pkg.License"; //$NON-NLS-1$ 47 public static final String PROP_DESC = "Pkg.Desc"; //$NON-NLS-1$ 48 public static final String PROP_DESC_URL = "Pkg.DescUrl"; //$NON-NLS-1$ 49 public static final String PROP_RELEASE_NOTE = "Pkg.RelNote"; //$NON-NLS-1$ 50 public static final String PROP_RELEASE_URL = "Pkg.RelNoteUrl"; //$NON-NLS-1$ 51 public static final String PROP_SOURCE_URL = "Pkg.SourceUrl"; //$NON-NLS-1$ 52 public static final String PROP_USER_SOURCE = "Pkg.UserSrc"; //$NON-NLS-1$ 53 public static final String PROP_OBSOLETE = "Pkg.Obsolete"; //$NON-NLS-1$ 54 55 private final int mRevision; 56 private final String mObsolete; 57 private final String mLicense; 58 private final String mDescription; 59 private final String mDescUrl; 60 private final String mReleaseNote; 61 private final String mReleaseUrl; 62 private final Archive[] mArchives; 63 private final RepoSource mSource; 64 65 /** 66 * Enum for the result of {@link Package#canBeUpdatedBy(Package)}. This used so that we can 67 * differentiate between a package that is totally incompatible, and one that is the same item 68 * but just not an update. 69 * @see #canBeUpdatedBy(Package) 70 */ 71 public static enum UpdateInfo { 72 /** Means that the 2 packages are not the same thing */ 73 INCOMPATIBLE, 74 /** Means that the 2 packages are the same thing but one does not upgrade the other */ 75 NOT_UPDATE, 76 /** Means that the 2 packages are the same thing, and one is the upgrade of the other */ 77 UPDATE; 78 } 79 80 /** 81 * Creates a new package from the attributes and elements of the given XML node. 82 * <p/> 83 * This constructor should throw an exception if the package cannot be created. 84 */ Package(RepoSource source, Node packageNode, Map<String,String> licenses)85 Package(RepoSource source, Node packageNode, Map<String,String> licenses) { 86 mSource = source; 87 mRevision = XmlParserUtils.getXmlInt (packageNode, SdkRepository.NODE_REVISION, 0); 88 mDescription = XmlParserUtils.getXmlString(packageNode, SdkRepository.NODE_DESCRIPTION); 89 mDescUrl = XmlParserUtils.getXmlString(packageNode, SdkRepository.NODE_DESC_URL); 90 mReleaseNote = XmlParserUtils.getXmlString(packageNode, SdkRepository.NODE_RELEASE_NOTE); 91 mReleaseUrl = XmlParserUtils.getXmlString(packageNode, SdkRepository.NODE_RELEASE_URL); 92 mObsolete = XmlParserUtils.getOptionalXmlString( 93 packageNode, SdkRepository.NODE_OBSOLETE); 94 95 mLicense = parseLicense(packageNode, licenses); 96 mArchives = parseArchives(XmlParserUtils.getFirstChild( 97 packageNode, SdkRepository.NODE_ARCHIVES)); 98 } 99 100 /** 101 * Manually create a new package with one archive and the given attributes. 102 * This is used to create packages from local directories in which case there must be 103 * one archive which URL is the actual target location. 104 * <p/> 105 * Properties from props are used first when possible, e.g. if props is non null. 106 * <p/> 107 * By design, this creates a package with one and only one archive. 108 */ Package( RepoSource source, Properties props, int revision, String license, String description, String descUrl, Os archiveOs, Arch archiveArch, String archiveOsPath)109 public Package( 110 RepoSource source, 111 Properties props, 112 int revision, 113 String license, 114 String description, 115 String descUrl, 116 Os archiveOs, 117 Arch archiveArch, 118 String archiveOsPath) { 119 120 if (description == null) { 121 description = ""; 122 } 123 if (descUrl == null) { 124 descUrl = ""; 125 } 126 127 mRevision = Integer.parseInt(getProperty(props, PROP_REVISION, Integer.toString(revision))); 128 mLicense = getProperty(props, PROP_LICENSE, license); 129 mDescription = getProperty(props, PROP_DESC, description); 130 mDescUrl = getProperty(props, PROP_DESC_URL, descUrl); 131 mReleaseNote = getProperty(props, PROP_RELEASE_NOTE, ""); 132 mReleaseUrl = getProperty(props, PROP_RELEASE_URL, ""); 133 mObsolete = getProperty(props, PROP_OBSOLETE, null); 134 135 // If source is null and we can find a source URL in the properties, generate 136 // a dummy source just to store the URL. This allows us to easily remember where 137 // a package comes from. 138 String srcUrl = getProperty(props, PROP_SOURCE_URL, null); 139 if (props != null && source == null && srcUrl != null) { 140 boolean isUser = Boolean.parseBoolean(props.getProperty(PROP_USER_SOURCE, 141 Boolean.TRUE.toString())); 142 source = new RepoSource(srcUrl, isUser); 143 } 144 mSource = source; 145 146 mArchives = new Archive[1]; 147 mArchives[0] = new Archive(this, 148 props, 149 archiveOs, 150 archiveArch, 151 archiveOsPath); 152 } 153 154 /** 155 * Utility method that returns a property from a {@link Properties} object. 156 * Returns the default value if props is null or if the property is not defined. 157 */ getProperty(Properties props, String propKey, String defaultValue)158 protected String getProperty(Properties props, String propKey, String defaultValue) { 159 if (props == null) { 160 return defaultValue; 161 } 162 return props.getProperty(propKey, defaultValue); 163 } 164 165 /** 166 * Save the properties of the current packages in the given {@link Properties} object. 167 * These properties will later be give the constructor that takes a {@link Properties} object. 168 */ saveProperties(Properties props)169 void saveProperties(Properties props) { 170 props.setProperty(PROP_REVISION, Integer.toString(mRevision)); 171 if (mLicense != null && mLicense.length() > 0) { 172 props.setProperty(PROP_LICENSE, mLicense); 173 } 174 175 if (mDescription != null && mDescription.length() > 0) { 176 props.setProperty(PROP_DESC, mDescription); 177 } 178 if (mDescUrl != null && mDescUrl.length() > 0) { 179 props.setProperty(PROP_DESC_URL, mDescUrl); 180 } 181 182 if (mReleaseNote != null && mReleaseNote.length() > 0) { 183 props.setProperty(PROP_RELEASE_NOTE, mReleaseNote); 184 } 185 if (mReleaseUrl != null && mReleaseUrl.length() > 0) { 186 props.setProperty(PROP_RELEASE_URL, mReleaseUrl); 187 } 188 if (mObsolete != null) { 189 props.setProperty(PROP_OBSOLETE, mObsolete); 190 } 191 192 if (mSource != null) { 193 props.setProperty(PROP_SOURCE_URL, mSource.getUrl()); 194 props.setProperty(PROP_USER_SOURCE, Boolean.toString(mSource.isUserSource())); 195 } 196 } 197 198 /** 199 * Parses the uses-licence node of this package, if any, and returns the license 200 * definition if there's one. Returns null if there's no uses-license element or no 201 * license of this name defined. 202 */ parseLicense(Node packageNode, Map<String, String> licenses)203 private String parseLicense(Node packageNode, Map<String, String> licenses) { 204 Node usesLicense = XmlParserUtils.getFirstChild( 205 packageNode, SdkRepository.NODE_USES_LICENSE); 206 if (usesLicense != null) { 207 Node ref = usesLicense.getAttributes().getNamedItem(SdkRepository.ATTR_REF); 208 if (ref != null) { 209 String licenseRef = ref.getNodeValue(); 210 return licenses.get(licenseRef); 211 } 212 } 213 return null; 214 } 215 216 /** 217 * Parses an XML node to process the <archives> element. 218 */ parseArchives(Node archivesNode)219 private Archive[] parseArchives(Node archivesNode) { 220 ArrayList<Archive> archives = new ArrayList<Archive>(); 221 222 if (archivesNode != null) { 223 String nsUri = archivesNode.getNamespaceURI(); 224 for(Node child = archivesNode.getFirstChild(); 225 child != null; 226 child = child.getNextSibling()) { 227 228 if (child.getNodeType() == Node.ELEMENT_NODE && 229 nsUri.equals(child.getNamespaceURI()) && 230 SdkRepository.NODE_ARCHIVE.equals(child.getLocalName())) { 231 archives.add(parseArchive(child)); 232 } 233 } 234 } 235 236 return archives.toArray(new Archive[archives.size()]); 237 } 238 239 /** 240 * Parses one <archive> element from an <archives> container. 241 */ parseArchive(Node archiveNode)242 private Archive parseArchive(Node archiveNode) { 243 Archive a = new Archive( 244 this, 245 (Os) XmlParserUtils.getEnumAttribute(archiveNode, SdkRepository.ATTR_OS, 246 Os.values(), null), 247 (Arch) XmlParserUtils.getEnumAttribute(archiveNode, SdkRepository.ATTR_ARCH, 248 Arch.values(), Arch.ANY), 249 XmlParserUtils.getXmlString(archiveNode, SdkRepository.NODE_URL), 250 XmlParserUtils.getXmlLong (archiveNode, SdkRepository.NODE_SIZE, 0), 251 XmlParserUtils.getXmlString(archiveNode, SdkRepository.NODE_CHECKSUM) 252 ); 253 254 return a; 255 } 256 257 /** 258 * Returns the source that created (and owns) this package. Can be null. 259 */ getParentSource()260 public RepoSource getParentSource() { 261 return mSource; 262 } 263 264 /** 265 * Returns true if the package is deemed obsolete, that is it contains an 266 * actual <code><obsolete></code> element. 267 */ isObsolete()268 public boolean isObsolete() { 269 return mObsolete != null; 270 } 271 272 /** 273 * Returns the revision, an int > 0, for all packages (platform, add-on, tool, doc). 274 * Can be 0 if this is a local package of unknown revision. 275 */ getRevision()276 public int getRevision() { 277 return mRevision; 278 } 279 280 /** 281 * Returns the optional description for all packages (platform, add-on, tool, doc) or 282 * for a lib. It is null if the element has not been specified in the repository XML. 283 */ getLicense()284 public String getLicense() { 285 return mLicense; 286 } 287 288 /** 289 * Returns the optional description for all packages (platform, add-on, tool, doc) or 290 * for a lib. Can be empty but not null. 291 */ getDescription()292 public String getDescription() { 293 return mDescription; 294 } 295 296 /** 297 * Returns the optional description URL for all packages (platform, add-on, tool, doc). 298 * Can be empty but not null. 299 */ getDescUrl()300 public String getDescUrl() { 301 return mDescUrl; 302 } 303 304 /** 305 * Returns the optional release note for all packages (platform, add-on, tool, doc) or 306 * for a lib. Can be empty but not null. 307 */ getReleaseNote()308 public String getReleaseNote() { 309 return mReleaseNote; 310 } 311 312 /** 313 * Returns the optional release note URL for all packages (platform, add-on, tool, doc). 314 * Can be empty but not null. 315 */ getReleaseNoteUrl()316 public String getReleaseNoteUrl() { 317 return mReleaseUrl; 318 } 319 320 /** 321 * Returns the archives defined in this package. 322 * Can be an empty array but not null. 323 */ getArchives()324 public Archive[] getArchives() { 325 return mArchives; 326 } 327 328 /** 329 * Returns whether the {@link Package} has at least one {@link Archive} compatible with 330 * the host platform. 331 */ hasCompatibleArchive()332 public boolean hasCompatibleArchive() { 333 for (Archive archive : mArchives) { 334 if (archive.isCompatible()) { 335 return true; 336 } 337 } 338 339 return false; 340 } 341 342 /** 343 * Returns a short description for an {@link IDescription}. 344 * Can be empty but not null. 345 */ getShortDescription()346 public abstract String getShortDescription(); 347 348 /** 349 * Returns a long description for an {@link IDescription}. 350 * Can be empty but not null. 351 */ getLongDescription()352 public String getLongDescription() { 353 StringBuilder sb = new StringBuilder(); 354 355 String s = getDescription(); 356 if (s != null) { 357 sb.append(s); 358 } 359 if (sb.length() > 0) { 360 sb.append("\n"); 361 } 362 363 sb.append(String.format("Revision %1$d%2$s", 364 getRevision(), 365 isObsolete() ? " (Obsolete)" : "")); 366 367 s = getDescUrl(); 368 if (s != null && s.length() > 0) { 369 sb.append(String.format("\n\nMore information at %1$s", s)); 370 } 371 372 s = getReleaseNote(); 373 if (s != null && s.length() > 0) { 374 sb.append("\n\nRelease note:\n").append(s); 375 } 376 377 s = getReleaseNoteUrl(); 378 if (s != null && s.length() > 0) { 379 sb.append("\nRelease note URL: ").append(s); 380 } 381 382 return sb.toString(); 383 } 384 385 /** 386 * Computes a potential installation folder if an archive of this package were 387 * to be installed right away in the given SDK root. 388 * <p/> 389 * Some types of packages install in a fix location, for example docs and tools. 390 * In this case the returned folder may already exist with a different archive installed 391 * at the desired location. 392 * For other packages types, such as add-on or platform, the folder name is only partially 393 * relevant to determine the content and thus a real check will be done to provide an 394 * existing or new folder depending on the current content of the SDK. 395 * 396 * @param osSdkRoot The OS path of the SDK root folder. 397 * @param suggestedDir A suggestion for the installation folder name, based on the root 398 * folder used in the zip archive. 399 * @param sdkManager An existing SDK manager to list current platforms and addons. 400 * @return A new {@link File} corresponding to the directory to use to install this package. 401 */ getInstallFolder( String osSdkRoot, String suggestedDir, SdkManager sdkManager)402 public abstract File getInstallFolder( 403 String osSdkRoot, String suggestedDir, SdkManager sdkManager); 404 405 /** 406 * Hook called right before an archive is installed. The archive has already 407 * been downloaded successfully and will be installed in the directory specified by 408 * <var>installFolder</var> when this call returns. 409 * <p/> 410 * The hook lets the package decide if installation of this specific archive should 411 * be continue. The installer will still install the remaining packages if possible. 412 * <p/> 413 * The base implementation always return true. 414 * 415 * @param archive The archive that will be installed 416 * @param monitor The {@link ITaskMonitor} to display errors. 417 * @param osSdkRoot The OS path of the SDK root folder. 418 * @param installFolder The folder where the archive will be installed. Note that this 419 * is <em>not</em> the folder where the archive was temporary 420 * unzipped. The installFolder, if it exists, contains the old 421 * archive that will soon be replaced by the new one. 422 * @return True if installing this archive shall continue, false if it should be skipped. 423 */ preInstallHook(Archive archive, ITaskMonitor monitor, String osSdkRoot, File installFolder)424 public boolean preInstallHook(Archive archive, ITaskMonitor monitor, 425 String osSdkRoot, File installFolder) { 426 // Nothing to do in base class. 427 return true; 428 } 429 430 /** 431 * Hook called right after an archive has been installed. 432 * 433 * @param archive The archive that has been installed. 434 * @param monitor The {@link ITaskMonitor} to display errors. 435 * @param installFolder The folder where the archive was successfully installed. 436 * Null if the installation failed. 437 */ postInstallHook(Archive archive, ITaskMonitor monitor, File installFolder)438 public void postInstallHook(Archive archive, ITaskMonitor monitor, File installFolder) { 439 // Nothing to do in base class. 440 } 441 442 /** 443 * Returns whether the give package represents the same item as the current package. 444 * <p/> 445 * Two packages are considered the same if they represent the same thing, except for the 446 * revision number. 447 * @param pkg the package to compare 448 * @return true if the item 449 */ sameItemAs(Package pkg)450 public abstract boolean sameItemAs(Package pkg); 451 452 /** 453 * Computes whether the given package is a suitable update for the current package. 454 * <p/> 455 * An update is just that: a new package that supersedes the current one. If the new 456 * package does not represent the same item or if it has the same or lower revision as the 457 * current one, it's not an update. 458 * 459 * @param replacementPackage The potential replacement package. 460 * @return One of the {@link UpdateInfo} values. 461 * 462 * @see #sameItemAs(Package) 463 */ canBeUpdatedBy(Package replacementPackage)464 public UpdateInfo canBeUpdatedBy(Package replacementPackage) { 465 if (replacementPackage == null) { 466 return UpdateInfo.INCOMPATIBLE; 467 } 468 469 // check they are the same item. 470 if (sameItemAs(replacementPackage) == false) { 471 return UpdateInfo.INCOMPATIBLE; 472 } 473 474 // check revision number 475 if (replacementPackage.getRevision() > this.getRevision()) { 476 return UpdateInfo.UPDATE; 477 } 478 479 // not an upgrade but not incompatible either. 480 return UpdateInfo.NOT_UPDATE; 481 } 482 483 484 /** 485 * Returns an ordering like this: 486 * - Tools. 487 * - Docs. 488 * - Platform n preview 489 * - Platform n 490 * - Platform n-1 491 * - Samples packages. 492 * - Add-on based on n preview 493 * - Add-on based on n 494 * - Add-on based on n-1 495 * - Extra packages. 496 */ compareTo(Package other)497 public int compareTo(Package other) { 498 int s1 = this.sortingScore(); 499 int s2 = other.sortingScore(); 500 return s1 - s2; 501 } 502 503 /** 504 * Computes the score for each package used by {@link #compareTo(Package)}. 505 */ sortingScore()506 private int sortingScore() { 507 // up to 31 bits (for signed stuff) 508 int type = 0; // max type=5 => 3 bits 509 int rev = getRevision(); // 12 bits... 4095 510 int offset = 0; // 16 bits... 511 if (this instanceof ToolPackage) { 512 type = 5; 513 } else if (this instanceof DocPackage) { 514 type = 4; 515 } else if (this instanceof PlatformPackage) { 516 type = 3; 517 } else if (this instanceof SamplePackage) { 518 type = 2; 519 } else if (this instanceof AddonPackage) { 520 type = 1; 521 } else { 522 // extras and everything else 523 type = 0; 524 } 525 526 if (this instanceof IPackageVersion) { 527 AndroidVersion v = ((IPackageVersion) this).getVersion(); 528 offset = v.getApiLevel(); 529 offset = offset * 2 + (v.isPreview() ? 1 : 0); 530 } 531 532 int n = (type << 28) + (offset << 12) + rev; 533 return 0 - n; 534 } 535 536 } 537