alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*
 *                 Sun Public License Notice
 * 
 * The contents of this file are subject to the Sun Public License
 * Version 1.0 (the "License"). You may not use this file except in
 * compliance with the License. A copy of the License is available at
 * http://www.sun.com/
 * 
 * The Original Code is NetBeans. The Initial Developer of the Original
 * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

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 {
    //	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 name, int mode) throws Schema2BeansException {
        String 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 (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(attrName, CREATE);
                GraphNode node = getGraphNode(typeName, REFERENCE);
                attrNode.setAlias(node);
            } else if (curElementType == Common.ELEMENT) {
                //	Get the element reference
                GraphNode node = getGraphNode(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 typeName, int instance) {
        element(typeName, typeName, typeName, null, instance, false, null);
    }

    public void addExtraDataNode(String typeName, Object data) throws Schema2BeansException {
        //System.out.println("** addExtraDataNode: typeName="+typeName+" data="+data);
        GraphNode node = getGraphNode(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 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");
        */
    }
    
    /**
     * set an extended property on a GraphNode
     */
    public void setExtendedProperty(String typeName, String propertyName,
                                    Object value) throws Schema2BeansException {
        GraphNode node = getGraphNode(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(name, GET);
            } catch (Schema2BeansException e) {
                throw new Schema2BeansRuntimeException(e);
            }
            node.setJavaType(javaType);
            node.setCreated(false);
        }
    }

    /**
     *	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.values().iterator();
        GraphNode		node;
        int			count = 0;
        List		list = new ArrayList();
	
        while (it.hasNext()) {
            node = (GraphNode)it.next();
            if (DDLogFlags.debug) {
                System.out.println("refCount="+node.getRefCount()+" created="+node.isCreated()+" javaType="+node.getJavaType()+" 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[ret.size()]);
    }

    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);
        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
... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.