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.SdkManager; 20 import com.android.sdklib.internal.repository.Archive.Arch; 21 import com.android.sdklib.internal.repository.Archive.Os; 22 import com.android.sdklib.repository.SdkRepository; 23 24 import org.w3c.dom.Node; 25 26 import java.io.File; 27 import java.util.ArrayList; 28 import java.util.Map; 29 import java.util.Properties; 30 31 /** 32 * A {@link Package} is the base class for "something" that can be downloaded from 33 * the SDK repository -- subclasses include {@link PlatformPackage}, {@link AddonPackage}, 34 * {@link DocPackage} and {@link ToolPackage}. 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 { 44 45 private static final String PROP_REVISION = "Pkg.Revision"; //$NON-NLS-1$ 46 private static final String PROP_LICENSE = "Pkg.License"; //$NON-NLS-1$ 47 private static final String PROP_DESC = "Pkg.Desc"; //$NON-NLS-1$ 48 private static final String PROP_DESC_URL = "Pkg.DescUrl"; //$NON-NLS-1$ 49 private static final String PROP_RELEASE_NOTE = "Pkg.RelNote"; //$NON-NLS-1$ 50 private static final String PROP_RELEASE_URL = "Pkg.RelNoteUrl"; //$NON-NLS-1$ 51 private static final String PROP_SOURCE_URL = "Pkg.SourceUrl"; //$NON-NLS-1$ 52 private static final String PROP_USER_SOURCE = "Pkg.UserSrc"; //$NON-NLS-1$ 53 private final int mRevision; 54 private final String mLicense; 55 private final String mDescription; 56 private final String mDescUrl; 57 private final String mReleaseNote; 58 private final String mReleaseUrl; 59 private final Archive[] mArchives; 60 private final RepoSource mSource; 61 62 /** 63 * Enum for the result of {@link Package#canBeUpdatedBy(Package)}. This used so that we can 64 * differentiate between a package that is totally incompatible, and one that is the same item 65 * but just not an update. 66 * @see #canBeUpdatedBy(Package) 67 */ 68 public static enum UpdateInfo { 69 /** Means that the 2 packages are not the same thing */ 70 INCOMPATIBLE, 71 /** Means that the 2 packages are the same thing but one does not upgrade the other */ 72 NOT_UPDATE, 73 /** Means that the 2 packages are the same thing, and one is the upgrade of the other */ 74 UPDATE; 75 } 76 77 /** 78 * Creates a new package from the attributes and elements of the given XML node. 79 * <p/> 80 * This constructor should throw an exception if the package cannot be created. 81 */ Package(RepoSource source, Node packageNode, Map<String,String> licenses)82 Package(RepoSource source, Node packageNode, Map<String,String> licenses) { 83 mSource = source; 84 mRevision = XmlParserUtils.getXmlInt (packageNode, SdkRepository.NODE_REVISION, 0); 85 mDescription = XmlParserUtils.getXmlString(packageNode, SdkRepository.NODE_DESCRIPTION); 86 mDescUrl = XmlParserUtils.getXmlString(packageNode, SdkRepository.NODE_DESC_URL); 87 mReleaseNote = XmlParserUtils.getXmlString(packageNode, SdkRepository.NODE_RELEASE_NOTE); 88 mReleaseUrl = XmlParserUtils.getXmlString(packageNode, SdkRepository.NODE_RELEASE_URL); 89 90 mLicense = parseLicense(packageNode, licenses); 91 mArchives = parseArchives(XmlParserUtils.getFirstChild( 92 packageNode, SdkRepository.NODE_ARCHIVES)); 93 } 94 95 /** 96 * Manually create a new package with one archive and the given attributes. 97 * This is used to create packages from local directories in which case there must be 98 * one archive which URL is the actual target location. 99 * <p/> 100 * Properties from props are used first when possible, e.g. if props is non null. 101 * <p/> 102 * By design, this creates a package with one and only one archive. 103 */ Package( RepoSource source, Properties props, int revision, String license, String description, String descUrl, Os archiveOs, Arch archiveArch, String archiveOsPath)104 public Package( 105 RepoSource source, 106 Properties props, 107 int revision, 108 String license, 109 String description, 110 String descUrl, 111 Os archiveOs, 112 Arch archiveArch, 113 String archiveOsPath) { 114 115 if (description == null) { 116 description = ""; 117 } 118 if (descUrl == null) { 119 descUrl = ""; 120 } 121 122 mRevision = Integer.parseInt(getProperty(props, PROP_REVISION, Integer.toString(revision))); 123 mLicense = getProperty(props, PROP_LICENSE, license); 124 mDescription = getProperty(props, PROP_DESC, description); 125 mDescUrl = getProperty(props, PROP_DESC_URL, descUrl); 126 mReleaseNote = getProperty(props, PROP_RELEASE_NOTE, ""); 127 mReleaseUrl = getProperty(props, PROP_RELEASE_URL, ""); 128 129 // If source is null and we can find a source URL in the properties, generate 130 // a dummy source just to store the URL. This allows us to easily remember where 131 // a package comes from. 132 String srcUrl = getProperty(props, PROP_SOURCE_URL, null); 133 if (props != null && source == null && srcUrl != null) { 134 boolean isUser = Boolean.parseBoolean(props.getProperty(PROP_USER_SOURCE, 135 Boolean.TRUE.toString())); 136 source = new RepoSource(srcUrl, isUser); 137 } 138 mSource = source; 139 140 mArchives = new Archive[1]; 141 mArchives[0] = new Archive(this, 142 props, 143 archiveOs, 144 archiveArch, 145 archiveOsPath); 146 } 147 148 /** 149 * Utility method that returns a property from a {@link Properties} object. 150 * Returns the default value if props is null or if the property is not defined. 151 */ getProperty(Properties props, String propKey, String defaultValue)152 protected String getProperty(Properties props, String propKey, String defaultValue) { 153 if (props == null) { 154 return defaultValue; 155 } 156 return props.getProperty(propKey, defaultValue); 157 } 158 159 /** 160 * Save the properties of the current packages in the given {@link Properties} object. 161 * These properties will later be give the constructor that takes a {@link Properties} object. 162 */ saveProperties(Properties props)163 void saveProperties(Properties props) { 164 props.setProperty(PROP_REVISION, Integer.toString(mRevision)); 165 if (mLicense != null && mLicense.length() > 0) { 166 props.setProperty(PROP_LICENSE, mLicense); 167 } 168 169 if (mDescription != null && mDescription.length() > 0) { 170 props.setProperty(PROP_DESC, mDescription); 171 } 172 if (mDescUrl != null && mDescUrl.length() > 0) { 173 props.setProperty(PROP_DESC_URL, mDescUrl); 174 } 175 176 if (mReleaseNote != null && mReleaseNote.length() > 0) { 177 props.setProperty(PROP_RELEASE_NOTE, mReleaseNote); 178 } 179 if (mReleaseUrl != null && mReleaseUrl.length() > 0) { 180 props.setProperty(PROP_RELEASE_URL, mReleaseUrl); 181 } 182 183 if (mSource != null) { 184 props.setProperty(PROP_SOURCE_URL, mSource.getUrl()); 185 props.setProperty(PROP_USER_SOURCE, Boolean.toString(mSource.isUserSource())); 186 } 187 } 188 189 /** 190 * Parses the uses-licence node of this package, if any, and returns the license 191 * definition if there's one. Returns null if there's no uses-license element or no 192 * license of this name defined. 193 */ parseLicense(Node packageNode, Map<String, String> licenses)194 private String parseLicense(Node packageNode, Map<String, String> licenses) { 195 Node usesLicense = XmlParserUtils.getFirstChild( 196 packageNode, SdkRepository.NODE_USES_LICENSE); 197 if (usesLicense != null) { 198 Node ref = usesLicense.getAttributes().getNamedItem(SdkRepository.ATTR_REF); 199 if (ref != null) { 200 String licenseRef = ref.getNodeValue(); 201 return licenses.get(licenseRef); 202 } 203 } 204 return null; 205 } 206 207 /** 208 * Parses an XML node to process the <archives> element. 209 */ parseArchives(Node archivesNode)210 private Archive[] parseArchives(Node archivesNode) { 211 ArrayList<Archive> archives = new ArrayList<Archive>(); 212 213 if (archivesNode != null) { 214 String nsUri = archivesNode.getNamespaceURI(); 215 for(Node child = archivesNode.getFirstChild(); 216 child != null; 217 child = child.getNextSibling()) { 218 219 if (child.getNodeType() == Node.ELEMENT_NODE && 220 nsUri.equals(child.getNamespaceURI()) && 221 SdkRepository.NODE_ARCHIVE.equals(child.getLocalName())) { 222 archives.add(parseArchive(child)); 223 } 224 } 225 } 226 227 return archives.toArray(new Archive[archives.size()]); 228 } 229 230 /** 231 * Parses one <archive> element from an <archives> container. 232 */ parseArchive(Node archiveNode)233 private Archive parseArchive(Node archiveNode) { 234 Archive a = new Archive( 235 this, 236 (Os) XmlParserUtils.getEnumAttribute(archiveNode, SdkRepository.ATTR_OS, 237 Os.values(), null), 238 (Arch) XmlParserUtils.getEnumAttribute(archiveNode, SdkRepository.ATTR_ARCH, 239 Arch.values(), Arch.ANY), 240 XmlParserUtils.getXmlString(archiveNode, SdkRepository.NODE_URL), 241 XmlParserUtils.getXmlLong (archiveNode, SdkRepository.NODE_SIZE, 0), 242 XmlParserUtils.getXmlString(archiveNode, SdkRepository.NODE_CHECKSUM) 243 ); 244 245 return a; 246 } 247 248 /** 249 * Returns the source that created (and owns) this package. Can be null. 250 */ getParentSource()251 public RepoSource getParentSource() { 252 return mSource; 253 } 254 255 /** 256 * Returns the revision, an int > 0, for all packages (platform, add-on, tool, doc). 257 * Can be 0 if this is a local package of unknown revision. 258 */ getRevision()259 public int getRevision() { 260 return mRevision; 261 } 262 263 /** 264 * Returns the optional description for all packages (platform, add-on, tool, doc) or 265 * for a lib. It is null if the element has not been specified in the repository XML. 266 */ getLicense()267 public String getLicense() { 268 return mLicense; 269 } 270 271 /** 272 * Returns the optional description for all packages (platform, add-on, tool, doc) or 273 * for a lib. Can be empty but not null. 274 */ getDescription()275 public String getDescription() { 276 return mDescription; 277 } 278 279 /** 280 * Returns the optional description URL for all packages (platform, add-on, tool, doc). 281 * Can be empty but not null. 282 */ getDescUrl()283 public String getDescUrl() { 284 return mDescUrl; 285 } 286 287 /** 288 * Returns the optional release note for all packages (platform, add-on, tool, doc) or 289 * for a lib. Can be empty but not null. 290 */ getReleaseNote()291 public String getReleaseNote() { 292 return mReleaseNote; 293 } 294 295 /** 296 * Returns the optional release note URL for all packages (platform, add-on, tool, doc). 297 * Can be empty but not null. 298 */ getReleaseNoteUrl()299 public String getReleaseNoteUrl() { 300 return mReleaseUrl; 301 } 302 303 /** 304 * Returns the archives defined in this package. 305 * Can be an empty array but not null. 306 */ getArchives()307 public Archive[] getArchives() { 308 return mArchives; 309 } 310 311 /** 312 * Returns whether the {@link Package} has at least one {@link Archive} compatible with 313 * the host platform. 314 */ hasCompatibleArchive()315 public boolean hasCompatibleArchive() { 316 for (Archive archive : mArchives) { 317 if (archive.isCompatible()) { 318 return true; 319 } 320 } 321 322 return false; 323 } 324 325 /** 326 * Returns a short description for an {@link IDescription}. 327 * Can be empty but not null. 328 */ getShortDescription()329 public abstract String getShortDescription(); 330 331 /** 332 * Returns a long description for an {@link IDescription}. 333 * Can be empty but not null. 334 */ getLongDescription()335 public String getLongDescription() { 336 StringBuilder sb = new StringBuilder(); 337 338 String s = getDescription(); 339 if (s != null) { 340 sb.append(s); 341 } 342 if (sb.length() > 0) { 343 sb.append("\n"); 344 } 345 346 sb.append(String.format("Revision %1$d", getRevision())); 347 348 s = getDescUrl(); 349 if (s != null && s.length() > 0) { 350 sb.append(String.format("\n\nMore information at %1$s", s)); 351 } 352 353 s = getReleaseNote(); 354 if (s != null && s.length() > 0) { 355 sb.append("\n\nRelease note:\n").append(s); 356 } 357 358 s = getReleaseNoteUrl(); 359 if (s != null && s.length() > 0) { 360 sb.append("\nRelease note URL: ").append(s); 361 } 362 363 return sb.toString(); 364 } 365 366 /** 367 * Computes a potential installation folder if an archive of this package were 368 * to be installed right away in the given SDK root. 369 * <p/> 370 * Some types of packages install in a fix location, for example docs and tools. 371 * In this case the returned folder may already exist with a different archive installed 372 * at the desired location. 373 * For other packages types, such as add-on or platform, the folder name is only partially 374 * relevant to determine the content and thus a real check will be done to provide an 375 * existing or new folder depending on the current content of the SDK. 376 * 377 * @param osSdkRoot The OS path of the SDK root folder. 378 * @param suggestedDir A suggestion for the installation folder name, based on the root 379 * folder used in the zip archive. 380 * @param sdkManager An existing SDK manager to list current platforms and addons. 381 * @return A new {@link File} corresponding to the directory to use to install this package. 382 */ getInstallFolder( String osSdkRoot, String suggestedDir, SdkManager sdkManager)383 public abstract File getInstallFolder( 384 String osSdkRoot, String suggestedDir, SdkManager sdkManager); 385 386 /** 387 * Returns whether the give package represents the same item as the current package. 388 * <p/> 389 * Two packages are considered the same if they represent the same thing, except for the 390 * revision number. 391 * @param pkg the package to compare 392 * @return true if the item 393 */ sameItemAs(Package pkg)394 public abstract boolean sameItemAs(Package pkg); 395 396 /** 397 * Computes whether the given package is a suitable update for the current package. 398 * <p/> 399 * An update is just that: a new package that supersedes the current one. If the new 400 * package does not represent the same item or if it has the same or lower revision as the 401 * current one, it's not an update. 402 * 403 * @param replacementPackage The potential replacement package. 404 * @return One of the {@link UpdateInfo} values. 405 * 406 * @see #sameItemAs(Package) 407 */ canBeUpdatedBy(Package replacementPackage)408 public UpdateInfo canBeUpdatedBy(Package replacementPackage) { 409 if (replacementPackage == null) { 410 return UpdateInfo.INCOMPATIBLE; 411 } 412 413 // check they are the same item. 414 if (sameItemAs(replacementPackage) == false) { 415 return UpdateInfo.INCOMPATIBLE; 416 } 417 418 // check revision number 419 if (replacementPackage.getRevision() > this.getRevision()) { 420 return UpdateInfo.UPDATE; 421 } 422 423 // not an upgrade but not incompatible either. 424 return UpdateInfo.NOT_UPDATE; 425 } 426 } 427