Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
Opensec UTIL - https://nakamura5akihito.github.io/ Copyright (C) 2015 Akihito Nakamura 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.
  
  package io.opensec.util.core.persist.castor;
  
  import java.util.List;



A Castor OQL utility. This class is used to compile the SearchCriteria to Castor OQL statement.

In the result statement, the property values in the criteria are converted to place holders in the statement: $1, $2, ... The parameter value list, in the order of the place holders, can be obtained separately.

By default, the result statements and parameter value list do not contain the LIMIT constraint. A method setLimitEnabled(boolean) changes the effectiveness of the LIMIT in the result, and isLimitEnabled() tests the current state.

Author(s):
Akihito Nakamura, AIST
  
  public class OQL
  {
  
      private static final String  _SELECT_        = "SELECT ";
      private static final String  _SELECT_COUNT_  = "SELECT count(*) ";
      private static final String  _DISTINCT_      = " DISTINCT ";
      private static final String  _FROM_          = " FROM ";
      private static final String  _WHERE_         = " WHERE ";
      private static final String  _AND_           = " AND ";
      private static final String  _OR_            = " OR ";
      private static final String  _LIKE_          = " LIKE ";
      private static final String  _IN_LIST_       = " IN LIST ";
      private static final String  _ORDER_BY_      = " ORDER BY ";
      private static final String  _DESC_          = " DESC";
      private static final String  _LIMIT_         = " LIMIT ";
      private static final String  _OFFSET_        = " OFFSET ";
      private static final String  _NIL_           = "nil";
      private static final String  _WILDCARD_      = "%";
      private static final char    _SPACE_         = ' ';
      private static final char    _PARAM_SIGN_    = '$';
      //private static final char  _PROP_CONNECTOR_  = '.';
  



    
The type of objects.
  
      private Class<?>  _type;
  
  
      public static final String  DEFAULT_ALIAS = "o";
      private String  _alias;



    
The OQL SELECT clause.
  
     private String  _selectClause;


    
The OQL FROM clause.
 
     private String  _fromClause;


    
The OQL WHERE clause, including ORDER BY clause.
 
     private String  _whereClause = "";


    
The OQL parameter values for WHERE clause.
 
     private final List<Object>  _paramVlaues = new ArrayList<Object>();



    
 
     private List<Order>  _orders = null;


    
The OQL ORDER clause.
 
     private String  _orderClause = "";



    
The OQL LIMIT.
 
     private Limit  _limit = null;
 
 
     private String  _limitClause = "";


    
A flag to indicate that the results should contain the LIMIT constraint in the statement and parameter list.
 
     private boolean  _limitEnabled = false;




    
Constructor.
 
     protected OQL()
     {
     }


    
Constructor.

Parameters:
type the type of the target objects.
criteria the criteria for the objects.
 
     public OQL(
                     final Class<?> type,
                     final SearchCriteria criteria
                     )
     {
         thistypecriteria );
 //        this( type, aliasFor( type ), criteria );
     }



    
Constructor.

Parameters:
type the type of the target objects.
alias the alias name for the target objects.
criteria the criteria of the objects.
 
     public OQL(
                     final Class<?> type,
                     final String alias,
                     final SearchCriteria criteria
                     )
     {
         if (type == null) {
             throw new IllegalStateException"no object type specified" );
         }
 
          = type;
          = alias;
         _compilecriteria );
     }



    
Returns the OQL statement.
 
     private void _buildStatement(
                     final List<Orderorders,
                     final Limit limit,
                     final StringBuilder stmt
                     )
     {
         stmt.append ).append ).append );
         if (orders == null) {
             stmt.append );
         } else {
             _buildOrderClauseordersstmt );
         }
 
         if (isLimitEnabled()) {
             if (limit == null) {
                 stmt.append );
             } else {
                 _buildLimitlimitstmt );
             }
         }
     }


    
Returns the OQL statement.

Returns:
an OQL statement.
 
     public String getStatement()
     {
         StringBuilder  stmt = new StringBuilder();
         _buildStatementnullnullstmt );
 
         return stmt.toString();
     }


    

