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.eclipse.adt.AdtPlugin; 20 import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; 21 import com.android.prefs.AndroidLocation; 22 import com.android.prefs.AndroidLocation.AndroidLocationException; 23 import com.android.sdklib.SdkConstants; 24 25 import org.w3c.dom.Document; 26 import org.w3c.dom.Element; 27 import org.xml.sax.ErrorHandler; 28 import org.xml.sax.InputSource; 29 import org.xml.sax.SAXException; 30 import org.xml.sax.SAXParseException; 31 32 import java.io.File; 33 import java.io.FileInputStream; 34 import java.io.FileNotFoundException; 35 import java.io.FileReader; 36 import java.io.IOException; 37 import java.util.ArrayList; 38 import java.util.Collections; 39 import java.util.List; 40 import java.util.Map; 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 Map<String, FolderConfiguration> 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 /** 196 * Adds or replaces a configuration in a given {@link LayoutDevice}. 197 * @param device the device to modify 198 * @param configName the configuration name to add or replace 199 * @param config the configuration to set 200 */ 201 public void addUserConfiguration(LayoutDevice device, String configName, 202 FolderConfiguration config) { 203 // check that the device does belong to the user list. 204 // the main goal is to make sure that this does not belong to the default/addon list. 205 if (mUserLayoutDevices.contains(device)) { 206 device.addConfig(configName, config); 207 } 208 } 209 210 /** 211 * Replaces a configuration in a given {@link LayoutDevice}. 212 * @param device the device to modify 213 * @param oldConfigName the name of the config to replace. If null, the new config is simply 214 * added. 215 * @param newConfigName the configuration name to add or replace 216 * @param config the configuration to set 217 */ 218 public void replaceUserConfiguration(LayoutDevice device, String oldConfigName, 219 String newConfigName, FolderConfiguration config) { 220 // check that the device does belong to the user list. 221 // the main goal is to make sure that this does not belong to the default/addon list. 222 if (mUserLayoutDevices.contains(device)) { 223 // if the old and new config name are different, remove the old one 224 if (oldConfigName != null && oldConfigName.equals(newConfigName) == false) { 225 device.removeConfig(oldConfigName); 226 } 227 228 // and then add the new one 229 device.addConfig(newConfigName, config); 230 } 231 } 232 233 /** 234 * Removes a configuration from a given user {@link LayoutDevice} 235 * @param device the device to modify 236 * @param configName the name of the config to remove 237 */ 238 public void removeUserConfiguration(LayoutDevice device, String configName) { 239 // check that the device does belong to the user list. 240 // the main goal is to make sure that this does not belong to the default/addon list. 241 if (mUserLayoutDevices.contains(device)) { 242 device.removeConfig(configName); 243 } 244 } 245 246 /** 247 * Saves the user-made {@link LayoutDevice}s to disk. 248 */ 249 public void save() { 250 try { 251 String userFolder = AndroidLocation.getFolder(); 252 File deviceXml = new File(userFolder, SdkConstants.FN_DEVICES_XML); 253 if (deviceXml.isDirectory() == false) { 254 write(deviceXml, mUserLayoutDevices); 255 } 256 } catch (AndroidLocationException e) { 257 // no user folder? simply don't save the user layout device. 258 // we could display the error, but it's likely something else did before, as 259 // nothing will work w/o it. 260 AdtPlugin.log(e, "Unable to find user directory"); 261 } 262 } 263 264 /** 265 * Loads the default built-in and user created Layout Devices. 266 * @param sdkOsLocation location of the SDK. 267 */ 268 void loadDefaultAndUserDevices(String sdkOsLocation) { 269 // load the default devices 270 loadDefaultLayoutDevices(sdkOsLocation); 271 272 // load the user devices; 273 try { 274 String userFolder = AndroidLocation.getFolder(); 275 File deviceXml = new File(userFolder, SdkConstants.FN_DEVICES_XML); 276 if (deviceXml.isFile()) { 277 parseLayoutDevices(deviceXml, mUserLayoutDevices); 278 } 279 } catch (AndroidLocationException e) { 280 // no user folder? simply don't load the user layout device 281 AdtPlugin.log(e, "Unable to find user directory"); 282 } 283 } 284 285 void parseAddOnLayoutDevice(File deviceXml) { 286 parseLayoutDevices(deviceXml, mAddOnLayoutDevices); 287 } 288 289 void sealAddonLayoutDevices() { 290 mAddOnLayoutDevices = Collections.unmodifiableList(mAddOnLayoutDevices); 291 292 combineLayoutDevices(); 293 } 294 295 /** 296 * Does the actual parsing of a devices.xml file. 297 * @param deviceXml the {@link File} to load/parse. This must be an existing file. 298 * @param list the list in which to write the parsed {@link LayoutDevice}. 299 */ 300 private void parseLayoutDevices(File deviceXml, List<LayoutDevice> list) { 301 // first we validate the XML 302 try { 303 Source source = new StreamSource(new FileReader(deviceXml)); 304 305 CaptureErrorHandler errorHandler = new CaptureErrorHandler(deviceXml.getAbsolutePath()); 306 307 Validator validator = LayoutDevicesXsd.getValidator(errorHandler); 308 validator.validate(source); 309 310 if (errorHandler.foundError() == false) { 311 // do the actual parsing 312 LayoutDeviceHandler handler = new LayoutDeviceHandler(); 313 314 SAXParser parser = mParserFactory.newSAXParser(); 315 parser.parse(new InputSource(new FileInputStream(deviceXml)), handler); 316 317 // get the parsed devices 318 list.addAll(handler.getDevices()); 319 } 320 } catch (SAXException e) { 321 AdtPlugin.log(e, "Error parsing %1$s", deviceXml.getAbsoluteFile()); 322 } catch (FileNotFoundException e) { 323 // this shouldn't happen as we check above. 324 } catch (IOException e) { 325 AdtPlugin.log(e, "Error reading %1$s", deviceXml.getAbsoluteFile()); 326 } catch (ParserConfigurationException e) { 327 AdtPlugin.log(e, "Error parsing %1$s", deviceXml.getAbsoluteFile()); 328 } 329 } 330 331 /** 332 * Creates some built-it layout devices. 333 */ 334 private void loadDefaultLayoutDevices(String sdkOsLocation) { 335 ArrayList<LayoutDevice> list = new ArrayList<LayoutDevice>(); 336 File toolsFolder = new File(sdkOsLocation, SdkConstants.OS_SDK_TOOLS_LIB_FOLDER); 337 if (toolsFolder.isDirectory()) { 338 File deviceXml = new File(toolsFolder, SdkConstants.FN_DEVICES_XML); 339 if (deviceXml.isFile()) { 340 parseLayoutDevices(deviceXml, list); 341 } 342 } 343 mDefaultLayoutDevices = Collections.unmodifiableList(list); 344 } 345 346 private void combineLayoutDevices() { 347 ArrayList<LayoutDevice> list = new ArrayList<LayoutDevice>(); 348 list.addAll(mDefaultLayoutDevices); 349 list.addAll(mAddOnLayoutDevices); 350 list.addAll(mUserLayoutDevices); 351 352 mLayoutDevices = Collections.unmodifiableList(list); 353 } 354 355 /** 356 * Writes the given {@link LayoutDevice}s into the given file. 357 * @param deviceXml the file to write. 358 * @param deviceList the LayoutDevice to write into the file. 359 */ 360 private void write(File deviceXml, List<LayoutDevice> deviceList) { 361 try { 362 // create a new document 363 DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); 364 docFactory.setNamespaceAware(true); 365 DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); 366 Document doc = docBuilder.newDocument(); 367 368 // create a base node 369 Element baseNode = doc.createElementNS( 370 LayoutDevicesXsd.NS_LAYOUT_DEVICE_XSD, 371 LayoutDevicesXsd.NODE_LAYOUT_DEVICES); 372 // create the prefix for the namespace 373 baseNode.setPrefix("d"); 374 doc.appendChild(baseNode); 375 376 // fill it with the layout devices. 377 for (LayoutDevice device : deviceList) { 378 device.saveTo(doc, baseNode); 379 } 380 381 // save the document to disk 382 // Prepare the DOM document for writing 383 Source source = new DOMSource(doc); 384 385 // Prepare the output file 386 File file = new File(deviceXml.getAbsolutePath()); 387 Result result = new StreamResult(file); 388 389 // Write the DOM document to the file 390 Transformer xformer = TransformerFactory.newInstance().newTransformer(); 391 xformer.transform(source, result); 392 } catch (Exception e) { 393 AdtPlugin.log(e, "Failed to write %s", deviceXml.getAbsolutePath()); 394 } 395 } 396 } 397