How many frameworks are there for converting XML to Java? Hard to count. As an experiment I tried my hand at one. I have two top-level classes plus an annotation:
public final class XMLFuzzy implements InvocationHandler { private static final XPath xpath = XPathFactory.newInstance(). newXPath(); private static final Map<Method, XPathExpression> expressions = new ConcurrentHashMap<>(); private final Node node; private final Converter converter; public static final class Factory { private final Converter converter; public Factory(final Converter converter) { this.converter = converter; } public <T> T of(@Nonnull final Class<T> itf, @Nonnull final Node node) { return XMLFuzzy.of(itf, node, converter); } } public static <T> T of(@Nonnull final Class<T> itf, @Nonnull final Node node, @Nonnull final Converter converter) { return itf.cast(newProxyInstance(itf.getClassLoader(), new Class[]{itf}, new XMLFuzzy(node, converter))); } private XMLFuzzy(final Node node, final Converter converter) { this.node = node; this.converter = converter; } @Override public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { return converter.convert(method.getReturnType(), expressions. computeIfAbsent(method, XMLFuzzy::compile). evaluate(node)); } private static XPathExpression compile(@Nonnull final Method method) { final String expression = asList(method.getAnnotations()).stream(). filter(From.class::isInstance). map(From.class::cast). findFirst(). orElseThrow(() -> new MissingAnnotation(method)). value(); try { return xpath.compile(expression); } catch (final XPathExpressionException e) { throw new BadXPath(method, expression, e); } } public static final class MissingAnnotation extends RuntimeException { private MissingAnnotation(final Method method) { super(format("Missing @X(xpath) annotation: %s", method)); } } public static final class BadXPath extends RuntimeException { private BadXPath(final Method method, final String expression, final XPathExpressionException e) { super(format("Bad @X(xpath) annotation on '%s': %s: %s", method, expression, e.getMessage())); setStackTrace(e.getStackTrace()); } } }
I have left out Converter
; it turns strings into objects of a given type, another example of overimplemented framework code in Java. And the annotation:
@Documented @Inherited @Retention(RUNTIME) @Target(METHOD) public @interface From { String value(); }
The idea is straight-forward: drive the object mapping from XML with XPaths. Credit to XMLBeam for introducing to me the elegant use of JDK proxies for this purpose.
Of course tests:
public final class XMLFuzzyTest { private Top top; @Before public void setUp() throws ParserConfigurationException, IOException, SAXException { final Document document = DocumentBuilderFactory.newInstance(). newDocumentBuilder(). parse(new InputSource(new StringReader(XML))); top = new XMLFuzzy.Factory(new Converter()).of(Top.class, document); } @Test public void shouldHandleString() { assertThat(top.a(), is(equalTo("apple"))); } @Test public void shouldHandlePrimitiveInt() { assertThat(top.b(), is(equalTo(3))); } @Test public void shouldHandleRURI() { assertThat(top.c(), is(equalTo(URI.create("http://some/where")))); } @Test(expected = MissingAnnotation.class) public void shouldThrowOnMissingAnnotation() { top.d(); } @Test(expected = BadXPath.class) public void shouldThrowOnBadXPath() { top.e(); } public interface Top { // For the purposes of this blog post, pretend Java supports // multiline string literals @Language("XML") String XML = "<top> <a>apple</a> <b>3</b> <c>http://some/where</c> </top>"; @From("//top/a") String a(); @From("//top/b") int b(); @From("//top/c") URI c(); void d(); @From("dis' ain't xpath") void e(); } }
No comments:
Post a Comment