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

org.netbeans.modules.schema2beansdev.TreeBuilder Maven / Gradle / Ivy

/*
 * 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.netbeans.modules.schema2beansdev;

import java.util.*;
import java.io.*;

import org.netbeans.modules.schema2beans.*;

//******************************************************************************
// BEGIN_NOI18N
// This class does not (and will not) cantain strings that need to be localized.
//******************************************************************************

/**
 *
 *  This class implements the Document Definition handler in order to build
 *  the internal tree representation of the DD DTD.
 *
 */
public class TreeBuilder implements DocDefHandler, TreeParser, HasPrefixGuesser {
    //	root element of the DTD graph
    GraphNode		rootNode;
    String 		docRoot;
    GenBeans.Config 	config;
    
    //	Current parsing pointers
    private Stack curParentGroupStack = new Stack();
    private GraphLink		curParentGroup;
    
    //	Global value of the type currently parsed (ELEMENT, ATTLIST, COMMENT)
    private Stack curElementTypeStack = new Stack();
    private int			curElementType;
    
    //	Current parsed attribute (

    private String defaultNamespace = null;
    
    
    TreeBuilder(GenBeans.Config config) {
        this.nameHash = new HashMap();
        this.curAttr = null;
        this.config = config;
    }
    
    /**
     *	Called once, when the DTD is started to be parsed.
     *	Create the GraphNode root element.
     *
     *	@param root root elemement name of the document (as the DOCTYPE
     *	specifies in the XML document)
     */
    public void startDocument(String root) {
        if (DDLogFlags.debug) {
            TraceLogger.put(TraceLogger.DEBUG, TraceLogger.SVC_DD,
                            DDLogFlags.DBG_DTD, 1,
                            DDLogFlags.STARTDOC, root);

            config.messageOut.println("Building the schema object graph.");
        }
        this.docRoot = root;
    }
    
    /**
     *	Called when the DTD parsing is over.
     *
     *	At this time, the DTD object graph is entirely built. The method
     *	checks the consitency of the built graph, and cleans things up a bit.
     *
     */
    public void endDocument() {
        if (DDLogFlags.debug) {
            TraceLogger.put(TraceLogger.DEBUG, TraceLogger.SVC_DD,
                            DDLogFlags.DBG_DTD, 1,
                            DDLogFlags.ENDDOC);
	
            config.messageOut.println("schema Object graph built.");
        }
	
        // Remove the starter groupings
        for (Iterator it = nameHash.values().iterator(); it.hasNext(); ) {
            GraphNode node = (GraphNode)it.next();
            GraphLink l = node.getGraphLink();
            if (l == null || l.name != null || l.getSibling() != null
                || l.isSequenceOr() || l.getGroupInstance() != Common.TYPE_1)
                continue;
            GraphLink firstChild = l.getFirstChild();
            if (firstChild != null && firstChild.getSibling() != null)
                continue;
            if (DDLogFlags.debug)
                config.messageOut.println("Removing starter group: "+l);
            node.setGraphLink(firstChild);
        }

        //
        //	We're done building the tree graph
        //	It's time now to generate the beans
        //
        try {
            findRootNode();
        } catch (Schema2BeansException e) {
            throw new Schema2BeansRuntimeException(e);
        }
	
        if (DDLogFlags.debug) {
            config.messageOut.println(this.dump());
        }
    }
    