Parameters:
orders the orders.
Returns:
a string representation of the statement.
 
     public String getStatement(
                     final List<Orderorders
                     )
     {
         StringBuilder  stmt = new StringBuilder();
         _buildStatementordersnullstmt );
 
         return stmt.toString();
     }


    

Parameters:
limit the limit.
Returns:
a string representation of the statement.
 
     public String getStatement(
                     final Limit limit
                     )
     {
         StringBuilder  stmt = new StringBuilder();
         _buildStatementnulllimitstmt );
 
         return stmt.toString();
     }


    

Parameters:
orders the orders.
limit the limit.
Returns:
a string representation of the statement.
 
     public String getStatement(
                     final List<Orderorders,
                     final Limit limit
                     )
     {
         StringBuilder  stmt = new StringBuilder();
         _buildStatementorderslimitstmt );
 
         return stmt.toString();
     }



    
Returns the OQL statement to count the objects.

Returns:
an OQL statement.
 
     public String getCountStatement()
     {
         StringBuilder  stmt = new StringBuilder();
         stmt.append ).append ).append );
 
         return stmt.toString();
     }



    

Returns:
the type of the query targets.
 
     public Class<?> getType()
     {
         if ( == null) {
             throw new IllegalStateException"no object type specified" );
         }
 
         return ;
     }



    
Returns the 'select' clause ("select" keyword included).

Returns:
the 'select' clause.
 
     public String getSelectClause()
     {
         return ;
     }



    
Returns the 'where' clause ("where" keyword included).

Returns:
the 'where' clause.
 
     public String getWhereClause()
     {
         return ;
     }



    

Returns:
a string representation of the OQL order clause.
 
     public String getOrdering()
     {
         return getOrdering );
     }



    

Parameters:
orders the ordering specification of the result objects.
Returns:
a string representation of the OQL order clause.
 
     public String getOrdering(
                     final List<Orderorders
                     )
     {
         int  n_orders = (orders == null ? 0 : orders.size());
         if (n_orders == 0) {
             return null;
         }
 
         final StringBuilder  s = new StringBuilder();
         for (int  i = 0; i < n_ordersi++) {
             if (i > 0) {
                 s.append"," );
             }
 
             final Order  order = orders.geti );
             _buildPropertyorder.getProperty(), s );
 
             if (order.isDescending()) {
                 s.append );
             }
         }
 
         return s.toString();
     }



    
Returns the LIMIT object.

Returns:
the LIMIT object.
 
     public Limit getLimit()
     {
         return ;
     }


    
Specifies whether LIMIT clauses should be created in the result statements.

Parameters:
enabled if true, LIMIT clauses should be created in the result statements.
 
     public void setLimitEnabled(
                     final boolean enabled
                     )
     {
          = enabled;
     }


    
Tests if LIMIT clauses should be created in the result statements.

Returns:
true if LIMIT clauses should be created in the result statements.
 
     public boolean isLimitEnabled()
     {
         return ;
     }



    
Returns the parameter values to be bound to the OQL statement.

Returns:
the parameter values.
 
     public Object[] getParameterValues()
     {
         Object[]  empty = new Object[0];
         if ( == null  ||  .size() == 0) {
             return empty;
         }
 
         return .toArrayempty );
     }



    
 
     private void _compile(
                     final SearchCriteria criteria
                     )
     {
         if (criteria != null) {
             StringBuilder  whereClause = new StringBuilder();
             _buildWherecriteriawhereClause );
              = whereClause.toString();
 
              = criteria.getOrders();
             StringBuilder  orderClause = new StringBuilder();
             _buildOrderClauseorderClause );
              = orderClause.toString();
 
             Limit  limit = criteria.getLimit();
             if (limit != null) {
                  = new Limitlimit.getCount(), limit.getOffset() );
                 StringBuilder  limitClause = new StringBuilder();
                 _buildLimitlimitClause );
                  = limitClause.toString();
             }
         }
 
         StringBuilder  selectClause = new StringBuilder();
         _buildSelectcriteriaselectClause );
          = selectClause.toString();
 
         StringBuilder  fromClause = new StringBuilder();
         _buildFromfromClause );
          = fromClause.toString();
     }
 
 
 
 //    /**
 //     * Creates a statement part for the specified LIMIT constraint.
 //     *
 //     * @param   limit
 //     *  the LIMIT constraint.
 //     * @return
 //     *  a statement part.
 //     */
 //    private String _createStatement(
 //                    final Limit limit
 //                    )
 //    {
 //        if (limit == null) {
 //            throw new NullPointerException();
 //        }
 //
 //        StringBuilder  stmt = new StringBuilder();
 //        List<Object>  params = new ArrayList<Object>(2);
 //        _buildLimit( limit, stmt, params );
 //
 //        return stmt.toString();
 //    }
 


    
