org.xmlbeam.XBProjector Maven / Gradle / Ivy
Show all versions of xmlprojector Show documentation
/**
* Copyright 2012 Sven Ewald
*
* 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 org.xmlbeam;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.URISyntaxException;
import java.text.Format;
import java.text.MessageFormat;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xmlbeam.annotation.XBAuto;
import org.xmlbeam.annotation.XBDelete;
import org.xmlbeam.annotation.XBDocURL;
import org.xmlbeam.annotation.XBRead;
import org.xmlbeam.annotation.XBUpdate;
import org.xmlbeam.annotation.XBValue;
import org.xmlbeam.annotation.XBWrite;
import org.xmlbeam.config.DefaultXMLFactoriesConfig;
import org.xmlbeam.config.XMLFactoriesConfig;
import org.xmlbeam.dom.DOMAccess;
import org.xmlbeam.evaluation.CanEvaluateOrProject;
import org.xmlbeam.evaluation.DefaultXPathEvaluator;
import org.xmlbeam.evaluation.DocumentResolver;
import org.xmlbeam.evaluation.InvocationContext;
import org.xmlbeam.evaluation.XPathEvaluator;
import org.xmlbeam.exceptions.XBException;
import org.xmlbeam.exceptions.XBIOException;
import org.xmlbeam.externalizer.Externalizer;
import org.xmlbeam.externalizer.ExternalizerAdapter;
import org.xmlbeam.intern.DOMChangeListener;
import org.xmlbeam.io.FileIO;
import org.xmlbeam.io.ProjectionIO;
import org.xmlbeam.io.StreamInput;
import org.xmlbeam.io.StreamOutput;
import org.xmlbeam.io.UrlIO;
import org.xmlbeam.types.DefaultTypeConverter;
import org.xmlbeam.types.StringRenderer;
import org.xmlbeam.types.TypeConverter;
import org.xmlbeam.types.XBAutoMap;
import org.xmlbeam.util.IOHelper;
import org.xmlbeam.util.intern.DOMHelper;
import org.xmlbeam.util.intern.DocScope;
import org.xmlbeam.util.intern.ReflectionHelper;
import org.xmlbeam.util.intern.Scope;
/**
*
* Overview
* The class XMLProjector is a tool to create, read or write so called "projections". Projections
* are Java interfaces associated to XML documents. Projections may contain methods annotated with
* XPath selectors. These XPath expressions define the subset of XML data which is "projected" to
* Java values and objects.
*
*
* Getters
* For getter methods (methods with a name-prefix "get", returning a value) the XPath-selected nodes
* are converted to the method return type. This works with all java primitive types, Strings, and
* lists or arrays containing primitives or Strings.
*
*
* Setters
* Setter methods (method with a name starting with "set", having a parameter) can be defined to
* modify the content of the associated XML document. Not all XPath capabilities define writable
* projections, so the syntax is limited to selectors of elements and attributes. In contrast to
* Java Beans a setter method may define a return value with the projection interface type. If this
* return value is defined, the current projection instance is returned. This allows the definition
* of projections according to the fluent interface pattern (aka Builder Pattern).
*
*
* Sub Projections
* For the purpose of accessing structured data elements in the XML document you may define
* "sub projections" which are projections associated to elements instead to documents. Sub
* projections can be used as return type of getters and as parameters of setters. This works even
* in arrays or lists. Because of the infamous Java type erasure you have to specify the component
* type of the sub projection for a getter returning a list of sub projections. This type is defined
* as second parameter "targetType" in the {@link XBRead} annotation.
*
*
* Dynamic Projections
* XPath expressions are evaluated during runtime when the corresponding methods are called. Its
* possible to use placeholder ("{0}, {1}, {2},... ) in the expression that will be substituted with
* method parameters before the expression is evaluated. Therefore getters and setters may have
* multiple parameters which will be applied via a {@link MessageFormat} to build up the final XPath
* expression. The first parameter of a setter will be used for both, setting the document value and
* replacing the placeholder "{0}".
*
*
* Projection Mixins
* A mixin is defined as an object implementing a super interface of a projection. You may associate
* a mixin with a projection type to add your own code to a projection. This way you can implement
* validators, make a projection comparable or even share common business logic between multiple
* projections.
*
*
* @author Sven Ewald
*/
@SuppressWarnings("serial")
public class XBProjector implements Serializable, ProjectionFactory {
private static final Externalizer NOOP_EXTERNALIZER = new ExternalizerAdapter();
private final ConfigBuilder configBuilder = new ConfigBuilder();
private Externalizer externalizer = NOOP_EXTERNALIZER;
private final Set flags;
/**
* A variation of the builder pattern. All methods to configure the projector are hidden in this
* builder class.
*/
public class ConfigBuilder implements ProjectionFactoryConfig {
/**
* Access the {@link XMLFactoriesConfig} as the given subtype to conveniently access
* additional methods.
*
* @param clazz
* @return casted XMLFactoriesConfig
*/
public T as(final Class clazz) {
return clazz.cast(xMLFactoriesConfig);
}
/**
* {@inheritDoc}
*/
@Override
public TypeConverter getTypeConverter() {
return XBProjector.this.typeConverter;
}
/**
* Cast the type converter to the current type.
*
* @param clazz
* @return Type converter casted down to clazz.
*/
public T getTypeConverterAs(final Class clazz) {
return clazz.cast(getTypeConverter());
}
/**
* {@inheritDoc}
*/
@Override
public ConfigBuilder setTypeConverter(final TypeConverter converter) {
XBProjector.this.typeConverter = converter;
return this;
}
/**
* {@inheritDoc}
*/
@Override
public ConfigBuilder setExternalizer(final Externalizer e10r) {
XBProjector.this.externalizer = e10r == null ? NOOP_EXTERNALIZER : e10r;
return this;
}
/**
* {@inheritDoc}
*/
@Override
public Externalizer getExternalizer() {
return XBProjector.this.externalizer;
}
/**
* @param clazz
* @return Externalizer cast down to the type clazz
*/
public T getExternalizerAs(final Class extends T> clazz) {
return clazz.cast(getExternalizer());
}
/**
* {@inheritDoc}
*/
@Override
@Deprecated
public TransformerFactory createTransformerFactory() {
return XBProjector.this.xMLFactoriesConfig.createTransformerFactory();
}
/**
* {@inheritDoc}
*/
@Override
@Deprecated
public DocumentBuilderFactory createDocumentBuilderFactory() {
return XBProjector.this.xMLFactoriesConfig.createDocumentBuilderFactory();
}
/**
* {@inheritDoc}
*/
@Override
@Deprecated
public XPathFactory createXPathFactory() {
return XBProjector.this.xMLFactoriesConfig.createXPathFactory();
}
/**
* {@inheritDoc}
*/
@Override
@Deprecated
public Transformer createTransformer(final Document... document) {
return XBProjector.this.xMLFactoriesConfig.createTransformer(document);
}
/**
* {@inheritDoc}
*/
@Override
@Deprecated
public DocumentBuilder createDocumentBuilder() {
return XBProjector.this.xMLFactoriesConfig.createDocumentBuilder();
}
/**
* {@inheritDoc}
*/
@Override
@Deprecated
public XPath createXPath(final Document... document) {
return XBProjector.this.xMLFactoriesConfig.createXPath(document);
}
/**
* @return StringRenderer used to convert objects into strings
*/
public StringRenderer getStringRenderer() {
return XBProjector.this.stringRenderer;
}
/**
* Cast the type StringRenderer to the current type.
*
* @param clazz
* @return StringRenderer casted down to clazz.
*/
public T getStringRendererAs(final Class clazz) {
return clazz.cast(getTypeConverter());
}
/**
* @param renderer
* to be used to convert objects into strings
* @return this for convenience
*/
public ConfigBuilder setStringRenderer(final StringRenderer renderer) {
XBProjector.this.stringRenderer = renderer;
return this;
}
@Override
@Deprecated
public Map getUserDefinedNamespaceMapping() {
return xMLFactoriesConfig.getUserDefinedNamespaceMapping();
}
}
/**
* A variation of the builder pattern. Mixin related methods are grouped behind this builder
* class.
*/
class MixinBuilder implements MixinHolder {
/**
* {@inheritDoc}
*/
@Override
public XBProjector addProjectionMixin(final Class projectionInterface, final M mixinImplementation) {
ensureIsValidProjectionInterface(projectionInterface);
Map, Object> map = mixins.containsKey(projectionInterface) ? mixins.get(projectionInterface) : new HashMap, Object>();
for (Class> type : ReflectionHelper.findAllCommonSuperInterfaces(projectionInterface, mixinImplementation.getClass())) {
map.put(type, mixinImplementation);
}
mixins.put(projectionInterface, map);
return XBProjector.this;
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("unchecked")
public M getProjectionMixin(final Class projectionInterface, final Class mixinInterface) {
if (!mixins.containsKey(projectionInterface)) {
return null;
}
return (M) mixins.get(projectionInterface).get(mixinInterface);
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("unchecked")
public M removeProjectionMixin(final Class projectionInterface, final Class mixinInterface) {
if (!mixins.containsKey(projectionInterface)) {
return null;
}
return (M) mixins.get(projectionInterface).remove(mixinInterface);
}
}
/**
* A variation of the builder pattern. IO related methods are grouped behind this builder class.
*/
class IOBuilder implements ProjectionIO {
/**
* {@inheritDoc}
*/
@Override
public FileIO file(final File file) {
return new DefaultFileIO(XBProjector.this, file);
}
/**
* {@inheritDoc}
*/
@Override
public FileIO file(final String fileName) {
return new DefaultFileIO(XBProjector.this, fileName);
}
/**
* {@inheritDoc}
*/
@Override
public UrlIO url(final String url) {
return new UrlIO(XBProjector.this, url);
}
/**
* {@inheritDoc}
*/
@Override
public StreamInput stream(final InputStream is) {
return new StreamInput(XBProjector.this, is);
}
/**
* {@inheritDoc}
*/
@Override
public StreamOutput stream(final OutputStream os) {
return new StreamOutput(XBProjector.this, os);
}
/**
* {@inheritDoc}
*/
@Override
public T fromURLAnnotation(final Class projectionInterface, final Object... optionalParams) throws IOException {
org.xmlbeam.annotation.XBDocURL doc = projectionInterface.getAnnotation(org.xmlbeam.annotation.XBDocURL.class);
if (doc == null) {
throw new IllegalArgumentException("Class " + projectionInterface.getCanonicalName() + " must have the " + XBDocURL.class.getName() + " annotation linking to the document source.");
}
UrlIO urlIO = url(MessageFormat.format(doc.value(), optionalParams));
urlIO.addRequestProperties(filterRequestParamsFromParams(doc.value(), optionalParams));
return urlIO.read(projectionInterface);
}
/**
* @param projectionInterface
* @param optionalParams
* @return
*/
@SuppressWarnings("unchecked")
Map filterRequestParamsFromParams(final String url, final Object... optionalParams) {
Map requestParams = new HashMap();
Format[] formats = new MessageFormat(url).getFormatsByArgumentIndex();
for (int i = 0; i < optionalParams.length; ++i) {
if (i >= formats.length) {
if ((optionalParams[i] instanceof Map, ?>)) {
requestParams.putAll((Map) optionalParams[i]);
}
continue;
}
if (formats[i] == null) {
if ((optionalParams[i] instanceof Map, ?>)) {
requestParams.putAll((Map) optionalParams[i]);
}
}
}
return requestParams;
}
/**
* {@inheritDoc}
*/
@Override
public String toURLAnnotationViaPOST(final Object projection, final Object... optionalParams) throws IOException, URISyntaxException {
Class> projectionInterface = checkProjectionInstance(projection).getProjectionInterface();
org.xmlbeam.annotation.XBDocURL doc = projectionInterface.getAnnotation(org.xmlbeam.annotation.XBDocURL.class);
if (doc == null) {
throw new IllegalArgumentException("Class " + projectionInterface.getCanonicalName() + " must have the " + XBDocURL.class.getName() + " annotation linking to the document source.");
}
UrlIO urlIO = url(MessageFormat.format(doc.value(), optionalParams));
urlIO.addRequestProperties(filterRequestParamsFromParams(doc.value(), optionalParams));
return urlIO.write(projection);
}
}
/**
* {@inheritDoc}
*/
@Override
@Scope(DocScope.IO)
public T projectEmptyDocument(final Class projectionInterface) {
Document document = xMLFactoriesConfig.createDocumentBuilder().newDocument();
return projectDOMNode(document, projectionInterface);
}
/**
* {@inheritDoc}
*/
@Override
@Scope(DocScope.IO)
public T projectEmptyElement(final String name, final Class projectionInterface) {
Document document = xMLFactoriesConfig.createDocumentBuilder().newDocument();
Element element = document.createElement(name);
return projectDOMNode(element, projectionInterface);
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("unchecked")
@Scope(DocScope.IO)
public T projectDOMNode(final Node documentOrElement, final Class projectionInterface) {
ensureIsValidProjectionInterface(projectionInterface);
if (documentOrElement == null) {
throw new IllegalArgumentException("Parameter node must not be null");
}
final Map, Object> mixinsForProjection = mixins.containsKey(projectionInterface) ? Collections.unmodifiableMap(mixins.get(projectionInterface)) : Collections., Object> emptyMap();
final ProjectionInvocationHandler projectionInvocationHandler = new ProjectionInvocationHandler(XBProjector.this, documentOrElement, projectionInterface, mixinsForProjection, flags.contains(Flags.TO_STRING_RENDERS_XML), flags.contains(Flags.ABSENT_IS_EMPTY));
final Set> interfaces = new HashSet>();
interfaces.add(projectionInterface);
interfaces.add(DOMAccess.class);
interfaces.add(Serializable.class);
if (flags.contains(Flags.SYNCHRONIZE_ON_DOCUMENTS)) {
final Document document = DOMHelper.getOwnerDocumentFor(documentOrElement);
InvocationHandler synchronizedInvocationHandler = new InvocationHandler() {
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
synchronized (document) {
return projectionInvocationHandler.invoke(proxy, method, args);
}
}
};
return ((T) Proxy.newProxyInstance(projectionInterface.getClassLoader(), interfaces.toArray(new Class>[interfaces.size()]), synchronizedInvocationHandler));
}
return ((T) Proxy.newProxyInstance(projectionInterface.getClassLoader(), interfaces.toArray(new Class>[interfaces.size()]), projectionInvocationHandler));
}
/**
* {@inheritDoc}
*/
@Override
@Scope(DocScope.IO)
public T projectXMLString(final String xmlContent, final Class projectionInterface) {
try {
final ByteArrayInputStream inputStream = new ByteArrayInputStream(xmlContent.getBytes("utf-8"));
return new StreamInput(this, inputStream).read(projectionInterface);
} catch (IOException e) {
throw new XBIOException(e);
}
}
/**
* @param xmlContent
* @return {@link DefaultXPathEvaluator}
*/
@Scope(DocScope.IO)
public CanEvaluateOrProject onXMLString(final String xmlContent) {
try {
final ByteArrayInputStream inputStream = new ByteArrayInputStream(xmlContent.getBytes("utf-8"));
return new CanEvaluateOrProject() {
@Override
public XPathEvaluator evalXPath(final String xpath) {
return new DefaultXPathEvaluator(XBProjector.this, new DocumentResolver() {
@Override
public Document resolve(final Class>... resourceAwareClass) {
return IOHelper.loadDocument(XBProjector.this, inputStream);
}
}, xpath);
}
@Override
public T createProjection(final Class projectionInterface) {
return projectXMLString(xmlContent, projectionInterface);
}
@Override
public XBAutoMap createMapOf(final Class valueType) {
final Document document = IOHelper.loadDocument(XBProjector.this, inputStream);
return createAutoMapForDocument(valueType, document);
}
};
} catch (IOException e) {
throw new XBIOException(e);
}
}
private final XMLFactoriesConfig xMLFactoriesConfig;
private final Map, Map, Object>> mixins = new HashMap, Map, Object>>();
private TypeConverter typeConverter = new DefaultTypeConverter(Locale.getDefault(), TimeZone.getTimeZone("GMT"));
private StringRenderer stringRenderer = (StringRenderer) typeConverter;
private final List> domChangeListeners = new LinkedList>();
/**
* Global projector configuration options.
*/
public enum Flags {
/**
* Enables thread safety by removing concurrent DOM access. Useful if the underlying DOM
* implementation is not thread safe.
*/
SYNCHRONIZE_ON_DOCUMENTS,
/**
* Let the projections toString() method render the projection target as XML. Be careful if
* your documents get large. toString() might be used frequently by the IDE your debugging
* in.
*/
TO_STRING_RENDERS_XML,
/**
* Option to strip empty nodes from the result.
*/
OMIT_EMPTY_NODES,
/**
* If a node is not present, handle it like it is empty.
*/
ABSENT_IS_EMPTY
}
/**
* Constructor. Use me to create a projector with defaults.
*
* @param optionalFlags
*/
public XBProjector(final Flags... optionalFlags) {
this(new DefaultXMLFactoriesConfig(), optionalFlags);
}
private static > Set unfold(final T[] array) {
if ((array == null) || (array.length == 0)) {
return Collections.emptySet();
}
EnumSet enumSet = EnumSet.of(array[0]);
for (int i = 1; i < array.length; ++i) {
enumSet.add(array[i]);
}
return enumSet;
}
/**
* @param xMLFactoriesConfig
* @param optionalFlags
*/
public XBProjector(final XMLFactoriesConfig xMLFactoriesConfig, final Flags... optionalFlags) {
this.xMLFactoriesConfig = xMLFactoriesConfig;
this.flags = unfold(optionalFlags);
}
/**
* Shortcut for creating a {@link ConfigBuilder} object to change the projectors configuration.
*
* @return a new ConfigBuilder for this projector.
*/
public ConfigBuilder config() {
return configBuilder;
}
/**
* Shortcut for creating a {@link MixinHolder} object add or remove mixins to projections.
*
* @return a new MixinBuilder for this projector.
*/
public MixinHolder mixins() {
return new MixinBuilder();
}
/**
* Ensures that the given object is a projection created by a projector.
*
* @param projection
* @return
*/
private DOMAccess checkProjectionInstance(final Object projection) {
if (java.lang.reflect.Proxy.isProxyClass(projection.getClass())) {
InvocationHandler invocationHandler = java.lang.reflect.Proxy.getInvocationHandler(projection);
if (invocationHandler instanceof ProjectionInvocationHandler) {
if (projection instanceof DOMAccess) {
return (DOMAccess) projection;
}
}
}
throw new IllegalArgumentException("Given object " + projection + " is not a projection.");
}
/**
* @param projectionInterface
*/
private void ensureIsValidProjectionInterface(final Class> projectionInterface) {
if (projectionInterface == null) {
throw new IllegalArgumentException("Parameter projectionInterface must not be null, but is.", new NullPointerException());
}
if ((!projectionInterface.isInterface())) {
throw new IllegalArgumentException("Parameter " + projectionInterface + " is not an interface");
}
if (projectionInterface.isAnnotation()) {
throw new IllegalArgumentException("Parameter " + projectionInterface + " is an annotation interface. Remove the @ and try again.");
}
for (Method method : projectionInterface.getMethods()) {
final boolean isRead = (method.getAnnotation(XBRead.class) != null);
final boolean isWrite = (method.getAnnotation(XBWrite.class) != null);
final boolean isDelete = (method.getAnnotation(XBDelete.class) != null);
final boolean isUpdate = (method.getAnnotation(XBUpdate.class) != null);
final boolean isBind = (method.getAnnotation(XBAuto.class) != null);
final boolean isExternal = (method.getAnnotation(XBDocURL.class) != null);
final boolean isThrowsException = (method.getExceptionTypes().length > 0);
if (countTrue(isRead, isWrite, isDelete, isUpdate, isBind) > 1) {
throw new IllegalArgumentException("Method " + method + " has to many annotations. Decide for one of @" + XBRead.class.getSimpleName() + ", @" + XBWrite.class.getSimpleName() + ", @" + XBUpdate.class.getSimpleName() + ", or @" + XBDelete.class.getSimpleName()
+ ", or @" + XBAuto.class.getSimpleName());
}
if (isExternal && (isWrite || isUpdate || isDelete)) {
throw new IllegalArgumentException("Method " + method + " was declared as writing projection but has a @" + XBDocURL.class.getSimpleName() + " annotation. Defining external projections is only possible when reading because there is no DOM attached.");
}
if (isRead) {
if (!ReflectionHelper.hasReturnType(method)) {
throw new IllegalArgumentException("Method " + method + " has @" + XBRead.class.getSimpleName() + " annotation, but has no return type.");
}
if (ReflectionHelper.isRawType(method.getGenericReturnType())) {
throw new IllegalArgumentException("Method " + method + " has @" + XBRead.class.getSimpleName() + " annotation, but has a raw return type.");
}
if (method.getExceptionTypes().length > 1) {
throw new IllegalArgumentException("Method " + method + " has @" + XBRead.class.getSimpleName() + " annotation, but declares to throw multiple exceptions. Which one should I throw?");
}
if (ReflectionHelper.isOptional(method.getReturnType()) && isThrowsException) {
throw new IllegalArgumentException("Method " + method + " has an Optional<> return type, but declares to throw an exception. Exception will never be thrown because return value must not be null.");
}
}
if (isWrite && isThrowsException) {
throw new IllegalArgumentException("Method " + method + " declares to throw exception " + method.getExceptionTypes()[0].getSimpleName() + " but is not a reading projection method. When should this exception be thrown?");
}
if (isWrite) {
if (!ReflectionHelper.hasParameters(method)) {
throw new IllegalArgumentException("Method " + method + " has @" + XBWrite.class.getSimpleName() + " annotaion, but has no paramerter");
}
}
if (isUpdate) {
if (!ReflectionHelper.hasParameters(method)) {
throw new IllegalArgumentException("Method " + method + " has @" + XBUpdate.class.getSimpleName() + " annotaion, but has no paramerter");
}
}
for (Class> clazz : method.getParameterTypes()) {
if (ReflectionHelper.isOptional(clazz)) {
throw new IllegalArgumentException("Method " + method + " has java.util.Optional as a parameter type. You simply never should not do this.");
}
}
int count = 0;
for (Annotation[] paramAnnotations : method.getParameterAnnotations()) {
for (Annotation a : paramAnnotations) {
if (XBValue.class.equals(a.annotationType())) {
if (!(isWrite || isUpdate)) {
throw new IllegalArgumentException("Method " + method + " is not a writing projection method, but has an @" + XBValue.class.getSimpleName() + " annotaion.");
}
if (count > 0) {
throw new IllegalArgumentException("Method " + method + " has multiple @" + XBValue.class.getSimpleName() + " annotaions.");
}
++count;
}
}
}
}
}
/**
* Count how many parameters are true.
*
* @return number of true values in parameter list.
*/
private static int countTrue(final boolean... b) {
if (b == null) {
return 0;
}
int count = 0;
for (boolean bb : b) {
if (bb) {
++count;
}
}
return count;
}
/**
* Access to the input/output features of this projector.
*
* @return A new IOBuilder providing methods to read or write projections.
*/
@Override
@Scope(DocScope.IO)
public ProjectionIO io() {
return new IOBuilder();
}
/**
* @param projection
* @return an XML string of the projection target.
*/
@SuppressWarnings("rawtypes")
@Override
public String asString(final Object projection) {
// TODO: create interface for getNode, use this instead.
if (projection instanceof AutoValue) {
return DOMHelper.toXMLString(this, ((AutoValue) projection).getNode());
}
if (projection instanceof AutoList) {
return DOMHelper.toXMLString(this, ((AutoList) projection).getNode());
}
if (projection instanceof AutoMap) {
return DOMHelper.toXMLString(this, ((AutoMap) projection).getNode());
}
if (!(projection instanceof DOMAccess)) {
throw new IllegalArgumentException("Argument is not a projection.");
}
final DOMAccess domAccess = (DOMAccess) projection;
return domAccess.asString();
}
/**
* read only access to flags. Use constructor to set.
*
* @return flags.
*/
public Set getFlags() {
return Collections.unmodifiableSet(flags);
}
void addDOMChangeListener(final DOMChangeListener listener) {
domChangeListeners.add(new WeakReference(listener));
}
/**
*
*/
void notifyDOMChangeListeners() {
for (ListIterator> i = domChangeListeners.listIterator(); i.hasNext();) {
DOMChangeListener listener = i.next().get();
if (listener == null) {
i.remove();
continue;
}
listener.domChanged();
}
}
/**
* Create an empty document and bind an XBAutoMap to it.
*
* @param valueType
* component type of map
* @return an empty Map view to the document
*/
public XBAutoMap autoMapEmptyDocument(final Class valueType) {
Document document = xMLFactoriesConfig.createDocumentBuilder().newDocument();
return createAutoMapForDocument(valueType, document);
}
private XBAutoMap createAutoMapForDocument(final Class valueType, final Document document) {
InvocationContext invocationContext = new InvocationContext(null, null, null, null, null, valueType, this);
return new AutoMap(document, invocationContext, valueType);
}
}