Home | History | Annotate | Download | only in repository
      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 &lt;description&gt; 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