Home | History | Annotate | Download | only in sdk
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
      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.ide.eclipse.adt.internal.sdk;
     18 
     19 import com.android.ide.common.resources.configuration.FolderConfiguration;
     20 import com.android.ide.eclipse.adt.AdtPlugin;
     21 import com.android.ide.eclipse.adt.internal.sdk.LayoutDevice.DeviceConfig;
     22 import com.android.prefs.AndroidLocation;
     23 import com.android.prefs.AndroidLocation.AndroidLocationException;
     24 import com.android.sdklib.SdkConstants;
     25 
     26 import org.w3c.dom.Document;
     27 import org.w3c.dom.Element;
     28 import org.xml.sax.ErrorHandler;
     29 import org.xml.sax.InputSource;
     30 import org.xml.sax.SAXException;
     31 import org.xml.sax.SAXParseException;
     32 
     33 import java.io.File;
     34 import java.io.FileInputStream;
     35 import java.io.FileNotFoundException;
     36 import java.io.FileReader;
     37 import java.io.IOException;
     38 import java.util.ArrayList;
     39 import java.util.Collections;
     40 import java.util.List;
     41 
     42 import javax.xml.parsers.DocumentBuilder;
     43 import javax.xml.parsers.DocumentBuilderFactory;
     44 import javax.xml.parsers.ParserConfigurationException;
     45 import javax.xml.parsers.SAXParser;
     46 import javax.xml.parsers.SAXParserFactory;
     47 import javax.xml.transform.Result;
     48 import javax.xml.transform.Source;
     49 import javax.xml.transform.Transformer;
     50 import javax.xml.transform.TransformerFactory;
     51 import javax.xml.transform.dom.DOMSource;
     52 import javax.xml.transform.stream.StreamResult;
     53 import javax.xml.transform.stream.StreamSource;
     54 import javax.xml.validation.Validator;
     55 
     56 /**
     57  * Manages the layout devices.
     58  * They can come from 3 sources: built-in, add-ons, user.
     59  */
     60 public class LayoutDeviceManager {
     61 
     62     /**
     63      * A SAX error handler that captures the errors and warnings.
     64      * This allows us to capture *all* errors and just not get an exception on the first one.
     65      */
     66     private static class CaptureErrorHandler implements ErrorHandler {
     67 
     68         private final String mSourceLocation;
     69 
     70         private boolean mFoundError = false;
     71 
     72         CaptureErrorHandler(String sourceLocation) {
     73             mSourceLocation = sourceLocation;
     74         }
     75 
     76         public boolean foundError() {
     77             return mFoundError;
     78         }
     79 
     80         /**
     81          * @throws SAXException
     82          */
     83         @Override
     84         public void error(SAXParseException ex) throws SAXException {
     85             mFoundError = true;
     86             AdtPlugin.log(ex, "Error validating %1$s", mSourceLocation);
     87         }
     88 
     89         /**
     90          * @throws SAXException
     91          */
     92         @Override
     93         public void fatalError(SAXParseException ex) throws SAXException {
     94             mFoundError = true;
     95             AdtPlugin.log(ex, "Error validating %1$s", mSourceLocation);
     96         }
     97 
     98         /**
     99          * @throws SAXException
    100          */
    101         @Override
    102         public void warning(SAXParseException ex) throws SAXException {
    103             // ignore those for now.
    104         }
    105     }
    106 
    107     private final SAXParserFactory mParserFactory;
    108 
    109     private List<LayoutDevice> mDefaultLayoutDevices =
    110         new ArrayList<LayoutDevice>();
    111     private List<LayoutDevice> mAddOnLayoutDevices =
    112         new ArrayList<LayoutDevice>();
    113     private final List<LayoutDevice> mUserLayoutDevices =
    114         new ArrayList<LayoutDevice>();
    115     private List<LayoutDevice> mLayoutDevices;
    116 
    117     LayoutDeviceManager() {
    118         mParserFactory = SAXParserFactory.newInstance();
    119         mParserFactory.setNamespaceAware(true);
    120     }
    121 
    122     public List<LayoutDevice> getCombinedList() {
    123         return mLayoutDevices;
    124     }
    125 
    126     public List<LayoutDevice> getDefaultLayoutDevices() {
    127         return mDefaultLayoutDevices;
    128     }
    129 
    130     public List<LayoutDevice> getAddOnLayoutDevice() {
    131         return mAddOnLayoutDevices;
    132     }
    133 
    134     public List<LayoutDevice> getUserLayoutDevices() {
    135         return mUserLayoutDevices;
    136     }
    137 
    138     public LayoutDevice getUserLayoutDevice(String name) {
    139         for (LayoutDevice d : mUserLayoutDevices) {
    140             if (d.getName().equals(name)) {
    141                 return d;
    142             }
    143         }
    144 
    145         return null;
    146     }
    147 
    148     public LayoutDevice addUserDevice(String name, float xdpi, float ydpi) {
    149         LayoutDevice d = new LayoutDevice(name);
    150         d.setXDpi(xdpi);
    151         d.setYDpi(ydpi);
    152         mUserLayoutDevices.add(d);
    153         combineLayoutDevices();
    154 
    155         return d;
    156     }
    157 
    158     public void removeUserDevice(LayoutDevice device) {
    159         if (mUserLayoutDevices.remove(device)) {
    160             combineLayoutDevices();
    161         }
    162     }
    163 
    164     /**
    165      * Replaces a device with a new one with new name and/or x/y dpi, and return the new device.
    166      * If the name and dpi values are identical the given device is returned an nothing is done
    167      * @param device the {@link LayoutDevice} to replace
    168      * @param newName the new name.
    169      * @param newXDpi the new X dpi value
    170      * @param newYDpi the new Y dpi value.
    171      * @return the new LayoutDevice
    172      */
    173     public LayoutDevice replaceUserDevice(LayoutDevice device, String newName,
    174             float newXDpi, float newYDpi) {
    175         if (device.getName().equals(newName) && device.getXDpi() == newXDpi &&
    176                 device.getYDpi() == newYDpi) {
    177             return device;
    178         }
    179 
    180         // else create a new device
    181         LayoutDevice newDevice = new LayoutDevice(newName);
    182         newDevice.setXDpi(newXDpi);
    183         newDevice.setYDpi(newYDpi);
    184 
    185         // and get the Folderconfiguration
    186         List<DeviceConfig> configs = device.getConfigs();
    187         newDevice.addConfigs(configs);
    188 
    189         // replace the old device with the new
    190         mUserLayoutDevices.remove(device);
    191         mUserLayoutDevices.add(newDevice);
    192         combineLayoutDevices();
    193 
    194         return newDevice;
    195     }
    196 
    197     /**
    198      * Adds or replaces a configuration in a given {@link LayoutDevice}.
    199      * @param device the device to modify
    200      * @param configName the configuration name to add or replace
    201      * @param config the configuration to set
    202      */
    203     public void addUserConfiguration(LayoutDevice device, String configName,
    204             FolderConfiguration config) {
    205         // check that the device does belong to the user list.
    206         // the main goal is to make sure that this does not belong to the default/addon list.
    207         if (mUserLayoutDevices.contains(device)) {
    208             device.addConfig(configName, config);
    209         }
    210     }
    211 
    212     /**
    213      * Replaces a configuration in a given {@link LayoutDevice}.
    214      * @param device the device to modify
    215      * @param oldConfigName the name of the config to replace. If null, the new config is simply
    216      * added.
    217      * @param newConfigName the configuration name to add or replace
    218      * @param config the configuration to set
    219      */
    220     public void replaceUserConfiguration(LayoutDevice device, String oldConfigName,
    221             String newConfigName, FolderConfiguration config) {
    222         // check that the device does belong to the user list.
    223         // the main goal is to make sure that this does not belong to the default/addon list.
    224         if (mUserLayoutDevices.contains(device)) {
    225             // if the old and new config name are different, remove the old one
    226             if (oldConfigName != null && oldConfigName.equals(newConfigName) == false) {
    227                 device.removeConfig(oldConfigName);
    228             }
    229 
    230             // and then add the new one
    231             device.addConfig(newConfigName, config);
    232         }
    233     }
    234 
    235     /**
    236      * Removes a configuration from a given user {@link LayoutDevice}
    237      * @param device the device to modify
    238      * @param configName the name of the config to remove
    239      */
    240     public void removeUserConfiguration(LayoutDevice device, String configName) {
    241         // check that the device does belong to the user list.
    242         // the main goal is to make sure that this does not belong to the default/addon list.
    243         if (mUserLayoutDevices.contains(device)) {
    244             device.removeConfig(configName);
    245         }
    246     }
    247 
    248     /**
    249      * Saves the user-made {@link LayoutDevice}s to disk.
    250      */
    251     public void save() {
    252         try {
    253             String userFolder = AndroidLocation.getFolder();
    254             File deviceXml = new File(userFolder, SdkConstants.FN_DEVICES_XML);
    255             if (deviceXml.isDirectory() == false) {
    256                 write(deviceXml, mUserLayoutDevices);
    257             }
    258         } catch (AndroidLocationException e) {
    259             // no user folder? simply don't save the user layout device.
    260             // we could display the error, but it's likely something else did before, as
    261             // nothing will work w/o it.
    262             AdtPlugin.log(e, "Unable to find user directory");
    263         }
    264     }
    265 
    266     /**
    267      * Loads the default built-in and user created Layout Devices.
    268      * @param sdkOsLocation location of the SDK.
    269      */
    270     void loadDefaultAndUserDevices(String sdkOsLocation) {
    271         // load the default devices
    272         loadDefaultLayoutDevices(sdkOsLocation);
    273 
    274         // load the user devices;
    275         try {
    276             String userFolder = AndroidLocation.getFolder();
    277             File deviceXml = new File(userFolder, SdkConstants.FN_DEVICES_XML);
    278             if (deviceXml.isFile()) {
    279                 parseLayoutDevices(deviceXml, mUserLayoutDevices);
    280             }
    281         } catch (AndroidLocationException e) {
    282             // no user folder? simply don't load the user layout device
    283             AdtPlugin.log(e, "Unable to find user directory");
    284         }
    285     }
    286 
    287     void parseAddOnLayoutDevice(File deviceXml) {
    288         parseLayoutDevices(deviceXml, mAddOnLayoutDevices);
    289     }
    290 
    291     void sealAddonLayoutDevices() {
    292         mAddOnLayoutDevices = Collections.unmodifiableList(mAddOnLayoutDevices);
    293 
    294         combineLayoutDevices();
    295     }
    296 
    297     /**
    298      * Does the actual parsing of a devices.xml file.
    299      * @param deviceXml the {@link File} to load/parse. This must be an existing file.
    300      * @param list the list in which to write the parsed {@link LayoutDevice}.
    301      */
    302     private void parseLayoutDevices(File deviceXml, List<LayoutDevice> list) {
    303         // first we validate the XML
    304         try {
    305             Source source = new StreamSource(new FileReader(deviceXml));
    306 
    307             CaptureErrorHandler errorHandler = new CaptureErrorHandler(deviceXml.getAbsolutePath());
    308 
    309             Validator validator = LayoutDevicesXsd.getValidator(errorHandler);
    310             validator.validate(source);
    311 
    312             if (errorHandler.foundError() == false) {
    313                 // do the actual parsing
    314                 LayoutDeviceHandler handler = new LayoutDeviceHandler();
    315 
    316                 SAXParser parser = mParserFactory.newSAXParser();
    317                 parser.parse(new InputSource(new FileInputStream(deviceXml)), handler);
    318 
    319                 // get the parsed devices
    320                 list.addAll(handler.getDevices());
    321             }
    322         } catch (SAXException e) {
    323             AdtPlugin.log(e, "Error parsing %1$s", deviceXml.getAbsoluteFile());
    324         } catch (FileNotFoundException e) {
    325             // this shouldn't happen as we check above.
    326         } catch (IOException e) {
    327             AdtPlugin.log(e, "Error reading %1$s", deviceXml.getAbsoluteFile());
    328         } catch (ParserConfigurationException e) {
    329             AdtPlugin.log(e, "Error parsing %1$s", deviceXml.getAbsoluteFile());
    330         }
    331     }
    332 
    333     /**
    334      * Creates some built-it layout devices.
    335      */
    336     private void loadDefaultLayoutDevices(String sdkOsLocation) {
    337         ArrayList<LayoutDevice> list = new ArrayList<LayoutDevice>();
    338         File toolsFolder = new File(sdkOsLocation, SdkConstants.OS_SDK_TOOLS_LIB_FOLDER);
    339         if (toolsFolder.isDirectory()) {
    340             File deviceXml = new File(toolsFolder, SdkConstants.FN_DEVICES_XML);
    341             if (deviceXml.isFile()) {
    342                 parseLayoutDevices(deviceXml, list);
    343             }
    344         }
    345         mDefaultLayoutDevices = Collections.unmodifiableList(list);
    346     }
    347 
    348     private void combineLayoutDevices() {
    349         ArrayList<LayoutDevice> list = new ArrayList<LayoutDevice>();
    350         list.addAll(mDefaultLayoutDevices);
    351         list.addAll(mAddOnLayoutDevices);
    352         list.addAll(mUserLayoutDevices);
    353 
    354         mLayoutDevices = Collections.unmodifiableList(list);
    355     }
    356 
    357     /**
    358      * Writes the given {@link LayoutDevice}s into the given file.
    359      * @param deviceXml the file to write.
    360      * @param deviceList the LayoutDevice to write into the file.
    361      */
    362     private void write(File deviceXml, List<LayoutDevice> deviceList) {
    363         try {
    364             // create a new document
    365             DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
    366             docFactory.setNamespaceAware(true);
    367             DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
    368             Document doc = docBuilder.newDocument();
    369 
    370             // create a base node
    371             Element baseNode = doc.createElementNS(
    372                     LayoutDevicesXsd.NS_LAYOUT_DEVICE_XSD,
    373                     LayoutDevicesXsd.NODE_LAYOUT_DEVICES);
    374             // create the prefix for the namespace
    375             baseNode.setPrefix("d");
    376             doc.appendChild(baseNode);
    377 
    378             // fill it with the layout devices.
    379             for (LayoutDevice device : deviceList) {
    380                 device.saveTo(doc, baseNode);
    381             }
    382 
    383             // save the document to disk
    384             // Prepare the DOM document for writing
    385             Source source = new DOMSource(doc);
    386 
    387             // Prepare the output file
    388             File file = new File(deviceXml.getAbsolutePath());
    389             Result result = new StreamResult(file);
    390 
    391             // Write the DOM document to the file
    392             Transformer xformer = TransformerFactory.newInstance().newTransformer();
    393             xformer.transform(source, result);
    394         } catch (Exception e) {
    395             AdtPlugin.log(e, "Failed to write %s", deviceXml.getAbsolutePath());
    396         }
    397     }
    398 }
    399