• 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.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