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

org.drools.lang.dsl.DefaultExpander Maven / Gradle / Ivy

There is a newer version: 10.0.0
Show newest version
package org.drools.lang.dsl;

/*
 * Copyright 2005 JBoss Inc
 * 
 * Licensed 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.
 */

import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.drools.compiler.DroolsError;
import org.drools.lang.Expander;
import org.drools.lang.ExpanderException;

/** 
 * The default expander uses String templates to provide pseudo natural language,
 * as well as general DSLs.
 * 
 * For most people, this should do the job just fine. 
 */
public class DefaultExpander
    implements
    Expander {

    // Be EXTREMELLY careful if you decide to change bellow regexp's
    //
    // bellow regexp is used to find and parse rule parts: header, LHS, RHS, trailer, etc
    private static final String  rulesExpr    = "(^\\s*rule.*?$.*?^\\s*when.*?)$(.*?)(^\\s*then.*?$)(.*?)(^\\s*end)";
    // bellow regexp is used to find and parse query parts: header, condition, trailer
    private static final String  queryExpr    = "(^\\s*query.*?)$(.*?)(^\\s*end)";

    // bellow we combine and compile above expressions into a pattern object
    private static final Pattern finder       = Pattern.compile( "(" + rulesExpr + "|" + queryExpr + ")",
                                                                 Pattern.DOTALL | Pattern.MULTILINE );
    // bellow pattern is used to find a pattern's constraint list
    private static final Pattern patternFinder = Pattern.compile( "\\((.*?)\\)" );

    private final Map                  mappings     = new HashMap();
    private final List                 keywords     = new LinkedList();
    private final List                 condition    = new LinkedList();
    private final List                 consequence  = new LinkedList();
    private final List                 cleanup      = new LinkedList();

    private List                 errors       = Collections.EMPTY_LIST;

    /**
     * Creates a new DefaultExpander
     */
    public DefaultExpander() {
        this.cleanup.add( new DefaultDSLMappingEntry( DSLMappingEntry.KEYWORD,
                                                      null,
                                                      "expander {name}",
                                                      "" ) );
    }

    /**
     * Add the new mapping to this expander.
     * @param mapping
     */
    public void addDSLMapping(final DSLMapping mapping) {
        this.mappings.put( mapping.getIdentifier(),
                           mapping );
        for ( final Iterator it = mapping.getEntries().iterator(); it.hasNext(); ) {
            final DSLMappingEntry entry = (DSLMappingEntry) it.next();
            if ( DSLMappingEntry.KEYWORD.equals(entry.getSection()) ) {
                this.keywords.add( entry );
            } else if ( DSLMappingEntry.CONDITION.equals(entry.getSection()) ) {
                this.condition.add( entry );
            } else if ( DSLMappingEntry.CONSEQUENCE.equals(entry.getSection()) ) {
                this.consequence.add( entry );
            } else {
                // if any, then add to them both condition and consequence
                this.condition.add( entry );
                this.consequence.add( entry );
            }
        }
    }

    /**
     * @inheritDoc
     * @throws IOException 
     */
    public String expand(final Reader drlReader) throws IOException {
        return this.expand( this.loadDrlFile( drlReader ) );
    }

    /**
     * @inheritDoc
     * @throws IOException 
     */
    public String expand(String drl) {
        drl = expandKeywords( drl );
        drl = cleanupExpressions( drl );
        final StringBuffer buf = expandConstructions( drl );
        return buf.toString();
    }

    /**
     * Expand constructions like rules and queries
     * 
     * @param drl
     * @return
     */
    private StringBuffer expandConstructions(final String drl) {
        // parse and expand specific areas
        final Matcher m = finder.matcher( drl );
        
        
        
        final StringBuffer buf = new StringBuffer();
        while ( m.find() ) {
            final StringBuffer expanded = new StringBuffer();
            final String constr = m.group( 1 ).trim();
            if ( constr.startsWith( "rule" ) ) {
                // match rule
                String headerFragment = m.group( 2 );
                expanded.append( headerFragment ); // adding rule header and attributes
                String lhsFragment = m.group( 3 );
                expanded.append( this.expandLHS( lhsFragment, countNewlines( headerFragment ) + 1 ) ); // adding expanded LHS
                String thenFragment = m.group( 4 ); 
                
                expanded.append( thenFragment ); // adding "then" header
                expanded.append( this.expandRHS( m.group( 5 ), countNewlines( headerFragment + lhsFragment + thenFragment ) + 1 ) ); // adding expanded RHS
                expanded.append( m.group( 6 ) ); // adding rule trailer
                expanded.append( "\n" );
            } else if ( constr.startsWith( "query" ) ) {
                // match query
                String fragment = m.group( 7 );
                expanded.append( fragment ); // adding query header and attributes
                expanded.append( this.expandLHS( m.group( 8 ), countNewlines( fragment ) + 1 ) ); // adding expanded LHS
                expanded.append( m.group( 9 ) ); // adding query trailer
                expanded.append( "\n" );
            } else {
                // strange behavior
                this.addError( new ExpanderException( "Unable to expand statement: " + constr,
                                                      0 ) );
            }
            m.appendReplacement( buf,
                                 expanded.toString().replaceAll( "\\$",
                                                                 "\\\\\\$" ) );
        }
        m.appendTail( buf );
        return buf;
    }

    private int countNewlines(final String drl) {
        char[] cs = drl.toCharArray();
        int count = 0;
        for ( int i = 0; i < cs.length; i++ ) {
            { if (cs[i] == '\n') count++; }
        }
        return count;
    }

    /**
     * Clean up constructions that exists only in the unexpanded code
     * 
     * @param drl
     * @return
     */
    private String cleanupExpressions(String drl) {
        // execute cleanup
        for ( final Iterator it = this.cleanup.iterator(); it.hasNext(); ) {
            final DSLMappingEntry entry = (DSLMappingEntry) it.next();
            drl = entry.getKeyPattern().matcher( drl ).replaceAll( entry.getValuePattern() );
        }
        return drl;
    }

    /**
     * Expand all configured keywords
     * 
     * @param drl
     * @return
     */
    private String expandKeywords(String drl) {
        // apply all keywords templates
        for ( final Iterator it = this.keywords.iterator(); it.hasNext(); ) {
            final DSLMappingEntry entry = (DSLMappingEntry) it.next();
            drl = entry.getKeyPattern().matcher( drl ).replaceAll( entry.getValuePattern() );
        }
        return drl;
    }

    /**
     * Expand LHS for a construction
     * @param lhs
     * @param lineOffset 
     * @return
     */
    private String expandLHS(final String lhs, int lineOffset) {
        final StringBuffer buf = new StringBuffer();
        final String[] lines = lhs.split( "\n" ); // since we assembled the string, we know line breaks are \n
        final String[] expanded = new String[lines.length]; // buffer for expanded lines
        int lastExpanded = -1;
        int lastPattern = -1;
        for ( int i = 0; i < lines.length; i++ ) {
            final String trimmed = lines[i].trim();
            expanded[++lastExpanded] = lines[i];

            if ( trimmed.length() == 0 || trimmed.startsWith( "#" ) || trimmed.startsWith( "//" ) ) { // comments
                // do nothing
            } else if ( trimmed.startsWith( ">" ) ) { // passthrough code
                // simply remove the passthrough mark character
                expanded[lastExpanded] = lines[i].replaceFirst( ">",
                                                                " " );
            } else { // regular expansion
                // expand the expression
                for ( final Iterator it = this.condition.iterator(); it.hasNext(); ) {
                    final DSLMappingEntry entry = (DSLMappingEntry) it.next();
                    String vp = entry.getValuePattern();
//                    System.out.println("toExpand, st: " + expanded[lastExpanded] + "|");
//                    System.out.println("kp: " + entry.getKeyPattern());
//                    System.out.println("vp: " + vp);
                    expanded[lastExpanded] = entry.getKeyPattern().matcher(expanded[lastExpanded]).replaceAll(vp);
                }

                // do we need to report errors for that?
                if ( lines[i].equals( expanded[lastExpanded] ) ) {
                    // report error
                    this.addError( new ExpanderException( "Unable to expand: " + lines[i].replaceAll( "[\n\r]", "" ).trim(),
                                                          i + lineOffset ) );
                }
                // but if the original starts with a "-", it means we need to add it
                // as a constraint to the previous pattern
                if ( trimmed.startsWith( "-" ) && (!lines[i].equals( expanded[lastExpanded] )) ) {
                    int lastMatchStart = -1;
                    int lastMatchEnd = -1;
                    String constraints = "";
                    if ( lastPattern >= 0 ) {
                        final Matcher m2 = patternFinder.matcher( expanded[lastPattern] );
                        while ( m2.find() ) {
                            lastMatchStart = m2.start();
                            lastMatchEnd = m2.end();
                            constraints = m2.group( 1 ).trim();
                        }
                    }
                    if ( lastMatchStart > -1 ) {
                        // rebuilding previous pattern structure
                        expanded[lastPattern] = expanded[lastPattern].substring( 0,
                                                                               lastMatchStart ) + "( " + constraints + ((constraints.length() == 0) ? "" : ", ") + expanded[lastExpanded].trim() + " )" + expanded[lastPattern].substring( lastMatchEnd );
                    } else {
                        // error, pattern not found to add constraint to
                        // TODO: can we report character position?
                        this.addError( new ExpanderException( "No pattern was found to add the constraint to: " + lines[i].trim(),
                                                              i + lineOffset ) );
                    }
                    lastExpanded--;
                } else {
                    lastPattern = lastExpanded;
                }
            }
        }
        for ( int i = 0; i <= lastExpanded; i++ ) {
            buf.append( expanded[i] );
            buf.append( "\n" );
        }

        return buf.toString();
    }

    /**
     * Expand RHS for rules
     * 
     * @param lhs
     * @return
     */
    private String expandRHS(final String lhs, int lineOffset) {
        final StringBuffer buf = new StringBuffer();
        final String[] lines = lhs.split( "\n" ); // since we assembled the string, we know line breaks are \n
        for ( int i = 0; i < lines.length; i++ ) {
            final String trimmed = lines[i].trim();

            if ( trimmed.length() == 0 || trimmed.startsWith( "#" ) || trimmed.startsWith( "//" ) ) { // comments
                buf.append( lines[i] );
            } else if ( trimmed.startsWith( ">" ) ) { // passthrough code
                buf.append( lines[i].replaceFirst( ">",
                                                   "" ) );
            } else { // regular expansions
                String expanded = lines[i];
                for ( final Iterator it = this.consequence.iterator(); it.hasNext(); ) {
                    final DSLMappingEntry entry = (DSLMappingEntry) it.next();
                    expanded = entry.getKeyPattern().matcher( expanded ).replaceAll( entry.getValuePattern() );
                }
                buf.append( expanded );
                // do we need to report errors for that?
                if ( lines[i].equals( expanded ) ) {
                    // report error
                    this.addError( new ExpanderException( "Unable to expand: " + lines[i],
                                                          i + lineOffset ) );
                }
            }
            buf.append( "\n" );
        }
        if( lines.length == 0 ) {
            buf.append( "\n" );
        }
        return buf.toString();
    }

    // Reads the stream into a String
    private String loadDrlFile(final Reader drl) throws IOException {
        final StringBuffer buf = new StringBuffer();
        final BufferedReader input = new BufferedReader( drl );
        String line = null;
        while ( (line = input.readLine()) != null ) {
            buf.append( line );
            buf.append( "\n" );
        }
        return buf.toString();
    }

    private void addError(final DroolsError error) {
        if ( this.errors == Collections.EMPTY_LIST ) {
            this.errors = new LinkedList();
        }
        this.errors.add( error );
    }

    /**
     * @inheritDoc
     */
    public List getErrors() {
        return this.errors;
    }

    /**
     * @inheritDoc
     */
    public boolean hasErrors() {
        return !this.errors.isEmpty();
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy