
fr.lteconsulting.hexa.server.qpath.QPath Maven / Gradle / Ivy
The newest version!
package fr.lteconsulting.hexa.server.qpath;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import org.slf4j.Logger;
import fr.lteconsulting.hexa.server.qpath.QPathResult.QPathResultRow;
import fr.lteconsulting.hexa.server.tools.LoggerFactory;
public class QPath
{
private Database db = null;
private DatabaseHelper dbh = null;
private final static Logger logger = LoggerFactory.getLogger();
private final HashMap singularizations = new HashMap();
private final HashMap pluralizations = new HashMap();
private final AutoDTOs autoDtos = new AutoDTOs( this );
public void init( Database database )
{
init( database, null );
}
public void init( Database database, DatabaseHelper databaseHelper )
{
this.db = database;
this.dbh = databaseHelper;
logger.info( "QPath initialized with database " + database.getCurrentDatabase() );
}
public void term()
{
db = null;
dbh = null;
logger.info( "QPath terminated" );
}
// function NewParsedQuery( $template )
// function NewQWalk( $table, $id, $idField = 'id', $rawRecord = null )
// function QPex()
public AutoDTOs getAutoDTOs()
{
return autoDtos;
}
/*
* Converts a QPath expression to the corresponding in SQL language
*/
public String toSql( String expression )
{
return parseEx( expression, null, null, null );
}
/*
* Executes a QPath expression and return a corresponding QPathResult
*/
public QPathResult queryEx( String expression )
{
return queryEx( expression, null, null );
}
public QPathResult queryEx( String expression, Integer limitStart, Integer limitSize )
{
String sql = parseEx( expression, null, limitStart, limitSize );
if( sql == null )
return null;
DBResults dbRes = db.sql( sql );
if( dbRes == null )
return null;
QPathResult res = new QPathResult( dbRes );
dbRes.close();
return res;
}
public Iterable queryExDTO( final Class clazz, String expression )
{
return queryExDTO( clazz, expression, null, null );
}
public Iterable queryExDTO( final Class clazz, String expression, Integer limitStart, Integer limitSize )
{
final QPathResult res = queryEx( expression, limitStart, limitSize );
return new Iterable()
{
final Iterator iterator = res.iterator();
@Override
public Iterator iterator()
{
return new Iterator()
{
@Override
public boolean hasNext()
{
return iterator.hasNext();
}
@Override
public T next()
{
return autoDtos.get( clazz ).convert( iterator.next() );
}
@Override
public void remove()
{
assert false;
}
};
}
};
}
/*
* Executes a QPath expression and return only one row, or null if there is
* none in the result
*/
public QPathResultRow queryOne( String expression )
{
final QPathResult res = queryEx( expression );
if( res == null )
return null;
if( res.getNbRows() < 1 )
return null; // easy way for the caller to know that no result were
// found
return new QPathResultRow()
{
@Override
public T get( String field )
{
return res.getValue( 0, field );
}
};
}
public T queryOneDTO( Class clazz, String expression )
{
return autoDtos.get( clazz ).convert( queryOne( expression ) );
}
private String parseEx( String expression, String whereStatement, Integer limitStart, Integer limitSize )
{
// allows to use variable arguments
// $args = func_get_args();
// $expression = call_user_func_array( 'sprintf', $args );
Token tree = _Parse( expression );
// Dump( $tree );
TravInfo travInfo = _Traverse( tree );
String sql = "SELECT " + travInfo.sql_fields + " FROM " + travInfo.sql_from;
if( whereStatement == null )
whereStatement = travInfo.sql_where;
if( !whereStatement.isEmpty() )
sql += " WHERE " + whereStatement;
if( travInfo.sql_group_by != null )
sql += " GROUP BY " + travInfo.sql_group_by;
if( travInfo.sql_order_by != null )
sql += " ORDER BY " + travInfo.sql_order_by + " ASC";
if( limitStart != null && limitSize != null )
sql += " LIMIT " + limitStart + ", " + limitSize;
return sql;
}
public void addPluralForm( String singular, String plural )
{
singularizations.put( plural, singular );
pluralizations.put( singular, plural );
}
public String pluralize( String str )
{
String pluralized = pluralizations.get( str );
if( pluralized == null )
return str + "s";
return pluralized;
}
public String singularize( String str )
{
String singularized = singularizations.get( str );
if( singularized == null )
return str.substring( 0, str.length() - 1 );// substr($str, 0, -1);
return singularized;
}
private Token _Parse( String toEval )
{
ArrayList tokens = new ArrayList();
int toEvalLen = toEval.length();
// while( $token = $this->_NextToken( $toeval, $toEvalLen, $pos ) )
// {
// $tokens = array_merge( $tokens, $token );
// }
NavigableString navToEval = new NavigableString( toEval );
while( true )
{
List nextTokens = _NextToken( navToEval, toEvalLen );
if( nextTokens == null )
break;
tokens.addAll( nextTokens );
}
// $stack = array();
ArrayList stack = new ArrayList();
while( true )
{
// etait déjà commenté en PHP
// echo "parser $pos stack-layout:'";
// for($i=0;$i";
// $token = array_shift( $tokens );
if( tokens.isEmpty() )
break;
Token token = tokens.remove( 0 );
// $nextToken = null;
// if( count($tokens) > 0 )
// $nextToken = &$tokens[0];
Token nextToken = null;
if( !tokens.isEmpty() )
nextToken = tokens.get( 0 );
// array_unshift( $stack, $token );
stack.add( 0, token );
while( _TryReduce( stack, nextToken ) > 0 )
;
// $this->Dump( $token );
// echo "
";
}
// if( count($stack)==1 && $stack[0]['t_type']=='e' )
// {
// echo "parse successful!!!
";
// return $stack[0];
// }
if( stack.size() == 1 && stack.get( 0 ).t_type == 'e' )
{
return stack.get( 0 );
}
// echo
// "QPath parse error with expression : $toeval
Stack content is :";
// Dump( $stack );
logger.error( "QPath parse error with expression : " + toEval + "
Stack content is :" + stack );
return null;
}
char tokens[] = new char[] { '(', ')', '[', ']', '{', '}', '?', 'G', 'F', 'O' };
private boolean _IsToken( char c )
{
switch( c )
{
case '(':
case ')':
case '[':
case ']':
case '{':
case '}':
case '?':
case 'G':
case 'A':
case 'F':
case 'O':
return true;
}
return false;
}
private boolean _IsReducable( ArrayList stack, String test, int testLen )
{
// $testLen = strlen($test);
if( stack.size() < testLen )
return false;
for( int i = 0; i < testLen; i++ )
{
if( test.charAt( i ) != stack.get( testLen - i - 1 ).t_type )
return false;
}
// echo "$test is reducable
";
return true;
}
// returns the next token and increments the position
// returns null when no more token to come
// function _NextToken( &$text, int toEvalLen, &$pos )
private List _NextToken( NavigableString text, int toEvalLen )
{
int len = toEvalLen;
// skip whitespaces
while( (text.pos < len) && (text.cur() == ' ') )
text.pos++;
// end of text...
if( text.pos >= len )
return null;
Token token = null;
// test one char tokens
int tokenSize = 1;
if( _IsToken( text.cur() ) )
{
token = new Token( text.cur() );
// if token is [ then produce the string going until next ]
if( text.cur() == '[' )
{
text.pos++; // pass the [
int i = 0;
while( (text.pos + i) < len )
{
if( text.ahead( i ) == ']' )
break;
i++;
}
Token tokString = new Token( 's', text.extract( i ) ); // rtrim(substr($text,$pos,$i))
// );
text.pos += i;
Token tokenClose = new Token( ']' );
text.pos++;
return Arrays.asList( token, tokString, tokenClose );
}
}
// test two chars tokens
if( (token == null) && (text.pos + 1 < len) )
{
tokenSize = 2;
if( text.cur() == '<' && text.ahead() == '-' )
token = new Token( 'o', "<-" );
else if( text.cur() == '-' && text.ahead() == '>' )
token = new Token( 'o', "->" );
}
// test for a token string
if( token == null )
{
int i = 0;
while( (text.pos + i) < len )
{
char c = text.ahead( i );
if( _IsToken( c ) )
break;
if( (c == '-') && (text.pos + i + 1 < len) && (text.ahead( i + 1 ) == '>') )
break;
if( (c == '<') && (text.pos + i + 1 < len) && (text.ahead( i + 1 ) == '-') )
break;
i++;
}
if( i > 0 )
{
tokenSize = i;
token = new Token( 's', text.extract( i ) );
}
}
if( token == null )
return null;
text.pos += tokenSize;
return Arrays.asList( token );
}
private static class Token
{
char t_type;
String t_val;
// extended fields
String type;
String value;
boolean muteFields;
Token left;
Token right;
String leftField;
String rightField;
ArrayList where = null;
ArrayList groupby = null;
String tableAlias;
Token val;
ArrayList add_field = null;
Token sort_field;
ArrayList add_where = null;
public Token( char type )
{
this( type, null );
}
public Token( char type, String val )
{
this.t_type = type;
this.t_val = val;
}
public Token type( String type )
{
this.type = type;
return this;
}
public Token value( String value )
{
this.value = value;
return this;
}
public Token muteFields( boolean muteFields )
{
this.muteFields = muteFields;
return this;
}
public Token left( Token left )
{
this.left = left;
return this;
}
public Token right( Token right )
{
this.right = right;
return this;
}
public Token leftField( String leftField )
{
this.leftField = leftField;
return this;
}
public Token rightField( String rightField )
{
this.rightField = rightField;
return this;
}
public Token where( String where )
{
if( this.where == null )
this.where = new ArrayList();
this.where.add( where );
return this;
}
public Token groupby( String groupby )
{
if( this.groupby == null )
this.groupby = new ArrayList();
this.groupby.add( groupby );
return this;
}
public Token tableAlias( String tableAlias )
{
this.tableAlias = tableAlias;
return this;
}
public Token val( Token val )
{
this.val = val;
return this;
}
public Token add_field( Token add_field )
{
if( this.add_field == null )
this.add_field = new ArrayList();
this.add_field.add( add_field );
return this;
}
public Token sort_field( Token sort_field )
{
this.sort_field = sort_field;
return this;
}
public Token add_where( Token add_where )
{
if( this.add_where == null )
this.add_where = new ArrayList();
this.add_where.add( add_where );
return this;
}
}
private int _TryReduce( ArrayList stack, Token nextToken )
{
if( _IsReducable( stack, "s", 1 ) )
{
Token reduced = stack.remove( 0 );
// array_unshift( $stack, array( 't_type'=>'e', 'type'=>'v',
// 'value'=>$reduced['t_val'] ) );
stack.add( 0, new Token( 'e' ).type( "v" ).value( reduced.t_val ) );
return 1;
}
if( _IsReducable( stack, "?e", 2 ) )
{
Token reduced = stack.remove( 0 );
stack.remove( 0 );
reduced.muteFields( true ); // $reduced['muteFields'] = "true";
stack.add( 0, reduced ); // array_unshift( $stack, $reduced );
return 1;
}
// if next token will be '[' we should not reduce this one...
if( _IsReducable( stack, "eoe", 3 ) && ((nextToken == null) || ((nextToken.t_type != '[') && (nextToken.t_type != 'G') && (nextToken.t_type != 'A'))) )
{
Token opRight = stack.remove( 0 );
Token op = stack.remove( 0 );
Token opLeft = stack.remove( 0 );
// $reduced = array( 't_type'=>'e', 'type'=>$op['t_val'],
// 'left'=>$opLeft, 'right'=>$opRight );
Token reduced = new Token( 'e' ).type( op.t_val ).left( opLeft ).right( opRight );
// if( isset( $op['leftField'] ) )
// $reduced = array_merge( $reduced, array(
// 'leftField'=>$op['leftField'] ) );
if( op.leftField != null )
reduced.leftField( op.leftField );
// if( isset( $op['rightField'] ) )
// $reduced = array_merge( $reduced, array(
// 'rightField'=>$op['rightField'] ) );
if( op.rightField != null )
reduced.rightField( op.rightField );
stack.add( 0, reduced );
return 1;
}
if( _IsReducable( stack, "(e)", 3 ) )
{
stack.remove( 0 );
Token reduced = stack.remove( 0 );
stack.remove( 0 );
stack.add( 0, reduced );
return 1;
}
if( _IsReducable( stack, "e[e]", 4 ) )
{
stack.remove( 0 );
Token where = stack.remove( 0 );
stack.remove( 0 );
Token reduced = stack.remove( 0 );
// TODO : faire gaffe a celui la
// if( ! isset( $reduced['where'] ) )
// $reduced['where'] = array();
// $reduced['where'][] = $where['value'];
reduced.where( where.value );
stack.add( 0, reduced );
return 1;
}
if( _IsReducable( stack, "eG[e]", 5 ) )
{
stack.remove( 0 );
Token field = stack.remove( 0 );
stack.remove( 0 );
stack.remove( 0 );
Token reduced = stack.remove( 0 );
// TODO : faire gaffe a celui la
// if( ! isset( $reduced['groupby'] ) )
// $reduced['groupby'] = array();
// $reduced['groupby'][] = $field['value'];
reduced.groupby( field.value );
stack.add( 0, reduced );
return 1;
}
if( _IsReducable( stack, "eA[e]", 5 ) )
{
stack.remove( 0 );
Token realTableName = stack.remove( 0 );
stack.remove( 0 );
stack.remove( 0 );
Token reduced = stack.remove( 0 );
reduced.tableAlias( realTableName.value );
stack.add( 0, reduced );
return 1;
}
if( _IsReducable( stack, "{e}o", 4 ) )
{
Token op = stack.remove( 0 );
stack.remove( 0 );
Token leftField = stack.remove( 0 );
stack.remove( 0 );
op.leftField( leftField.value );
stack.add( 0, op );
return 1;
}
if( _IsReducable( stack, "o{e}", 4 ) )
{
stack.remove( 0 );
Token rightField = stack.remove( 0 );
stack.remove( 0 );
Token op = stack.remove( 0 );
op.rightField( rightField.value );
stack.add( 0, op );
return 1;
}
if( _IsReducable( stack, "F[e]", 4 ) )
{
stack.remove( 0 );
Token field = stack.remove( 0 );
stack.remove( 0 );
stack.remove( 0 );
// array_unshift( $stack, array( 't_type'=>'f', 'val'=>$field ) );
stack.add( 0, new Token( 'f' ).val( field ) );
return 1;
}
if( _IsReducable( stack, "fe", 2 ) )
{
Token expr = stack.remove( 0 );
Token field = stack.remove( 0 );
// TODO : faire gaffe a celui la
// if( ! isset( $expr['add_field'] ) )
// $expr['add_field'] = array();
// $expr['add_field'][] = $field['val'];
expr.add_field( field.val );
stack.add( 0, expr );
return 1;
}
if( _IsReducable( stack, "O[e]", 4 ) )
{
stack.remove( 0 );
Token field = stack.remove( 0 );
stack.remove( 0 );
stack.remove( 0 );
// array_unshift( $stack, array( 't_type'=>'t', 'val'=>$field ) );
stack.add( 0, new Token( 't' ).val( field ) );
return 1;
}
if( _IsReducable( stack, "te", 2 ) )
{
Token expr = stack.remove( 0 );
Token field = stack.remove( 0 );
expr.sort_field( field.val );
stack.add( 0, expr );
return 1;
}
if( _IsReducable( stack, "[e]", 3 ) )
{
stack.remove( 0 );
Token add_where = stack.remove( 0 );
stack.remove( 0 );
// array_unshift( $stack, array( 't_type'=>'w', 'val'=>$add_where )
// );
stack.add( 0, new Token( 'w' ).val( add_where ) );
return 1;
}
if( _IsReducable( stack, "we", 2 ) )
{
Token expr = stack.remove( 0 );
Token add_where = stack.remove( 0 );
// TODO : faire gaffe a celui la
// if( ! isset( $expr['add_where'] ) )
// $expr['add_where'] = array();
// $expr['add_where'][] = $add_where['val'];
expr.add_where( add_where.val );
stack.add( 0, expr );
return 1;
}
return 0;
}
private static class TravInfo
{
String table;
String tableAlias;
String sql_from;
String sql_fields;
String sql_where;
// StringBuilder sql_where = new StringBuilder();
String sql_group_by;
String sql_order_by;
}
private TravInfo _Traverse( Token tree )
{
TravInfo travInfo = new TravInfo();
if( tree.type.equals( "->" ) || tree.type.equals( "<-" ) )
{
TravInfo leftTravInfo = _Traverse( tree.left );
TravInfo rightTravInfo = _Traverse( tree.right );
String leftTableAlias = leftTravInfo.table;
if( leftTravInfo.tableAlias != null )
leftTableAlias = leftTravInfo.tableAlias;
String rightTableAlias = rightTravInfo.table;
if( rightTravInfo.tableAlias != null )
rightTableAlias = rightTravInfo.tableAlias;
String leftField, rightField;
if( tree.type.equals( "->" ) )
{
leftField = leftTableAlias + "." + singularize( rightTravInfo.table ) + "_id";
rightField = rightTableAlias + ".id";
}
else if( tree.type.equals( "<-" ) )
{
leftField = leftTableAlias + ".id";
rightField = rightTableAlias + "." + singularize( leftTravInfo.table ) + "_id";
}
else
{
leftField = "";
rightField = "";
}
// custom fields
if( tree.leftField != null )
leftField = leftTableAlias + "." + tree.leftField;
if( tree.rightField != null )
rightField = rightTableAlias + "." + tree.rightField;
travInfo.table = leftTravInfo.table;
if( leftTravInfo.tableAlias != null )
travInfo.tableAlias = leftTravInfo.tableAlias;
travInfo.sql_from = " ( " + leftTravInfo.sql_from + " LEFT JOIN " + rightTravInfo.sql_from + " ON " + leftField + "=" + rightField + " ) ";
travInfo.sql_where = " (" + leftTravInfo.sql_where + ") AND (" + rightTravInfo.sql_where + ") ";
if( tree.where != null )
{
for( String clause : tree.where )
travInfo.sql_where += " AND (" + leftTableAlias + "." + clause + ")";
}
if( tree.add_where != null )
{
// foreach( $tree['add_where'] as $clause )
// $travInfo["sql_where"] .= ' AND (' . $clause['value'] . ')';
for( Token clause : tree.add_where )
travInfo.sql_where += " AND (" + clause.value + ")";
}
// group by
ArrayList groupby = new ArrayList();
if( leftTravInfo.sql_group_by != null )
groupby.add( leftTravInfo.sql_group_by );
if( rightTravInfo.sql_group_by != null )
groupby.add( rightTravInfo.sql_group_by );
// $gb = implode( ',', $groupby );
// if( strlen( $gb ) > 0 )
// $travInfo["sql_group_by"] = $gb;
if( !groupby.isEmpty() )
travInfo.sql_group_by = Tools.implode( ",", groupby );
// fields
travInfo.sql_fields = "";
if( leftTravInfo.sql_fields != null )
{
if( rightTravInfo.sql_fields != null )
travInfo.sql_fields = leftTravInfo.sql_fields + ", " + rightTravInfo.sql_fields;
else
travInfo.sql_fields = leftTravInfo.sql_fields;
}
else
{
if( rightTravInfo.sql_fields != null )
travInfo.sql_fields = rightTravInfo.sql_fields;
}
// added fields
if( tree.add_field != null )
{
ArrayList fields = new ArrayList();
for( Token f : tree.add_field )
fields.add( f.value );
if( travInfo.sql_fields != null && travInfo.sql_fields != "" )
fields.add( travInfo.sql_fields );
travInfo.sql_fields = Tools.implode( ",", fields );
}
// sort fields
ArrayList sortby = new ArrayList();
if( leftTravInfo.sql_order_by != null )
sortby.add( leftTravInfo.sql_order_by );
if( rightTravInfo.sql_order_by != null )
sortby.add( rightTravInfo.sql_order_by );
if( tree.sort_field != null )
sortby.add( tree.sort_field.value );
if( !sortby.isEmpty() )
travInfo.sql_order_by = Tools.implode( ", ", sortby );
}
else if( tree.type.equals( "v" ) )
{
travInfo.table = tree.value;
String realTable = tree.value;
String aliasTable = tree.value;
if( tree.tableAlias != null )
{
aliasTable = tree.tableAlias;
travInfo.tableAlias = tree.tableAlias;
travInfo.sql_from = realTable + " AS " + tree.tableAlias;
}
else
{
travInfo.sql_from = realTable;
}
if( tree.where != null )
{
ArrayList clauses = new ArrayList();
for( String clause : tree.where )
clauses.add( aliasTable + "." + clause );
travInfo.sql_where = Tools.implode( " AND ", clauses );
}
else
{
travInfo.sql_where = "1=1";
}
if( tree.add_where != null )
{
for( Token clause : tree.add_where )
travInfo.sql_where += " AND (" + clause.value + ")";
}
if( tree.groupby != null )
{
ArrayList groupBy = new ArrayList();
for( String field : tree.groupby )
groupBy.add( aliasTable + "." + field );
travInfo.sql_group_by = Tools.implode( ", ", groupBy );
}
// maybe fields are to be muted...
if( tree.muteFields == false )
travInfo.sql_fields = _GetFields( realTable, aliasTable );
else
travInfo.sql_fields = null;
if( tree.add_field != null )
{
ArrayList fields = new ArrayList();
for( Token f : tree.add_field )
fields.add( f.value );
if( travInfo.sql_fields != null )
fields.add( travInfo.sql_fields );
travInfo.sql_fields = Tools.implode( ",", fields );
}
// order by
if( tree.sort_field != null )
travInfo.sql_order_by = tree.sort_field.value;
}
else
{
return null;
}
return travInfo;
}
private void ensureDatabaseHelper()
{
if( dbh != null )
return;
dbh = new DatabaseHelper( db );
}
private String _GetFields( String tableName, String aliasName )
{
ensureDatabaseHelper();
ArrayList tableFields = dbh.getTableFields( tableName );
StringBuilder b = new StringBuilder();
boolean fAddComa = false;
for( String field : tableFields )
{
if( fAddComa )
b.append( ", " );
else
fAddComa = true;
b.append( aliasName );
b.append( "." );
b.append( field );
b.append( " AS `" );
b.append( aliasName );
b.append( "." );
b.append( field );
b.append( "`" );
}
return b.toString();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy