• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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>&lt;obsolete&gt;</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