Clover coverage report - Drools - 2.0-rc2
Coverage timestamp: Wed May 11 2005 07:12:26 BST
file stats: LOC: 533   Methods: 12
NCLOC: 297   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
PythonInterp.java 63.5% 83.7% 91.7% 78.8%
coverage coverage
 1    package org.drools.semantics.python;
 2   
 3    /*
 4    * $Id: PythonInterp.java,v 1.7.2.4 2005/05/03 23:45:46 mproctor Exp $
 5    *
 6    * Copyright 2002-2004 (C) The Werken Company. All Rights Reserved.
 7    *
 8    * Redistribution and use of this software and associated documentation
 9    * ("Software"), with or without modification, are permitted provided that the
 10    * following conditions are met:
 11    *
 12    * 1. Redistributions of source code must retain copyright statements and
 13    * notices. Redistributions must also contain a copy of this document.
 14    *
 15    * 2. Redistributions in binary form must reproduce the above copyright notice,
 16    * this list of conditions and the following disclaimer in the documentation
 17    * and/or other materials provided with the distribution.
 18    *
 19    * 3. The name "drools" must not be used to endorse or promote products derived
 20    * from this Software without prior written permission of The Werken Company.
 21    * For written permission, please contact bob@werken.com.
 22    *
 23    * 4. Products derived from this Software may not be called "drools" nor may
 24    * "drools" appear in their names without prior written permission of The Werken
 25    * Company. "drools" is a registered trademark of The Werken Company.
 26    *
 27    * 5. Due credit should be given to The Werken Company.
 28    * (http://drools.werken.com/).
 29    *
 30    * THIS SOFTWARE IS PROVIDED BY THE WERKEN COMPANY AND CONTRIBUTORS ``AS IS''
 31    * AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 32    * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 33    * ARE DISCLAIMED. IN NO EVENT SHALL THE WERKEN COMPANY OR ITS CONTRIBUTORS BE
 34    * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 35    * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 36    * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 37    * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 38    * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 39    * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 40    * POSSIBILITY OF SUCH DAMAGE.
 41    *
 42    */
 43   
 44    import java.io.BufferedReader;
 45    import java.io.ByteArrayInputStream;
 46    import java.io.IOException;
 47    import java.io.InputStreamReader;
 48    import java.io.Serializable;
 49    import java.util.Iterator;
 50    import java.util.Map;
 51   
 52    import org.drools.WorkingMemory;
 53    import org.drools.rule.Declaration;
 54    import org.drools.rule.Rule;
 55    import org.drools.semantics.base.ClassObjectType;
 56    import org.drools.spi.DefaultKnowledgeHelper;
 57    import org.drools.spi.Functions;
 58    import org.drools.spi.ObjectType;
 59    import org.drools.spi.RuleBaseContext;
 60    import org.drools.spi.Tuple;
 61    import org.python.core.Py;
 62    import org.python.core.PyCode;
 63    import org.python.core.PyDictionary;
 64    import org.python.core.PyModule;
 65    import org.python.core.PyObject;
 66    import org.python.core.PyString;
 67    import org.python.core.PySystemState;
 68    import org.python.core.parser;
 69    import org.python.parser.ast.modType;
 70   
 71    /**
 72    * Base class for Jython interpreter-based Python semantic components.
 73    *
 74    * @author <a href="mailto:bob@eng.werken.com">bob mcwhirter </a>
 75    */
 76    public class PythonInterp implements Serializable
 77    {
 78    /** The line separator system property ("\n" on UNIX). */
 79    private static final String LINE_SEPARATOR = System.getProperty( "line.separator" );
 80   
 81    // ------------------------------------------------------------
 82    // Class Initialization
 83    // ------------------------------------------------------------
 84   
 85    // ------------------------------------------------------------
 86    // Instance members
 87    // ------------------------------------------------------------
 88   
 89    /** The rule. */
 90    private final Rule rule;
 91   
 92    /** Text. */
 93    private transient String text;
 94   
 95    /** Original Text */
 96    private final String origininalText;
 97   
 98    private final String type;
 99   
 100    /** The code. */
 101    private transient PyCode code;
 102   
 103    /** The AST node. */
 104    private transient modType node;
 105   
 106    private transient PyDictionary globals;
 107   
 108    /**
 109    * Initialise Jython's PySystemState
 110    */
 111    static
 112    {
 113  1 PySystemState.initialize( );
 114   
 115  1 PySystemState systemState = Py.getSystemState( );
 116  1 if ( systemState == null )
 117    {
 118  0 systemState = new PySystemState( );
 119    }
 120  1 Py.setSystemState( systemState );
 121    }
 122   
 123    // ------------------------------------------------------------
 124    // Constructors
 125    // ------------------------------------------------------------
 126   
 127    /**
 128    * Construct.
 129    */
 130  27 protected PythonInterp(String text,
 131    Rule rule,
 132    String type)
 133    {
 134  27 this.rule = rule;
 135  27 this.origininalText = text;
 136  27 this.type = type;
 137   
 138  27 compile( );
 139   
 140    }
 141   
 142  27 private void compile()
 143    {
 144  27 StringBuffer globalText = new StringBuffer( );
 145   
 146  27 Iterator it = rule.getImporter( ).getImports( ).iterator( );
 147   
 148  27 while ( it.hasNext( ) )
 149    {
 150  66 globalText.append( convertToPythonImport( ( String ) it.next( ) ) );
 151  66 globalText.append( ";" );
 152  66 globalText.append( LINE_SEPARATOR );
 153    }
 154   
 155  27 globalText.append( "def q(cond,on_true,on_false):\n" );
 156  27 globalText.append( " if cond:\n" );
 157  27 globalText.append( " return on_true\n" );
 158  27 globalText.append( " else:\n" );
 159  27 globalText.append( " return on_false\n" );
 160   
 161  27 Functions functions = rule.getRuleSet( ).getFunctions( "python" );
 162  27 if ( functions != null )
 163    {
 164  0 globalText.append( stripOuterIndention( functions.getText( ) ) );
 165    }
 166   
 167  27 if ( this.globals == null )
 168    {
 169  27 this.globals = getGlobals( globalText.toString( ) );
 170    }
 171   
 172  27 this.text = stripOuterIndention( this.origininalText );
 173   
 174  27 try
 175    {
 176  27 this.node = (modType) parser.parse( this.text,
 177    type );
 178  27 this.code = Py.compile( this.node,
 179    "<jython>" );
 180    }
 181    catch ( Exception e )
 182    {
 183  0 throw new RuntimeException( e.getLocalizedMessage( ) );
 184    }
 185    }
 186   
 187  66 private String convertToPythonImport(String importEntry)
 188    {
 189  66 int lastDot = importEntry.lastIndexOf( '.' );
 190  66 String packageText = importEntry.substring( 0, lastDot );
 191  66 String className = importEntry.substring( lastDot + 1, importEntry.length( ) );
 192  66 return "from " + packageText + " import " + className;
 193    }
 194   
 195    /**
 196    * Parses a python script and returns the globals It is used to be able to
 197    * inject imports and functions into code when being executed by
 198    * Py.runCode(...)
 199    *
 200    * @param text
 201    * @return PyDictionary globals
 202    */
 203  27 public PyDictionary getGlobals(String text)
 204    {
 205  27 PyModule module = new PyModule( "main",
 206    new PyDictionary( ) );
 207   
 208  27 PyObject locals = module.__dict__;
 209   
 210  27 Py.exec( Py.compile_flags( text,
 211    "<string>",
 212    "exec",
 213    null ),
 214    locals,
 215    locals );
 216   
 217  27 return (PyDictionary) locals;
 218    }
 219   
 220    /**
 221    * Trims leading indention from the block of text. Since Python relies on
 222    * indention as part of its syntax, any XML indention introduced needs to be
 223    * stripped out. For example, this:
 224    *
 225    * <pre>
 226    *
 227    *
 228    *
 229    * | &lt;python:consequence&gt;
 230    * | if hello == 'Hello':
 231    * | print &quot;Hi&quot;
 232    * | else:
 233    * | print &quot;Bye&quot;
 234    * | &lt;/python:consequence&gt;
 235    *
 236    *
 237    *
 238    * </pre>
 239    *
 240    * is transformed into:
 241    *
 242    * <pre>
 243    *
 244    *
 245    *
 246    * | &lt;python:consequence&gt;
 247    * |if hello == 'Hello':
 248    * | print &quot;Hi&quot;
 249    * |else:
 250    * | print &quot;Bye&quot;
 251    * | &lt;/python:consequence&gt;
 252    *
 253    *
 254    *
 255    * </pre>
 256    *
 257    * @param text
 258    * the block of text to be stripped
 259    * @return the block of text stripped of its leading indention
 260    */
 261  27 protected static String stripOuterIndention(String text)
 262    {
 263  27 try
 264    {
 265  27 if ( null == text )
 266    {
 267  0 return null;
 268    }
 269   
 270  27 BufferedReader br = new BufferedReader( new InputStreamReader( new ByteArrayInputStream( text.getBytes( ) ) ) );
 271   
 272  27 StringBuffer unindentedText = new StringBuffer( text.length( ) );
 273   
 274  27 int lineNo = 0;
 275  27 try
 276    {
 277  27 String indent = null;
 278  27 for ( String line = br.readLine( ); null != line; line = br.readLine( ) )
 279    {
 280  52 lineNo++;
 281  52 if ( "".equals( line.trim( ) ) )
 282    {
 283    // Blank lines are passed through unmodified
 284  5 unindentedText.append( line + LINE_SEPARATOR );
 285  5 continue;
 286    }
 287   
 288  47 if ( null == indent )
 289    {
 290    // The first non-bank line determines
 291    // the outer indention level
 292  27 indent = line.substring( 0,
 293    line.indexOf( line.trim( ) ) );
 294    }
 295   
 296  47 if ( line.length( ) < indent.length( ) || !line.startsWith( indent ) )
 297    {
 298    // This can catch some poorly indented Python syntax
 299  0 throw new RuntimeException( "Bad Text Indention: Line " + lineNo + ": |" + formatForException( line ) + "|" + LINE_SEPARATOR + formatForException( text ) );
 300    }
 301   
 302    // Remove the outer most indention from the line
 303  47 if ( line.startsWith( indent ) )
 304    {
 305  47 unindentedText.append( line.substring( indent.length( ) ) );
 306    }
 307  47 unindentedText.append( LINE_SEPARATOR );
 308    }
 309    }
 310    catch ( IOException e )
 311    {
 312  0 throw new RuntimeException( e.getMessage( ) );
 313    }
 314   
 315    // Remove extraneous trailing LINE_SEPARATOR
 316  27 if ( unindentedText.length( ) > 0 )
 317    {
 318  27 unindentedText.deleteCharAt( unindentedText.length( ) - 1 );
 319    }
 320   
 321  27 return unindentedText.toString( );
 322    }
 323    catch ( Exception e )
 324    {
 325    // [TODO]
 326    // The whole point of this try/catch block is to ensure that
 327    // exceptions make it out to the user; it seems something is
 328    // swallowing everything except RuntimeExceptions.
 329  0 if ( e instanceof RuntimeException )
 330    {
 331  0 throw (RuntimeException) e;
 332    }
 333   
 334  0 throw new RuntimeException( e.getMessage( ) );
 335    }
 336    }
 337   
 338    /**
 339    * Helper method to format the text block for display in error messages.
 340    * Since Python syntax errors can easily occur due to bad indention, this
 341    * method replaces all tabs with "{{tab}}" and all spaces with ".".
 342    *
 343    * @param text
 344    * the text to be formatted
 345    * @return the text with all tabs and spaces replaced for easier viewing
 346    */
 347  0 private static String formatForException(String text)
 348    {
 349  0 StringBuffer sbuf = new StringBuffer( text.length( ) * 2 );
 350  0 for ( int i = 0, max = text.length( ); i < max; i++ )
 351    {
 352  0 final char nextChar = text.charAt( i );
 353  0 if ( '\t' == nextChar )
 354    {
 355  0 sbuf.append( "{{tab}}" );
 356    }
 357    else
 358    {
 359  0 sbuf.append( nextChar );
 360    }
 361    }
 362   
 363  0 return sbuf.toString( );
 364    }
 365   
 366    // ------------------------------------------------------------
 367    // Instance methods
 368    // ------------------------------------------------------------
 369   
 370    /**
 371    * Retrieve the text to evaluate.
 372    *
 373    * @return The text to evaluate.
 374    */
 375  1 public String getText()
 376    {
 377  1 return this.origininalText;
 378    }
 379   
 380  8 protected Rule getRule()
 381    {
 382  8 return this.rule;
 383    }
 384   
 385    /**
 386    * Retrieve the compiled code.
 387    *
 388    * @return The code.
 389    */
 390  27 protected PyCode getCode()
 391    {
 392  27 if ( this.code == null )
 393    {
 394  0 compile( );
 395    }
 396  27 return this.code;
 397    }
 398   
 399    /**
 400    * Retrieve the AST node.
 401    *
 402    * @return The node.
 403    */
 404  21 protected modType getNode()
 405    {
 406  21 return this.node;
 407    }
 408   
 409  27 protected PyDictionary getGlobals()
 410    {
 411  27 return this.globals;
 412    }
 413   
 414    /**
 415    * Configure a <code>PyDictionary</code> using a <code>Tuple</code> for
 416    * variable bindings.
 417    *
 418    * @param tuple
 419    * Tuple containing variable bindings.
 420    *
 421    * @return The dictionary
 422    */
 423  27 protected PyDictionary setUpDictionary(Tuple tuple,
 424    Iterator declIter) throws Exception
 425    {
 426  27 Declaration eachDecl;
 427   
 428  27 ObjectType objectType;
 429  27 String type;
 430  27 Class clazz;
 431  27 int nestedClassPosition;
 432  27 int dotPosition;
 433   
 434  27 PyDictionary dict = new PyDictionary( );
 435   
 436    // dict.setdefault( new PyString( "q" ), qFunc ); //add tenerary
 437    // function
 438   
 439  27 RuleBaseContext ruleBaseContext = rule.getRuleSet( ).getRuleBaseContext( );
 440  27 ClassLoader cl = (ClassLoader) ruleBaseContext.get( "smf-classLoader" );
 441  27 if ( cl == null )
 442    {
 443  0 cl = Thread.currentThread( ).getContextClassLoader( );
 444  0 ruleBaseContext.put( "smf-classLoader",
 445    cl );
 446    }
 447   
 448  27 if ( cl == null )
 449    {
 450  0 cl = getClass( ).getClassLoader( );
 451  0 ruleBaseContext.put( "smf-classLoader",
 452    cl );
 453    }
 454   
 455  27 while ( declIter.hasNext( ) )
 456    {
 457  25 eachDecl = (Declaration) declIter.next( );
 458   
 459  25 dict.setdefault( new PyString( eachDecl.getIdentifier( ).intern( ) ),
 460    Py.java2py( tuple.get( eachDecl ) ) );
 461   
 462  25 objectType = eachDecl.getObjectType( );
 463   
 464  25 if ( objectType instanceof ClassObjectType )
 465    {
 466  25 clazz = ((ClassObjectType) objectType).getType( );
 467  25 type = clazz.getName( );
 468   
 469  25 nestedClassPosition = type.indexOf( '$' );
 470   
 471  25 if ( nestedClassPosition != -1 )
 472    {
 473  23 type = type.substring( 0,
 474    nestedClassPosition );
 475  23 clazz = cl.loadClass( type );
 476    }
 477   
 478  25 if ( type.indexOf( "java.lang" ) == -1 )
 479    {
 480  23 dotPosition = type.lastIndexOf( '.' );
 481  23 if ( dotPosition != -1 )
 482    {
 483  23 type = type.substring( dotPosition + 1 );
 484    }
 485  23 dict.setdefault( new PyString( type.intern( ) ),
 486    Py.java2py( clazz ) );
 487    }
 488    }
 489   
 490  25 WorkingMemory workingMemory = tuple.getWorkingMemory( );
 491   
 492  25 dict.setdefault( new PyString( "drools".intern( ) ),
 493    Py.java2py( new DefaultKnowledgeHelper( this.rule,
 494    tuple ) ) );
 495   
 496  25 Map appDataMap = workingMemory.getApplicationDataMap( );
 497   
 498  25 for ( Iterator keyIter = appDataMap.keySet( ).iterator( ); keyIter.hasNext( ); )
 499    {
 500  10 String key = (String) keyIter.next( );
 501  10 Object value = appDataMap.get( key );
 502   
 503  10 dict.setdefault( new PyString( key.intern( ) ),
 504    Py.java2py( value ) );
 505   
 506  10 clazz = value.getClass( );
 507  10 type = clazz.getName( );
 508   
 509  10 nestedClassPosition = type.indexOf( '$' );
 510   
 511  10 if ( nestedClassPosition != -1 )
 512    {
 513  3 type = type.substring( 0,
 514    nestedClassPosition );
 515  3 clazz = cl.loadClass( type );
 516    }
 517   
 518  10 if ( type.indexOf( "java.lang" ) == -1 )
 519    {
 520  5 dotPosition = type.lastIndexOf( '.' );
 521  5 if ( dotPosition != -1 )
 522    {
 523  5 type = type.substring( dotPosition + 1 );
 524    }
 525  5 dict.setdefault( new PyString( type.intern( ) ),
 526    Py.java2py( clazz ) );
 527    }
 528    }
 529    }
 530   
 531  27 return dict;
 532    }
 533    }