blob: 657bb143a696b0b21566fca9c809516fcfdb7bf5 [file] [log] [blame]
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.sdklib.internal.repository;
import com.android.sdklib.SdkManager;
import com.android.sdklib.internal.repository.Archive.Arch;
import com.android.sdklib.internal.repository.Archive.Os;
import com.android.sdklib.repository.SdkRepository;
import org.w3c.dom.Node;
import java.io.File;
import java.util.ArrayList;
import java.util.Map;
import java.util.Properties;
/**
* A {@link Package} is the base class for "something" that can be downloaded from
* the SDK repository -- subclasses include {@link PlatformPackage}, {@link AddonPackage},
* {@link DocPackage} and {@link ToolPackage}.
* <p/>
* A package has some attributes (revision, description) and a list of archives
* which represent the downloadable bits.
* <p/>
* Packages are contained by a {@link RepoSource} (a download site).
* <p/>
* Derived classes must implement the {@link IDescription} methods.
*/
public abstract class Package implements IDescription {
private static final String PROP_REVISION = "Pkg.Revision"; //$NON-NLS-1$
private static final String PROP_LICENSE = "Pkg.License"; //$NON-NLS-1$
private static final String PROP_DESC = "Pkg.Desc"; //$NON-NLS-1$
private static final String PROP_DESC_URL = "Pkg.DescUrl"; //$NON-NLS-1$
private static final String PROP_RELEASE_NOTE = "Pkg.RelNote"; //$NON-NLS-1$
private static final String PROP_RELEASE_URL = "Pkg.RelNoteUrl"; //$NON-NLS-1$
private static final String PROP_SOURCE_URL = "Pkg.SourceUrl"; //$NON-NLS-1$
private static final String PROP_USER_SOURCE = "Pkg.UserSrc"; //$NON-NLS-1$
private final int mRevision;
private final String mLicense;
private final String mDescription;
private final String mDescUrl;
private final String mReleaseNote;
private final String mReleaseUrl;
private final Archive[] mArchives;
private final RepoSource mSource;
/**
* Enum for the result of {@link Package#canBeUpdatedBy(Package)}. This used so that we can
* differentiate between a package that is totally incompatible, and one that is the same item
* but just not an update.
* @see #canBeUpdatedBy(Package)
*/
public static enum UpdateInfo {
/** Means that the 2 packages are not the same thing */
INCOMPATIBLE,
/** Means that the 2 packages are the same thing but one does not upgrade the other */
NOT_UPDATE,
/** Means that the 2 packages are the same thing, and one is the upgrade of the other */
UPDATE;
}
/**
* Creates a new package from the attributes and elements of the given XML node.
* <p/>
* This constructor should throw an exception if the package cannot be created.
*/
Package(RepoSource source, Node packageNode, Map<String,String> licenses) {
mSource = source;
mRevision = XmlParserUtils.getXmlInt (packageNode, SdkRepository.NODE_REVISION, 0);
mDescription = XmlParserUtils.getXmlString(packageNode, SdkRepository.NODE_DESCRIPTION);
mDescUrl = XmlParserUtils.getXmlString(packageNode, SdkRepository.NODE_DESC_URL);
mReleaseNote = XmlParserUtils.getXmlString(packageNode, SdkRepository.NODE_RELEASE_NOTE);
mReleaseUrl = XmlParserUtils.getXmlString(packageNode, SdkRepository.NODE_RELEASE_URL);
mLicense = parseLicense(packageNode, licenses);
mArchives = parseArchives(XmlParserUtils.getFirstChild(
packageNode, SdkRepository.NODE_ARCHIVES));
}
/**
* Manually create a new package with one archive and the given attributes.
* This is used to create packages from local directories in which case there must be
* one archive which URL is the actual target location.
* <p/>
* Properties from props are used first when possible, e.g. if props is non null.
* <p/>
* By design, this creates a package with one and only one archive.
*/
public Package(
RepoSource source,
Properties props,
int revision,
String license,
String description,
String descUrl,
Os archiveOs,
Arch archiveArch,
String archiveOsPath) {
if (description == null) {
description = "";
}
if (descUrl == null) {
descUrl = "";
}
mRevision = Integer.parseInt(getProperty(props, PROP_REVISION, Integer.toString(revision)));
mLicense = getProperty(props, PROP_LICENSE, license);
mDescription = getProperty(props, PROP_DESC, description);
mDescUrl = getProperty(props, PROP_DESC_URL, descUrl);
mReleaseNote = getProperty(props, PROP_RELEASE_NOTE, "");
mReleaseUrl = getProperty(props, PROP_RELEASE_URL, "");
// If source is null and we can find a source URL in the properties, generate
// a dummy source just to store the URL. This allows us to easily remember where
// a package comes from.
String srcUrl = getProperty(props, PROP_SOURCE_URL, null);
if (props != null && source == null && srcUrl != null) {
boolean isUser = Boolean.parseBoolean(props.getProperty(PROP_USER_SOURCE,
Boolean.TRUE.toString()));
source = new RepoSource(srcUrl, isUser);
}
mSource = source;
mArchives = new Archive[1];
mArchives[0] = new Archive(this,
props,
archiveOs,
archiveArch,
archiveOsPath);
}
/**
* Utility method that returns a property from a {@link Properties} object.
* Returns the default value if props is null or if the property is not defined.
*/
protected String getProperty(Properties props, String propKey, String defaultValue) {
if (props == null) {
return defaultValue;
}
return props.getProperty(propKey, defaultValue);
}
/**
* Save the properties of the current packages in the given {@link Properties} object.
* These properties will later be give the constructor that takes a {@link Properties} object.
*/
void saveProperties(Properties props) {
props.setProperty(PROP_REVISION, Integer.toString(mRevision));
if (mLicense != null && mLicense.length() > 0) {
props.setProperty(PROP_LICENSE, mLicense);
}
if (mDescription != null && mDescription.length() > 0) {
props.setProperty(PROP_DESC, mDescription);
}
if (mDescUrl != null && mDescUrl.length() > 0) {
props.setProperty(PROP_DESC_URL, mDescUrl);
}
if (mReleaseNote != null && mReleaseNote.length() > 0) {
props.setProperty(PROP_RELEASE_NOTE, mReleaseNote);
}
if (mReleaseUrl != null && mReleaseUrl.length() > 0) {
props.setProperty(PROP_RELEASE_URL, mReleaseUrl);
}
if (mSource != null) {
props.setProperty(PROP_SOURCE_URL, mSource.getUrl());
props.setProperty(PROP_USER_SOURCE, Boolean.toString(mSource.isUserSource()));
}
}
/**
* Parses the uses-licence node of this package, if any, and returns the license
* definition if there's one. Returns null if there's no uses-license element or no
* license of this name defined.
*/
private String parseLicense(Node packageNode, Map<String, String> licenses) {
Node usesLicense = XmlParserUtils.getFirstChild(
packageNode, SdkRepository.NODE_USES_LICENSE);
if (usesLicense != null) {
Node ref = usesLicense.getAttributes().getNamedItem(SdkRepository.ATTR_REF);
if (ref != null) {
String licenseRef = ref.getNodeValue();
return licenses.get(licenseRef);
}
}
return null;
}
/**
* Parses an XML node to process the <archives> element.
*/
private Archive[] parseArchives(Node archivesNode) {
ArrayList<Archive> archives = new ArrayList<Archive>();
if (archivesNode != null) {
for(Node child = archivesNode.getFirstChild();
child != null;
child = child.getNextSibling()) {
if (child.getNodeType() == Node.ELEMENT_NODE &&
SdkRepository.NS_SDK_REPOSITORY.equals(child.getNamespaceURI()) &&
SdkRepository.NODE_ARCHIVE.equals(child.getLocalName())) {
archives.add(parseArchive(child));
}
}
}
return archives.toArray(new Archive[archives.size()]);
}
/**
* Parses one <archive> element from an <archives> container.
*/
private Archive parseArchive(Node archiveNode) {
Archive a = new Archive(
this,
(Os) XmlParserUtils.getEnumAttribute(archiveNode, SdkRepository.ATTR_OS,
Os.values(), null),
(Arch) XmlParserUtils.getEnumAttribute(archiveNode, SdkRepository.ATTR_ARCH,
Arch.values(), Arch.ANY),
XmlParserUtils.getXmlString(archiveNode, SdkRepository.NODE_URL),
XmlParserUtils.getXmlLong (archiveNode, SdkRepository.NODE_SIZE, 0),
XmlParserUtils.getXmlString(archiveNode, SdkRepository.NODE_CHECKSUM)
);
return a;
}
/**
* Returns the source that created (and owns) this package. Can be null.
*/
public RepoSource getParentSource() {
return mSource;
}
/**
* Returns the revision, an int > 0, for all packages (platform, add-on, tool, doc).
* Can be 0 if this is a local package of unknown revision.
*/
public int getRevision() {
return mRevision;
}
/**
* Returns the optional description for all packages (platform, add-on, tool, doc) or
* for a lib. It is null if the element has not been specified in the repository XML.
*/
public String getLicense() {
return mLicense;
}
/**
* Returns the optional description for all packages (platform, add-on, tool, doc) or
* for a lib. Can be empty but not null.
*/
public String getDescription() {
return mDescription;
}
/**
* Returns the optional description URL for all packages (platform, add-on, tool, doc).
* Can be empty but not null.
*/
public String getDescUrl() {
return mDescUrl;
}
/**
* Returns the optional release note for all packages (platform, add-on, tool, doc) or
* for a lib. Can be empty but not null.
*/
public String getReleaseNote() {
return mReleaseNote;
}
/**
* Returns the optional release note URL for all packages (platform, add-on, tool, doc).
* Can be empty but not null.
*/
public String getReleaseNoteUrl() {
return mReleaseUrl;
}
/**
* Returns the archives defined in this package.
* Can be an empty array but not null.
*/
public Archive[] getArchives() {
return mArchives;
}
/**
* Returns whether the {@link Package} has at least one {@link Archive} compatible with
* the host platform.
*/
public boolean hasCompatibleArchive() {
for (Archive archive : mArchives) {
if (archive.isCompatible()) {
return true;
}
}
return false;
}
/**
* Returns a short description for an {@link IDescription}.
* Can be empty but not null.
*/
public abstract String getShortDescription();
/**
* Returns a long description for an {@link IDescription}.
* Can be empty but not null.
*/
public String getLongDescription() {
StringBuilder sb = new StringBuilder();
String s = getDescription();
if (s != null) {
sb.append(s);
}
if (sb.length() > 0) {
sb.append("\n");
}
sb.append(String.format("Revision %1$d", getRevision()));
s = getDescUrl();
if (s != null && s.length() > 0) {
sb.append(String.format("\n\nMore information at %1$s", s));
}
s = getReleaseNote();
if (s != null && s.length() > 0) {
sb.append("\n\nRelease note:\n").append(s);
}
s = getReleaseNoteUrl();
if (s != null && s.length() > 0) {
sb.append("\nRelease note URL: ").append(s);
}
return sb.toString();
}
/**
* Computes a potential installation folder if an archive of this package were
* to be installed right away in the given SDK root.
* <p/>
* Some types of packages install in a fix location, for example docs and tools.
* In this case the returned folder may already exist with a different archive installed
* at the desired location.
* For other packages types, such as add-on or platform, the folder name is only partially
* relevant to determine the content and thus a real check will be done to provide an
* existing or new folder depending on the current content of the SDK.
*
* @param osSdkRoot The OS path of the SDK root folder.
* @param suggestedDir A suggestion for the installation folder name, based on the root
* folder used in the zip archive.
* @param sdkManager An existing SDK manager to list current platforms and addons.
* @return A new {@link File} corresponding to the directory to use to install this package.
*/
public abstract File getInstallFolder(
String osSdkRoot, String suggestedDir, SdkManager sdkManager);
/**
* Returns whether the give package represents the same item as the current package.
* <p/>
* Two packages are considered the same if they represent the same thing, except for the
* revision number.
* @param pkg the package to compare
* @return true if the item
*/
public abstract boolean sameItemAs(Package pkg);
/**
* Computes whether the given package is a suitable update for the current package.
* <p/>
* An update is just that: a new package that supersedes the current one. If the new
* package does not represent the same item or if it has the same or lower revision as the
* current one, it's not an update.
*
* @param replacementPackage The potential replacement package.
* @return One of the {@link UpdateInfo} values.
*
* @see #sameItemAs(Package)
*/
public UpdateInfo canBeUpdatedBy(Package replacementPackage) {
if (replacementPackage == null) {
return UpdateInfo.INCOMPATIBLE;
}
// check they are the same item.
if (sameItemAs(replacementPackage) == false) {
return UpdateInfo.INCOMPATIBLE;
}
// check revision number
if (replacementPackage.getRevision() > this.getRevision()) {
return UpdateInfo.UPDATE;
}
// not an upgrade but not incompatible either.
return UpdateInfo.NOT_UPDATE;
}
}