    /**
     *	Either create the GraphNode for the element named name, or
     *	get it from the hash table. This method can be called either
     *	by the startElement() method (an element definition has been
     *	found in the DTD) or by the element() method (an element is
     *	referenced by another one).
     *
     *	@param name name of the element as the DTD parser reads it
     *	@param original true if the element definition has been
     *	read, false if we are just asking to reference the element.
     *	This parameter allows to check than an element is not
     *	defined twice and is at least defined once.
     *	@return the unique GraphNode object of the named element
     */
    private static final int CREATE 	= 1;
    private static final int GET 		= 2;
    private static final int REFERENCE 	= 3;
    private GraphNode getGraphNode(String uniqueName, String name, int mode) throws Schema2BeansException {
        //String uniqueName = name;
        uniqueName = name;
        
        //  Find out if we already know about it
        GraphNode node = (GraphNode)this.nameHash.get(uniqueName);
	
        if (node != null) {
            //	We know about it
            if (false && node.isCreated() && (mode == CREATE)) {
                throw new Schema2BeansException(Common.getMessage("DuplicateElement_msg", uniqueName));
            }
        }
        else {
            //
            //  First time we hear about this element. Create the GraphNode
            //  Object and its GraphLink link. The purpose of this GraphLink
            //  object is to hold the siblings/children links for this node.
            //  This GraphLink does _not_ reference the element.
            //  (graphLink.element = null).
            //
            node = new GraphNode(name, uniqueName);
            node.setGraphLink(new GraphLink(null));
            this.nameHash.put(uniqueName, node);
            //System.out.println("Created new GraphNode: "+node);
        }
	
        //
        //  Called to get the original: mark it. If we are later called again
        //  to get the original that means we have two element definition in
        //  the DTD and we can throw an exception (see above).
        //  Called to get a reference: increment the node usage. The root
        //  of the node is never referenced and will keep a refCount=0.
        //
        if (mode == CREATE)
            node.setCreated(true);
        else
            if (mode == REFERENCE)
                node.incrRefCount();
	
        return node;
    }
    
    
    /**
     *	Called each time a DTD . The first element name doesn't
     *	generate a call to this method (@see startElement).
     *
     *	This is where the subtree of the element definition is built.
     *	The element to add might be a child or sibling to the previous
     *	element. If the element is preceded by a '(', this is child
     *	(@see startGroupElements), otherwise the element is a sibling.
     *
     *	@param name the name of the element defined within the 
     *	declaration.
     *	@param instance has one of the three values: TYPE_0_1,
     *	TYPE_1, TYPE_0_N, TYPE_1_N
     *
     */
    public void element(String uniqueName, String typeName,
                        String attrName, String attrNamespace,
                        int instance, boolean externalType,
                        String defaultValue) {
        if (DDLogFlags.debug) {
            TraceLogger.put(TraceLogger.DEBUG, TraceLogger.SVC_DD,
                            DDLogFlags.DBG_DTD, 1,
                            DDLogFlags.ELEMENT,
                            attrName + " : " + typeName + instanceToString(instance, false));
        }
        try {
            if (curElementType == Common.NONE && !externalType) {
                if (DDLogFlags.debug)
                    System.out.println("Top element def for "+attrName);
                GraphNode attrNode = getGraphNode(uniqueName, attrName, CREATE);
                GraphNode node = getGraphNode(uniqueName, typeName, REFERENCE);
                attrNode.setAlias(node);
            } else if (curElementType == Common.ELEMENT) {
                //	Get the element reference
                GraphNode node = getGraphNode(uniqueName, typeName, REFERENCE);
	    
                GraphLink link = new GraphLink(attrName, attrNamespace);
                link.setDefaultValue(defaultValue);
                //System.out.println("curParentGroup="+curParentGroup+" curParentGroup.sibling="+curParentGroup.getSibling());
                curParentGroup.addChild(link);
                link.element = node;
                link.setElementInstance(instance);

                if (externalType)
                    node.setJavaType(typeName);
                //System.out.println("Created new GraphLink: "+link+" link.parent="+link.parent+" link.sibling="+link.sibling);
            } else if (curElementType == Common.ATTLIST) {
                //	If the current attribute is completly built, signal the
                //	Parser by throwing the exception.
                if (this.curAttr.isComplete())
                    throw new DocDefParser.MissingEndOfEltException(curAttr.getPropertyName());
		
                if (defaultValue != null)
                    this.curAttr.setDefaultValue(defaultValue);
                this.curAttr.addValue(attrName, attrNamespace);
                if (externalType)
                    curAttr.setJavaType(typeName);
            }
        } catch (Schema2BeansException e) {
            throw new Schema2BeansRuntimeException(e);
        }
    }
    public void element(String uniqueName, String typeName, int instance) {
        element(uniqueName, typeName, typeName, null, instance, false, null);
    }