Limit LIMIT c OFFSET 0 LIMIT c OFFSET o
 
     private void _buildLimit(
                     final Limit limit,
                     final StringBuilder stmt
                     )
     {
         if (limit == null) {
             return;
         }
 
         stmt.append );
         stmt.appendlimit.getCount() );
         stmt.append );
         stmt.appendlimit.getOffset() );
     }



    
Order ORDER BY T.a ORDER BY T.a DESC ORDER BY T.a DESC, T.b
 
     private void _buildOrderClause(
                     final List<Orderorders,
                     final StringBuilder stmt
                     )
     {
         int  n_orders = (orders == null ? 0 : orders.size());
         if (n_orders == 0) {
             return;
         }
 
         stmt.append );
         for (int  i = 0; i < n_ordersi++) {
             if (i > 0) {
                 stmt.append"," );
             }
 
             final Order  order = orders.geti );
             _buildPropertyorder.getProperty(), stmt );
             //throws SearchException
 
             if (order.isDescending()) {
                 stmt.append );
             }
         }
     }
 
 
 
     //==============================================================
     //  WHERE clause
     //==============================================================
 
    
WHERE
 
     private void _buildWhere(
                     final SearchCriteria criteria,
                     final StringBuilder stmt,
                     final List<Objectparams
                     )
     {
         Binding  binding = (criteria == null ? null : criteria.getBinding());
 //        FulltextMatch  match = (criteria == null ? null : criteria.getFulltextMatch());
 //        Binding  matchBinding = null;
 
 //        if (match != null) {
 //            matchBinding = _toFulltextBinding( match );
 //        }
 
 //        if (binding == null) {
 //            binding = matchBinding;
 //        } else {
 //            if (matchBinding != null) {
 //                binding = new AndBinding( binding, matchBinding );
 //            }
 //        }
 
         if (binding != null ){
             stmt.append );
             _buildBindingbindingstmtparams );
             //throws SearchException
         }
     }



    
 
     private void _buildBinding(
                     final Binding binding,
                     final StringBuilder stmt,
                     final List<Objectparams
                     )
     {
         if (binding instanceof PropertyBinding) {
             _buildPropertyBinding( (PropertyBinding)bindingstmtparams );
             //throws SearchException
 
         } else if (binding instanceof LogicalBinding) {
             _buildLogicalBinding( (LogicalBinding)bindingstmtparams );
             //throws SearchException
 
         } else if (binding instanceof NotBinding) {
             _buildNotBinding( (NotBinding)bindingstmtparams );
             //throws SearchException
 
         } else {
             throw new IllegalArgumentException"unsupported Binding: "
                             + String.valueOfbinding ) );
         }
     }



    
LogicalBinding (b_1 AND b_2 AND ... AND b_n) (b_1 OR b_2 OR ... OR b_n)
 
     private void _buildLogicalBinding(
                     final LogicalBinding binding,
                     final StringBuilder stmt,
                     final List<Objectparams
                     )
     {
         final int  size = binding.size();
 
         if (size < 2) {
             throw new IllegalArgumentException(
                             "LogicalBinding with less than two element bindings" );
         }
 
         final String  logic = (binding instanceof AndBinding) ?  : ;
         stmt.append" (" );
         for (int  i = 0; i < sizei++) {
             if (i > 0) {
                 stmt.appendlogic );
             }
             _buildBindingbinding.getElementAti ), stmtparams );
         }
         stmt.append")" );
     }



    
NotBinding NOT (...)
 
     private void _buildNotBinding(
                     final NotBinding binding,
                     final StringBuilder stmt,
                     final List<Objectparams
                     )
     {
         stmt.append" NOT (" );
         _buildBindingbindingstmtparams );
         stmt.append")" );
     }



    
