2 * Copyright (C) 2009 The Android Open Source Project
\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
8 * http://www.apache.org/licenses/LICENSE-2.0
\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
17 package com.android.sdklib.internal.repository;
\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
31 import org.w3c.dom.Node;
\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
40 * Represents an add-on XML node in an SDK repository.
\r
42 public class AddonPackage extends Package
\r
43 implements IPackageVersion, IPlatformDependency, IExactApiLevelDependency, ILayoutlibVersion {
\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
48 private final String mVendor;
\r
49 private final String mName;
\r
50 private final AndroidVersion mVersion;
\r
53 * The helper handling the layoutlib version.
\r
55 private final LayoutlibVersionMixin mLayoutlibVersion;
\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
62 public Lib(String name, String description) {
\r
64 mDescription = description;
\r
67 public String getName() {
\r
71 public String getDescription() {
\r
72 return mDescription;
\r
76 public int hashCode() {
\r
77 final int prime = 31;
\r
79 result = prime * result + ((mDescription == null) ? 0 : mDescription.hashCode());
\r
80 result = prime * result + ((mName == null) ? 0 : mName.hashCode());
\r
85 public boolean equals(Object obj) {
\r
92 if (!(obj instanceof Lib)) {
\r
95 Lib other = (Lib) obj;
\r
96 if (mDescription == null) {
\r
97 if (other.mDescription != null) {
\r
100 } else if (!mDescription.equals(other.mDescription)) {
\r
103 if (mName == null) {
\r
104 if (other.mName != null) {
\r
107 } else if (!mName.equals(other.mName)) {
\r
114 private final Lib[] mLibs;
\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
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
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
135 mVersion = new AndroidVersion(apiLevel, codeName);
\r
137 mLibs = parseLibs(XmlParserUtils.getFirstChild(packageNode, SdkRepoConstants.NODE_LIBS));
\r
139 mLayoutlibVersion = new LayoutlibVersionMixin(packageNode);
\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
148 * By design, this creates a package with one and only one archive.
\r
150 static Package create(IAndroidTarget target, Properties props) {
\r
151 return new AddonPackage(target, props);
\r
154 @VisibleForTesting(visibility=Visibility.PRIVATE)
\r
155 protected AddonPackage(IAndroidTarget target, Properties props) {
\r
156 this(null /*source*/, target, props);
\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
165 target.getDescription(), //description
\r
167 Os.getCurrentOs(), //archiveOs
\r
168 Arch.getCurrentArch(), //archiveArch
\r
169 target.getLocation() //archiveOsPath
\r
172 mVersion = target.getVersion();
\r
173 mName = target.getName();
\r
174 mVendor = target.getVendor();
\r
175 mLayoutlibVersion = new LayoutlibVersionMixin(props);
\r
177 IOptionalLibrary[] optLibs = target.getOptionalLibraries();
\r
178 if (optLibs == null || optLibs.length == 0) {
\r
179 mLibs = new Lib[0];
\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
189 * Creates a broken addon which we know failed to load properly.
\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
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
201 String shortDesc = String.format("%1$s by %2$s, Android API %3$s, revision %4$s [*]",
\r
207 String longDesc = String.format(
\r
209 "[*] Addon failed to load: %2$s",
\r
213 int apiLevel = IExactApiLevelDependency.API_LEVEL_INVALID;
\r
216 apiLevel = Integer.parseInt(api);
\r
217 } catch(NumberFormatException e) {
\r
221 return new BrokenPackage(null/*props*/, shortDesc, longDesc,
\r
222 IMinApiLevelDependency.MIN_API_LEVEL_NOT_SPECIFIED,
\r
227 public int getExactApiLevel() {
\r
228 return mVersion.getApiLevel();
\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
236 void saveProperties(Properties props) {
\r
237 super.saveProperties(props);
\r
239 mVersion.saveProperties(props);
\r
240 mLayoutlibVersion.saveProperties(props);
\r
242 if (mName != null) {
\r
243 props.setProperty(PROP_NAME, mName);
\r
245 if (mVendor != null) {
\r
246 props.setProperty(PROP_VENDOR, mVendor);
\r
251 * Parses a <libs> element.
\r
253 private Lib[] parseLibs(Node libsNode) {
\r
254 ArrayList<Lib> libs = new ArrayList<Lib>();
\r
256 if (libsNode != null) {
\r
257 String nsUri = libsNode.getNamespaceURI();
\r
258 for(Node child = libsNode.getFirstChild();
\r
260 child = child.getNextSibling()) {
\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
270 return libs.toArray(new Lib[libs.size()]);
\r
274 * Parses a <lib> element from a <libs> container.
\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
281 /** Returns the vendor, a string, for add-on packages. */
\r
282 public String getVendor() {
\r
286 /** Returns the name, a string, for add-on packages or for libraries. */
\r
287 public String getName() {
\r
292 * Returns the version of the platform dependency of this package.
\r
294 * An add-on has the same {@link AndroidVersion} as the platform it depends on.
\r
296 public AndroidVersion getVersion() {
\r
300 /** Returns the libs defined in this add-on. Can be an empty array but not null. */
\r
301 public Lib[] getLibs() {
\r
306 * Returns the layoutlib version.
\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
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
315 * @since sdk-addon-2.xsd
\r
317 public Pair<Integer, Integer> getLayoutlibVersion() {
\r
318 return mLayoutlibVersion.getLayoutlibVersion();
\r
322 * Returns a description of this package that is suitable for a list display.
\r
327 public String getListDescription() {
\r
328 return String.format("%1$s by %2$s%3$s",
\r
331 isObsolete() ? " (Obsolete)" : "");
\r
335 * Returns a short description for an {@link IDescription}.
\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
342 mVersion.getApiString(),
\r
344 isObsolete() ? " (Obsolete)" : "");
\r
348 * Returns a long description for an {@link IDescription}.
\r
350 * The long description is whatever the XML contains for the <description> field,
\r
351 * or the short description if the former is empty.
\r
354 public String getLongDescription() {
\r
355 String s = getDescription();
\r
356 if (s == null || s.length() == 0) {
\r
357 s = getShortDescription();
\r
360 if (s.indexOf("revision") == -1) {
\r
361 s += String.format("\nRevision %1$d%2$s",
\r
363 isObsolete() ? " (Obsolete)" : "");
\r
366 s += String.format("\nRequires SDK Platform Android API %1$s",
\r
367 mVersion.getApiString());
\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
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
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
385 public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
\r
386 File addons = new File(osSdkRoot, SdkConstants.FD_ADDONS);
\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
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
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
414 // We shouldn't really get here. I mean, seriously, we tried hard enough.
\r
419 public boolean sameItemAs(Package pkg) {
\r
420 if (pkg instanceof AddonPackage) {
\r
421 AddonPackage newPkg = (AddonPackage)pkg;
\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
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
445 public boolean equals(Object obj) {
\r
449 if (!super.equals(obj)) {
\r
452 if (!(obj instanceof AddonPackage)) {
\r
455 AddonPackage other = (AddonPackage) obj;
\r
456 if (mLayoutlibVersion == null) {
\r
457 if (other.mLayoutlibVersion != null) {
\r
460 } else if (!mLayoutlibVersion.equals(other.mLayoutlibVersion)) {
\r
463 if (!Arrays.equals(mLibs, other.mLibs)) {
\r
466 if (mName == null) {
\r
467 if (other.mName != null) {
\r
470 } else if (!mName.equals(other.mName)) {
\r
473 if (mVendor == null) {
\r
474 if (other.mVendor != null) {
\r
477 } else if (!mVendor.equals(other.mVendor)) {
\r
480 if (mVersion == null) {
\r
481 if (other.mVersion != null) {
\r
484 } else if (!mVersion.equals(other.mVersion)) {
\r