2 * Copyright (C) 2009 The Android Open Source Project
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
8 * http://www.eclipse.org/org/documents/epl-v10.php
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.
17 package com.android.ide.eclipse.adt.internal.sdk;
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;
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;
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;
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;
57 * Manages the layout devices.
58 * They can come from 3 sources: built-in, add-ons, user.
60 public class LayoutDeviceManager {
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.
66 private static class CaptureErrorHandler implements ErrorHandler {
68 private final String mSourceLocation;
70 private boolean mFoundError = false;
72 CaptureErrorHandler(String sourceLocation) {
73 mSourceLocation = sourceLocation;
76 public boolean foundError() {
81 * @throws SAXException
83 public void error(SAXParseException ex) throws SAXException {
85 AdtPlugin.log(ex, "Error validating %1$s", mSourceLocation);
89 * @throws SAXException
91 public void fatalError(SAXParseException ex) throws SAXException {
93 AdtPlugin.log(ex, "Error validating %1$s", mSourceLocation);
97 * @throws SAXException
99 public void warning(SAXParseException ex) throws SAXException {
100 // ignore those for now.
104 private final SAXParserFactory mParserFactory;
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;
114 LayoutDeviceManager() {
115 mParserFactory = SAXParserFactory.newInstance();
116 mParserFactory.setNamespaceAware(true);
119 public List<LayoutDevice> getCombinedList() {
120 return mLayoutDevices;
123 public List<LayoutDevice> getDefaultLayoutDevices() {
124 return mDefaultLayoutDevices;
127 public List<LayoutDevice> getAddOnLayoutDevice() {
128 return mAddOnLayoutDevices;
131 public List<LayoutDevice> getUserLayoutDevices() {
132 return mUserLayoutDevices;
135 public LayoutDevice getUserLayoutDevice(String name) {
136 for (LayoutDevice d : mUserLayoutDevices) {
137 if (d.getName().equals(name)) {
145 public LayoutDevice addUserDevice(String name, float xdpi, float ydpi) {
146 LayoutDevice d = new LayoutDevice(name);
149 mUserLayoutDevices.add(d);
150 combineLayoutDevices();
155 public void removeUserDevice(LayoutDevice device) {
156 if (mUserLayoutDevices.remove(device)) {
157 combineLayoutDevices();
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
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) {
177 // else create a new device
178 LayoutDevice newDevice = new LayoutDevice(newName);
179 newDevice.setXDpi(newXDpi);
180 newDevice.setYDpi(newYDpi);
182 // and get the Folderconfiguration
183 Map<String, FolderConfiguration> configs = device.getConfigs();
184 newDevice.addConfigs(configs);
186 // replace the old device with the new
187 mUserLayoutDevices.remove(device);
188 mUserLayoutDevices.add(newDevice);
189 combineLayoutDevices();
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
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);
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
215 * @param newConfigName the configuration name to add or replace
216 * @param config the configuration to set
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);
228 // and then add the new one
229 device.addConfig(newConfigName, config);
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
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);
247 * Saves the user-made {@link LayoutDevice}s to disk.
251 String userFolder = AndroidLocation.getFolder();
252 File deviceXml = new File(userFolder, SdkConstants.FN_DEVICES_XML);
253 if (deviceXml.isDirectory() == false) {
254 write(deviceXml, mUserLayoutDevices);
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");
265 * Loads the default built-in and user created Layout Devices.
266 * @param sdkOsLocation location of the SDK.
268 void loadDefaultAndUserDevices(String sdkOsLocation) {
269 // load the default devices
270 loadDefaultLayoutDevices(sdkOsLocation);
272 // load the user devices;
274 String userFolder = AndroidLocation.getFolder();
275 File deviceXml = new File(userFolder, SdkConstants.FN_DEVICES_XML);
276 if (deviceXml.isFile()) {
277 parseLayoutDevices(deviceXml, mUserLayoutDevices);
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");
285 void parseAddOnLayoutDevice(File deviceXml) {
286 parseLayoutDevices(deviceXml, mAddOnLayoutDevices);
289 void sealAddonLayoutDevices() {
290 mAddOnLayoutDevices = Collections.unmodifiableList(mAddOnLayoutDevices);
292 combineLayoutDevices();
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}.
300 private void parseLayoutDevices(File deviceXml, List<LayoutDevice> list) {
301 // first we validate the XML
303 Source source = new StreamSource(new FileReader(deviceXml));
305 CaptureErrorHandler errorHandler = new CaptureErrorHandler(deviceXml.getAbsolutePath());
307 Validator validator = LayoutDevicesXsd.getValidator(errorHandler);
308 validator.validate(source);
310 if (errorHandler.foundError() == false) {
311 // do the actual parsing
312 LayoutDeviceHandler handler = new LayoutDeviceHandler();
314 SAXParser parser = mParserFactory.newSAXParser();
315 parser.parse(new InputSource(new FileInputStream(deviceXml)), handler);
317 // get the parsed devices
318 list.addAll(handler.getDevices());
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());
332 * Creates some built-it layout devices.
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);
343 mDefaultLayoutDevices = Collections.unmodifiableList(list);
346 private void combineLayoutDevices() {
347 ArrayList<LayoutDevice> list = new ArrayList<LayoutDevice>();
348 list.addAll(mDefaultLayoutDevices);
349 list.addAll(mAddOnLayoutDevices);
350 list.addAll(mUserLayoutDevices);
352 mLayoutDevices = Collections.unmodifiableList(list);
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.
360 private void write(File deviceXml, List<LayoutDevice> deviceList) {
362 // create a new document
363 DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
364 docFactory.setNamespaceAware(true);
365 DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
366 Document doc = docBuilder.newDocument();
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);
376 // fill it with the layout devices.
377 for (LayoutDevice device : deviceList) {
378 device.saveTo(doc, baseNode);
381 // save the document to disk
382 // Prepare the DOM document for writing
383 Source source = new DOMSource(doc);
385 // Prepare the output file
386 File file = new File(deviceXml.getAbsolutePath());
387 Result result = new StreamResult(file);
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());