/******************************************************************************* * Copyright (C) 2011 by Harry Blauberg * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. ******************************************************************************/ package org.jaml.core; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; import java.lang.reflect.Method; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.Future; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import org.apache.log4j.Logger; import org.jaml.api.Defaults; import org.jaml.api.IMarkupExtension; import org.jaml.api.IParserHandle; import org.jaml.api.IParserHandleConsumer; import org.jaml.api.IProportionHandler; import org.jaml.api.ITypeConverter; import org.jaml.api.ParsingInstructions; import org.jaml.cache.ClassCacheLibrary; import org.jaml.cache.Env; import org.jaml.container.ClassCache; import org.jaml.container.ParameterContainer; import org.jaml.exceptions.JamlObjectNotInstantiableException; import org.jaml.objects.Element; import org.jaml.objects.ParentObject; import org.jaml.structs.ClassInfo; import org.jaml.structs.Pair; import org.jaml.util.IOUtils; import org.jaml.util.ReflectionUtils; import org.jaml.util.ThreadUtils; /** * JAML reader * */ public class JamlReader { private static final Logger log = Logger.getLogger(JamlReader.class); private static final XMLInputFactory factory = XMLInputFactory .newInstance(); private JamlReader() { } public static Element load(Reader inputReader) { return load(null, inputReader); } public static <T> Element load(IParserHandle<T> parserHandle, Reader inputReader) { Element rootElement = null; ParentObject current = null; Element temp = null; Pair<Object, Map<ParsingInstructions, String>> pair = null; try { XMLStreamReader reader = factory.createXMLStreamReader(inputReader); while (reader.hasNext()) { reader.next(); if (reader.isStartElement()) { log.debug("isStartElement: " + reader.getName()); // Parse the element pair = handleStartElement(parserHandle, reader); if (current != null) { // Get new element and add this new element as a child // to the current element temp = current.add(pair.getFirst()); // Handle proportions handleProportions(current, temp); } else { // Very first element temp = new Element(null, pair.getFirst()); // Save it as root element rootElement = temp; } // Apply parsing instructions to the element applyInstructions(temp, pair.getSecond()); // Handle object map handleObjects(rootElement, temp); // Set it as new current element current = temp; } else if (reader.isCharacters()) { String textElement = reader.getText().trim(); if (!textElement.isEmpty() && current != null) { log.debug("isCharacters: " + textElement); temp = current.add(textElement); handleProportions(current, temp); } } else if (reader.isStandalone()) { log.debug("isStandalone: " + reader.getText().trim()); } else if (reader.isWhiteSpace()) { log.debug("isWhiteSpace: " + reader.getText().trim()); } else if (reader.isEndElement()) { log.debug("isEndElement: " + reader.getName().toString().trim()); // Get the parent element as new current element current = current.getParent(); } } reader.close(); } catch (XMLStreamException e) { e.printStackTrace(); } return rootElement; } private static void handleObjects(Element root, Element child) { // Put the object into the global scope if 'Name' // parsing instruction is given if (child.hasInstruction(ParsingInstructions.Name)) { root.storeObject(child.getInstruction(ParsingInstructions.Name), child.getContent()); } } private static void handleProportions(ParentObject parent, Element child) { Unit<?> unit = null; Element element = null; if (Element.class.isInstance(parent)) { element = Element.class.cast(parent); } else if (Unit.class.isInstance(parent)) { unit = Unit.class.cast(parent); } Object parentObj = element != null ? element.getContent() : unit != null ? unit.getRoot() : null; Object childObj = child.isValid() ? child.getContent() : null; if (parentObj != null && childObj != null) { Pair<Class<?>, Class<?>> key = new Pair<Class<?>, Class<?>>( parentObj.getClass(), childObj.getClass()); IProportionHandler handler = Env.get().getProportions().get(key); if (handler != null) { log.debug("Invoking: " + handler); handler.process(parentObj, childObj); } } } private static void applyInstructions(Element element, Map<ParsingInstructions, String> parsingInstructions) { for (ParsingInstructions instruction : parsingInstructions.keySet()) { element.setInstruction(instruction, parsingInstructions.get(instruction)); } } private static <T> Pair<Object, Map<ParsingInstructions, String>> handleStartElement( IParserHandle<T> parserHandle, XMLStreamReader reader) { Object obj = null; Map<ParsingInstructions, String> parsingInstructions = new HashMap<ParsingInstructions, String>(); ParsingInstructions instruction = null; ClassInfo classInfo = ReflectionUtils.getClassByNamespace(reader .getName()); try { ClassCacheLibrary.getInstance().addToCacheIfRequired(classInfo); ClassCache cache = ClassCacheLibrary.getInstance().getCache( classInfo.getFQP()); if (cache == null) { throw new JamlObjectNotInstantiableException(classInfo.getFQP()); } obj = cache.getType().newInstance(); String attrName = null; String attrValue = null; String attrNameSpace = null; String attrPrefix = null; ParameterContainer paramContainer = null; Method setter = null; List<Class<?>[]> filtered = null; Class<?> clazz = null; ITypeConverter converter = null; Object tmp = null; for (int i = 0; i < reader.getAttributeCount(); i++) { attrName = reader.getAttributeName(i).getLocalPart(); attrValue = reader.getAttributeValue(i); attrNameSpace = reader.getAttributeName(i).getNamespaceURI(); attrPrefix = reader.getAttributeName(i).getPrefix(); // Check if attribute is in another name space than the if (!(attrNameSpace.isEmpty() && attrPrefix.isEmpty())) { log.debug(attrPrefix + ":" + attrName + " is in another namespace: " + attrNameSpace); if (attrNameSpace.equals(Defaults.parserNamespace)) { instruction = ParsingInstructions.valueOf(attrName); parsingInstructions.put(instruction, attrValue); } } else { // Check if attribute is writable if (cache.isWritable(attrName)) { IMarkupExtension markup = Env.get().getMarkups() .get(attrValue); if (markup != null) { handleMarkup(markup, attrValue); } else { paramContainer = cache .getWritableArguments(attrName); filtered = paramContainer.filterByNumber(1); for (Class<?>[] classes : filtered) { clazz = classes[0]; converter = Env.get().getConverters() .get(clazz); if (converter != null) { checkIfParserHandleConsumerAndSetIt( parserHandle, converter); tmp = converter.convertString(attrValue); log.debug(converter + " " + tmp); setter = ReflectionUtils .searchSetterMethod(cache, attrName, tmp.getClass()); if (setter == null) { log.error(String .format("Could not set '%s' on type '%s'!", tmp.getClass(), obj.getClass())); } else { try { setter.invoke(obj, tmp); break; } catch (Exception e) { String stackTrace = IOUtils .getStackTraceAsStr(e); String fqnClassName = e.getClass() .getName(); String text = String.format( "(%s)[%s] %s: %s => %s", attrValue, attrName, fqnClassName, e.getMessage(), stackTrace); log.error(text); } } } else { log.error("Missing converter for: " + clazz); } } } } else { log.error(String.format( "Field '%s' is not accessable to be set!", attrName)); } } } } catch (Exception e) { log.error(e.getClass().getSimpleName() + ": " + e.getMessage(), e); } return new Pair<Object, Map<ParsingInstructions, String>>(obj, parsingInstructions); } @SuppressWarnings({ "rawtypes", "unchecked" }) private static <T> void checkIfParserHandleConsumerAndSetIt( IParserHandle<T> handle, Object object) { if (handle == null) return; if (object instanceof IParserHandleConsumer) { IParserHandleConsumer parserHandleConsumer = (IParserHandleConsumer) object; parserHandleConsumer.setIParserHandle(handle); log.debug("Giving parser handle to object of class: " + object.getClass()); } } private static void handleMarkup(IMarkupExtension extension, String value) { String markup = value.substring(1, value.length() - 1); log.debug("Markup: '" + markup + "'"); String[] splitted = markup.split(" "); String symbol = splitted[0]; String arg = splitted[1]; log.debug(symbol + ", " + extension); extension.handleMarkup(arg, null); } public static <T> Element load(IParserHandle<T> parserHandle, InputStream inputStream) { return load(parserHandle, new InputStreamReader(inputStream)); } public static Element load(InputStream inputStream) { return load(null, inputStream); } public static Element load(String input) { return load(new StringReader(input)); } public static Future<Element> loadAsync(final Reader reader) { return ThreadUtils.exec(new Callable<Element>() { @Override public Element call() throws Exception { return load(reader); } }); } public static Future<Element> loadAsync(final InputStream stream) { return ThreadUtils.exec(new Callable<Element>() { @Override public Element call() throws Exception { return load(stream); } }); } public static Future<Element> loadAsync(final String input) { return ThreadUtils.exec(new Callable<Element>() { @Override public Element call() throws Exception { return load(input); } }); } @SuppressWarnings("unchecked") public static <T> T loadAndUnwrap(Reader reader) { return (T) (load(reader).getContent()); } public static <T> T loadAndUnwrap(InputStream inputStream) { return loadAndUnwrap(new InputStreamReader(inputStream)); } public static <T> T loadAndUnwrap(String input) { return loadAndUnwrap(new StringReader(input)); } public static <T> Future<T> loadAndUnwrapAsync(final Reader reader) { return ThreadUtils.exec(new Callable<T>() { @Override public T call() throws Exception { return loadAndUnwrap(reader); } }); } public static <T> Future<T> loadAndUnwrapAsync(final InputStream stream) { return ThreadUtils.exec(new Callable<T>() { @Override public T call() throws Exception { return loadAndUnwrap(stream); } }); } public static <T> Future<T> loadAndUnwrapAsync(final String input) { return ThreadUtils.exec(new Callable<T>() { @Override public T call() throws Exception { return loadAndUnwrap(input); } }); } }