PropertyBinding
 
     private void _buildPropertyBinding(
                     final PropertyBinding binding,
                     final StringBuilder stmt,
                     final List<Objectparams
                     )
     {
         if (binding instanceof RelationalBinding) {
             _buildRelationalBinding( (RelationalBinding)bindingstmtparams );
             //throws SearchException
 
         } else if (binding instanceof InBinding) {
             _buildInBinding( (InBinding)bindingstmtparams );
             //throws SearchException
 
         } else if (binding instanceof LikeBinding) {
             _buildLikeBinding( (LikeBinding)bindingstmtparams );
             //throws SearchException
 
         } else if (binding instanceof TextMatchBinding) {
             _buildTextMatchBinding( (TextMatchBinding)bindingstmtparams );
             //throws SearchException
 
         } else if (binding instanceof NullBinding) {
             _buildNullBinding( (NullBinding)bindingstmt );
             //throws SearchException
 
         } else {
             throw new IllegalArgumentException"unsupported PropertyBinding: "
                             + String.valueOfbinding ) );
         }
     }



    
NullBinding is_undefiend(T.p) is_defnied(T.p)
 
     private void _buildNullBinding(
                     final NullBinding binding,
                     final StringBuilder stmt
                     )
     {
         String  property = binding.getProperty();
 
         if (binding.isNotNull()) {
             stmt.append" is_defined(" );
         } else {
             stmt.append" is_undefined(" );
         }
 
         _buildPropertypropertystmt );
         //throws SearchException
 
         stmt.append")" );
     }



    
RelationalBinding p EQ 2001 p GE 2001
 
     private void _buildRelationalBinding(
                     final RelationalBinding binding,
                     final StringBuilder stmt,
                     final List<Objectparams
                     )
     {
         String  property = binding.getProperty();
         Object  value = binding.getValue();
         Relation  rel = binding.getRelation();
 
         if (value == null) {
             NullBinding  nullBinding = new NullBinding();
             nullBinding.setPropertyproperty );
             if (. == rel) {
                 nullBinding.setNotNullfalse );
             } else if (. == rel) {
                 nullBinding.setNotNulltrue );
             } else {
                 throw new IllegalArgumentException(
                                 "invalid null-valued RelationalBinding: "
                                 + String.valueOfbinding ) );
             }
             _buildNullBindingnullBindingstmt );
             //throws SearchException
 
         } else {
             _buildPropertypropertystmt );
             //throws SearchException
             stmt.append );
             stmt.appendrel.operator() );
             stmt.append );
             _buildParametervaluestmtparams );
         }
     }



    
LikeBinding T.p LIKE 'foo%'
 
     private void _buildLikeBinding(
                     final LikeBinding binding,
                     final StringBuilder stmt,
                     final List<Objectparams
                     )
     {
         _buildPropertybinding.getProperty(), stmt );
         //throws SearchException
         stmt.append );
         _buildParameterbinding.getPattern(), stmtparams );
     }



    
InBinding T.p IN LIST(v_1, v_2, ..., v_n) v_i = "nil", if v_i == null.
 
     private void _buildInBinding(
                     final InBinding binding,
                     final StringBuilder stmt,
                     final List<Objectparams
                     )
     {
         // It's OK if null is contained.
 //        if (n_values == 0) {
 //            throw new SearchException( "invalid InBinding: empty value list" );
 //        }
 
 
         // T.p
         _buildPropertybinding.getProperty(), stmt );
         //throws SearchException
 
         // T.p IN LIST(
         stmt.append ).append"(" );
 
         // T.p IN LIST(v_1, v_2, ..., v_n
         boolean  containsNull = false;
         int  n_valuesAdded = 0;
 
         for (Object  value : binding.getValues()) {
             if (value == null) {
                 containsNull = true;
             } else {
                 if (n_valuesAdded > 0) {
                     stmt.append"," );
                 }
                 _buildParametervaluestmtparams );
                 n_valuesAdded++;
             }
         }
 
         if (binding.isNullContained()  ||  containsNull) {
             if (n_valuesAdded > 0) {
                 stmt.append"," );
             }
             // T.p IN LIST(v_1, v_2, ..., v_n, nil
             stmt.append );
         }
 
         // T.p IN LIST(v_1, v_2, ..., v_n)
         stmt.append")" );
     }



    
Appends a query parameter to the statement for the specified value and appends the value to the parameter list. The parameter is the form of "$i", where i is the position in the list.
 
     private void _buildParameter(
                     final Object value,
                     final StringBuilder stmt,
                     final List<Objectparams
                     )
     {
         params.addvalue );
         stmt.append ).appendparams.size() );
     }
 
 
 
     //==============================================================
     //  FROM clause
     //==============================================================
 
    
FROM FROM foo.bar.Type Type
 
     private void _buildFrom(
                     final String alias,
                     final StringBuilder stmt
                     )
     {
         stmt.append ).appendgetType().getName() );
         stmt.append ).appendalias );
     }
 
 
 
     //==============================================================
     //  SELECT clause
     //==============================================================
 
    
SELECT SELECT {DISTINCT} T SELECT {DISTINCT} T.a, T.b, T.c
 
     private void _buildSelect(
                     final SearchCriteria criteria,
                     final StringBuilder stmt
                     )
     {
         final List<Projection>  projections =
             (criteria == null ? null : criteria.getProjections());
         final int  n_projections = (projections == null ? 0 : projections.size());
         final boolean  distinct =
             (criteria == null ? true : criteria.isDistinct());
 
         // SELECT
         stmt.append );
         if (distinct) {
             // SELECT DISTINCT
             stmt.append );
         }
 
         if (n_projections == 0) {
             //SELECT {DISTINCT} T
             stmt.append );
         } else {
             //SELECT {DISTINCT} T.a,T.b,T.c
             for (int  i = 0; i < n_projectionsi++) {
                 if (i > 0) {
                     stmt.append"," );
                 }
                 _buildProjectionprojections.geti ), stmt );
                 //throws SearchException
             }
         }
     }



    
Projection COUNT(*) MAX(T.a) T.a
 
     private void _buildProjection(
                     final Projection p,
                     final StringBuilder stmt
                     )
     {
         if (p instanceof Aggregation) {
             Aggregation  aggr = (Aggregation)p;
             stmt.appendaggr.getFunction().name() );
             stmt.append"(" );
             String  expr = aggr.getExpression();
             if (..equalsexpr )) {
                 stmt.appendexpr );
             } else {
                 _buildPropertyexprstmt );
                 //throws SearchException
             }
             stmt.append")" );
         } else if (p instanceof PropertyProjection) {
             PropertyProjection  pp = (PropertyProjection)p;
             _buildPropertypp.getProperty(), stmt );
             //throws SearchException
         } else {
             throw new IllegalArgumentException"unsupported projection: " + p );
         }
     }



    
T.p
 
     private void _buildProperty(
                     final String property,
                     final StringBuilder stmt
                     )
     {
         if (property == null  ||  property.length() == 0) {
             throw new IllegalArgumentException"no property specified" );
         }
 
         stmt.append ).append"." ).appendproperty );
     }



    
Returns an alias name of the specified Java type. The alias name, say "alias" of class foo.bar.Baz, is used in an OQL like: "SELECT alias FROM foo.bar.Baz alias WHERE alias.p = ...".

In this implementation, the alias name is equal to the simple name of the class. For example, for the above class foo.bar.Baz, the alias name is "Baz" and is used like: "SELECT Baz FROM foo.bar.Baz Baz WHERE Baz.year = 2001".

Parameters:
type the type.
Returns:
the alias name.
    public static String aliasFor(
                    final Class<?> type
                    )
    {
        if (type.isArray()) {
            throw new IllegalArgumentException"array type" );
        }
        return type.getSimpleName();
    }
    ////////////////////////////////////////////////////////////////
    //  text match
    ////////////////////////////////////////////////////////////////

    
