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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 346 public abstract String getShortDescription(); 347 348 /** 349 * Returns a long description for an {@link IDescription}. 350 * Can be empty but not null. 351 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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