Java tutorial
/* This file is part of Aspa. Aspa is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Aspa 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 General Public License for more details. You should have received a copy of the GNU General Public License along with Aspa; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package gr.omadak.leviathan.asp.objects; import antlr.ASTFactory; import antlr.collections.AST; import gr.omadak.leviathan.asp.CommonConstants; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.log4j.Logger; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; public class XmlObjectParser { private static ASTFactory factory = new ASTFactory(); private static Logger LOG = Logger.getLogger(XmlObjectParser.class); private Map types; private Map registeredObjects; private int lastArgType; private int objectType; private int astArgType; private int translateRoot; private int lastIndex; private String url; private Map idMethods; private String lastState; public XmlObjectParser(Map types) { this.types = types; try { objectType = getType("OBJECT"); astArgType = getType("AST_ARG"); translateRoot = getType("TRANSLATE_ROOT"); } catch (DocumentException doc) { throw new RuntimeException("Failed to get basic types" + ":OBJECT, AST_ARG, TRANSLATE_ROOT"); } } public XmlObjectParser(Map types, Map objects) { this(types); registeredObjects = objects; } public void configure(GenericClass clazz, URL url) throws DocumentException { Document doc = loadDocument(url); this.url = url.toString(); Element root = doc.getRootElement(); if (root != null) { configureClass(clazz, root); if (idMethods != null) { for (Iterator it = idMethods.keySet().iterator(); it.hasNext();) { Object key = it.next(); Object[] value = (Object[]) idMethods.get(key); Method indicated = (Method) value[0]; Property prop = (Property) value[1]; if (indicated != null && prop != null && value[2] != null) { int index = ((Integer) value[2]).intValue(); prop.setIndicatedMethod(indicated, index); } else { LOG.warn("Failed to configure indicated method:" + (indicated == null ? "null" : indicated.getName()) + " indicated by property:" + (prop == null ? "null" : prop.getName()) + " with index:" + value[2]); } } idMethods.clear(); idMethods = null; } } } public void configureMethods(Map placeIn, URL url) throws DocumentException { Document doc = loadDocument(url); Element root = doc.getRootElement(); List classes = doc.selectNodes("class"); Map classMap = classes.isEmpty() ? Collections.EMPTY_MAP : collectInnerClasses(classes); List methods = root.selectNodes("method"); StringBuffer errors = new StringBuffer(); for (Iterator it = methods.iterator(); it.hasNext();) { Element methodDef = (Element) it.next(); try { Method method = configureMethod(methodDef, classMap); String name = method.getName().toUpperCase(); if (placeIn.containsKey(name)) { Object obj = placeIn.get(name); if (obj instanceof List) { ((List) obj).add(method); } else { List item = new ArrayList(); item.add(obj); item.add(method); placeIn.put(name, item); } } else { placeIn.put(name, method); } } catch (DocumentException docex) { errors.append(docex).append('\n'); } } if (errors.length() > 0) { throw new DocumentException(url + "\n" + errors.toString()); } } private void configureClass(GenericClass clazz, Element element) throws DocumentException { String clazzName = element.attributeValue("name"); if (clazzName != null) { clazz.setName(clazzName); } boolean standalone = !Boolean.valueOf(element.attributeValue("dependant")).booleanValue() && element.attributeValue("standalone") != null && Boolean.valueOf(element.attributeValue("standalone")).booleanValue(); clazz.setStandalone(standalone); List innerClasses = element.selectNodes("class"); Map innerMap = innerClasses.isEmpty() ? Collections.EMPTY_MAP : collectInnerClasses(innerClasses); List properties = element.selectNodes("property"); for (Iterator it = properties.iterator(); it.hasNext();) { configureProperty(clazz, (Element) it.next(), innerMap); } List methods = element.selectNodes("method"); for (Iterator it = methods.iterator(); it.hasNext();) { configureMethod(clazz, (Element) it.next(), innerMap); } List constructors = element.selectNodes("constructor"); for (Iterator it = constructors.iterator(); it.hasNext();) { configureConstructor(clazz, (Element) it.next(), innerMap); } List delete = element.selectNodes("delete"); if (!delete.isEmpty()) { if (delete.size() > 1) { LOG.warn("Only first delete node is used"); } Element elDelete = (Element) delete.get(0); AST deleteAST = translate(elDelete, null, false, 0); clazz.setDeleteAST(deleteAST); } configureInclude(clazz, element); } private Map collectInnerClasses(List classes) throws DocumentException { Map result = new HashMap(); for (Iterator it = classes.iterator(); it.hasNext();) { Element classElement = (Element) it.next(); XmlDependantASPClass clazz = new XmlDependantASPClass(); configureClass(clazz, classElement); result.put(clazz.getName().toUpperCase(), clazz); } return result; } private Document loadDocument(URL url) throws DocumentException { return new SAXReader().read(url); } private ASPClass getUseClass(String use, Map innerClasses) { use = use.trim().toUpperCase(); Object val = registeredObjects.get(use); if (val == null) { val = innerClasses.get(use); if (val == null) { LOG.error("use attribute:" + use + " does not define an ASPClass"); } } ASPClass result; if (val instanceof ASPClass) { result = (ASPClass) val; } else { result = null; } return result; } private void configureConstructor(GenericClass clazz, Element elem, Map innerClasses) throws DocumentException { elem.addAttribute("name", clazz.getName()); elem.addAttribute("type", "CONSTRUCTOR"); Method constr = configureMethod(elem, innerClasses); clazz.addConstructor(constr); } private Method configureMethod(Element methodEl, Map innerClasses) throws DocumentException { String methodName = methodEl.attributeValue("name"); assertValue(methodName, "Name not specified for method"); String type = methodEl.attributeValue("type"); assertValue(type, "Method type not specified"); String use = methodEl.attributeValue("use"); Method method = null; int dType; try { dType = getType(type); } catch (DocumentException de) { dType = objectType; } if (use != null) { ASPDependantClass aspClazz = (ASPDependantClass) getUseClass(use, innerClasses); if (aspClazz != null) { ASPMethodWrapper mWrapper = new ASPMethodWrapper(aspClazz); mWrapper.setName(methodName); mWrapper.setReturnType(dType); method = mWrapper; } } else { ASPClass retClass = null; if (!"OBJECT".equals(type)) { if (dType == objectType) { retClass = getUseClass(type, innerClasses); if (retClass == null) { throw new DocumentException("in method:" + methodName + " type:" + type + " is not recognized and is not a known class"); } } } List args = getArgs(methodEl); Element map = methodEl.element("map"); if (map != null) { method = new ASPMethodMap(methodName, dType, args); configureMethodMap((ASPMethodMap) method, map, innerClasses); } else { AST translation = translate(methodEl, null, false, 0); method = new GenericMethod(methodName, dType, args, translation); } String id = methodEl.attributeValue("id"); if (id != null) { if (idMethods == null) { idMethods = new HashMap(); idMethods.put(id, new Object[] { method, null, null }); } else { Object[] mem = (Object[]) idMethods.get(id); mem[0] = method; } } if (retClass != null) { method.setRetObjectClass(retClass); } } if (method != null) { configureInclude(method, methodEl); } return method; } private void configureMethod(GenericClass clazz, Element methodEl, Map innerClasses) throws DocumentException { Method method = configureMethod(methodEl, innerClasses); if (method != null) { boolean isDefault = Boolean.valueOf(methodEl.attributeValue("default")).booleanValue(); if (isDefault) { clazz.setDefaultMethod(method); } else { clazz.addMember(method); } } else { throw new DocumentException(url + ": Failed to create method:" + methodEl.getText()); } } private void configureMethodMap(ASPMethodMap mMap, Element map, Map innerClasses) throws DocumentException { List keys = map.selectNodes("key"); for (Iterator it = keys.iterator(); it.hasNext();) { Element keyEl = (Element) it.next(); String key = keyEl.attributeValue("value"); boolean isNStr = false; boolean isDefault = false; if (key == null) { isNStr = Boolean.valueOf(keyEl.attributeValue("nstr")).booleanValue(); isDefault = !isNStr; } if (keyEl.attributeValue("use") != null) { if (isNStr || isDefault) { throw new DocumentException("use attribute can be defined for String cases only"); } ASPClass useClass = getUseClass(keyEl.attributeValue("use"), innerClasses); if (useClass == null) { throw new DocumentException("Failed to resolve class:" + keyEl.attributeValue("use")); } mMap.addCase(key, useClass); } else { AST trans = translate(keyEl, null, false, 0); if (trans == null) { throw new DocumentException(url + " : Defined null ast for key:" + key); } else { if (isDefault) { mMap.setDefaultCase(trans); } else if (isNStr) { mMap.setNonStringCase(trans); } else { mMap.addCase(key, trans); } } } } mMap.setCaseSensitive(Boolean.valueOf(map.attributeValue("casesensitive")).booleanValue()); } private List getArgs(Element el) throws DocumentException { List result = new ArrayList(); for (Iterator it = el.elementIterator(); it.hasNext();) { Element arg = (Element) it.next(); if ("arg".equals(arg.getName())) { String type = arg.attributeValue("type"); assertValue(type, "Type of arg is required"); int objType = getType(type); result.add(new Integer(objType)); } else if ("args".equals(arg.getName())) { result.add(new Integer(CommonConstants.ALL_ARGS)); } } return result; } private void assertValue(String value, String msg) throws DocumentException { if (value == null) { throw new DocumentException(url + ":" + msg); } } private int getType(String name) throws DocumentException { Integer iType = (Integer) types.get(name); if (iType == null) { if (registeredObjects != null && registeredObjects.containsKey(name.toUpperCase())) { return objectType; } else { throw new DocumentException(url + " : code not found for type:" + name); } } return iType.intValue(); } private void configureInclude(ASPObject obj, Element objDef) throws DocumentException { String dep = objDef.attributeValue("requires"); if (dep != null) { obj.addDependency(dep); } } private void configureProperty(GenericClass clazz, Element propertyEl, Map innerClasses) throws DocumentException { lastArgType = -1; String propName = propertyEl.attributeValue("name"); assertValue(propName, "Name not specified for property"); String use = propertyEl.attributeValue("use"); Property property = null; boolean isDefault = Boolean.valueOf(propertyEl.attributeValue("default")).booleanValue(); lastState = null; if (use != null) { ASPDependantClass aspClazz = (ASPDependantClass) getUseClass(use, innerClasses); if (aspClazz != null) { property = new ASPPropertyWrapper(aspClazz); ((ASPPropertyWrapper) property).setName(propName); } } else { String type = propertyEl.attributeValue("type"); assertValue(type, "Property type not specified"); int dType; ASPClass retClass = null; try { dType = getType(type); } catch (DocumentException de) { dType = -1; } if (dType == objectType || dType == -1) { retClass = getUseClass(type, innerClasses); if (retClass == null) { throw new DocumentException("type:" + type + " is not recognized and is not a known class"); } dType = objectType; } Element read = propertyEl.element("read"); AST readAST = null; AST writeAST = null; if (read != null) { readAST = translate(read, null, false, 0); } Element write = propertyEl.element("write"); if (write != null) { writeAST = translate(write, null, true, 0); } property = new GenericASPProperty(propName, readAST, writeAST, dType); if (retClass != null) { property.setRetObjectClass(retClass); } if (lastArgType != -1) { property.setArgType(lastArgType); } } if (property != null) { if (lastState != null) { if (idMethods == null) { idMethods = new HashMap(); idMethods.put(lastState, new Object[] { null, property, new Integer(lastIndex) }); } else { Object[] obj = (Object[]) idMethods.get(lastState); obj[1] = property; obj[2] = new Integer(lastIndex); } } lastState = null; if (isDefault) { clazz.setDefaultProperty(property); } else { clazz.addMember(property); } configureInclude(property, propertyEl); } else { throw new DocumentException(url + " : Failed to create Property:" + propertyEl.getText()); } } private AST createNode(int type, String name) { AST result = factory.create(type, name); if (name == null) { LOG.error("Passed null", new Exception("Test")); } return result; } private AST translate(Element el, AST root, boolean evalFirstLevelArgs, int cDepth) throws DocumentException { List astChildren = new ArrayList(); boolean firstAST = cDepth > 0; for (Iterator it = el.elementIterator(); it.hasNext();) { Element ch = (Element) it.next(); if ("ast".equals(ch.getName())) { firstAST = true; String type = ch.attributeValue("name"); String text = ch.attributeValue("text"); assertValue(type, "Name of ast elements is required"); text = text == null ? type : text; int dType = getType(type); AST node = createNode(dType, text); if (ch.elementIterator().hasNext()) { translate(ch, node, evalFirstLevelArgs, cDepth + 1); } astChildren.add(node); } else if ("arg".equals(ch.getName())) { if (root == null) { if (evalFirstLevelArgs) { String type = ch.attributeValue("type"); assertValue(type, "Type of arg is required"); int objType = getType(type); lastArgType = objType; } } else { String index = ch.attributeValue("index"); assertValue(index, "Argument index should be specified"); try { Integer.parseInt(index); } catch (NumberFormatException nfe) { throw new DocumentException(url + " : Invalid value:" + index + " for attribute index"); } astChildren.add(createNode(astArgType, index)); } } else if ("args".equals(ch.getName())) { if (firstAST) { astChildren.add(ch.attributeValue("mode") == null ? factory.create(CommonConstants.ALL_ARGS) : createNode(CommonConstants.ALL_ARGS, ch.attributeValue("mode"))); } } else if ("this".equals(ch.getName())) { astChildren.add(createNode(CommonConstants.INSTANCE, "this")); } else if ("state".equals(ch.getName())) { String id = ch.attributeValue("methodId"); assertValue(id, "The id of the method affected is not defined"); String index = ch.attributeValue("index"); assertValue(index, "The index of the argument for the affected method " + "is not defined"); try { lastIndex = Integer.parseInt(index) - 1; } catch (NumberFormatException nfe) { throw new DocumentException("The index for the affected mathod is invalid:" + index); } lastState = id; } else { System.err.println("Unexpected element:" + ch.getName()); } } if (astChildren.isEmpty()) { return null; } if (root == null && astChildren.size() > 1) { root = createNode(translateRoot, "TRANSLATE_ROOT"); } AST current = null; for (Iterator it = astChildren.iterator(); it.hasNext();) { if (root == null) { root = (AST) it.next(); } else if (root.getFirstChild() == null) { current = (AST) it.next(); root.setFirstChild(current); } else { if (current == null) { current = root.getFirstChild(); } AST next = (AST) it.next(); current.setNextSibling(next); current = next; } } return root; } }