/**
* Copyright (c) 2014-2017 by the respective copyright holders.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.smarthome.core.thing.xml.internal;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Map;
import org.eclipse.smarthome.config.core.ConfigDescription;
import org.eclipse.smarthome.config.xml.util.ConverterAttributeMapValidator;
import org.eclipse.smarthome.config.xml.util.GenericUnmarshaller;
import org.eclipse.smarthome.config.xml.util.NodeIterator;
import org.eclipse.smarthome.core.thing.type.AbstractDescriptionType;
import com.thoughtworks.xstream.converters.ConversionException;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
/**
* The {@link AbstractDescriptionTypeConverter} is an abstract implementation of the {@code XStream} {@link Converter}
* interface used as helper class to convert type
* definition information within an XML document into a concrete type object.
* <p>
* This class should be used for each type definition which inherits from the {@link AbstractDescriptionType} class.
*
* @param <T> the result type of the conversion
*
* @author Michael Grammling - Initial Contribution
*/
public abstract class AbstractDescriptionTypeConverter<T> extends GenericUnmarshaller<T> {
protected ConverterAttributeMapValidator attributeMapValidator;
private final String type;
/**
* Creates a new instance of this class with the specified parameter.
*
* @param clazz the class of the result type (must not be null)
* @param type the name of the type (e.g. "thing-type", "channel-type")
*/
public AbstractDescriptionTypeConverter(Class<T> clazz, String type) {
super(clazz);
this.type = type;
this.attributeMapValidator = new ConverterAttributeMapValidator(new String[][] { { "id", "true" } });
}
/**
* Returns the {@code id} attribute of the specific XML type definition.
*
* @param attributes the attributes where to extract the ID information (must not be null)
* @return the ID of the type definition (neither null, nor empty)
*/
protected String getID(Map<String, String> attributes) {
return attributes.get("id");
}
/**
* Returns the full extracted UID of the specific XML type definition.
*
* @param attributes the attributes where to extract the ID information (must not be null)
* @param context the context where to extract the binding ID information (must not be null)
*
* @return the UID of the type definition (neither null, nor empty)
*/
protected String getUID(Map<String, String> attributes, UnmarshallingContext context) {
String bindingId = (String) context.get("thing-descriptions.bindingId");
String typeId = getID(attributes);
String uid = String.format("%s:%s", bindingId, typeId);
return uid;
}
/**
* Returns the value of the {@code label} tag from the specific XML type definition.
*
* @param nodeIterator the iterator to be used to extract the information (must not be null)
* @return the value of the label (neither null, nor empty)
* @throws ConversionException if the label could not be read
*/
protected String readLabel(NodeIterator nodeIterator) throws ConversionException {
return (String) nodeIterator.nextValue("label", true);
}
/**
* Returns the value of the {@code description} tag from the specific XML type definition.
*
* @param nodeIterator the iterator to be used to extract the information (must not be null)
* @return the value of the description (could be null or empty)
*/
protected String readDescription(NodeIterator nodeIterator) {
return (String) nodeIterator.nextValue("description", false);
}
private URI readConfigDescriptionURI(NodeIterator nodeIterator) throws ConversionException {
String uriText = nodeIterator.nextAttribute("config-description-ref", "uri", false);
if (uriText != null) {
try {
return new URI(uriText);
} catch (NullPointerException | URISyntaxException ex) {
throw new ConversionException(
"The URI '" + uriText + "' in node " + "'config-description-ref' is invalid!", ex);
}
}
return null;
}
private ConfigDescription readConfigDescription(NodeIterator nodeIterator) {
Object nextNode = nodeIterator.next();
if (nextNode != null) {
if (nextNode instanceof ConfigDescription) {
return (ConfigDescription) nextNode;
}
nodeIterator.revert();
}
return null;
}
/**
* Returns the value of the {@code config-description-ref} and the {@code config-description} tags from the specific
* XML type definition.
*
* @param nodeIterator the iterator to be used to extract the information (must not be null)
*
* @return the URI and configuration object
* (contains two elements: URI - could be null, ConfigDescription - could be null)
*/
protected Object[] getConfigDescriptionObjects(NodeIterator nodeIterator) {
URI configDescriptionURI = readConfigDescriptionURI(nodeIterator);
ConfigDescription configDescription = null;
if (configDescriptionURI == null) {
configDescription = readConfigDescription(nodeIterator);
if (configDescription != null) {
configDescriptionURI = configDescription.getURI();
}
}
return new Object[] { configDescriptionURI, configDescription };
}
/**
* The abstract unmarshal method which must be implemented by the according type converter.
*
* @param reader the reader to be used to read XML information from a stream (not null)
*
* @param context the context to be used for the XML document conversion (not null)
*
* @param attributes the attributes map containing attributes of the type - only UID -
* (not null, could be empty)
*
* @param nodeIterator the iterator to be used to simply extract information in the right
* order and appearance from the XML stream
*
* @return the concrete type definition object (could be null)
*
* @throws ConversionException if any conversion error occurs
*/
protected abstract T unmarshalType(HierarchicalStreamReader reader, UnmarshallingContext context,
Map<String, String> attributes, NodeIterator nodeIterator) throws ConversionException;
@Override
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
// read attributes
Map<String, String> attributes = this.attributeMapValidator.readValidatedAttributes(reader);
// set automatically extracted URI for a possible 'config-description' section
context.put("config-description.uri", this.type + ":" + getUID(attributes, context));
// read values
List<?> nodes = (List<?>) context.convertAnother(context, List.class);
NodeIterator nodeIterator = new NodeIterator(nodes);
// create object
Object object = unmarshalType(reader, context, attributes, nodeIterator);
nodeIterator.assertEndOfType();
return object;
}
}