blob: d2b16df2d3a07217829937318138435050b2aa42 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.axiom.truth.xml;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.ServiceLoader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.stax.StAXSource;
import javax.xml.transform.stream.StreamSource;
import org.apache.axiom.truth.xml.spi.XML;
import org.apache.axiom.truth.xml.spi.XMLFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import com.google.common.truth.FailureMetadata;
import com.google.common.truth.SimpleSubjectBuilder;
import com.google.common.truth.Subject;
import com.google.common.truth.Truth;
/**
* Google Truth extension for XML.
*/
public final class XMLTruth {
private static final Subject.Factory<XMLSubject,Object> SUBJECT_FACTORY = new Subject.Factory<XMLSubject,Object>() {
@Override
public XMLSubject createSubject(FailureMetadata metadata, Object that) {
return new XMLSubject(metadata, that);
}
};
private static final List<XMLFactory<?>> factories;
static {
factories = new ArrayList<>();
factories.add(new XMLFactory<XML>() {
@Override
public Class<XML> getExpectedType() {
return XML.class;
}
@Override
public XML createXML(XML xml) {
return xml;
}
});
factories.add(new XMLFactory<InputStream>() {
@Override
public Class<InputStream> getExpectedType() {
return InputStream.class;
}
@Override
public XML createXML(final InputStream in) {
return new StAXXML(new Parsable() {
@Override
XMLStreamReader createXMLStreamReader(XMLInputFactory factory) throws XMLStreamException {
return factory.createXMLStreamReader(in);
}
});
}
});
factories.add(new XMLFactory<Reader>() {
@Override
public Class<Reader> getExpectedType() {
return Reader.class;
}
@Override
public XML createXML(final Reader reader) {
return new StAXXML(new Parsable() {
@Override
XMLStreamReader createXMLStreamReader(XMLInputFactory factory) throws XMLStreamException {
return factory.createXMLStreamReader(reader);
}
});
}
});
factories.add(new XMLFactory<StreamSource>() {
@Override
public Class<StreamSource> getExpectedType() {
return StreamSource.class;
}
@Override
public XML createXML(final StreamSource source) {
return new StAXXML(new Parsable() {
@Override
XMLStreamReader createXMLStreamReader(XMLInputFactory factory) throws XMLStreamException {
return factory.createXMLStreamReader(source);
}
});
}
});
factories.add(new XMLFactory<InputSource>() {
@Override
public Class<InputSource> getExpectedType() {
return InputSource.class;
}
@Override
public XML createXML(InputSource is) {
StreamSource source = new StreamSource();
source.setInputStream(is.getByteStream());
source.setReader(is.getCharacterStream());
source.setPublicId(is.getPublicId());
source.setSystemId(is.getSystemId());
return xml(source);
}
});
factories.add(new XMLFactory<URL>() {
@Override
public Class<URL> getExpectedType() {
return URL.class;
}
@Override
public XML createXML(URL url) {
return xml(new StreamSource(url.toString()));
}
});
factories.add(new XMLFactory<File>() {
@Override
public Class<File> getExpectedType() {
return File.class;
}
@Override
public XML createXML(File file) {
return xml(new StreamSource(file.toURI().toString()));
}
});
factories.add(new XMLFactory<String>() {
@Override
public Class<String> getExpectedType() {
return String.class;
}
@Override
public XML createXML(String xml) {
return xml(new StringReader(xml));
}
});
factories.add(new XMLFactory<byte[]>() {
@Override
public Class<byte[]> getExpectedType() {
return byte[].class;
}
@Override
public XML createXML(byte[] bytes) {
return xml(new ByteArrayInputStream(bytes));
}
});
factories.add(new XMLFactory<Document>() {
@Override
public Class<Document> getExpectedType() {
return Document.class;
}
@Override
public XML createXML(Document document) {
return new DOMXML(document);
}
});
factories.add(new XMLFactory<Element>() {
@Override
public Class<Element> getExpectedType() {
return Element.class;
}
@Override
public XML createXML(Element element) {
return new DOMXML(element);
}
});
factories.add(new XMLFactory<XMLStreamReader>() {
@Override
public Class<XMLStreamReader> getExpectedType() {
return XMLStreamReader.class;
}
@Override
public XML createXML(final XMLStreamReader source) {
return new StAXXML(new XMLStreamReaderProvider() {
@Override
XMLStreamReader getXMLStreamReader(boolean expandEntityReferences) throws XMLStreamException {
if (expandEntityReferences) {
throw new UnsupportedOperationException("Can't expand entity references on a user supplied XMLStreamReader");
}
return source;
}
});
}
});
factories.add(new XMLFactory<StAXSource>() {
@Override
public Class<StAXSource> getExpectedType() {
return StAXSource.class;
}
@Override
public XML createXML(StAXSource source) {
return xml(source.getXMLStreamReader());
}
});
for (XMLFactory<?> factory : ServiceLoader.load(
XMLFactory.class, XMLTruth.class.getClassLoader())) {
factories.add(factory);
}
}
private XMLTruth() {}
/**
* Get the {@link Subject.Factory} to be used with {@link Truth#assertAbout(Subject.Factory)}.
*
* @return a {@link Subject.Factory} for {@link XMLSubject} instances
*/
public static Subject.Factory<XMLSubject, Object> xml() {
return SUBJECT_FACTORY;
}
/**
* Prepare XML data so that it will be accessed through a particular API. Use this method for
* objects that represent XML data, but that implement more than one API supported by the
* factory returned by {@link #xml()} (e.g. DOM and the Axiom API).
*
* @param type
* the API to use (e.g. {@link Document}
* @param object
* an object implementing that API
* @return an object that can be passed to {@link SimpleSubjectBuilder#that(Object)} or
* {@link XMLSubject#hasSameContentAs(Object)}
*/
public static <T> Object xml(Class<T> type, T object) {
return createXML(type, object);
}
private static <T> XML createXML(Class<T> type, T object) {
XMLFactory<?> factory = null;
for (XMLFactory<?> candidate : factories) {
Class<?> expectedType = candidate.getExpectedType();
if ((type == null || expectedType.isAssignableFrom(type)) && expectedType.isInstance(object)) {
if (factory != null) {
throw new IllegalArgumentException("Multiple matching XMLFactory instances found");
} else {
factory = candidate;
}
}
}
if (factory == null) {
throw new IllegalArgumentException("No XMLFactory found for type " + object.getClass().getName());
} else {
return createXML0(factory, object);
}
}
static XML xml(Object object) {
return createXML(null, object);
}
private static <T> XML createXML0(XMLFactory<T> factory, Object object) {
return factory.createXML(factory.getExpectedType().cast(object));
}
}