All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.objectweb.fractal.mind.adl.xml.DTDHandler Maven / Gradle / Ivy

There is a newer version: 0.1-beta-2
Show newest version
/***
 * Fractal ADL Parser
 * Copyright (C) 2002-2004 France Telecom R&D
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Contact: [email protected]
 *
 * Author: Eric Bruneton
 * Contributors: Ali Erdem Ozcan
 */

package org.objectweb.fractal.mind.adl.xml;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import org.objectweb.asm.Type;
import org.xml.sax.SAXException;

import com.wutka.dtd.DTD;
import com.wutka.dtd.DTDAttribute;
import com.wutka.dtd.DTDCardinal;
import com.wutka.dtd.DTDChoice;
import com.wutka.dtd.DTDContainer;
import com.wutka.dtd.DTDElement;
import com.wutka.dtd.DTDItem;
import com.wutka.dtd.DTDMixed;
import com.wutka.dtd.DTDName;
import com.wutka.dtd.DTDParser;
import com.wutka.dtd.DTDProcessingInstruction;
import com.wutka.dtd.DTDSequence;

class DTDHandler {

  void checkDTD(final InputStream is, final XMLNodeClassLoader loader)
      throws IOException, ClassNotFoundException, SAXException {
    // Associates DTD elements to AST node names
    // Keys are AST node names, values are DTDElement objects
    final Map astElements = new HashMap();

    final DTD dtd = new DTDParser(new InputStreamReader(is)).parse();
    final Iterator i = dtd.items.iterator();
    while (i.hasNext()) {
      final Object item = i.next();

      if (item instanceof DTDProcessingInstruction) {
        final String pi = ((DTDProcessingInstruction) item).getText();

        if (pi.startsWith("add")) {
          final Map args = checkProcessingInstruction(pi);
          final String ast = args.get("ast");
          final String itf = args.get("itf");

          if (ast == null || itf == null) {
            throw new SAXException("Invalid processing instruction "
                + "('ast' and/or 'itf' argument missing): " + pi);
          }

          DTDElement astElement = astElements.get(ast);
          if (astElement == null) {
            astElement = new DTDElement(ast);
            astElement.setContent(new DTDSequence());
            astElements.put(ast, astElement);
          }
          checkASTClass(loader.loadClass(itf), astElement);

          loader.addASTNodeInterface(ast, itf);
        } else if (pi.startsWith("map")) {
          final Map args = checkProcessingInstruction(pi);
          final String ast = args.get("ast");
          final String xml = args.get("xml");
          final String type = args.get("type");

          if (ast == null || (xml == null && type == null)) {
            throw new SAXException("Invalid processing instruction "
                + "('ast', 'xml' and/or 'type' argument missing): " + pi);
          }

          if (xml != null) {
            if (xml.indexOf('.') == -1) {
              loader.addASTNodeMapping(ast, xml);
            } else {
              if (ast.indexOf('.') == -1) {
                throw new SAXException("Invalid processing instruction "
                    + "(incompatible 'xml' and 'ast' arguments)" + pi);
              }
              loader.addASTAttributeMapping(ast, xml);
            }
          }

          if (type != null) {
            if (ast.indexOf('.') != -1) {
              throw new SAXException("Invalid processing instruction "
                  + "(incompatible 'type' and 'ast' arguments)" + pi);
            }
            loader.addASTTypeMapping(ast, type);
          }
        }
      } else if (item instanceof DTDElement) {
        final DTDElement xmlElem = (DTDElement) item;
        final String xmlName = xmlElem.getName();
        final String astName = loader.getASTName(xmlName);
        final DTDElement astElem = astElements.get(astName);
        if (astElem == null) {
          throw new SAXException(
              "Invalid DTD : no AST node defined for element '" + xmlName + "'");
        }
        checkDTDItem(xmlElem.getContent(), new HashSet());
        checkDTDItem(astElem.getContent(), new HashSet());
        final Arities xmlArities = getArities(xmlElem.getContent(), null);
        final Arities astArities = getArities(astElem.getContent(), null);

        Iterator x = xmlArities.keySet().iterator();
        while (x.hasNext()) {
          final String xmlSubNode = (String) x.next();
          final String astSubNode = loader.getASTType(loader
              .getASTName(xmlSubNode));
          final Arity xmlSubNodeArity = xmlArities.get(xmlSubNode);
          final Arity astSubNodeArity = astArities.get(astSubNode);
          if (astSubNodeArity == null) {
            throw new SAXException(
                "Invalid DTD : no AST node defined for sub element '"
                    + xmlSubNode + "' of element '" + xmlName + "'");
          }
          if ((astSubNodeArity.max > 1 && xmlSubNodeArity.max <= 1)
              || (astSubNodeArity.max <= 1 && xmlSubNodeArity.max > 1)) {
            throw new SAXException("Invalid DTD : arity of sub element '"
                + xmlSubNode + "' of element '" + xmlName
                + "' incompatible with arity of AST" + " sub node '"
                + astSubNode + "' of AST node '" + astName + "'");
          }
        }

        Iterator a = astArities.keySet().iterator();
        while (a.hasNext()) {
          final String astSubNode = (String) a.next();
          final Set astSubNodeNames = loader.getASTNames(astSubNode);
          boolean found = false;
          for (final String astSubNodeName : astSubNodeNames) {
            final String xmlSubNode = loader.getXMLElement(astSubNodeName);
            final Arity xmlSubNodeArity = xmlArities.get(xmlSubNode);
            if (xmlSubNodeArity != null) {
              found = true;
              break;
            }
          }
          if (!found)
            throw new SAXException("Invalid DTD : no sub element defined in '"
                + xmlName + "' element for sub node '" + astSubNode
                + "' of AST node '" + astName + "'");
        }

        x = xmlElem.attributes.keySet().iterator();
        while (x.hasNext()) {
          final String xmlAttr = (String) x.next();
          final String astAttr = loader.getASTAttribute(xmlName, xmlAttr);
          if (astElem.getAttribute(astAttr) == null) {
            throw new SAXException(
                "Invalid DTD : no AST attribute defined for XML attribute '"
                    + xmlAttr + "' of element '" + xmlName + "'");
          }
        }

        a = astElem.attributes.keySet().iterator();
        while (a.hasNext()) {
          final String astAttr = (String) a.next();
          final String xmlAttr = loader.getXMLAttribute(astName, astAttr);
          if (xmlElem.getAttribute(xmlAttr) == null) {
            throw new SAXException(
                "Invalid DTD : no XML attribute defined in element '" + xmlName
                    + "' for attribute '" + astAttr + "' of AST node '"
                    + astName + "'");
          }
        }
      }
    }
  }