matchAll == true: (p LIKE w_1 AND p LIKE w_2 AND ... AND p LIKE w_m) matchAll == false: (p LIKE w_1 OR p LIKE w_2 OR ... OR p LIKE w_m)
    private void _buildTextMatchBinding(
                    final TextMatchBinding binding,
                    final StringBuilder stmt,
                    final List<Objectparams
                    )
    {
        int  size = binding.getText().size();
        if (size == 1) {
            String  pattern =  + binding.getText().iterator().next() + ;
            LikeBinding  like = new LikeBindingbinding.getProperty(), pattern );
            _buildLikeBindinglikestmtparams );
        } else if (size > 1) {
            LogicalBinding  logic = null;
            if (binding.isMatchAll()) {
                logic = new AndBinding();
            } else {
                logic = new OrBinding();
            }
            String  prop = binding.getProperty();
            for (String  w : binding.getText()) {
                String  pattern =  + w + ;
                LikeBinding  like = new LikeBindingproppattern );
                logic.addElementlike );
            }
            _buildLogicalBindinglogicstmtparams );
        } else {
            // size < 1
            throw new IllegalArgumentException"no text in TextMatchBinding" );
        }
    }
//    /**
//     * matchAll == true:
//     *      (p_1 LIKE w_1  OR  p_2 LIKE w_1  OR ... OR  p_n LIKE w_1)
//     *  AND
//     *      (p_1 LIKE w_2  OR  p_2 LIKE w_2  OR ... OR  p_n LIKE w_2)
//     *      .....
//     *  AND
//     *      (p_1 LIKE w_m  OR  p_2 LIKE w_m  OR ... OR  p_n LIKE w_m)
//     *
//     * matchAll == false:
//     *      (p_1 LIKE w_1  OR  p_2 LIKE w_1  OR ... OR  p_n LIKE w_1)
//     *  OR
//     *      (p_1 LIKE w_2  OR  p_2 LIKE w_2  OR ... OR  p_n LIKE w_2)
//     *      .....
//     *  OR
//     *      (p_1 LIKE w_m  OR  p_2 LIKE w_m  OR ... OR  p_n LIKE w_m)
//     */
//    private Binding _toFulltextBinding(
//                    final FulltextMatch match
//                    )
//    {
//        if (match == null) {
//            return null;
//        }
//        Collection<String>  properties = match.getProperties();
//        Collection<String>  patterns = match.getPatterns();
//        int  n_patterns = patterns.size();
//        if (n_patterns == 0  ||  properties.size() == 0) {
//            return null;
//        }
//        if (n_patterns == 1) {
//            return _createFulltextBinding( properties, patterns.iterator().next() );
//        }
//        LogicalBinding  logicalBinding = null;
//        if (match.isMatchAll()) {
//            logicalBinding = new AndBinding();
//        } else {
//            logicalBinding = new OrBinding();
//        }
//        for (String  pattern : patterns) {
//            Binding  binding = _createFulltextBinding( properties, pattern );
//            logicalBinding.addElement( binding );
//        }
//        return logicalBinding;
//    }
//    /**
//     * Returns a Binding for the specified search word.
//     *      p_1 LIKE w
//     *      p_1 LIKE w  OR  p_2 LIKE w  OR ... OR  p_n LIKE w
//     *
//     * @param   pattern
//     *  the full-text search word.
//     * @return
//     *  the result binding
//     *  or null if the word is empty.
//     */
//    private Binding _createFulltextBinding(
//                    final Collection<String> properties,
//                    final String pattern
//                    )
//    {
//        if (properties == null  ||  properties.size() == 0) {
//            return null;
//        }
//        final int  n_properties = properties.size();
//        if (n_properties == 1) {
//            return _createFulltextBinding( properties.iterator().next(), pattern );
//        }
//        OrBinding  or_binding = new OrBinding();
//        for (String  property : properties) {
//            LikeBinding  binding = _createFulltextBinding( property, pattern );
//            or_binding.addElement( binding );
//        }
//        return or_binding;
//    }
//    /**
//     * Creates a LIKE binding for the specified property
//     * and full-text search word.
//     *
//     * @param   property
//     *  the name of the property.
//     * @param   pattern
//     *  the full-text search word.
//     * @return
//     *  the LIKE binding
//     *  or null if the word is empty.
//     */
//    private LikeBinding _createFulltextBinding(
//                    final String property,
//                    final String pattern
//                    )
//    {
//        LikeBinding  binding = new LikeBinding();
//        binding.setProperty( property );
//        binding.setPattern( _WILDCARD_ + pattern + _WILDCARD_ );
//        return binding;
//    }
// OQL
New to GrepCode? Check out our FAQ X