reteoodotdumpvisitor.java

来自「drools 一个开放源码的规则引擎」· Java 代码 · 共 443 行

JAVA
443
字号
package org.drools.reteoo;

/*
 * $Id: ReteooDotDumpVisitor.java,v 1.11 2005/02/02 00:23:22 mproctor Exp $
 *
 * Copyright 2004-2004 (C) The Werken Company. All Rights Reserved.
 *
 * Redistribution and use of this software and associated documentation
 * ("Software"), with or without modification, are permitted provided that the
 * following conditions are met:
 *
 * 1. Redistributions of source code must retain copyright statements and
 * notices. Redistributions must also contain a copy of this document.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. The name "drools" must not be used to endorse or promote products derived
 * from this Software without prior written permission of The Werken Company.
 * For written permission, please contact bob@werken.com.
 *
 * 4. Products derived from this Software may not be called "drools" nor may
 * "drools" appear in their names without prior written permission of The Werken
 * Company. "drools" is a trademark of The Werken Company.
 *
 * 5. Due credit should be given to The Werken Company. (http://werken.com/)
 *
 * THIS SOFTWARE IS PROVIDED BY THE WERKEN COMPANY AND CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE WERKEN COMPANY OR ITS CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 */

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import org.drools.rule.Declaration;

/**
 * Produces a graph in GraphViz DOT format.
 *
 * @see http://www.research.att.com/sw/tools/graphviz/
 * @see http://www.pixelglow.com/graphviz/
 *
 * @author Andy Barnett
 */
public class ReteooDotDumpVisitor extends ReflectiveVisitor
{
    /** String displayed for Null values. */
    private static final String NULL_STRING = "<NULL>";

    /** Amount of indention for Node and Edge lines. */
    private static final String INDENT = "    ";

    /** The PrintStream where the DOT output will be written. */
    private final PrintStream out;

    /**
     * Keeps track of visited JoinNode DOT IDs. This mapping allows the visitor
     * to recognize JoinNodes it has already visited and as a consequence link
     * existing nodes back together. This is vital to the Visitor being able to
     * link two JoinNodeInputs together through their common JoinNode.
     */
    private Set visitedNodes = new HashSet( );

    /** Counter used to produce distinct DOT IDs for Null Nodes. */
    private int nullDotId;

    /**
     * Constructor.
     */
    public ReteooDotDumpVisitor(PrintStream outPrintStream)
    {
        out = outPrintStream;
    }

    /**
     * Default visitor if an unknown object is visited.
     */
    public void visitObject(Object object)
    {
        makeNode( object,
                  "Unknown Object",
                  "object: " + object + newline + "class: " + object.getClass( ) );
    }

    /**
     * Null visitor if a NULL object gets visited. Unique String objects are
     * generated to ensure every NULL object is distinct.
     */
    public void visitNull()
    {
        makeNode( "NULL" + nullDotId++,
                  NULL_STRING );
    }

    /**
     * RuleBaseImpl visits its Rete.
     */
    public void visitRuleBaseImpl(RuleBaseImpl ruleBase)
    {
        visit( ruleBase.getRete( ) );
    }

    /**
     * Rete visits each of its ObjectTypeNodes.
     */
    public void visitRete(Rete rete)
    {
        makeNode( rete,
                  "RETE-OO" );
        for ( Iterator i = rete.getObjectTypeNodeIterator( ); i.hasNext( ); )
        {
            Object nextNode = i.next( );
            makeEdge( rete,
                      nextNode );
            visitNode( nextNode );
        }
    }

    /**
     * ObjectTypeNode displays its objectType and then visits each of its
     * ParameterNodes.
     */
    public void visitObjectTypeNode(ObjectTypeNode node)
    {
        makeNode( node,
                  "ObjectTypeNode",
                  "objectType: " + node.getObjectType( ) );
        for ( Iterator i = node.getParameterNodeIterator( ); i.hasNext( ); )
        {
            Object nextNode = i.next( );
            makeEdge( node,
                      nextNode );
            visitNode( nextNode );
        }
    }