  private Map checkProcessingInstruction(final String pi)
      throws SAXException {
    final Map m = new HashMap();
    final StringTokenizer st = new StringTokenizer(pi);
    if (st.hasMoreTokens()) {
      st.nextToken();
    } else {
      throw new SAXException("Invalid processing instruction: " + pi);
    }
    while (st.hasMoreTokens()) {
      final String t = st.nextToken();
      final int p = t.indexOf("=\"");
      if (p == -1 || !t.endsWith("\"")) {
        throw new SAXException("Invalid processing instruction: " + pi);
      }
      final String key = t.substring(0, p);
      final String value = t.substring(p + 2, t.length() - 1);
      m.put(key, value);
    }
    return m;
  }

  private void checkASTClass(final Class c, final DTDElement elt) {
    final Set meths = new HashSet();
    final Method[] methods = c.getMethods();
    for (final Method method : methods) {
      meths.add(method.getName() + Type.getMethodDescriptor(method));
    }
    final Iterator i = meths.iterator();
    while (i.hasNext()) {
      final String meth = (String) i.next();
      if (meth.startsWith("get") && meth.endsWith("()Ljava/lang/String;")) {
        String attr = meth.substring(3, meth.indexOf('('));
        if (meths.contains("set" + attr + "(Ljava/lang/String;)V")) {
          attr = Character.toLowerCase(attr.charAt(0)) + attr.substring(1);
          elt.setAttribute(attr, new DTDAttribute(attr));
        }
      } else if (meth.startsWith("get") && meth.indexOf("()") != -1) {
        String subElt = meth.substring(3, meth.indexOf('('));
        String subEltDesc = meth.substring(meth.indexOf(')') + 1);
        DTDName dtdName = null;
        if (subEltDesc.charAt(0) == '[') {
          if (subElt.endsWith("s")) {
            subElt = subElt.substring(0, subElt.length() - 1);
            subEltDesc = subEltDesc.substring(1);
            final String addMeth = "add" + subElt + "(" + subEltDesc + ")V";
            final String removeMeth = "remove" + subElt + "(" + subEltDesc
                + ")V";
            if (meths.contains(addMeth) && meths.contains(removeMeth)) {
              subElt = Character.toLowerCase(subElt.charAt(0))
                  + subElt.substring(1);
              dtdName = new DTDName(subElt);
              dtdName.setCardinal(DTDCardinal.ZEROMANY);
            }
          }
        } else if (meths.contains("set" + subElt + "(" + subEltDesc + ")V")) {
          subElt = Character.toLowerCase(subElt.charAt(0))
              + subElt.substring(1);
          dtdName = new DTDName(subElt);
        }
        if (dtdName != null) {
          boolean add = true;
          final DTDItem[] items = ((DTDSequence) elt.getContent()).getItems();
          for (int j = 0; j < items.length; ++j) {
            if (((DTDName) items[j]).getValue().equals(dtdName.getValue())) {
              add = false;
              break;
            }
          }
          if (add) {
            ((DTDSequence) elt.getContent()).add(dtdName);
          }
        }
      }
    }
  }

  private void checkDTDItem(final DTDItem item, final Set names) {
    if (item instanceof DTDContainer) {
      final DTDItem[] items = ((DTDContainer) item).getItems();
      for (final DTDItem element : items) {
        checkDTDItem(element, names);
      }
    } else if (item instanceof DTDName) {
      final String name = ((DTDName) item).getValue();
      if (names.contains(name)) {
        throw new RuntimeException(
            "Regular expressions with several occurences "
                + "of the same sub element name are not supported");
      }
      names.add(name);
    }
  }

  private Arities getArities(final DTDItem item, final Arity arity) {
    final Arities result = new Arities();
    final Arity a = arity == null ? new Arity(item) : arity
        .mult(new Arity(item));
    if (item instanceof DTDChoice) {
      for (final DTDItem subItem : ((DTDChoice) item).getItems()) {
        result.union(getArities(subItem, a));
      }
    } else if (item instanceof DTDSequence) {
      for (final DTDItem subItem : ((DTDSequence) item).getItems()) {
        result.add(getArities(subItem, a));
      }
    } else if (item instanceof DTDMixed) {
      for (final DTDItem subItem : ((DTDMixed) item).getItems()) {
        result.union(getArities(subItem, a));
      }
    } else if (item instanceof DTDName) {
      result.put(((DTDName) item).getValue(), a);
    }
    return result;
  }

  static class Arities extends HashMap {

    void add(final Arities arities) {
      final Iterator i = arities.keySet().iterator();
      while (i.hasNext()) {
        final String item = (String) i.next();
        final Arity a = getArity(item);
        final Arity b = arities.getArity(item);
        put(item, a.add(b));
      }
    }

    void mult(final Arities arities) {
      final Iterator i = arities.keySet().iterator();
      while (i.hasNext()) {
        final String item = (String) i.next();
        final Arity a = getArity(item);
        final Arity b = arities.getArity(item);
        put(item, a.mult(b));
      }
    }

    void union(final Arities arities) {
      final Iterator i = arities.keySet().iterator();
      while (i.hasNext()) {
        final String item = (String) i.next();
        final Arity a = getArity(item);
        final Arity b = arities.getArity(item);
        put(item, a.union(b));
      }
    }

    private Arity getArity(final String item) {
      final Arity a = get(item);
      return a == null ? new Arity(0, 0) : a;
    }
  }

  static class Arity {

    final float min;

    final float max;

    Arity(final float min, final float max) {
      this.min = min;
      this.max = max;
    }

    Arity(final DTDItem i) {
      final DTDCardinal c = i.getCardinal();
      if (c == DTDCardinal.NONE) {
        min = 1;
        max = 1;
      } else if (c == DTDCardinal.OPTIONAL) {
        min = 0;
        max = 1;
      } else if (c == DTDCardinal.ZEROMANY) {
        min = 0;
        max = Float.POSITIVE_INFINITY;
      } else { // if (c == DTDCardinal.ONEMANY)
        min = 0;
        max = Float.POSITIVE_INFINITY;
      }
    }

    Arity add(final Arity a) {
      return new Arity(min + a.min, max + a.max);
    }

    Arity mult(final Arity a) {
      return new Arity(min * a.min, max * a.max);
    }

    Arity union(final Arity a) {
      return new Arity(Math.min(min, a.min), Math.max(max, a.max));
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy