Home | History | Annotate | Download | only in repository
      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>&lt;obsolete&gt;</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      * - Add-on based on n preview
    490      * - Platform n
    491      * - Add-on based on n
    492      * - Platform n-1
    493      * - Add-on based on n-1
    494      * - Extra packages.
    495      */
    496     public int compareTo(Package other) {
    497         int s1 = this.sortingScore();
    498         int s2 = other.sortingScore();
    499         return s1 - s2;
    500     }
    501 
    502     /**
    503      * Computes the score for each package used by {@link #compareTo(Package)}.
    504      */
    505     private int sortingScore() {
    506         int type = 0;
    507         int rev = getRevision();
    508         int offset = 0;
    509         if (this instanceof ToolPackage) {
    510             type = 3;
    511         } else if (this instanceof DocPackage) {
    512             type = 2;
    513         } else if (this instanceof PlatformPackage || this instanceof AddonPackage ||
    514                 this instanceof SamplePackage) {
    515             type = 1;
    516             AndroidVersion v = ((IPackageVersion) this).getVersion();
    517             offset = v.getApiLevel();
    518             offset = offset * 2 + (v.isPreview() ? 1 : 0);
    519             offset = offset * 2 + ((this instanceof AddonPackage) ? 0 :
    520                     ((this instanceof SamplePackage) ? 1 : 2));
    521         } else {
    522             // extras and everything else
    523             type = 0;
    524         }
    525 
    526         int n = (type << 28) + (offset << 12) + rev;
    527         return 0 - n;
    528     }
    529 
    530 }
    531