    /**
     * ParameterNode displays its declaration and then visits its TupleSink.
     */
    public void visitParameterNode(ParameterNode node)
    {
        makeTupleSourceNode( node, "ParameterNode", "TupleSource", "decl: " + format( node.getDeclaration( ) ) );
    }

    private void makeTupleSourceNode( TupleSource node, String nodeType, String tupleType, String label )
    {
        makeNode( node,
                  nodeType,
                  tupleType,
                  label );
        for ( Iterator i = node.getTupleSinks( ).iterator(); i.hasNext(); )
        {
            Object nextNode = i.next();
            makeEdge( node,
                      nextNode );
            visitNode( nextNode );
        }
    }

    /**
     * ConditionNode displays its condition and tuple Declarations and then
     * visits its TupleSink.
     */
    public void visitConditionNode(ConditionNode node)
    {
        makeTupleSourceNode( node,
                  "ConditionNode",
                  "TupleSource/TupleSink",
                  "condition: " + node.getCondition( ) + newline + format( node.getTupleDeclarations( ),
                                                                           "tuple" ) );
    }

    /**
     * JoinNodeInput displays its side (LEFT/RIGHT) and then visits its
     * JoinNode.
     */
    public void visitJoinNodeInput(JoinNodeInput node)
    {
        makeNode( node,
                  "JoinNodeInput",
                  "TupleSink",
                  node.getSide( ) == JoinNodeInput.LEFT ? "LEFT" : "RIGHT" );
        Object nextNode = node.getJoinNode( );
        makeEdge( node,
                  nextNode );
        visitNode( nextNode );
    }

    /**
     * JoinNode displays its common Declarations and tuple Declarations and then
     * visits its TupleSink.
     */
    public void visitJoinNode(JoinNode node)
    {
        makeTupleSourceNode( node,
                  "JoinNode",
                  "TupleSource",
                  format( node.getCommonDeclarations( ),
                          "common" ) + newline + format( node.getTupleDeclarations( ),
                                                         "tuple" ) );
    }

    /**
     * TerminalNode displays its rule.
     */
    public void visitTerminalNode(TerminalNode node)
    {
        makeNode( node,
                  "TerminalNode",
                  "TupleSink",
                  "rule: " + node.getRule( ).getName( ) );
    }

    /**
     * Helper method to ensure nodes are not visited more than once.
     */
    private void visitNode(Object node)
    {
        if ( !visitedNodes.contains( dotId( node ) ) )
        {
            visitedNodes.add( dotId( node ) );
            visit( node );
        }
    }

    /**
     * Helper method for makeNode().
     */
    private void makeNode(Object object,
                          String type,
                          String label)
    {
        makeNode( object,
                  type,
                  null,
                  label );
    }

    /**
     * Helper method for makeNode().
     */
    private void makeNode(Object object,
                          String nodeType,
                          String tupleType,
                          String label)
    {
        makeNode( object,
                  nodeType + "@" + dotId( object ) + newline + (null == tupleType ? "" : "(" + tupleType + ")" + newline ) + label );
    }

    /**
     * Outputs a DOT node line: "ID" [label="..."];
     */
    private void makeNode(Object object,
                          String label)
    {
        out.println( INDENT + "\"" + dotId( object ) + "\" " +
            "[" + getStyle( object ) + ", label=\"" + format( label ) + "\"];" );
    }

    /**
     * Outputs a DOT edge line: "FROM_ID" -> "TO_ID";
     */
    private void makeEdge(Object fromNode,
                          Object toNode)
    {
        out.println( INDENT + "\"" + dotId( fromNode ) + "\" -> \"" + dotId( toNode ) + "\";" );
    }

    /**
     * The identity hashCode for the given object is used as its unique DOT
     * identifier.
     */
    private static String dotId(Object object)
    {
        return Integer.toHexString( System.identityHashCode( object ) ).toUpperCase( );
    }