    public void addExtraDataNode(String uniqueName, String typeName, Object data) throws Schema2BeansException {
        //System.out.println("** addExtraDataNode: typeName="+typeName+" data="+data);
        GraphNode node = getGraphNode(uniqueName, typeName, GET);
        node.addExtraData(data);
    }

    public void addExtraDataCurLink(Object data) {
        //System.out.println("** addExtraDataCurLink: data="+data+" curParentGroup.name="+((curParentGroup == null) ? "curParentGroup null" : curParentGroup.name));
        if (curElementType == Common.ATTLIST) {
            //System.out.println("curElementType == Common.ATTLIST, curAttr="+curAttr);
            if (curAttr != null)
                curAttr.addExtraData(data);
        } else {
            //System.out.println("lastChild="+curParentGroup.getLastChild());
            if (curParentGroup != null && curParentGroup.getLastChild() != null) {
                curParentGroup.getLastChild().extraData.add(data);
            }
        }
    }

    public void addExtraDataNode(String uniqueName, String typeName, Object[] data) throws Schema2BeansException {
        //System.out.println("** addExtraDataNode: typeName="+typeName+" data="+data);
        GraphNode node = getGraphNode(uniqueName, typeName, GET);
        if (data != null)
            for (int i=0; i < data.length; i++)
                node.addExtraData(data[i]);
    }

    public void setUnion(String uniqueName, String typeName, boolean value) throws Schema2BeansException {
        GraphNode node = getGraphNode(uniqueName, typeName, GET);
        node.setUnion(value);
    }

    public void addExtraDataCurLink(Object[] data) {
        //System.out.println("** addExtraDataCurLink: data="+data+" curParentGroup.name="+((curParentGroup == null) ? "curParentGroup null" : curParentGroup.name));
        if (curElementType == Common.ATTLIST) {
            //System.out.println("curElementType == Common.ATTLIST, curAttr="+curAttr);
            if (curAttr != null && data != null)
                for (int i=0; i < data.length; i++)
                    curAttr.addExtraData(data[i]);
        } else {
            //System.out.println("lastChild="+curParentGroup.getLastChild());
            if (curParentGroup != null && curParentGroup.getLastChild() != null) {
                if (data != null)
                    for (int i=0; i < data.length; i++)
                        curParentGroup.getLastChild().extraData.add(data[i]);
            }
        }
    }

    public void nillable(boolean value) {
        //System.out.println("nillable="+value);
        if (curParentGroup != null && curParentGroup.getLastChild() != null)
            curParentGroup.getLastChild().setNillable(value);
        /*
        else
            System.err.println("no parent group for nillable");
        */
    }
    
    public void setAbstract(String uniqueName, String name, boolean value) {
        //System.out.println("Setting javaType of "+name+" to "+javaType);
        if (this.curElementType == Common.ATTLIST) {
        } else {
            //	Get the element reference
            GraphNode node;
            try {
                node = getGraphNode(uniqueName, name, GET);
            } catch (Schema2BeansException e) {
                throw new Schema2BeansRuntimeException(e);
            }
            node.setAbstract(value);
        }
    }
    
    /**
     * set an extended property on a GraphNode
     */
    public void setExtendedProperty(String uniqueName, String typeName, String propertyName,
                                    Object value) throws Schema2BeansException {
        GraphNode node = getGraphNode(uniqueName, typeName, GET);
        node.setExtendedProperty(propertyName, value);
    }

