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