    /**
     * Formats a Set of Declarations for display.
     */
    private String format(Set declarationSet,
                          String declString)
    {
        if ( declarationSet.isEmpty( ) )
        {
            return "No " + declString + " declarations";
        }

        Declaration[] declarations = (Declaration[]) declarationSet.toArray( new Declaration[]{} );

        StringBuffer label = new StringBuffer( );
        int i = 0;
        for ( int max = declarations.length - 1; i < max; i++ )
        {
            label.append( declString + "Decl: " + format( declarations[i] ) + newline );
        }
        label.append( declString + "Decl: " + format( declarations[i] ) );

        return label.toString( );
    }

    /**
     * Formats a single Declaration for display.
     */
    private static String format(Declaration declaration)
    {
        return null == declaration ? NULL_STRING : declaration.getIdentifier( ) + " (" + declaration.getObjectType( ) + ")";
    }

    /**
     * Formats text for compatibility with GraphViz DOT.
     * Converting line-breaks into '\' + 'n'.
     * Escapes double-quotes.
     */
    private static String format(String label)
    {
        if ( null == label )
        {
            return NULL_STRING;
        }

        BufferedReader br = new BufferedReader( new InputStreamReader( new ByteArrayInputStream( label.getBytes( ) ) ) );

        StringBuffer buffer = new StringBuffer( );
        try
        {
            boolean firstLine = true;
            for ( String line = br.readLine( ); null != line; line = br.readLine( ) )
            {
                if ( line.trim( ).length( ) == 0 )
                {
                    continue;
                }
                if ( firstLine )
                {
                    firstLine = false;
                }
                else
                {
                    buffer.append( "\\n" );
                }
                
                if ( -1 == line.indexOf( '"' ) )
                {
                    buffer.append( line );
                }
                else
                {
                    for ( int i = 0, max = line.length( ); i < max; ++i )
                    {
                        char c = line.charAt( i );
                        if ( '"' == c )
                        {
                            buffer.append( '\\' + c );
                        }
                        else
                        {
                            buffer.append( c );
                        }
                    }
                }
            }
        }
        catch ( IOException e )
        {
            throw new RuntimeException( "Error formatting '" + label + "': " + e.getMessage( ) );
        }

        return buffer.toString( );
    }

    /**
     * Returns the desired Node shape for the given object.
     * 
     * @param node The Rete graph Node to be formatted
     * 
     * @return a DOT style-string for the given Node
     */
    private String getStyle( Object node )
    {
        String style;
        
        if ( node instanceof Rete )
        {
            // Preferably this would be a "rectangle" with rounded sides
            // but GraphViz doesn't support "rounded" & "filled" on one node.
            style = "style=\"filled\", shape=\"ellipse\"";
        }
        else if ( node instanceof ObjectTypeNode )
        {
            style = "style=\"filled\", fillcolor=\"cyan4\", shape=\"rectangle\"";
        }
        else if ( node instanceof ParameterNode )
        {
            style = "style=\"filled\", fillcolor=\"cyan3\", shape=\"rectangle\"";
        }
        else if ( node instanceof ConditionNode )
        {
            style = "style=\"filled\", fillcolor=\"yellow3\", shape=\"diamond\"";
        }
        else if ( node instanceof JoinNodeInput )
        {
            style = "style=\"filled\", fillcolor=\"chartreuse\", shape=\"invtriangle\"";
        }
        else if ( node instanceof JoinNode )
        {
            style = "style=\"filled\", fillcolor=\"green\", shape=\"house\"";
        }
        else if ( node instanceof TerminalNode )
        {
            // Preferably this would be a "rectangle" with rounded sides
            // but GraphViz doesn't support "rounded" & "filled" on one node.
            style = "style=\"filled\", shape=\"ellipse\"";
        }
        else
        {
            // Preferably this would be a "rectangle" with rounded sides
            // but GraphViz doesn't support "rounded" & "filled" on one node.
            style = "style=\"filled\", fillcolor=\"yellow3\", shape=\"ellipse\"";
        }
        
        return style;
    }
}

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?