    /**
     * Called to request that the graph node named name be of a certain
     * Java class.  If the current element type is an attribute, then
     * we set the javaType of that attribute instead.
     * @param javaType is the name of a Java class (eg, "java.lang.Integer", or "int").
     */
    public void javaType(String uniqueName, String name, String javaType) {
        //System.out.println("Setting javaType of "+name+" to "+javaType);
        if (this.curElementType == Common.ATTLIST) {
            curAttr.setJavaType(javaType);
        } else {
            //	Get the element reference
            GraphNode node;
            try {
                node = getGraphNode(uniqueName, name, GET);
            } catch (Schema2BeansException e) {
                throw new Schema2BeansRuntimeException(e);
            }
            node.setJavaType(javaType);
            node.setCreated(false);
        }
    }
    
    public void setExtension(String uniqueName, String typeName, String extendsName) throws Schema2BeansException {
        if (curElementType == Common.ATTLIST) {
        } else {
            GraphNode node;
            GraphNode extendsNode;
            try {
                node = getGraphNode(uniqueName, typeName, GET);
                extendsNode = getGraphNode(null, extendsName, REFERENCE);
            } catch (Schema2BeansException e) {
                throw new Schema2BeansRuntimeException(e);
            }
            node.setExtension(extendsNode);
        }
    }

    /**
     *	Called when a parenthese is found, meaning that the following
     *	elements (element() calls) should be considered as semantically
     *	grouped.
     *
     *	Creates a child GraphLink from the current link to group
     *	all the further elements of this group. If any propriety
     *	is defined for this group (as *, ? or + or |) this will be set later
     *	on the current link (as the parent of any of the elements graph link
     *	objects).
     */
    public void startGroupElements() {
        if (DDLogFlags.debug) {
            TraceLogger.put(TraceLogger.DEBUG, TraceLogger.SVC_DD,
                            DDLogFlags.DBG_DTD, 5,
                            DDLogFlags.STARTGRP);
        }
	
        //	A new parenthese in the parsing makes a new GraphLink.
        if (this.curElementType == Common.ELEMENT) {
            GraphLink link = new GraphLink(null);
            curParentGroup.addChild(link);
            curParentGroup = link;
            //System.out.println("curParentGroup="+curParentGroup);
        }
        else {
            if (this.curElementType == Common.ATTLIST)
                this.curAttr.setEnum(true);
        }
    }
    
    /**
     *	We are done creating the elements of a same group,
     *	set the current link to the parent of the group.
     *	This will allow either to start creating siblings (if element()
     *	is called) or go the next parent level (if this same method
     *	is called again).
     */
    public void endGroupElements(int instance) {
        if (DDLogFlags.debug) {
            TraceLogger.put(TraceLogger.DEBUG, TraceLogger.SVC_DD,
                            DDLogFlags.DBG_DTD, 5,
                            DDLogFlags.ENDGRP,
                            instanceToString(instance, false));
        }
	
        if (curElementType == Common.ELEMENT) {
            curParentGroup.setGroupInstance(instance);
            //System.out.println("curParentGroup="+curParentGroup+" instance="+instance);
            curParentGroup = curParentGroup.getParent();
        }
        else
            if (this.curElementType == Common.ATTLIST)
                this.curAttr.setEnum(false);
    }

    public void setDefaultNamespace(String ns) {
        defaultNamespace = ns;
    }

    public String getDefaultNamespace() {
        return defaultNamespace;
    }
    
