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