/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.tradefed.config; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; /** * Parses a configuration.xml file. *

* See TODO for expected format */ class ConfigurationXmlParser { /** * SAX callback object. Handles parsing data from the xml tags. */ static class ConfigHandler extends DefaultHandler { private static final String OBJECT_TAG = "object"; private static final String OPTION_TAG = "option"; private static final String INCLUDE_TAG = "include"; private static final String TEMPLATE_INCLUDE_TAG = "template-include"; private static final String CONFIG_TAG = "configuration"; private static final String DEVICE_TAG = "device"; /** Note that this simply hasn't been implemented; it is not intentionally forbidden. */ static final String INNER_TEMPLATE_INCLUDE_ERROR = "Configurations which contain a tag, not having a 'default' " + "attribute, may not be the target of any or tag. " + "However, configuration '%s' attempted to include configuration '%s', which " + "contains a tag without a 'default' attribute."; // Settings private final IConfigDefLoader mConfigDefLoader; private final ConfigurationDef mConfigDef; private final Map mTemplateMap; private final String mName; private final boolean mInsideParentDeviceTag; // State-holding members private String mCurrentConfigObject; private String mCurrentDeviceObject; private Boolean isMultiDeviceConfigMode = false; private List mListDevice = new ArrayList(); private List mOutsideTag = new ArrayList(); private Boolean isLocalConfig = null; ConfigHandler( ConfigurationDef def, String name, IConfigDefLoader loader, String parentDeviceObject, Map templateMap) { mName = name; mConfigDef = def; mConfigDefLoader = loader; mCurrentDeviceObject = parentDeviceObject; mInsideParentDeviceTag = (parentDeviceObject != null) ? true : false; if (templateMap == null) { mTemplateMap = Collections.emptyMap(); } else { mTemplateMap = templateMap; } } @Override public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException { if (OBJECT_TAG.equals(localName)) { final String objectTypeName = attributes.getValue("type"); if (objectTypeName == null) { throw new SAXException(new ConfigurationException( " must have a 'type' attribute")); } if (GlobalConfiguration.isBuiltInObjType(objectTypeName) || Configuration.isBuiltInObjType(objectTypeName)) { throw new SAXException(new ConfigurationException(String.format(" " + "cannot be type '%s' this is a reserved type.", objectTypeName))); } addObject(objectTypeName, attributes); } else if (DEVICE_TAG.equals(localName)) { if (mCurrentDeviceObject != null) { throw new SAXException(new ConfigurationException( " tag cannot be included inside another device")); } // tag is a device tag (new format) for multi device definition. String deviceName = attributes.getValue("name"); if (deviceName == null) { throw new SAXException( new ConfigurationException("device tag requires a name value")); } if (deviceName.equals(ConfigurationDef.DEFAULT_DEVICE_NAME)) { throw new SAXException(new ConfigurationException(String.format("device name " + "cannot be reserved name: '%s'", ConfigurationDef.DEFAULT_DEVICE_NAME))); } if (deviceName.contains(String.valueOf(OptionSetter.NAMESPACE_SEPARATOR))) { throw new SAXException(new ConfigurationException(String.format("device name " + "cannot contain reserved character: '%s'", OptionSetter.NAMESPACE_SEPARATOR))); } isMultiDeviceConfigMode = true; mConfigDef.setMultiDeviceMode(true); mCurrentDeviceObject = deviceName; addObject(localName, attributes); } else if (Configuration.isBuiltInObjType(localName)) { // tag is a built in local config object if (isLocalConfig == null) { isLocalConfig = true; } else if (!isLocalConfig) { throwException(String.format( "Attempted to specify local object '%s' for global config!", localName)); } if (mCurrentDeviceObject == null && Configuration.doesBuiltInObjSupportMultiDevice(localName)) { // Keep track of all the BuildInObj outside of device tag for final check // if it turns out we are in multi mode, we will throw an exception. mOutsideTag.add(localName); } // if we are inside a device object, some tags are not allowed. if (mCurrentDeviceObject != null) { if (!Configuration.doesBuiltInObjSupportMultiDevice(localName)) { // Prevent some tags to be inside of a device in multi device mode. throw new SAXException(new ConfigurationException( String.format("Tag %s should not be included in a tag.", localName))); } } addObject(localName, attributes); } else if (GlobalConfiguration.isBuiltInObjType(localName)) { // tag is a built in global config object if (isLocalConfig == null) { // FIXME: config type should be explicit rather than inferred isLocalConfig = false; } else if (isLocalConfig) { throwException(String.format( "Attempted to specify global object '%s' for local config!", localName)); } addObject(localName, attributes); } else if (OPTION_TAG.equals(localName)) { String optionName = attributes.getValue("name"); if (optionName == null) { throwException("Missing 'name' attribute for option"); } String optionKey = attributes.getValue("key"); // Key is optional at this stage. If it's actually required, another stage in the // configuration validation will throw an exception. String optionValue = attributes.getValue("value"); if (optionValue == null) { throwException("Missing 'value' attribute for option '" + optionName + "'"); } if (mCurrentConfigObject != null) { // option is declared within a config object - namespace it with object class // name optionName = String.format("%s%c%s", mCurrentConfigObject, OptionSetter.NAMESPACE_SEPARATOR, optionName); } if (mCurrentDeviceObject != null) { // preprend the device name in extra if inside a device config object. optionName = String.format("{%s}%s", mCurrentDeviceObject, optionName); } mConfigDef.addOptionDef(optionName, optionKey, optionValue, mName); } else if (CONFIG_TAG.equals(localName)) { String description = attributes.getValue("description"); if (description != null) { // Ensure that we only set the description the first time and not when it is // loading the configuration. if (mConfigDef.getDescription() == null || mConfigDef.getDescription().isEmpty()) { mConfigDef.setDescription(description); } } } else if (INCLUDE_TAG.equals(localName)) { String includeName = attributes.getValue("name"); if (includeName == null) { throwException("Missing 'name' attribute for include"); } try { mConfigDefLoader.loadIncludedConfiguration( mConfigDef, mName, includeName, mCurrentDeviceObject, mTemplateMap); } catch (ConfigurationException e) { if (e instanceof TemplateResolutionError) { throwException(String.format(INNER_TEMPLATE_INCLUDE_ERROR, mConfigDef.getName(), includeName)); } throw new SAXException(e); } } else if (TEMPLATE_INCLUDE_TAG.equals(localName)) { final String templateName = attributes.getValue("name"); if (templateName == null) { throwException("Missing 'name' attribute for template-include"); } if (mCurrentDeviceObject != null) { // TODO: Add this use case. throwException("