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.IAndroidTarget; 21 import com.android.sdklib.SdkConstants; 22 import com.android.sdklib.SdkManager; 23 import com.android.sdklib.IAndroidTarget.IOptionalLibrary; 24 import com.android.sdklib.internal.repository.Archive.Arch; 25 import com.android.sdklib.internal.repository.Archive.Os; 26 import com.android.sdklib.repository.SdkRepository; 27 28 import org.w3c.dom.Node; 29 30 import java.io.File; 31 import java.util.ArrayList; 32 import java.util.Map; 33 import java.util.Properties; 34 35 /** 36 * Represents an add-on XML node in an SDK repository. 37 */ 38 public class AddonPackage extends Package 39 implements IPackageVersion, IPlatformDependency { 40 41 private static final String PROP_NAME = "Addon.Name"; //$NON-NLS-1$ 42 private static final String PROP_VENDOR = "Addon.Vendor"; //$NON-NLS-1$ 43 44 private final String mVendor; 45 private final String mName; 46 private final AndroidVersion mVersion; 47 48 /** An add-on library. */ 49 public static class Lib { 50 private final String mName; 51 private final String mDescription; 52 53 public Lib(String name, String description) { 54 mName = name; 55 mDescription = description; 56 } 57 58 public String getName() { 59 return mName; 60 } 61 62 public String getDescription() { 63 return mDescription; 64 } 65 } 66 67 private final Lib[] mLibs; 68 69 /** 70 * Creates a new add-on package from the attributes and elements of the given XML node. 71 * <p/> 72 * This constructor should throw an exception if the package cannot be created. 73 */ 74 AddonPackage(RepoSource source, Node packageNode, Map<String,String> licenses) { 75 super(source, packageNode, licenses); 76 mVendor = XmlParserUtils.getXmlString(packageNode, SdkRepository.NODE_VENDOR); 77 mName = XmlParserUtils.getXmlString(packageNode, SdkRepository.NODE_NAME); 78 int apiLevel = XmlParserUtils.getXmlInt (packageNode, SdkRepository.NODE_API_LEVEL, 0); 79 String codeName = XmlParserUtils.getXmlString(packageNode, SdkRepository.NODE_CODENAME); 80 if (codeName.length() == 0) { 81 codeName = null; 82 } 83 mVersion = new AndroidVersion(apiLevel, codeName); 84 85 mLibs = parseLibs(XmlParserUtils.getFirstChild(packageNode, SdkRepository.NODE_LIBS)); 86 } 87 88 /** 89 * Creates a new platform package based on an actual {@link IAndroidTarget} (which 90 * {@link IAndroidTarget#isPlatform()} false) from the {@link SdkManager}. 91 * This is used to list local SDK folders in which case there is one archive which 92 * URL is the actual target location. 93 * <p/> 94 * By design, this creates a package with one and only one archive. 95 */ 96 AddonPackage(IAndroidTarget target, Properties props) { 97 super( null, //source 98 props, //properties 99 target.getRevision(), //revision 100 null, //license 101 target.getDescription(), //description 102 null, //descUrl 103 Os.getCurrentOs(), //archiveOs 104 Arch.getCurrentArch(), //archiveArch 105 target.getLocation() //archiveOsPath 106 ); 107 108 mVersion = target.getVersion(); 109 mName = target.getName(); 110 mVendor = target.getVendor(); 111 112 IOptionalLibrary[] optLibs = target.getOptionalLibraries(); 113 if (optLibs == null || optLibs.length == 0) { 114 mLibs = new Lib[0]; 115 } else { 116 mLibs = new Lib[optLibs.length]; 117 for (int i = 0; i < optLibs.length; i++) { 118 mLibs[i] = new Lib(optLibs[i].getName(), optLibs[i].getDescription()); 119 } 120 } 121 } 122 123 /** 124 * Save the properties of the current packages in the given {@link Properties} object. 125 * These properties will later be given to a constructor that takes a {@link Properties} object. 126 */ 127 @Override 128 void saveProperties(Properties props) { 129 super.saveProperties(props); 130 131 mVersion.saveProperties(props); 132 if (mName != null) { 133 props.setProperty(PROP_NAME, mName); 134 } 135 if (mVendor != null) { 136 props.setProperty(PROP_VENDOR, mVendor); 137 } 138 } 139 140 /** 141 * Parses a <libs> element. 142 */ 143 private Lib[] parseLibs(Node libsNode) { 144 ArrayList<Lib> libs = new ArrayList<Lib>(); 145 146 if (libsNode != null) { 147 String nsUri = libsNode.getNamespaceURI(); 148 for(Node child = libsNode.getFirstChild(); 149 child != null; 150 child = child.getNextSibling()) { 151 152 if (child.getNodeType() == Node.ELEMENT_NODE && 153 nsUri.equals(child.getNamespaceURI()) && 154 SdkRepository.NODE_LIB.equals(child.getLocalName())) { 155 libs.add(parseLib(child)); 156 } 157 } 158 } 159 160 return libs.toArray(new Lib[libs.size()]); 161 } 162 163 /** 164 * Parses a <lib> element from a <libs> container. 165 */ 166 private Lib parseLib(Node libNode) { 167 return new Lib(XmlParserUtils.getXmlString(libNode, SdkRepository.NODE_NAME), 168 XmlParserUtils.getXmlString(libNode, SdkRepository.NODE_DESCRIPTION)); 169 } 170 171 /** Returns the vendor, a string, for add-on packages. */ 172 public String getVendor() { 173 return mVendor; 174 } 175 176 /** Returns the name, a string, for add-on packages or for libraries. */ 177 public String getName() { 178 return mName; 179 } 180 181 /** 182 * Returns the version of the platform dependency of this package. 183 * <p/> 184 * An add-on has the same {@link AndroidVersion} as the platform it depends on. 185 */ 186 public AndroidVersion getVersion() { 187 return mVersion; 188 } 189 190 /** Returns the libs defined in this add-on. Can be an empty array but not null. */ 191 public Lib[] getLibs() { 192 return mLibs; 193 } 194 195 /** Returns a short description for an {@link IDescription}. */ 196 @Override 197 public String getShortDescription() { 198 return String.format("%1$s by %2$s, Android API %3$s, revision %4$s%5$s", 199 getName(), 200 getVendor(), 201 mVersion.getApiString(), 202 getRevision(), 203 isObsolete() ? " (Obsolete)" : ""); 204 } 205 206 /** 207 * Returns a long description for an {@link IDescription}. 208 * 209 * The long description is whatever the XML contains for the <description> field, 210 * or the short description if the former is empty. 211 */ 212 @Override 213 public String getLongDescription() { 214 String s = getDescription(); 215 if (s == null || s.length() == 0) { 216 s = getShortDescription(); 217 } 218 219 if (s.indexOf("revision") == -1) { 220 s += String.format("\nRevision %1$d%2$s", 221 getRevision(), 222 isObsolete() ? " (Obsolete)" : ""); 223 } 224 225 s += String.format("\nRequires SDK Platform Android API %1$s", 226 mVersion.getApiString()); 227 return s; 228 } 229 230 /** 231 * Computes a potential installation folder if an archive of this package were 232 * to be installed right away in the given SDK root. 233 * <p/> 234 * An add-on package is typically installed in SDK/add-ons/"addon-name"-"api-level". 235 * The name needs to be sanitized to be acceptable as a directory name. 236 * However if we can find a different directory under SDK/add-ons that already 237 * has this add-ons installed, we'll use that one. 238 * 239 * @param osSdkRoot The OS path of the SDK root folder. 240 * @param suggestedDir A suggestion for the installation folder name, based on the root 241 * folder used in the zip archive. 242 * @param sdkManager An existing SDK manager to list current platforms and addons. 243 * @return A new {@link File} corresponding to the directory to use to install this package. 244 */ 245 @Override 246 public File getInstallFolder(String osSdkRoot, String suggestedDir, SdkManager sdkManager) { 247 File addons = new File(osSdkRoot, SdkConstants.FD_ADDONS); 248 249 // First find if this add-on is already installed. If so, reuse the same directory. 250 for (IAndroidTarget target : sdkManager.getTargets()) { 251 if (!target.isPlatform() && 252 target.getVersion().equals(mVersion) && 253 target.getName().equals(getName()) && 254 target.getVendor().equals(getVendor())) { 255 return new File(target.getLocation()); 256 } 257 } 258 259 // Compute a folder directory using the addon declared name and vendor strings. 260 // This purposely ignores the suggestedDir. 261 String name = String.format("addon_%s_%s_%s", //$NON-NLS-1$ 262 getName(), getVendor(), mVersion.getApiString()); 263 name = name.toLowerCase(); 264 name = name.replaceAll("[^a-z0-9_-]+", "_"); //$NON-NLS-1$ //$NON-NLS-2$ 265 name = name.replaceAll("_+", "_"); //$NON-NLS-1$ //$NON-NLS-2$ 266 267 for (int i = 0; i < 100; i++) { 268 String name2 = i == 0 ? name : String.format("%s-%d", name, i); //$NON-NLS-1$ 269 File folder = new File(addons, name2); 270 if (!folder.exists()) { 271 return folder; 272 } 273 } 274 275 // We shouldn't really get here. I mean, seriously, we tried hard enough. 276 return null; 277 } 278 279 /** 280 * Makes sure the base /add-ons folder exists before installing. 281 */ 282 @Override 283 public boolean preInstallHook(Archive archive, 284 ITaskMonitor monitor, 285 String osSdkRoot, 286 File installFolder) { 287 File addonsRoot = new File(osSdkRoot, SdkConstants.FD_ADDONS); 288 if (!addonsRoot.isDirectory()) { 289 addonsRoot.mkdir(); 290 } 291 292 return super.preInstallHook(archive, monitor, osSdkRoot, installFolder); 293 } 294 295 @Override 296 public boolean sameItemAs(Package pkg) { 297 if (pkg instanceof AddonPackage) { 298 AddonPackage newPkg = (AddonPackage)pkg; 299 300 // check they are the same add-on. 301 return getName().equals(newPkg.getName()) && 302 getVendor().equals(newPkg.getVendor()) && 303 getVersion().equals(newPkg.getVersion()); 304 } 305 306 return false; 307 } 308 } 309