OSDN Git Service

SDK Manager2: revamp package diff logic.
[android-x86/sdk.git] / sdkmanager / libs / sdklib / src / com / android / sdklib / internal / repository / AddonPackage.java
1 /*\r
2  * Copyright (C) 2009 The Android Open Source Project\r
3  *\r
4  * Licensed under the Apache License, Version 2.0 (the "License");\r
5  * you may not use this file except in compliance with the License.\r
6  * You may obtain a copy of the License at\r
7  *\r
8  *      http://www.apache.org/licenses/LICENSE-2.0\r
9  *\r
10  * Unless required by applicable law or agreed to in writing, software\r
11  * distributed under the License is distributed on an "AS IS" BASIS,\r
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
13  * See the License for the specific language governing permissions and\r
14  * limitations under the License.\r
15  */\r
16 \r
17 package com.android.sdklib.internal.repository;\r
18 \r
19 import com.android.annotations.VisibleForTesting;\r
20 import com.android.annotations.VisibleForTesting.Visibility;\r
21 import com.android.sdklib.AndroidVersion;\r
22 import com.android.sdklib.IAndroidTarget;\r
23 import com.android.sdklib.SdkConstants;\r
24 import com.android.sdklib.SdkManager;\r
25 import com.android.sdklib.IAndroidTarget.IOptionalLibrary;\r
26 import com.android.sdklib.internal.repository.Archive.Arch;\r
27 import com.android.sdklib.internal.repository.Archive.Os;\r
28 import com.android.sdklib.repository.SdkRepoConstants;\r
29 import com.android.util.Pair;\r
30 \r
31 import org.w3c.dom.Node;\r
32 \r
33 import java.io.File;\r
34 import java.util.ArrayList;\r
35 import java.util.Arrays;\r
36 import java.util.Map;\r
37 import java.util.Properties;\r
38 \r
39 /**\r
40  * Represents an add-on XML node in an SDK repository.\r
41  */\r
42 public class AddonPackage extends Package\r
43     implements IPackageVersion, IPlatformDependency, IExactApiLevelDependency, ILayoutlibVersion {\r
44 \r
45     private static final String PROP_NAME      = "Addon.Name";      //$NON-NLS-1$\r
46     private static final String PROP_VENDOR    = "Addon.Vendor";    //$NON-NLS-1$\r
47 \r
48     private final String mVendor;\r
49     private final String mName;\r
50     private final AndroidVersion mVersion;\r
51 \r
52     /**\r
53      * The helper handling the layoutlib version.\r
54      */\r
55     private final LayoutlibVersionMixin mLayoutlibVersion;\r
56 \r
57     /** An add-on library. */\r
58     public static class Lib {\r
59         private final String mName;\r
60         private final String mDescription;\r
61 \r
62         public Lib(String name, String description) {\r
63             mName = name;\r
64             mDescription = description;\r
65         }\r
66 \r
67         public String getName() {\r
68             return mName;\r
69         }\r
70 \r
71         public String getDescription() {\r
72             return mDescription;\r
73         }\r
74 \r
75         @Override\r
76         public int hashCode() {\r
77             final int prime = 31;\r
78             int result = 1;\r
79             result = prime * result + ((mDescription == null) ? 0 : mDescription.hashCode());\r
80             result = prime * result + ((mName == null) ? 0 : mName.hashCode());\r
81             return result;\r
82         }\r
83 \r
84         @Override\r
85         public boolean equals(Object obj) {\r
86             if (this == obj) {\r
87                 return true;\r
88             }\r
89             if (obj == null) {\r
90                 return false;\r
91             }\r
92             if (!(obj instanceof Lib)) {\r
93                 return false;\r
94             }\r
95             Lib other = (Lib) obj;\r
96             if (mDescription == null) {\r
97                 if (other.mDescription != null) {\r
98                     return false;\r
99                 }\r
100             } else if (!mDescription.equals(other.mDescription)) {\r
101                 return false;\r
102             }\r
103             if (mName == null) {\r
104                 if (other.mName != null) {\r
105                     return false;\r
106                 }\r
107             } else if (!mName.equals(other.mName)) {\r
108                 return false;\r
109             }\r
110             return true;\r
111         }\r
112     }\r
113 \r
114     private final Lib[] mLibs;\r
115 \r
116     /**\r
117      * Creates a new add-on package from the attributes and elements of the given XML node.\r
118      * This constructor should throw an exception if the package cannot be created.\r
119      *\r
120      * @param source The {@link SdkSource} where this is loaded from.\r
121      * @param packageNode The XML element being parsed.\r
122      * @param nsUri The namespace URI of the originating XML document, to be able to deal with\r
123      *          parameters that vary according to the originating XML schema.\r
124      * @param licenses The licenses loaded from the XML originating document.\r
125      */\r
126     AddonPackage(SdkSource source, Node packageNode, String nsUri, Map<String,String> licenses) {\r
127         super(source, packageNode, nsUri, licenses);\r
128         mVendor   = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_VENDOR);\r
129         mName     = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_NAME);\r
130         int apiLevel = XmlParserUtils.getXmlInt   (packageNode, SdkRepoConstants.NODE_API_LEVEL, 0);\r
131         String codeName = XmlParserUtils.getXmlString(packageNode, SdkRepoConstants.NODE_CODENAME);\r
132         if (codeName.length() == 0) {\r
133             codeName = null;\r
134         }\r
135         mVersion = new AndroidVersion(apiLevel, codeName);\r
136 \r
137         mLibs = parseLibs(XmlParserUtils.getFirstChild(packageNode, SdkRepoConstants.NODE_LIBS));\r
138 \r
139         mLayoutlibVersion = new LayoutlibVersionMixin(packageNode);\r
140     }\r
141 \r
142     /**\r
143      * Creates a new platform package based on an actual {@link IAndroidTarget} (which\r
144      * {@link IAndroidTarget#isPlatform()} false) from the {@link SdkManager}.\r
145      * This is used to list local SDK folders in which case there is one archive which\r
146      * URL is the actual target location.\r
147      * <p/>\r
148      * By design, this creates a package with one and only one archive.\r
149      */\r
150     static Package create(IAndroidTarget target, Properties props) {\r
151         return new AddonPackage(target, props);\r
152     }\r
153 \r
154     @VisibleForTesting(visibility=Visibility.PRIVATE)\r
155     protected AddonPackage(IAndroidTarget target, Properties props) {\r
156         this(null /*source*/, target, props);\r
157     }\r
158 \r
159     @VisibleForTesting(visibility=Visibility.PRIVATE)\r
160     protected AddonPackage(SdkSource source, IAndroidTarget target, Properties props) {\r
161         super(  source,                     //source\r
162                 props,                      //properties\r
163                 target.getRevision(),       //revision\r
164                 null,                       //license\r
165                 target.getDescription(),    //description\r
166                 null,                       //descUrl\r
167                 Os.getCurrentOs(),          //archiveOs\r
168                 Arch.getCurrentArch(),      //archiveArch\r
169                 target.getLocation()        //archiveOsPath\r
170                 );\r
171 \r
172         mVersion = target.getVersion();\r
173         mName     = target.getName();\r
174         mVendor   = target.getVendor();\r
175         mLayoutlibVersion = new LayoutlibVersionMixin(props);\r
176 \r
177         IOptionalLibrary[] optLibs = target.getOptionalLibraries();\r
178         if (optLibs == null || optLibs.length == 0) {\r
179             mLibs = new Lib[0];\r
180         } else {\r
181             mLibs = new Lib[optLibs.length];\r
182             for (int i = 0; i < optLibs.length; i++) {\r
183                 mLibs[i] = new Lib(optLibs[i].getName(), optLibs[i].getDescription());\r
184             }\r
185         }\r
186     }\r
187 \r
188     /**\r
189      * Creates a broken addon which we know failed to load properly.\r
190      *\r
191      * @param archiveOsPath The absolute OS path of the addon folder.\r
192      * @param props The properties parsed from the addon manifest (not the source.properties).\r
193      * @param error The error indicating why this addon failed to be loaded.\r
194      */\r
195     static Package create(String archiveOsPath, Map<String, String> props, String error) {\r
196         String name     = props.get(SdkManager.ADDON_NAME);\r
197         String vendor   = props.get(SdkManager.ADDON_VENDOR);\r
198         String api      = props.get(SdkManager.ADDON_API);\r
199         String revision = props.get(SdkManager.ADDON_REVISION);\r
200 \r
201         String shortDesc = String.format("%1$s by %2$s, Android API %3$s, revision %4$s [*]",\r
202                 name,\r
203                 vendor,\r
204                 api,\r
205                 revision);\r
206 \r
207         String longDesc = String.format(\r
208                 "%1$s\n" +\r
209                 "[*] Addon failed to load: %2$s",\r
210                 shortDesc,\r
211                 error);\r
212 \r
213         int apiLevel = IExactApiLevelDependency.API_LEVEL_INVALID;\r
214 \r
215         try {\r
216             apiLevel = Integer.parseInt(api);\r
217         } catch(NumberFormatException e) {\r
218             // ignore\r
219         }\r
220 \r
221         return new BrokenPackage(null/*props*/, shortDesc, longDesc,\r
222                 IMinApiLevelDependency.MIN_API_LEVEL_NOT_SPECIFIED,\r
223                 apiLevel,\r
224                 archiveOsPath);\r
225     }\r
226 \r
227     public int getExactApiLevel() {\r
228         return mVersion.getApiLevel();\r
229     }\r
230 \r
231     /**\r
232      * Save the properties of the current packages in the given {@link Properties} object.\r
233      * These properties will later be given to a constructor that takes a {@link Properties} object.\r
234      */\r
235     @Override\r
236     void saveProperties(Properties props) {\r
237         super.saveProperties(props);\r
238 \r
239         mVersion.saveProperties(props);\r
240         mLayoutlibVersion.saveProperties(props);\r
241 \r
242         if (mName != null) {\r
243             props.setProperty(PROP_NAME, mName);\r
244         }\r
245         if (mVendor != null) {\r
246             props.setProperty(PROP_VENDOR, mVendor);\r
247         }\r
248     }\r
249 \r
250     /**\r
251      * Parses a <libs> element.\r
252      */\r
253     private Lib[] parseLibs(Node libsNode) {\r
254         ArrayList<Lib> libs = new ArrayList<Lib>();\r
255 \r
256         if (libsNode != null) {\r
257             String nsUri = libsNode.getNamespaceURI();\r
258             for(Node child = libsNode.getFirstChild();\r
259                 child != null;\r
260                 child = child.getNextSibling()) {\r
261 \r
262                 if (child.getNodeType() == Node.ELEMENT_NODE &&\r
263                         nsUri.equals(child.getNamespaceURI()) &&\r
264                         SdkRepoConstants.NODE_LIB.equals(child.getLocalName())) {\r
265                     libs.add(parseLib(child));\r
266                 }\r
267             }\r
268         }\r
269 \r
270         return libs.toArray(new Lib[libs.size()]);\r
271     }\r
272 \r
273     /**\r
274      * Parses a <lib> element from a <libs> container.\r
275      */\r
276     private Lib parseLib(Node libNode) {\r
277         return new Lib(XmlParserUtils.getXmlString(libNode, SdkRepoConstants.NODE_NAME),\r
278                        XmlParserUtils.getXmlString(libNode, SdkRepoConstants.NODE_DESCRIPTION));\r
279     }\r
280 \r
281     /** Returns the vendor, a string, for add-on packages. */\r
282     public String getVendor() {\r
283         return mVendor;\r
284     }\r
285 \r
286     /** Returns the name, a string, for add-on packages or for libraries. */\r
287     public String getName() {\r
288         return mName;\r
289     }\r
290 \r
291     /**\r
292      * Returns the version of the platform dependency of this package.\r
293      * <p/>\r
294      * An add-on has the same {@link AndroidVersion} as the platform it depends on.\r
295      */\r
296     public AndroidVersion getVersion() {\r
297         return mVersion;\r
298     }\r
299 \r
300     /** Returns the libs defined in this add-on. Can be an empty array but not null. */\r
301     public Lib[] getLibs() {\r
302         return mLibs;\r
303     }\r
304 \r
305     /**\r
306      * Returns the layoutlib version.\r
307      * <p/>\r
308      * The first integer is the API of layoublib, which should be > 0.\r
309      * It will be equal to {@link ILayoutlibVersion#LAYOUTLIB_API_NOT_SPECIFIED} (0)\r
310      * if the layoutlib version isn't specified.\r
311      * <p/>\r
312      * The second integer is the revision for that given API. It is >= 0\r
313      * and works as a minor revision number, incremented for the same API level.\r
314      *\r
315      * @since sdk-addon-2.xsd\r
316      */\r
317     public Pair<Integer, Integer> getLayoutlibVersion() {\r
318         return mLayoutlibVersion.getLayoutlibVersion();\r
319     }\r
320 \r
321     /**\r
322      * Returns a description of this package that is suitable for a list display.\r
323      * <p/>\r
324      * {@inheritDoc}\r
325      */\r
326     @Override\r
327     public String getListDescription() {\r
328         return String.format("%1$s by %2$s%3$s",\r
329                 getName(),\r
330                 getVendor(),\r
331                 isObsolete() ? " (Obsolete)" : "");\r
332     }\r
333 \r
334     /**\r
335      * Returns a short description for an {@link IDescription}.\r
336      */\r
337     @Override\r
338     public String getShortDescription() {\r
339         return String.format("%1$s by %2$s, Android API %3$s, revision %4$s%5$s",\r
340                 getName(),\r
341                 getVendor(),\r
342                 mVersion.getApiString(),\r
343                 getRevision(),\r
344                 isObsolete() ? " (Obsolete)" : "");\r
345     }\r
346 \r
347     /**\r
348      * Returns a long description for an {@link IDescription}.\r
349      *\r
350      * The long description is whatever the XML contains for the &lt;description&gt; field,\r
351      * or the short description if the former is empty.\r
352      */\r
353     @Override\r
354     public String getLongDescription() {\r
355         String s = getDescription();\r
356         if (s == null || s.length() == 0) {\r
357             s = getShortDescription();\r
358         }\r
359 \r
360         if (s.indexOf("revision") == -1) {\r
361             s += String.format("\nRevision %1$d%2$s",\r
362                     getRevision(),\r
363                     isObsolete() ? " (Obsolete)" : "");\r
364         }\r
365 \r
366         s += String.format("\nRequires SDK Platform Android API %1$s",\r
367                 mVersion.getApiString());\r
368         return s;\r
369     }\r
370 \r
371     /**\r
372      * Computes a potential installation folder if an archive of this package were\r
373      * to be installed right away in the given SDK root.\r
374      * <p/>\r
375      * An add-on package is typically installed in SDK/add-ons/"addon-name"-"api-level".\r
376      * The name needs to be sanitized to be acceptable as a directory name.\r
377      * However if we can find a different directory under SDK/add-ons that already\r
378      * has this add-ons installed, we'll use that one.\r
379      *\r
380      * @param osSdkRoot The OS path of the SDK root folder.\r
381      * @param sdkManager An existing SDK manager to list current platforms and addons.\r
382      * @return A new {@link File} corresponding to the directory to use to install this package.\r
383      */\r
384     @Override\r
385     public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {\r
386         File addons = new File(osSdkRoot, SdkConstants.FD_ADDONS);\r
387 \r
388         // First find if this add-on is already installed. If so, reuse the same directory.\r
389         for (IAndroidTarget target : sdkManager.getTargets()) {\r
390             if (!target.isPlatform() &&\r
391                     target.getVersion().equals(mVersion) &&\r
392                     target.getName().equals(getName()) &&\r
393                     target.getVendor().equals(getVendor())) {\r
394                 return new File(target.getLocation());\r
395             }\r
396         }\r
397 \r
398         // Compute a folder directory using the addon declared name and vendor strings.\r
399         // This purposely ignores the suggestedDir.\r
400         String name = String.format("addon_%s_%s_%s",     //$NON-NLS-1$\r
401                                     getName(), getVendor(), mVersion.getApiString());\r
402         name = name.toLowerCase();\r
403         name = name.replaceAll("[^a-z0-9_-]+", "_");      //$NON-NLS-1$ //$NON-NLS-2$\r
404         name = name.replaceAll("_+", "_");                //$NON-NLS-1$ //$NON-NLS-2$\r
405 \r
406         for (int i = 0; i < 100; i++) {\r
407             String name2 = i == 0 ? name : String.format("%s-%d", name, i); //$NON-NLS-1$\r
408             File folder = new File(addons, name2);\r
409             if (!folder.exists()) {\r
410                 return folder;\r
411             }\r
412         }\r
413 \r
414         // We shouldn't really get here. I mean, seriously, we tried hard enough.\r
415         return null;\r
416     }\r
417 \r
418     @Override\r
419     public boolean sameItemAs(Package pkg) {\r
420         if (pkg instanceof AddonPackage) {\r
421             AddonPackage newPkg = (AddonPackage)pkg;\r
422 \r
423             // check they are the same add-on.\r
424             return getName().equals(newPkg.getName()) &&\r
425                     getVendor().equals(newPkg.getVendor()) &&\r
426                     getVersion().equals(newPkg.getVersion());\r
427         }\r
428 \r
429         return false;\r
430     }\r
431 \r
432     @Override\r
433     public int hashCode() {\r
434         final int prime = 31;\r
435         int result = super.hashCode();\r
436         result = prime * result + ((mLayoutlibVersion == null) ? 0 : mLayoutlibVersion.hashCode());\r
437         result = prime * result + Arrays.hashCode(mLibs);\r
438         result = prime * result + ((mName == null) ? 0 : mName.hashCode());\r
439         result = prime * result + ((mVendor == null) ? 0 : mVendor.hashCode());\r
440         result = prime * result + ((mVersion == null) ? 0 : mVersion.hashCode());\r
441         return result;\r
442     }\r
443 \r
444     @Override\r
445     public boolean equals(Object obj) {\r
446         if (this == obj) {\r
447             return true;\r
448         }\r
449         if (!super.equals(obj)) {\r
450             return false;\r
451         }\r
452         if (!(obj instanceof AddonPackage)) {\r
453             return false;\r
454         }\r
455         AddonPackage other = (AddonPackage) obj;\r
456         if (mLayoutlibVersion == null) {\r
457             if (other.mLayoutlibVersion != null) {\r
458                 return false;\r
459             }\r
460         } else if (!mLayoutlibVersion.equals(other.mLayoutlibVersion)) {\r
461             return false;\r
462         }\r
463         if (!Arrays.equals(mLibs, other.mLibs)) {\r
464             return false;\r
465         }\r
466         if (mName == null) {\r
467             if (other.mName != null) {\r
468                 return false;\r
469             }\r
470         } else if (!mName.equals(other.mName)) {\r
471             return false;\r
472         }\r
473         if (mVendor == null) {\r
474             if (other.mVendor != null) {\r
475                 return false;\r
476             }\r
477         } else if (!mVendor.equals(other.mVendor)) {\r
478             return false;\r
479         }\r
480         if (mVersion == null) {\r
481             if (other.mVersion != null) {\r
482                 return false;\r
483             }\r
484         } else if (!mVersion.equals(other.mVersion)) {\r
485             return false;\r
486         }\r
487         return true;\r
488     }\r
489 }\r