    private void findRootNode() throws Schema2BeansException {
        //
        //  Find out who's the root of the graph
        //  The root of the graph is the graph node that has not been
        //  referenced. We might find zero, one or several nodes that
        //  could be the root:
        //	0: throw an exception
        //	1: use the node as the root (check with doc root if specified)
        //	1-n: use the doc root value if specified or ask
        //	for the node to use.
        //
        Iterator	 	it = this.nameHash.keySet().iterator();
        GraphNode		node;
        int			count = 0;
        List		list = new ArrayList();
	
        while (it.hasNext()) {
            String uniqueName = (String) it.next();
            node = (GraphNode) nameHash.get(uniqueName);
            if (DDLogFlags.debug) {
                System.out.println("refCount="+node.getRefCount()+" created="+node.isCreated()+" javaType="+node.getJavaType()+" uniqueName="+uniqueName+" node="+node);
            }
            if (node.isCreated() && node.getRefCount() == 0) {
                count++;
                list.add(node);
            }
        }

        if (count > 1) {
            // Attempt to find 1 that is most qualified
            int highestPoints = 0;
            GraphNode highestNode = null;
            int tieCount = 0;
            for (Iterator highit = list.iterator(); highit.hasNext(); ) {
                int points = 0;
                node = (GraphNode) highit.next();
                if (node.getAlias() != null) {
                    ++points;
                    if (node.getAlias().getRefCount() == 1)
                        ++points;
                }
                // See if the default namespace is the same as this node's.
                if (defaultNamespace == null ? node.getNamespace() == null : defaultNamespace.equals(node.getNamespace()))
                    ++points;
                GraphLink link = node.getGraphLink();
                //System.out.println("link="+link+" link.name="+link.name);
                if (link != null && !"#PCDATA".equals(link.name)) {
                    ++points;
                    GraphLink firstChild = link.getFirstChild();
                    if (firstChild != null) {
                        ++points;
                        if (firstChild.getSibling() != null)
                            ++points;
                        if (firstChild.getFirstChild() != null)
                            ++points;
                    }
                    GraphLink sibling = link.getSibling();
                    if (sibling != null) {
                        ++points;
                        if (sibling.getSibling() != null)
                            ++points;
                        if (sibling.getFirstChild() != null)
                            ++points;
                    }
                }
                //System.out.println("points="+points+" node="+node);
                if (points > highestPoints) {
                    highestPoints = points;
                    highestNode = node;
                    tieCount = 0;
                } else if (points == highestPoints) {
                    ++tieCount;
                }
            }
            if (tieCount == 0 && highestNode != null) {
                count = 1;
                list.clear();
                list.add(highestNode);
            }
        }

        if (count == 1) {
            this.rootNode = (GraphNode)list.get(0);
            //	Only one element not referenced in the graph
            if (this.docRoot != null) {
                if (!this.docRoot.equals(this.rootNode.getName())) {
                    String str = "Mismatch between doc root name specified (" +
                        this.docRoot +
                        ") and the root name found in the DTD graph (" +
                        this.rootNode.getName() +")";
                    throw new IllegalStateException(str);
                }
            }
        } else if (count == 0) {
            this.rootNode = null;
            if (docRoot != null) {
                it = this.nameHash.values().iterator();
                while (it.hasNext()) {
                    node = (GraphNode)it.next();
                    if (docRoot.equals(node.getName())) {
                        rootNode = node;
                        break;
                    }
                }
            }
            if (rootNode == null)
                throw new IllegalStateException(Common.getMessage("NoRootElementCandidate"));
        } else {
            //	List the elements and pick the root (if specified) or ask
            config.messageOut.println("The following elements could be the root "
                                      + "of the document:");
            for (int i=0; i=list.size()) {
                        throw new IllegalStateException(errStr);
                    }
                    else {
                        this.rootNode = (GraphNode)list.get(i);
                    }
                }
                catch(Exception e) {
                    TraceLogger.error(e);
                    throw new Schema2BeansNestedException(errStr, e);
                }
            }
        }
	
        if (DDLogFlags.debug)
            config.messageOut.println("Using " + this.rootNode.getName()
                                      + " as the root of the document.");
    }
    
    //
    static String instanceToString(int instance, boolean bean) {
        switch (instance) {
	    case Common.TYPE_0_1:
            if (bean)
                return "[0,1]";
            else
                return "?";
	    case Common.TYPE_0_N:
            if (bean)
                return "[0,n]";
            else
                return "*";
	    case Common.TYPE_1_N:
            if (bean)
                return "[1,n]";
            else
                return "+";
        }
        return "";
    }
    
    //
    static String typeToString(int type) {
        switch (type) {
	    case Common.COMMENT:
            return "comment";
	    case Common.ELEMENT:
            return "element";
	    case Common.ATTLIST:
            return "attlist";
        }
        return "unknown value: " + type;
    }
    
    /**
     *	TreeParser interface. This is what the BeanBuilder uses to get
     *	elements of the tree. The goal is to try to keep separated
     *	the object graph implementation from its usage.
     *	Not sure, this is very useful though, since the tree builder
     *	knows the gory details of the graph. Just a gentle way to ask
     *	for the graph.
     */
    public GraphNode[] getNodes() {
        //
        // Try to give the results back with some order from top to bottom
        // (right now, it's BFS (Breadth First Search)).
        //
        int maxSize = nameHash.values().size();
        List ret = new ArrayList(maxSize);
        Map insertedNodes = new HashMap();
        Map ignoredNodes = new HashMap();
        getNodesInsertNode(rootNode, ret, insertedNodes, ignoredNodes);
        getNodes(rootNode.getGraphLink(), ret, insertedNodes, ignoredNodes);
        if (!config.isRemoveUnreferencedNodes()) {
            for (Iterator it = nameHash.values().iterator();
                 it.hasNext(); ) {
                GraphNode node = (GraphNode) it.next();
                if (!insertedNodes.containsKey(node) && !ignoredNodes.containsKey(node)) {
                    config.messageOut.println(Common.getMessage("MSG_FoundUnreferencedNode", node.toString()));
                    ret.add(node);
                }
            }
        }
        /*
            for (int i = 0; i < maxSize; ++i)
                System.out.println("ret["+i+"]="+ret[i]);
        */
        return (GraphNode[]) ret.toArray(new GraphNode[0]);
    }

    private void getNodes(GraphLink l, List ret,
                          Map insertedNodes, Map ignoredNodes) {
        Stack linkStack = new Stack();
        linkStack.push(l);
        while (!linkStack.isEmpty()) {
            l = (GraphLink) linkStack.pop();
            for (; l != null; l = l.getSibling()) {
                if (l.element != null) {
                    if (!insertedNodes.containsKey(l.element)) {
                        getNodesInsertNode(l.element, ret, insertedNodes, ignoredNodes);
                        linkStack.push(l.element.getGraphLink());
                    }
                }
                linkStack.push(l.getFirstChild());
            }
        }
    }

    private void getNodesInsertNode(GraphNode node, List ret,
                                    Map insertedNodes, Map ignoredNodes) {
        if (insertedNodes.containsKey(node)) {
            //System.out.println("Found a duplicate in my insert journey: "+node);
            return;
        }
        ret.add(node);
        insertedNodes.put(node, null);
        if (node.getExtension() != null)
            getNodesInsertNode(node.getExtension(), ret, insertedNodes, ignoredNodes);
        GraphNode alias = node.getAlias();
        if (alias != null && !insertedNodes.containsKey(alias)) {
            if (alias.getRefCount() <= 1) {
                // Only referenced by the thing which has an alias pointer to it.
                ignoredNodes.put(alias, null);
            } else {
                ret.add(alias);
                insertedNodes.put(alias, null);
            }
        }
        return;
    }
    
    public GraphNode getNode(String uniqueName) {
        return (GraphNode)this.nameHash.get(uniqueName);
    }
    
    public GraphNode getRoot() {
        return this.rootNode;
    }
    
    private static final String INDENT = "  ";
    
    static void dumpAttributes(GraphNode elt, StringBuffer str, String indent) {
        AttrProp[] attrList = elt.getAttributes();
	
        for (int i=0; i




© 2015 - 2025 Weber Informatics LLC | Privacy Policy