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

org.hcjf.layers.query.Query Maven / Gradle / Ivy

package org.hcjf.layers.query;

import org.hcjf.bson.BsonDocument;
import org.hcjf.errors.HCJFRuntimeException;
import org.hcjf.layers.Layers;
import org.hcjf.layers.crud.IdentifiableLayerInterface;
import org.hcjf.layers.query.evaluators.*;
import org.hcjf.layers.query.functions.*;
import org.hcjf.layers.query.model.*;
import org.hcjf.properties.SystemProperties;
import org.hcjf.service.Service;
import org.hcjf.service.ServiceSession;
import org.hcjf.service.ServiceThread;
import org.hcjf.utils.Introspection;
import org.hcjf.utils.LruMap;
import org.hcjf.utils.NamedUuid;
import org.hcjf.utils.Strings;
import org.hcjf.utils.bson.BsonParcelable;

import java.text.ParseException;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * This class contains all the parameter needed to create a query.
 * This kind of queries works over any data collection.
 * @author javaito
 */
public class Query extends EvaluatorCollection implements Queryable {

    public static final String QUERY_BSON_FIELD_NAME = "__query__";
    private static final LruMap cache;

    private final QueryId id;
    private final QueryResource resource;
    private final List resources;
    private Integer limit;
    private Integer underlyingLimit;
    private Integer start;
    private Integer underlyingStart;
    private final List groupParameters;
    private final List orderParameters;
    private final List returnParameters;
    private final List joins;
    private boolean returnAll;
    //private String stringRepresentation;

    static {
        //Init query compiler cache
        cache = new LruMap<>(SystemProperties.getInteger(SystemProperties.Query.COMPILER_CACHE_SIZE));

        //Publishing default function layers...
        Layers.publishLayer(MathQueryFunctionLayer.class);
        Layers.publishLayer(StringQueryFunctionLayer.class);
        Layers.publishLayer(DateQueryFunctionLayer.class);
        Layers.publishLayer(ReferenceFunctionLayer.class);
        Layers.publishLayer(BsonQueryFunctionLayer.class);
        Layers.publishLayer(CollectionQueryFunction.class);
        Layers.publishLayer(ObjectQueryFunction.class);
        Layers.publishLayer(QueryBsonBuilderLayer.class);
        Layers.publishLayer(GeoQueryFunctionLayer.class);

        //Publishing default aggregate function layers...
        Layers.publishLayer(CountQueryAggregateFunctionLayer.class);
        Layers.publishLayer(SumAggregateFunctionLayer.class);
        Layers.publishLayer(ProductAggregateFunctionLayer.class);
        Layers.publishLayer(MeanAggregateFunctionLayer.class);
        Layers.publishLayer(MaxAggregateFunctionLayer.class);
        Layers.publishLayer(MinAggregateFunctionLayer.class);
        Layers.publishLayer(DistinctQueryAggregateFunction.class);
        Layers.publishLayer(GeoUnionAggregateFunctionLayer.class);
        Layers.publishLayer(GeoDistanceAggregateFunctionLayer.class);
        Layers.publishLayer(EvalExpressionAggregateFunctionLayer.class);
        Layers.publishLayer(ContextAggregateFunction.class);
    }

    public Query(QueryResource resource, QueryId id) {
        this.id = id;
        this.groupParameters = new ArrayList<>();
        this.orderParameters = new ArrayList<>();
        this.returnParameters = new ArrayList<>();
        this.joins = new ArrayList<>();
        this.resource = resource;
        this.resources = new ArrayList<>();
        this.resources.add(this.resource);
    }

    public Query(String resource) {
        this(new QueryResource(resource));
    }

    public Query(QueryResource resource){
        this(resource, new QueryId());
    }

    private Query(Query source) {
        super(source);
        this.id = new QueryId();
        this.resource = source.resource;
        this.resources = new ArrayList<>();
        this.resources.add(this.resource);
        this.limit = source.limit;
        this.start = source.start;
        this.returnAll = source.returnAll;
        this.orderParameters = new ArrayList<>();
        this.orderParameters.addAll(source.orderParameters);
        this.returnParameters = new ArrayList<>();
        this.returnParameters.addAll(source.returnParameters);
        this.groupParameters = new ArrayList<>();
        this.groupParameters.addAll(source.groupParameters);
        this.joins = new ArrayList<>();
        this.joins.addAll(source.joins);
    }

    private QueryParameter checkQueryParameter(QueryParameter queryParameter) {
        if(queryParameter instanceof QueryField) {
            QueryField queryField = (QueryField) queryParameter;
            QueryResource resource = queryField.getResource();
        } else if(queryParameter instanceof QueryFunction) {
            QueryFunction function = (QueryFunction) queryParameter;
            for(Object functionParameter : function.getParameters()) {
                if(functionParameter instanceof QueryParameter) {
                    checkQueryParameter((QueryParameter) functionParameter);
                }
            }
        }
        return queryParameter;
    }

    @Override
    protected Evaluator checkEvaluator(Evaluator evaluator) {
        if(evaluator instanceof FieldEvaluator) {
            FieldEvaluator fieldEvaluator = (FieldEvaluator) evaluator;
            if(fieldEvaluator.getLeftValue() instanceof QueryParameter) {
                checkQueryParameter((QueryParameter) fieldEvaluator.getLeftValue());
            }
            if(fieldEvaluator.getRightValue() instanceof QueryParameter) {
                checkQueryParameter((QueryParameter) fieldEvaluator.getRightValue());
            }
        }
        return evaluator;
    }

    /**
     * Verify if the query indicates return all the fields of the result set.
     * @return Return all.
     */
    public final boolean returnAll() {
        return returnAll || returnParameters.isEmpty();
    }

    /**
     * Returns the parameterized query based in this instance of query.
     * @return Parameterized query instance.
     */
    public final ParameterizedQuery getParameterizedQuery() {
        return new ParameterizedQuery(this);
    }

    /**
     * Return the id of the query.
     * @return Id of the query.
     */
    public final QueryId getId() {
        return id;
    }

    /**
     * Return the list of joins.
     * @return Joins.
     */
    public List getJoins() {
        return Collections.unmodifiableList(joins);
    }

    /**
     * Return the resource query object.
     * @return Resource query.
     */
    public QueryResource getResource() {
        return resource;
    }

    /**
     * Return the resource name.
     * @return Resource name.
     */
    public final String getResourceName() {
        return resource.getResourceName();
    }

    /**
     * Returns the list of resource of the query.
     * @return List of resource fo the query.
     */
    public List getResources() {
        return resources;
    }

    /**
     * Return the limit of the query.
     * @return Query limit.
     */
    public final Integer getLimit() {
        return limit;
    }

    /**
     * Set the query limit.
     * @param limit Query limit.
     */
    public final void setLimit(Integer limit) {
        this.limit = limit;
    }

    /**
     * Returns the query underlying limit.
     * @return Underlying limit.
     */
    public Integer getUnderlyingLimit() {
        return underlyingLimit;
    }

    /**
     * Set the underlying limit.
     * @param underlyingLimit Underlying limit value.
     */
    public void setUnderlyingLimit(Integer underlyingLimit) {
        this.underlyingLimit = underlyingLimit;
    }

    /**
     * Return the object that represents the first element of the result.
     * @return Firts object of the result.
     */
    public final Integer getStart() {
        return start != null ? start : 0;
    }

    /**
     * Set the first object of the result.
     * @param start First object of the result.
     */
    public final void setStart(Integer start) {
        this.start = start;
    }

    /**
     * Returns the underlying start.
     * @return Underlying start value.
     */
    public Integer getUnderlyingStart() {
        return underlyingStart;
    }

    /**
     * Set the underlying start.
     * @param underlyingStart Underlying start value.
     */
    public void setUnderlyingStart(Integer underlyingStart) {
        this.underlyingStart = underlyingStart;
    }

    /**
     * Return all the group fields of the query.
     * @return Group field of the query.
     */
    public List getGroupParameters() {
        return Collections.unmodifiableList(groupParameters);
    }

    /**
     * Add a name of the field for group the data collection. This name must be exist
     * like a setter/getter method in the instances of the data collection.
     * @param groupField Name of the pair getter/setter.
     * @return Return the same instance of this class.
     */
    public final Query addGroupField(String groupField) {
        return addGroupField(new QueryReturnField(this, groupField));
    }

    /**
     * Add a name of the field for group the data collection. This name must be exist
     * like a setter/getter method in the instances of the data collection.
     * @param groupField Name of the pair getter/setter.
     * @return Return the same instance of this class.
     */
    public final Query addGroupField(QueryReturnParameter groupField) {
        groupParameters.add((QueryReturnParameter)checkQueryParameter((QueryParameter) groupField));
        return this;
    }

    /**
     * Return the unmodifiable list with order fields.
     * @return Order fields.
     */
    public final List getOrderParameters() {
        return Collections.unmodifiableList(orderParameters);
    }

    /**
     * Add a name of the field for order the data collection. This name must be exist
     * like a setter/getter method in the instances of the data collection.
     * @param orderField Name of the pair getter/setter.
     * @return Return the same instance of this class.
     */
    public final Query addOrderField(String orderField) {
        return addOrderField(orderField, SystemProperties.getBoolean(SystemProperties.Query.DEFAULT_DESC_ORDER));
    }

    /**
     * Add a name of the field for order the data collection. This name must be exist
     * like a setter/getter method in the instances of the data collection.
     * @param orderField Name of the pair getter/setter.
     * @param desc Desc property.
     * @return Return the same instance of this class.
     */
    public final Query addOrderField(String orderField, boolean desc) {
        return addOrderParameter(new QueryOrderField(this, orderField, desc));
    }

    /**
     * Add a name of the field for order the data collection. This name must be exist
     * like a setter/getter method in the instances of the data collection.
     * @param orderParameter Order parameter.
     * @return Return the same instance of this class.
     */
    public final Query addOrderParameter(QueryOrderParameter orderParameter) {
        orderParameters.add((QueryOrderParameter) checkQueryParameter((QueryParameter) orderParameter));
        return this;
    }

    /**
     * Return an unmodifiable list with the return fields.
     * @return Return fields.
     */
    public final List getReturnParameters() {
        return Collections.unmodifiableList(returnParameters);
    }

    /**
     * Add the name of the field to be returned in the result set.
     * @param returnField Field name.
     * @return Return the same instance of this class.
     */
    public final Query addReturnField(String returnField) {
        if(returnField.equals(SystemProperties.get(SystemProperties.Query.ReservedWord.RETURN_ALL))) {
            returnAll = true;
        } else {
            addReturnField(new QueryReturnField(this, returnField));
        }
        return this;
    }

    /**
     * Add the name of the field to be returned in the result set.
     * @param returnParameter Return parameter.
     * @return Return the same instance of this class.
     */
    public final Query addReturnField(QueryReturnParameter returnParameter) {
        if(returnParameter instanceof QueryReturnField && ((QueryReturnField)returnParameter).getFieldPath().equals(
                SystemProperties.get(SystemProperties.Query.ReservedWord.RETURN_ALL))) {
            returnAll = true;
        } else {
            returnParameters.add((QueryReturnParameter) checkQueryParameter((QueryParameter) returnParameter));
        }
        return this;
    }

    /**
     * Add join instance to the query.
     * @param join Join instance.
     */
    public final void addJoin(Join join) {
        if(join != null && !joins.contains(join)) {
            joins.add(join);
        } else {
            if(join == null) {
                throw new NullPointerException("Null join instance");
            }
        }
    }

    /**
     * This method evaluate each object of the collection and sort filtered
     * object to create a result add with the object filtered and sorted.
     * If there are order fields added then the result implementation is a
     * {@link TreeSet} implementation else the result implementation is a
     * {@link LinkedHashSet} implementation in order to guarantee the data order
     * from the source
     * @param dataSource Data source to evaluate the query.
     * @param  Kind of instances of the data collection.
     * @return Result add filtered and sorted.
     */
    @Override
    public final  Collection evaluate(Collection dataSource) {
        return evaluate((query) -> dataSource, new Queryable.IntrospectionConsumer<>());
    }

    /**
     * This method evaluate each object of the collection and sort filtered
     * object to create a result add with the object filtered and sorted.
     * If there are order fields added then the result implementation is a
     * {@link TreeSet} implementation else the result implementation is a
     * {@link LinkedHashSet} implementation in order to guarantee the data order
     * from the source
     * @param dataSource Data source to evaluate the query.
     * @param consumer Data source consumer.
     * @param  Kind of instances of the data collection.
     * @return Result add filtered and sorted.
     */
    @Override
    public final  Collection evaluate(Collection dataSource, Queryable.Consumer consumer) {
        return evaluate((query) -> dataSource, consumer);
    }

    /**
     * This method evaluate each object of the collection and sort filtered
     * object to create a result add with the object filtered and sorted.
     * If there are order fields added then the result implementation is a
     * {@link TreeSet} implementation else the result implementation is a
     * {@link LinkedHashSet} implementation in order to guarantee the data order
     * from the source
     * @param dataSource Data source to evaluate the query.
     * @param  Kind of instances of the data collection.
     * @return Result add filtered and sorted.
     */
    @Override
    public final  Collection evaluate(Queryable.DataSource dataSource) {
        return evaluate(dataSource, new Queryable.IntrospectionConsumer<>());
    }

    /**
     * This method evaluate each object of the collection and sort filtered
     * object to create a result add with the object filtered and sorted.
     * If there are order fields added then the result implementation is a
     * {@link TreeSet} implementation else the result implementation is a
     * {@link LinkedHashSet} implementation in order to guarantee the data order
     * from the source
     * @param dataSource Data source to evaluate the query.
     * @param consumer Data source consumer.
     * @param  Kind of instances of the data collection.
     * @return Result add filtered and sorted.
     */
    @Override
    public final  Collection evaluate(Queryable.DataSource dataSource, Queryable.Consumer consumer) {
        Collection result;
        Map groupables = null;
        List aggregateFunctions = new ArrayList<>();
        if(!(Thread.currentThread() instanceof ServiceThread)) {
            //If the current thread is not a service thread then we call this
            //method again using a service thread.
            result = Service.call(()->evaluate(dataSource, consumer), ServiceSession.getGuestSession());
        } else {
            Long totalTime = System.currentTimeMillis();

            //Initialize the evaluators cache because the evaluators in the simple
            //query are valid into the platform evaluation environment.
            initializeEvaluatorsCache();

            //Creating result data collection.
            if (orderParameters.size() > 0) {
                //If the query has order fields then creates a tree set with
                //a comparator using the order fields.
                result = new TreeSet<>((o1, o2) -> {
                    int compareResult = 0;

                    Comparable comparable1;
                    Comparable comparable2;
                    for (QueryOrderParameter orderField : orderParameters) {
                        try {
                            if (orderField instanceof QueryOrderFunction) {
                                comparable1 = consumer.resolveFunction(((QueryOrderFunction) orderField), o1, dataSource);
                                comparable2 = consumer.resolveFunction(((QueryOrderFunction) orderField), o2, dataSource);
                            } else {
                                comparable1 = consumer.get(o1, (QueryParameter) orderField, dataSource);
                                comparable2 = consumer.get(o2, (QueryParameter) orderField, dataSource);
                            }
                        } catch (ClassCastException ex) {
                            throw new HCJFRuntimeException("Order field must be comparable");
                        }

                        if (comparable1 == null ^ comparable2 == null) {
                            compareResult = (comparable1 == null) ? -1 : 1;
                        } else if (comparable1 == null && comparable2 == null) {
                            compareResult = 0;
                        } else {
                            compareResult = comparable1.compareTo(comparable2) * (orderField.isDesc() ? -1 : 1);
                        }

                        if (compareResult != 0) {
                            break;
                        }
                    }

                    if (compareResult == 0) {
                        compareResult = o1.hashCode() - o2.hashCode();
                    }

                    return compareResult;
                });
            } else {
                //If the query has not order fields then creates a linked hash set to
                //maintain the natural order of the data.
                result = new ArrayList<>();
            }

            Long timeCollectingData = System.currentTimeMillis();
            Integer evaluatingCount = 0;
            Integer formattingCount = 0;
            Long timeEvaluatingConditions = 0L;
            Long timeFormattingData = 0L;
            Set presentFields = new TreeSet<>();

            //Getting data from data source.
            Collection data;
            try {
                if (joins.size() > 0) {
                    data = (Collection) join((DataSource) dataSource, (Consumer) consumer);
                } else {
                    //If the query has not joins then data source must return data from
                    //resource of the query.
                    if(getResource() instanceof QueryDynamicResource) {
                        data = (Collection) resolveDynamicResource((QueryDynamicResource)getResource());
                    } else {
                        //Creates the first query for the original resource.
                        Query resolveQuery = new Query(getResource());
                        resolveQuery.returnAll = true;

                        resolveQuery.setLimit(getLimit());
                        resolveQuery.setUnderlyingLimit(getUnderlyingLimit());
                        resolveQuery.setStart(getStart());
                        resolveQuery.setUnderlyingStart(getUnderlyingStart());
                        for(QueryOrderParameter orderParameter : getOrderParameters()) {
                            resolveQuery.addOrderParameter(orderParameter);
                        }

                        copyEvaluators(resolveQuery, this);
                        data = dataSource.getResourceData(verifyInstance(resolveQuery, consumer));
                    }
                }
                timeCollectingData = System.currentTimeMillis() - timeCollectingData;

                //Filtering data
                boolean add;

                //Collect all the aggregate functions into the array list.
                List returnParametersAsArray = new ArrayList<>();
                for (QueryReturnParameter returnParameter : getReturnParameters()) {
                    if(returnParameter instanceof QueryReturnFunction && ((QueryReturnFunction)returnParameter).isAggregate()) {
                        aggregateFunctions.add((QueryReturnFunction) returnParameter);
                    }
                    returnParametersAsArray.add(returnParameter.getAlias());
                }

                StringBuilder hashCode;
                Groupable groupable;
                if (!groupParameters.isEmpty()) {
                    groupables = new HashMap<>();
                }

                for (O object : data) {
                    Long timeEvaluating = System.currentTimeMillis();
                    add = verifyCondition(object, dataSource, consumer);
                    timeEvaluating = System.currentTimeMillis() - timeEvaluating;
                    timeEvaluatingConditions += timeEvaluating;
                    evaluatingCount++;
                    if (add) {
                        Long timeFormatting = System.currentTimeMillis();
                        if (object instanceof Enlarged) {
                            Enlarged enlargedObject;
                            if(returnAll) {
                                enlargedObject = ((Enlarged) object).clone();
                                presentFields.addAll(enlargedObject.keySet());
                            } else {
                                enlargedObject = ((Enlarged) object).clone(returnParametersAsArray.toArray(new String[]{}));
                            }
                            object = (O) enlargedObject;
                            String name;
                            Object value;
                            for (QueryReturnParameter returnParameter : getReturnParameters()) {
                                name = null;
                                value = null;
                                if (returnParameter instanceof QueryReturnField) {
                                    QueryReturnField returnField = (QueryReturnField) returnParameter;
                                    name = returnField.getAlias();
                                    value = consumer.get((O) enlargedObject, returnField, dataSource);
                                } else if (returnParameter instanceof QueryReturnFunction && !((QueryReturnFunction)returnParameter).isAggregate()) {
                                    QueryReturnFunction function = (QueryReturnFunction) returnParameter;
                                    name = function.getAlias();
                                    value = consumer.resolveFunction(function, enlargedObject, dataSource);
                                }
                                if(name != null && value != null) {
                                    presentFields.add(name);
                                    enlargedObject.put(name, value);
                                }
                            }
                        }

                        if (!groupParameters.isEmpty() && object instanceof Groupable) {
                            groupable = (Groupable) object;
                            hashCode = new StringBuilder();
                            Object groupValue;
                            for (QueryReturnParameter returnParameter : groupParameters) {
                                if (returnParameter instanceof QueryReturnField) {
                                    groupValue = consumer.get(object, ((QueryReturnField) returnParameter), dataSource);
                                } else {
                                    groupValue = consumer.resolveFunction(((QueryReturnFunction) returnParameter), object, dataSource);
                                }
                                if(groupValue == null) {
                                    hashCode.append(SystemProperties.get(SystemProperties.Query.ReservedWord.NULL).hashCode());
                                } else {
                                    hashCode.append(groupValue.hashCode());
                                }
                            }
                            if (groupables.containsKey(hashCode.toString())) {
                                groupables.get(hashCode.toString()).group(groupable);
                            } else {
                                groupables.put(hashCode.toString(), groupable);
                            }
                        } else {
                            result.add(object);
                        }
                        timeFormatting = System.currentTimeMillis() - timeFormatting;
                        formattingCount++;
                        timeFormattingData += timeFormatting;
                    }
                }

                if(groupables != null) {
                    result.addAll((Collection) groupables.values());
                }
            } finally {
                clearEvaluatorsCache();
            }

            Long timeAggregatingData = System.currentTimeMillis();
            if(aggregateFunctions.size() > 0) {
                for (QueryReturnFunction function : aggregateFunctions) {
                    result = consumer.resolveFunction(function, result, dataSource);
                }
            }

            if(result.size() > 0 && result.iterator().next() instanceof Enlarged && !returnAll) {
                result.forEach(O -> ((Enlarged)O).purge());
            }

            if(getStart() != 0 || getLimit() != null) {
                if (getLimit() != null) {
                    result = result.stream().skip(getStart()).limit(getLimit()).collect(Collectors.toList());
                } else {
                    result = result.stream().skip(getStart()).collect(Collectors.toList());
                }
            }
            timeAggregatingData = System.currentTimeMillis() - timeAggregatingData;
            totalTime = System.currentTimeMillis() - totalTime;

            ResultSet resultSet = new ResultSet<>(totalTime, timeCollectingData,
                    timeEvaluatingConditions,
                    evaluatingCount == 0 ? 0 :timeEvaluatingConditions / evaluatingCount,
                    timeFormattingData,
                    formattingCount == 0 ? 0 :timeFormattingData / formattingCount,
                    timeAggregatingData,
                    presentFields,
                    result);
            result = resultSet;
        }

        return result;
    }

    /**
     * Resolves dynamic resource and returns a collection with enlarged objects.
     * @param resource Dynamic resource instance.
     * @return Result set.
     */
    private Collection resolveDynamicResource(QueryDynamicResource resource) {
        QueryDynamicResource queryDynamicResource = (QueryDynamicResource) getResource();
        Collection data = Query.evaluate(queryDynamicResource.getQuery());

        if(queryDynamicResource.getPath() != null && !queryDynamicResource.getPath().isBlank()) {
            Collection resultPath = resolveResourcePath(data, queryDynamicResource.getPath());
            data = new ArrayList<>();
            for(Object dataObject : resultPath) {
                data.add(new JoinableMap(Introspection.toMap(dataObject)));
            }
        }
        return data;
    }

    /**
     * Resolve the introspection over the result set making union of the fields values.
     * @param resultSet Result set to make introspection.
     * @param path Path in order to found the value.
     * @return Returns the union of all the values.
     */
    private Collection resolveResourcePath(Collection resultSet, String path) {
        Collection result = new ArrayList<>();
        Object pathValue;
        for(Object row : resultSet) {
            pathValue = Introspection.resolve(row, path);
            if(pathValue != null) {
                if (pathValue instanceof Collection) {
                    result.addAll(((Collection) pathValue));
                } else {
                    result.add(pathValue);
                }
            }
        }
        return result;
    }

    private Queryable verifyInstance(Query query, Consumer consumer) {
        Queryable result = query;
        if(consumer instanceof ParameterizedQuery.ParameterizedConsumer &&
                ((ParameterizedQuery.ParameterizedConsumer)consumer).getParameters().size() > 0) {
            ParameterizedQuery parameterizedQuery = query.getParameterizedQuery();
            for(Object parameter : ((ParameterizedQuery.ParameterizedConsumer)consumer).getParameters()) {
                parameterizedQuery.add(parameter);
            }
            result = parameterizedQuery;
        }
        return result;
    }

    /**
     * This method verify if the conditions of the query are true or not.
     * @param object Object to use as condition parameters.
     * @return Returns if the evaluation of conditions are true or false in the otherwise.
     */
    public final boolean verifyCondition(Object object) {
        Consumer consumer = new Queryable.IntrospectionConsumer<>();
        Collection collection = List.of(object);
        return verifyCondition(object, Q->collection, consumer);
    }

    /**
     * This method verify if the conditions of the query are true or not.
     * @param object Object to use as condition parameters.
     * @param dataSource Data source.
     * @param consumer Consumer.
     * @return Returns if the evaluation of conditions are true or false in the otherwise.
     */
    private boolean verifyCondition(Object object, DataSource dataSource, Consumer consumer) {
        Boolean result = true;
        for (Evaluator evaluator : getEvaluators()) {
            if (evaluator instanceof BooleanEvaluator &&
                    ((BooleanEvaluator) evaluator).getValue() instanceof QueryParameter &&
                    ((QueryParameter)((BooleanEvaluator) evaluator).getValue()).isUnderlying()) {
                continue;
            }
            if (!isEvaluatorDone(evaluator)) {
                result &= evaluator.evaluate(object, dataSource, consumer);
                if (!result) {
                    break;
                }
            }
        }
        return result;
    }

    /**
     * This method check if the evaluator is evaluated previously into the current session.
     * @param evaluator Checking evaluator.
     * @return Return true if the evaluator is done and false in the otherwise.
     */
    private boolean isEvaluatorDone(Evaluator evaluator) {
        boolean result = false;

        ServiceSession session = ServiceSession.getCurrentSession();
        if(session != null) {
            List evaluatorsCache = (List) session.getProperties().get(
                    SystemProperties.get(SystemProperties.Query.EVALUATORS_CACHE_NAME));
            if(evaluatorsCache != null) {
                result = evaluatorsCache.contains(evaluator);
            }
        }

        return result;
    }

    /**
     * Initialize the evaluators cache into the current session.
     */
    private void initializeEvaluatorsCache() {
        ServiceSession session = ServiceSession.getCurrentIdentity();
        if(session != null) {
            session.put(SystemProperties.get(SystemProperties.Query.EVALUATORS_CACHE_NAME),
                    new ArrayList());
            session.put(SystemProperties.get(SystemProperties.Query.EVALUATOR_LEFT_VALUES_CACHE_NAME),
                    new HashMap());
            session.put(SystemProperties.get(SystemProperties.Query.EVALUATOR_RIGHT_VALUES_CACHE_NAME),
                    new HashMap());
        }
    }

    /**
     * Removes the evaluators cache of the current session.
     */
    private void clearEvaluatorsCache() {
        ServiceSession session = ServiceSession.getCurrentIdentity();
        if(session != null) {
            session.remove(SystemProperties.get(SystemProperties.Query.EVALUATORS_CACHE_NAME));
            session.remove(SystemProperties.get(SystemProperties.Query.EVALUATOR_LEFT_VALUES_CACHE_NAME));
            session.remove(SystemProperties.get(SystemProperties.Query.EVALUATOR_RIGHT_VALUES_CACHE_NAME));
        }
    }

    /**
     * This method add into the current session an instance that must be skipped of the
     * platform evaluation process.
     * @param evaluator Evaluator to skip.
     */
    public static void skipEvaluator(Evaluator evaluator) {
        ServiceSession session = ServiceSession.getCurrentSession();
        if(session != null) {
            List evaluatorsCache = (List) session.getProperties().get(
                    SystemProperties.get(SystemProperties.Query.EVALUATORS_CACHE_NAME));
            if(evaluatorsCache != null) {
                evaluatorsCache.add(evaluator);
            }
        }
    }

    /**
     * Copy all the evaluator from the source collection to destiny collection.
     * @param dest Destiny collection.
     * @param src Source collection.
     */
    private void copyEvaluators(EvaluatorCollection dest, EvaluatorCollection src) {
        for(Evaluator evaluator : src.getEvaluators()) {
            if(evaluator instanceof FieldEvaluator) {
                dest.addEvaluator(((FieldEvaluator)evaluator).copy());
            } else if(evaluator instanceof BooleanEvaluator) {
                BooleanEvaluator booleanEvaluator = (BooleanEvaluator) evaluator;
                if(booleanEvaluator.getValue() instanceof QueryFunction) {
                    QueryFunction queryFunction = (QueryFunction) booleanEvaluator.getValue();
                    if(queryFunction.isUnderlying()) {
                        dest.addEvaluator(evaluator);
                    }
                }
            } else if(evaluator instanceof And) {
                copyEvaluators(dest.and(), (EvaluatorCollection) evaluator);
            } else if(evaluator instanceof Or) {
                copyEvaluators(dest.or(), (EvaluatorCollection) evaluator);
            }
        }
    }

    private Collection setResource(Collection resultSet, String resourceName) {
        for(Joinable joinable : resultSet) {
            if(joinable instanceof  JoinableMap) {
                ((JoinableMap)joinable).setResource(resourceName);
            }
        }
        return resultSet;
    }

    /**
     * Evaluates the join operation.
     * @param dataSource Data source instance.
     * @param consumer Consumer instance.
     * @return Collection that is the result of the join operation.
     */
    private Collection join(Queryable.DataSource dataSource, Queryable.Consumer consumer) {
        Query query = new Query(getResource());
        query.addReturnField(SystemProperties.get(SystemProperties.Query.ReservedWord.RETURN_ALL));
        for (Evaluator evaluator : getEvaluatorsFromResource(this, query, query.getResource())) {
            query.addEvaluator(evaluator);
        }

        Collection rightData;
        Collection leftData = getJoinData(query, dataSource, consumer);

        for(Join join : getJoins()) {
            //Creates the first query for the original resource.
            query = new Query(join.getResource());
            query.addReturnField(SystemProperties.get(SystemProperties.Query.ReservedWord.RETURN_ALL));
            for (Evaluator evaluator : optimizeJoin(leftData, join)) {
                query.addEvaluator(evaluator);
            }
            for (Evaluator evaluator : getEvaluatorsFromResource(this, query, join.getResource())) {
                query.addEvaluator(evaluator);
            }
            rightData = getJoinData(query, dataSource, consumer);
            leftData = product(leftData, rightData, join, dataSource, consumer);
        }
        return leftData;
    }

    /**
     * Get the data for each part of the join.
     * @param query Query associated to the join.
     * @param dataSource Data source instance.
     * @param consumer Consumer instance.
     * @return Returns the result set.
     */
    private Collection getJoinData(Query query, Queryable.DataSource dataSource, Queryable.Consumer consumer) {
        Collection result;
        if(query.getResource() instanceof  QueryDynamicResource) {
            result = resolveDynamicResource((QueryDynamicResource) query.getResource());
        } else {
            result = dataSource.getResourceData(verifyInstance(query, consumer));
        }
        return setResource(result, query.getResourceName());
    }

    /**
     * This method analyze the join structure and creates a set of evaluators in order to improve the performance of
     * the sub queries used to select the objects of the right resource of the join.
     * @param leftData Collection with the left data.
     * @param join Join structure.
     * @return Returns a set of the new filters in order to reduce the information of the right data.
     */
    private Collection optimizeJoin(Collection leftData, Join join) {
        Collection result = new ArrayList<>();

        if(join.getType().equals(Join.JoinType.JOIN) ||
                join.getType().equals(Join.JoinType.INNER) ||
                join.getType().equals(Join.JoinType.LEFT)) {
            if(join.getEvaluators().size() == 1) {
                if(join.getEvaluators().stream().findFirst().get() instanceof Equals) {
                    //the join was identified with only one equality (...ON resource1.field = resource2.field)
                    Equals equals = (Equals) join.getEvaluators().stream().findFirst().get();
                    if(equals.getLeftValue() instanceof QueryField && equals.getRightValue() instanceof QueryField) {
                        QueryField foreignKey = null;
                        QueryField key = null;
                        if (!((QueryField) equals.getLeftValue()).getResource().equals(join.getResource()) &&
                                ((QueryField) equals.getRightValue()).getResource().equals(join.getResource())) {
                            foreignKey = (QueryField) equals.getLeftValue();
                            key = (QueryField) equals.getRightValue();
                        } else if (!((QueryField) equals.getRightValue()).getResource().equals(join.getResource()) &&
                                ((QueryField) equals.getLeftValue()).getResource().equals(join.getResource())) {
                            foreignKey = (QueryField) equals.getRightValue();
                            key = (QueryField) equals.getLeftValue();
                        }
                        if(foreignKey != null) {
                            Collection reducerList = new HashSet();
                            for(Object currentObject : leftData) {
                                Object foreignKeyValue = Introspection.resolve(currentObject, foreignKey.getFieldPath());
                                if(foreignKeyValue != null) {
                                    reducerList.add(foreignKeyValue);
                                }
                            }
                            In inEvaluator = new In(key, reducerList);
                            result.add(inEvaluator);
                        }
                    }
                }
            }
        }

        return result;
    }

    /**
     * Evaluates the join and creates the product of the intersection between the first resource and the second resource.
     * @param left Left data to the product.
     * @param right Right data to the product.
     * @param join Join object to evaluate the kind and the evaluators of the product.
     * @param dataSource Datasource instance.
     * @param consumer Consumer instance.
     * @return Collection that is the result of the join operation.
     */
    private Collection product(Collection left, Collection right, Join join,
                                         Queryable.DataSource dataSource, Queryable.Consumer consumer) {

        Collection leftCopy = null;
        Collection rightCopy = null;
        switch (join.getType()) {
            case LEFT: {
                leftCopy = new ArrayList<>();
                leftCopy.addAll(left);
                break;
            }
            case RIGHT: {
                rightCopy = new ArrayList<>();
                rightCopy.addAll(right);
                break;
            }
            case FULL: {
                leftCopy = new ArrayList<>();
                leftCopy.addAll(left);
                rightCopy = new ArrayList<>();
                rightCopy.addAll(right);
                break;
            }
        }

        Collection result = new ArrayList<>();
        Joinable row;
        Boolean rowEvaluation;
        for(Joinable leftJoinable : left) {
            for(Joinable rightJoinable : right) {
                row = leftJoinable.join(getResourceName(), join.getResourceName(), rightJoinable);
                rowEvaluation = false;

                for(Evaluator evaluator : join.getEvaluators()) {
                    if(!(rowEvaluation = evaluator.evaluate(row, dataSource, consumer))) {
                        break;
                    }
                }

                if(join.getOuter()) {
                    rowEvaluation = !rowEvaluation;
                }

                if(rowEvaluation) {
                    result.add(row);
                    switch (join.getType()) {
                        case LEFT: {
                            leftCopy.remove(leftJoinable);
                            break;
                        }
                        case RIGHT: {
                            rightCopy.remove(rightJoinable);
                            break;
                        }
                        case FULL: {
                            leftCopy.remove(leftJoinable);
                            rightCopy.remove(rightJoinable);
                            break;
                        }
                    }
                }
            }
        }

        switch (join.getType()) {
            case LEFT: {
                result.addAll(leftCopy);
                break;
            }
            case RIGHT: {
                result.addAll(rightCopy);
                break;
            }
            case FULL: {
                result.addAll(leftCopy);
                result.addAll(rightCopy);
                break;
            }
        }

        return result;
    }

    /**
     * Return the list of evaluator for the specific resource.
     * @param collection Evaluator collection.
     * @param resource Resource type.
     * @return List of evaluators.
     */
    private List getEvaluatorsFromResource(EvaluatorCollection collection, EvaluatorCollection parent, QueryResource resource) {
        List result = new ArrayList<>();
        QueryParameter queryParameter;
        for(Evaluator evaluator : collection.getEvaluators()) {
            if(evaluator instanceof FieldEvaluator) {
                FieldEvaluator fieldEvaluator = (FieldEvaluator) evaluator;
                boolean evaluatorAdded = false;

                if (fieldEvaluator.getLeftValue() instanceof QueryParameter) {
                    queryParameter = (QueryParameter) fieldEvaluator.getLeftValue();
                    if(queryParameter.verifyResource(resource)) {
                        result.add(evaluator);
                        evaluatorAdded = true;
                    }
                }

                if (!evaluatorAdded) {
                    if (fieldEvaluator.getRightValue() instanceof QueryParameter) {
                        queryParameter = (QueryParameter) fieldEvaluator.getRightValue();
                        if(queryParameter.verifyResource(resource)) {
                            result.add(evaluator);
                        }
                    }
                }
            } else if(evaluator instanceof BooleanEvaluator) {
                if(((BooleanEvaluator)evaluator).getValue() instanceof QueryParameter) {
                    queryParameter = (QueryParameter) ((BooleanEvaluator)evaluator).getValue();
                    if(queryParameter.verifyResource(resource)) {
                        result.add(evaluator);
                    }
                }
            } else if(evaluator instanceof EvaluatorCollection) {
                EvaluatorCollection subCollection = null;
                if(evaluator instanceof And) {
                    subCollection = new And(parent);
                } else if(evaluator instanceof Or) {
                    subCollection = new Or(parent);
                }
                for(Evaluator subEvaluator : getEvaluatorsFromResource((EvaluatorCollection)evaluator, subCollection, resource)) {
                    subCollection.addEvaluator(subEvaluator);
                }
            }
        }
        return result;
    }

    /**
     * Return a copy of this query without all the evaluator and order fields of the
     * parameter collections.
     * @param evaluatorsToRemove Evaluators to optimizeJoin.
     * @return Reduced copy of the query.
     */
    public final Query reduce(Collection evaluatorsToRemove) {
        Query copy = new Query(this);
        copy.evaluators.addAll(this.evaluators);

        if(evaluatorsToRemove != null && !evaluatorsToRemove.isEmpty()) {
            reduceCollection(copy, evaluatorsToRemove);
        }

        return copy;
    }


    public final Query reduceFieldEvaluator(String fieldName, Class... evaluatorType) {
        return reduce(getFieldEvaluators(fieldName, evaluatorType));
    }

    /**
     * Reduce recursively all the collection into the query.
     * @param collection Collection to optimizeJoin.
     * @param evaluatorsToRemove Evaluator to remove.
     */
    private final void reduceCollection(EvaluatorCollection collection, Collection evaluatorsToRemove) {
        for(Evaluator evaluatorToRemove : evaluatorsToRemove) {
            collection.removeEvaluator(evaluatorToRemove);
            collection.addEvaluator(new TrueEvaluator());
        }

        for(Evaluator evaluator : collection.getEvaluators()) {
            if(evaluator instanceof Or || evaluator instanceof And) {
                reduceCollection((EvaluatorCollection)evaluator, evaluatorsToRemove);
            }
        }
    }

    /**
     * Creates a string representation of the query object.
     * @return String representation.
     */
    @Override
    public synchronized String toString() {
//        if(stringRepresentation == null) {
            Strings.Builder resultBuilder = new Strings.Builder();

            //Print select
            resultBuilder.append(SystemProperties.get(SystemProperties.Query.ReservedWord.SELECT));
            resultBuilder.append(Strings.WHITE_SPACE);
            if (returnAll) {
                resultBuilder.append(SystemProperties.get(SystemProperties.Query.ReservedWord.RETURN_ALL));
                SystemProperties.get(SystemProperties.Query.ReservedWord.ARGUMENT_SEPARATOR);
                resultBuilder.append(Strings.WHITE_SPACE);
            }
            for (QueryReturnParameter field : getReturnParameters()) {
                resultBuilder.append(field);
                if (field.getAlias() != null) {
                    resultBuilder.append(Strings.WHITE_SPACE).append(SystemProperties.get(SystemProperties.Query.ReservedWord.AS));
                    resultBuilder.append(Strings.WHITE_SPACE).append(field.getAlias());
                }
                resultBuilder.append(Strings.EMPTY_STRING, SystemProperties.get(SystemProperties.Query.ReservedWord.ARGUMENT_SEPARATOR));
            }
            resultBuilder.cleanBuffer();

            //Print from
            resultBuilder.append(Strings.WHITE_SPACE);
            resultBuilder.append(SystemProperties.get(SystemProperties.Query.ReservedWord.FROM));
            resultBuilder.append(Strings.WHITE_SPACE);
            resultBuilder.append(getResource().toString());
            resultBuilder.append(Strings.WHITE_SPACE);

            //Print joins
            for (Join join : joins) {
                if (!(join.getType() == Join.JoinType.JOIN)) {
                    resultBuilder.append(join.getType());
                    resultBuilder.append(Strings.WHITE_SPACE);
                }
                resultBuilder.append(SystemProperties.get(SystemProperties.Query.ReservedWord.JOIN)).append(Strings.WHITE_SPACE);
                resultBuilder.append(join.getResource().toString()).append(Strings.WHITE_SPACE);
                resultBuilder.append(SystemProperties.get(SystemProperties.Query.ReservedWord.ON)).append(Strings.WHITE_SPACE);
                if (join.getEvaluators().size() > 0) {
                    toStringEvaluatorCollection(resultBuilder, join);
                }
            }

            if (evaluators.size() > 0) {
                resultBuilder.append(SystemProperties.get(SystemProperties.Query.ReservedWord.WHERE)).append(Strings.WHITE_SPACE);
                toStringEvaluatorCollection(resultBuilder, this);
            }

            if (groupParameters.size() > 0) {
                resultBuilder.append(SystemProperties.get(SystemProperties.Query.ReservedWord.GROUP_BY)).append(Strings.WHITE_SPACE);
                for (QueryReturnParameter groupParameter : groupParameters) {
                    resultBuilder.append(groupParameter, SystemProperties.Query.ReservedWord.ARGUMENT_SEPARATOR);
                }
                resultBuilder.append(Strings.WHITE_SPACE);
                resultBuilder.cleanBuffer();
            }

            if (orderParameters.size() > 0) {
                resultBuilder.append(SystemProperties.get(SystemProperties.Query.ReservedWord.ORDER_BY)).append(Strings.WHITE_SPACE);
                for (QueryOrderParameter orderField : orderParameters) {
                    resultBuilder.append(orderField);
                    if (orderField.isDesc()) {
                        resultBuilder.append(Strings.WHITE_SPACE).append(SystemProperties.get(SystemProperties.Query.ReservedWord.DESC));
                    }
                    resultBuilder.append(Strings.EMPTY_STRING, SystemProperties.get(SystemProperties.Query.ReservedWord.ARGUMENT_SEPARATOR));
                }
                resultBuilder.cleanBuffer();
            }

            if (getStart() != null) {
                resultBuilder.append(Strings.WHITE_SPACE).append(SystemProperties.get(SystemProperties.Query.ReservedWord.START));
                resultBuilder.append(Strings.WHITE_SPACE).append(getStart());
            }

            if (getUnderlyingStart() != null) {
                if(getStart() == null) {
                    resultBuilder.append(Strings.WHITE_SPACE).append(SystemProperties.get(SystemProperties.Query.ReservedWord.START)).append(Strings.WHITE_SPACE);
                }
                resultBuilder.append(Strings.ARGUMENT_SEPARATOR).append(getUnderlyingStart());
            }

            if (getLimit() != null) {
                resultBuilder.append(Strings.WHITE_SPACE).append(SystemProperties.get(SystemProperties.Query.ReservedWord.LIMIT));
                resultBuilder.append(Strings.WHITE_SPACE).append(getLimit());
            }

            if (getUnderlyingLimit() != null) {
                if(getLimit() == null) {
                    resultBuilder.append(Strings.WHITE_SPACE).append(SystemProperties.get(SystemProperties.Query.ReservedWord.LIMIT)).append(Strings.WHITE_SPACE);
                }
                resultBuilder.append(Strings.ARGUMENT_SEPARATOR).append(getUnderlyingLimit());
            }
            return resultBuilder.toString();
//        }
//
//        return stringRepresentation;
    }

    /**
     * Creates a string representation of evaluator collection.
     * @param result Buffer with the current result.
     * @param collection Collection in order to create the string representation.
     */
    private void toStringEvaluatorCollection(Strings.Builder result, EvaluatorCollection collection) {
        String separator = Strings.EMPTY_STRING;
        String separatorValue = collection instanceof Or ?
                SystemProperties.get(SystemProperties.Query.ReservedWord.OR) :
                SystemProperties.get(SystemProperties.Query.ReservedWord.AND);
        for(Evaluator evaluator : collection.getEvaluators()) {
            if(evaluator instanceof Or) {
                if(!separator.isEmpty()) {
                    result.append(SystemProperties.get(SystemProperties.Query.ReservedWord.OR));
                }
                result.append(Strings.WHITE_SPACE);
                if(((Or)evaluator).getEvaluators().size() == 1) {
                    toStringEvaluatorCollection(result, (Or) evaluator);
                } else {
                    result.append(Strings.START_GROUP);
                    toStringEvaluatorCollection(result, (Or) evaluator);
                    result.append(Strings.END_GROUP);
                }
                result.append(Strings.WHITE_SPACE);
            } else if(evaluator instanceof And) {
                if(!separator.isEmpty()) {
                    result.append(SystemProperties.get(SystemProperties.Query.ReservedWord.AND));
                }
                result.append(Strings.WHITE_SPACE);
                if (collection instanceof Query) {
                    toStringEvaluatorCollection(result, (And) evaluator);
                } else {
                    if (((And) evaluator).getEvaluators().size() == 1) {
                        toStringEvaluatorCollection(result, (And) evaluator);
                    } else {
                        result.append(Strings.START_GROUP);
                        toStringEvaluatorCollection(result, (And) evaluator);
                        result.append(Strings.END_GROUP);
                    }
                }
                result.append(Strings.WHITE_SPACE);
            } else if(evaluator instanceof BooleanEvaluator) {
                result.append(separator);
                BooleanEvaluator booleanEvaluator = (BooleanEvaluator) evaluator;
                if(booleanEvaluator.isTrueForced()) {
                    result.append(Boolean.TRUE.toString());
                } else {
                    result = toStringFieldEvaluatorValue(booleanEvaluator.getValue(), booleanEvaluator.getClass(), result);
                }
                result.append(Strings.WHITE_SPACE);
            } else if(evaluator instanceof FieldEvaluator) {
                result.append(separator);
                FieldEvaluator fieldEvaluator = (FieldEvaluator) evaluator;
                if(fieldEvaluator.isTrueForced()) {
                    result.append(Boolean.TRUE.toString());
                } else {
                    if (fieldEvaluator.getLeftValue() == null) {
                        result.append(SystemProperties.get(SystemProperties.Query.ReservedWord.NULL));
                    } else {
                        result = toStringFieldEvaluatorValue(fieldEvaluator.getLeftValue(), fieldEvaluator.getLeftValue().getClass(), result);
                    }
                    result.append(Strings.WHITE_SPACE);
                    if (fieldEvaluator instanceof Distinct) {
                        result.append(SystemProperties.get(SystemProperties.Query.ReservedWord.DISTINCT)).append(Strings.WHITE_SPACE);
                    } else if (fieldEvaluator instanceof Equals) {
                        result.append(SystemProperties.get(SystemProperties.Query.ReservedWord.EQUALS)).append(Strings.WHITE_SPACE);
                    } else if (fieldEvaluator instanceof GreaterThanOrEqual) {
                        result.append(SystemProperties.get(SystemProperties.Query.ReservedWord.GREATER_THAN_OR_EQUALS)).append(Strings.WHITE_SPACE);
                    } else if (fieldEvaluator instanceof GreaterThan) {
                        result.append(SystemProperties.get(SystemProperties.Query.ReservedWord.GREATER_THAN)).append(Strings.WHITE_SPACE);
                    } else if (fieldEvaluator instanceof NotIn) {
                        result.append(SystemProperties.get(SystemProperties.Query.ReservedWord.NOT_IN)).append(Strings.WHITE_SPACE);
                    } else if (fieldEvaluator instanceof In) {
                        result.append(SystemProperties.get(SystemProperties.Query.ReservedWord.IN)).append(Strings.WHITE_SPACE);
                    } else if (fieldEvaluator instanceof Like) {
                        result.append(SystemProperties.get(SystemProperties.Query.ReservedWord.LIKE)).append(Strings.WHITE_SPACE);
                    } else if (fieldEvaluator instanceof SmallerThanOrEqual) {
                        result.append(SystemProperties.get(SystemProperties.Query.ReservedWord.SMALLER_THAN_OR_EQUALS)).append(Strings.WHITE_SPACE);
                    } else if (fieldEvaluator instanceof SmallerThan) {
                        result.append(SystemProperties.get(SystemProperties.Query.ReservedWord.SMALLER_THAN)).append(Strings.WHITE_SPACE);
                    }
                    if (fieldEvaluator.getRightValue() == null) {
                        result.append(SystemProperties.get(SystemProperties.Query.ReservedWord.NULL));
                    } else {
                        result = toStringFieldEvaluatorValue(fieldEvaluator.getRightValue(), fieldEvaluator.getRightValue().getClass(), result);
                    }
                }
                result.append(Strings.WHITE_SPACE);
            }
            separator = separatorValue + Strings.WHITE_SPACE;
        }
    }

    /**
     * Creates the string representation of the field evaluator.
     * @param value Object to create the string representation.
     * @param type Object type.
     * @param result Buffer with the current result.
     * @return String representation of the field evaluator.
     */
    private static Strings.Builder toStringFieldEvaluatorValue(Object value, Class type, Strings.Builder result) {
        if(FieldEvaluator.ReplaceableValue.class.isAssignableFrom(type)) {
            result.append(SystemProperties.get(SystemProperties.Query.ReservedWord.REPLACEABLE_VALUE));
        } else if(FieldEvaluator.QueryValue.class.isAssignableFrom(type)) {
            result.append(Strings.START_GROUP);
            result.append(((FieldEvaluator.QueryValue)value).getQuery().toString());
            result.append(Strings.END_GROUP);
        } else if(String.class.isAssignableFrom(type)) {
            result.append(SystemProperties.get(SystemProperties.Query.ReservedWord.STRING_DELIMITER));
            result.append(value);
            result.append(SystemProperties.get(SystemProperties.Query.ReservedWord.STRING_DELIMITER));
        } else if(Date.class.isAssignableFrom(type)) {
            result.append(SystemProperties.get(SystemProperties.Query.ReservedWord.STRING_DELIMITER));
            result.append(SystemProperties.getDateFormat(SystemProperties.Query.DATE_FORMAT).format((Date)value));
            result.append(SystemProperties.get(SystemProperties.Query.ReservedWord.STRING_DELIMITER));
        } else if(Collection.class.isAssignableFrom(type)) {
            result.append(Strings.START_GROUP);
            String separator = Strings.EMPTY_STRING;
            for(Object object : (Collection)value) {
                if(object != null) {
                    result.append(separator);
                    result = toStringFieldEvaluatorValue(object, object.getClass(), result);
                    separator = SystemProperties.get(SystemProperties.Query.ReservedWord.ARGUMENT_SEPARATOR);
                }
            }
            result.append(Strings.END_GROUP);
        } else {
            result.append(value.toString());
        }
        return result;
    }

    /**
     * This method is a particular implementation to create a bson document from a query instance.
     * @return Returns a bson document.
     */
    @Override
    public BsonDocument toBson() {
        BsonDocument document = new BsonDocument();
        document.put(PARCELABLE_CLASS_NAME, getClass().getName());
        document.put(QUERY_BSON_FIELD_NAME, toString());
        return document;
    }

    /**
     * This particular implementation do nothing to populate the instance.
     * @param document Bson document to populate the parcelable.
     * @param 

Expected bson parcelable type. * @return Returns the same instance. */ @Override public

P populate(BsonDocument document) { return (P) this; } /** * Evaluates the query using a readable data source. * @param query Query to evaluate. * @return Collections of joinable map instances. */ public static Collection evaluate(String query) { return evaluate(compile(query)); } /** * Evaluates the query using a readable data source. * @param queryable Query to evaluate. * @return Collections of joinable map instances. */ public static Collection evaluate(Queryable queryable) { Collection result; Query query; if(queryable instanceof Query) { query = (Query) queryable; } else { query = ((ParameterizedQuery)queryable).getQuery(); } //if(query.getJoins().isEmpty() && query.getOrderParameters().isEmpty()) { // result = Layers.get(ReadRowsLayerInterface.class, queryable.getResourceName()).readRows(queryable); //} else { // result = queryable.evaluate(new Queryable.ReadableDataSource()); //} //return result; return queryable.evaluate(new Queryable.ReadableDataSource()); } /** * This method evaluate if the uuid instance is a uuid type 5 and contains * some name of the registered resource and invoke the read method of the resource. * @param uuid Resource id. * @param Expected data type. * @return Resource instance. */ public static O evaluate(UUID uuid) { String resourceName = NamedUuid.getName(uuid); IdentifiableLayerInterface identifiableLayerInterface = Layers.get(IdentifiableLayerInterface.class, resourceName); return (O) identifiableLayerInterface.read(uuid); } /** * Creates a query with next structure 'SELECT * FROM {resourceName}' * @param resourceName Resource name. * @return Returns a single query instance. */ public static Query compileSingleQuery(String resourceName) { return compile(String.format(SystemProperties.get(SystemProperties.Query.SINGLE_PATTERN), resourceName)); } /** * Create a query instance from sql definition. * @param sql Sql definition. * @return Query instance. */ public static Query compile(String sql) { return compile(sql, true); } /** * Create a query instance from sql definition. * @param sql Sql definition * @param ignoreCache Boolean value to indicate if the cache must be ignored or not. * @return Query instance. */ public static Query compile(String sql, boolean ignoreCache) { Query result = null; if(!ignoreCache) { result = cache.get(sql); } if(result == null) { List richTexts = Strings.groupRichText(sql); List groups = Strings.replaceableGroup(Strings.removeLines(richTexts.get(richTexts.size() - 1))); result = compile(groups, richTexts, groups.size() - 1); if(!ignoreCache) { cache.put(sql, result); } } return result; } /** * Create a query instance from sql definition. * @param groups * @param startGroup * @return Query instance. */ private static Query compile(List groups, List richTexts, Integer startGroup) { Query query; Pattern pattern = SystemProperties.getPattern(SystemProperties.Query.SELECT_REGULAR_EXPRESSION); Matcher matcher = pattern.matcher(groups.get(startGroup)); if(matcher.matches()) { String selectBody = matcher.group(SystemProperties.get(SystemProperties.Query.SELECT_GROUP_INDEX)); selectBody = selectBody.replaceFirst(Strings.CASE_INSENSITIVE_REGEX_FLAG+SystemProperties.get(SystemProperties.Query.ReservedWord.SELECT), Strings.EMPTY_STRING); String fromBody = matcher.group(SystemProperties.get(SystemProperties.Query.FROM_GROUP_INDEX)); fromBody = fromBody.replaceFirst(Strings.CASE_INSENSITIVE_REGEX_FLAG+SystemProperties.get(SystemProperties.Query.ReservedWord.FROM), Strings.EMPTY_STRING); String conditionalBody = matcher.group(SystemProperties.get(SystemProperties.Query.CONDITIONAL_GROUP_INDEX)); if(conditionalBody != null && conditionalBody.endsWith(SystemProperties.get(SystemProperties.Query.ReservedWord.STATEMENT_END))) { conditionalBody = conditionalBody.substring(0, conditionalBody.indexOf(SystemProperties.get(SystemProperties.Query.ReservedWord.STATEMENT_END))-1); } String resourceValue = matcher.group(SystemProperties.get(SystemProperties.Query.RESOURCE_VALUE_INDEX)); String dynamicResource = matcher.group(SystemProperties.get(SystemProperties.Query.DYNAMIC_RESOURCE_INDEX)); String dynamicResourceAlias = matcher.group(SystemProperties.get(SystemProperties.Query.DYNAMIC_RESOURCE_ALIAS_INDEX)); query = new Query(createResource(resourceValue, dynamicResource, dynamicResourceAlias, groups, richTexts)); if(conditionalBody != null) { Pattern conditionalPatter = SystemProperties.getPattern(SystemProperties.Query.CONDITIONAL_REGULAR_EXPRESSION, Pattern.CASE_INSENSITIVE); List conditionalElements = List.of(conditionalPatter.split(conditionalBody)).stream().filter(S -> !S.isBlank()).collect(Collectors.toList()); String element; String elementValue; for (int i = 0; i < conditionalElements.size(); i++) { element = conditionalElements.get(i++).trim(); elementValue = conditionalElements.get(i).trim(); if (element.equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.JOIN)) || element.equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.FULL)) || element.equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.INNER)) || element.equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.LEFT)) || element.equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.RIGHT))) { Join.JoinType type = Join.JoinType.valueOf(element.toUpperCase()); if(type != Join.JoinType.JOIN) { elementValue = conditionalElements.get(++i).trim(); } String joinConditionalBody; QueryResource joinResource; Pattern joinPattern = SystemProperties.getPattern(SystemProperties.Query.JOIN_REGULAR_EXPRESSION); Matcher joinMatcher = joinPattern.matcher(elementValue); if(joinMatcher.matches()) { String joinDynamicResource = joinMatcher.group(SystemProperties.get(SystemProperties.Query.JOIN_DYNAMIC_RESOURCE_INDEX)); String joinResourceValue = joinMatcher.group(SystemProperties.get(SystemProperties.Query.JOIN_RESOURCE_VALUE_INDEX)); String joinDynamicResourceAlias = joinMatcher.group(SystemProperties.get(SystemProperties.Query.JOIN_DYNAMIC_RESOURCE_ALIAS_INDEX)); joinResource = createResource(joinResourceValue, joinDynamicResource, joinDynamicResourceAlias, groups, richTexts); joinConditionalBody = joinMatcher.group(SystemProperties.get(SystemProperties.Query.JOIN_CONDITIONAL_BODY_INDEX)); joinConditionalBody = Strings.reverseGrouping(joinConditionalBody, groups); joinConditionalBody = Strings.reverseRichTextGrouping(joinConditionalBody, richTexts); } else { throw new HCJFRuntimeException("Join syntax wrong, near '%s'", elementValue); } Join join = new Join(query, joinResource, type); query.getResources().add(join.getResource()); completeEvaluatorCollection(query, joinConditionalBody, groups, richTexts, join, 0, new AtomicInteger(0)); query.addJoin(join); } else if (element.equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.WHERE))) { completeEvaluatorCollection(query, elementValue, groups, richTexts, query, 0, new AtomicInteger(0)); } else if (element.equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.ORDER_BY))) { for (String orderField : elementValue.split(SystemProperties.get( SystemProperties.Query.ReservedWord.ARGUMENT_SEPARATOR))) { query.addOrderParameter((QueryOrderParameter) processStringValue(query, groups, richTexts, orderField, null, QueryOrderParameter.class, new ArrayList<>())); } } else if (element.equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.GROUP_BY))) { for (String orderField : elementValue.split(SystemProperties.get( SystemProperties.Query.ReservedWord.ARGUMENT_SEPARATOR))) { query.addGroupField((QueryReturnParameter) processStringValue(query, groups, richTexts, orderField, null, QueryReturnParameter.class, new ArrayList<>())); } } else if (element.equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.LIMIT))) { if(elementValue == null || elementValue.isBlank()) { throw new HCJFRuntimeException("Undeclared limit value"); } String[] limitValues = elementValue.split(Strings.ARGUMENT_SEPARATOR); if(limitValues.length > 0 && !limitValues[0].isBlank()) { try { query.setLimit(Integer.parseInt(limitValues[0].trim())); } catch (NumberFormatException ex) { throw new HCJFRuntimeException("The limit value must be an integer", ex); } } if(limitValues.length > 1 && !limitValues[1].isBlank()) { try { query.setUnderlyingLimit(Integer.parseInt(limitValues[1].trim())); } catch (NumberFormatException ex) { throw new HCJFRuntimeException("The underlying limit value must be an integer", ex); } } } else if (element.equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.START))) { if(elementValue == null || elementValue.isBlank()) { throw new HCJFRuntimeException("Undeclared start value"); } String[] startValues = elementValue.split(Strings.ARGUMENT_SEPARATOR); if(startValues.length > 0 && !startValues[0].isBlank()) { try { query.setStart(Integer.parseInt(startValues[0].trim())); } catch (NumberFormatException ex) { throw new HCJFRuntimeException("The start value must be an integer", ex); } } if(startValues.length > 1 && !startValues[1].isBlank()) { try { query.setUnderlyingStart(Integer.parseInt(startValues[1].trim())); } catch (NumberFormatException ex) { throw new HCJFRuntimeException("The underlying start value must be an integer", ex); } } } } } for(String returnField : selectBody.split(SystemProperties.get( SystemProperties.Query.ReservedWord.ARGUMENT_SEPARATOR))) { query.addReturnField((QueryReturnParameter) processStringValue(query, groups, richTexts, returnField, null, QueryReturnParameter.class, new ArrayList<>())); } } else { String value = groups.get(startGroup); int place = Strings.getNoMatchPlace(matcher, groups.get(startGroup)); String nearFrom = Strings.getNearFrom(value, place, 5); throw new HCJFRuntimeException("Query match fail near from ( '...%s...' ), query body: '%s'", nearFrom, value); } return query; } /** * Creates the resource implementation depends of the values. * @param resourceValue Resource value definition. * @param dynamicResource Dynamic resource value. * @param dynamicResourceAlias Dynamic resource alias. * @param groups Groups collection. * @param richTexts Rich texts collection. * @return Returns the resource implementation. */ private static QueryResource createResource(String resourceValue, String dynamicResource, String dynamicResourceAlias, List groups, List richTexts) { QueryResource result; if(dynamicResource.isBlank()) { result = new QueryResource(resourceValue.trim()); } else { String path = null; if (resourceValue.indexOf(Strings.CLASS_SEPARATOR) > 0) { path = resourceValue.substring(resourceValue.indexOf(Strings.CLASS_SEPARATOR) + 1).trim(); resourceValue = resourceValue.substring(0, resourceValue.indexOf(Strings.CLASS_SEPARATOR)); } resourceValue = Strings.reverseGrouping(resourceValue, groups); resourceValue = Strings.reverseRichTextGrouping(resourceValue, richTexts); resourceValue = resourceValue.substring(1, resourceValue.length() - 1); Query subQuery; if (resourceValue.toUpperCase().startsWith(SystemProperties.get(SystemProperties.Query.ReservedWord.SELECT))) { subQuery = compile(resourceValue); } else { subQuery = compileSingleQuery(resourceValue); } result = new QueryDynamicResource(dynamicResourceAlias.trim(), subQuery, path); } return result; } /** * Complete the evaluator collections with all the evaluator definitions in the groups. * @param groups Where groups. * @param parentCollection Parent collection. * @param definitionIndex Definition index into the groups. */ private static final void completeEvaluatorCollection(Query query, String startElement, List groups, List richTexts, EvaluatorCollection parentCollection, Integer definitionIndex, AtomicInteger placesIndex) { Pattern wherePatter = SystemProperties.getPattern(SystemProperties.Query.EVALUATOR_COLLECTION_REGULAR_EXPRESSION, Pattern.CASE_INSENSITIVE); String[] evaluatorDefinitions; if(startElement != null) { evaluatorDefinitions = wherePatter.split(startElement); } else { evaluatorDefinitions = wherePatter.split(groups.get(definitionIndex)); } EvaluatorCollection collection = null; List pendingDefinitions = new ArrayList<>(); for(String definition : evaluatorDefinitions) { definition = definition.trim(); if (definition.equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.AND))) { if (collection == null) { if(parentCollection instanceof Query || parentCollection instanceof Join || parentCollection instanceof And) { collection = parentCollection; } else { collection = parentCollection.and(); } } else if (collection instanceof Or) { if(parentCollection instanceof Query || parentCollection instanceof Join || parentCollection instanceof And) { collection = parentCollection; } else { collection = parentCollection.and(); } } } else if (definition.equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.OR))) { if (collection == null) { if(parentCollection instanceof Or) { collection = parentCollection; } else { collection = parentCollection.or(); } } else if(collection instanceof Query || collection instanceof Join || collection instanceof And) { if(parentCollection instanceof Or) { collection = parentCollection; } else { collection = parentCollection.or(); } } } else { pendingDefinitions.add(definition); if(collection != null) { for(String pendingDefinition : pendingDefinitions) { processDefinition(query, pendingDefinition, collection, groups, richTexts, placesIndex); } pendingDefinitions.clear(); } else if(pendingDefinitions.size() > 1) { throw new IllegalArgumentException(""); } } } for(String pendingDefinition : pendingDefinitions) { if(collection != null) { processDefinition(query, pendingDefinition, collection, groups, richTexts, placesIndex); } else { processDefinition(query, pendingDefinition, parentCollection, groups, richTexts, placesIndex); } } } /** * Creates a conditional evaluator from string representation. * @param definition String definition of the conditional. * @param collection Evaluator collection to put the conditional processed. * @param groups Sub representation of the main representation. * @param placesIndex Place counter of the group list. */ private static void processDefinition(Query query, String definition, EvaluatorCollection collection, List groups, List richTexts, AtomicInteger placesIndex) { String[] evaluatorValues; Object leftValue; Object rightValue; String firstArgument; String secondArgument; String operator; Evaluator evaluator; if (definition.startsWith(Strings.REPLACEABLE_GROUP)) { Integer index = Integer.parseInt(definition.replace(Strings.REPLACEABLE_GROUP, Strings.EMPTY_STRING)); completeEvaluatorCollection(query, null, groups, richTexts, collection, index, placesIndex); } else { evaluatorValues = definition.split(SystemProperties.get(SystemProperties.Query.OPERATION_REGULAR_EXPRESSION)); boolean operatorDone = false; firstArgument = Strings.EMPTY_STRING; secondArgument = Strings.EMPTY_STRING; operator = Strings.EMPTY_STRING; for (String evaluatorValue : evaluatorValues) { evaluatorValue = evaluatorValue.trim(); if (evaluatorValue.equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.NOT))) { operator += evaluatorValue + Strings.WHITE_SPACE; } else if (evaluatorValue.equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.NOT_2))) { operator += evaluatorValue; } else if (evaluatorValue.equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.DISTINCT)) || evaluatorValue.equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.DISTINCT_2)) || evaluatorValue.equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.EQUALS)) || evaluatorValue.equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.GREATER_THAN)) || evaluatorValue.equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.GREATER_THAN_OR_EQUALS)) || evaluatorValue.equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.IN)) || evaluatorValue.equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.LIKE)) || evaluatorValue.equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.SMALLER_THAN)) || evaluatorValue.equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.SMALLER_THAN_OR_EQUALS))) { operator += evaluatorValue; operatorDone = true; } else if (operatorDone) { secondArgument += evaluatorValue + Strings.WHITE_SPACE; } else { firstArgument += evaluatorValue + Strings.WHITE_SPACE; } } List presentFields = new ArrayList<>(); if (operator == null || operator.trim().isEmpty()) { leftValue = processStringValue(query, groups, richTexts, firstArgument.trim(), placesIndex, QueryParameter.class, presentFields); evaluator = new BooleanEvaluator(leftValue); } else { leftValue = processStringValue(query, groups, richTexts, firstArgument.trim(), placesIndex, QueryParameter.class, presentFields); if (leftValue instanceof String) { leftValue = Strings.reverseGrouping((String) leftValue, groups); } rightValue = processStringValue(query, groups, richTexts, secondArgument.trim(), placesIndex, QueryParameter.class, presentFields); if (rightValue instanceof String) { rightValue = Strings.reverseGrouping((String) rightValue, groups); } operator = operator.trim(); if (operator.equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.DISTINCT)) || operator.equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.DISTINCT_2))) { evaluator = new Distinct(leftValue, rightValue); } else if (operator.equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.EQUALS))) { evaluator = new Equals(leftValue, rightValue); } else if (operator.equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.GREATER_THAN))) { evaluator = new GreaterThan(leftValue, rightValue); } else if (operator.equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.GREATER_THAN_OR_EQUALS))) { evaluator = new GreaterThanOrEqual(leftValue, rightValue); } else if (operator.equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.IN))) { evaluator = new In(leftValue, rightValue); } else if (operator.equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.LIKE))) { evaluator = new Like(leftValue, rightValue); } else if (operator.equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.NOT_IN))) { evaluator = new NotIn(leftValue, rightValue); } else if (operator.equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.SMALLER_THAN))) { evaluator = new SmallerThan(leftValue, rightValue); } else if (operator.equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.SMALLER_THAN_OR_EQUALS))) { evaluator = new SmallerThanOrEqual(leftValue, rightValue); } else { throw new HCJFRuntimeException("Unsupported operator '%s', near '%s'", operator, definition); } } if(evaluator instanceof BaseEvaluator) { ((BaseEvaluator)evaluator).setEvaluatorFields(presentFields); } collection.addEvaluator(evaluator); } } /** * Process the string representation to obtain the specific object type. * @param groups Sub representation of the main representation. * @param stringValue String representation to process. * @param placesIndex Place counter of the group list. * @param parameterClass Parameter class. * @return Return the specific implementation of the string representation. */ private static Object processStringValue(Query query, List groups, List richTexts, String stringValue, AtomicInteger placesIndex, Class parameterClass, List presentFields) { Object result = null; String trimmedStringValue = stringValue.trim(); if(trimmedStringValue.equals(SystemProperties.get(SystemProperties.Query.ReservedWord.REPLACEABLE_VALUE))) { //If the string value is equals than "?" then the value object is an instance of ReplaceableValue. result = new FieldEvaluator.ReplaceableValue(placesIndex.getAndAdd(1)); } else if(trimmedStringValue.equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.NULL))) { result = null; } else if(trimmedStringValue.equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.TRUE))) { result = true; } else if(trimmedStringValue.equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.FALSE))) { result = false; } else if(trimmedStringValue.startsWith(SystemProperties.get(SystemProperties.Query.ReservedWord.STRING_DELIMITER))) { if (trimmedStringValue.endsWith(SystemProperties.get(SystemProperties.Query.ReservedWord.STRING_DELIMITER))) { //If the string value start and end with "'" then the value can be a string or a date object. trimmedStringValue = trimmedStringValue.substring(1, trimmedStringValue.length() - 1); trimmedStringValue = richTexts.get(Integer.parseInt(trimmedStringValue.replace(Strings.REPLACEABLE_RICH_TEXT, Strings.EMPTY_STRING))); //Clean the value to remove all the skip characters into the string value. trimmedStringValue = trimmedStringValue.replace(Strings.RICH_TEXT_SKIP_CHARACTER + Strings.RICH_TEXT_SEPARATOR, Strings.RICH_TEXT_SEPARATOR); try { result = SystemProperties.getDateFormat(SystemProperties.Query.DATE_FORMAT).parse(trimmedStringValue); } catch (Exception ex) { //The value is not a date then the value is a string while(trimmedStringValue.contains(Strings.REPLACEABLE_GROUP)) { trimmedStringValue = Strings.reverseGrouping(trimmedStringValue, groups); } result = trimmedStringValue; } } else { throw new HCJFRuntimeException("Expecting string en delimiter, near %s", trimmedStringValue); } } else if(trimmedStringValue.startsWith(Strings.REPLACEABLE_GROUP)) { Integer index = Integer.parseInt(trimmedStringValue.replace(Strings.REPLACEABLE_GROUP, Strings.EMPTY_STRING)); String group = groups.get(index); if(group.toUpperCase().startsWith(SystemProperties.get(SystemProperties.Query.ReservedWord.SELECT))) { result = new FieldEvaluator.QueryValue(Query.compile(groups, richTexts, index)); } else { //If the string value start with "(" and end with ")" then the value is a collection. Collection collection = new ArrayList<>(); for (String subStringValue : group.split(SystemProperties.get(SystemProperties.Query.ReservedWord.ARGUMENT_SEPARATOR))) { collection.add(processStringValue(query, groups, richTexts, subStringValue.trim(), placesIndex, parameterClass, presentFields)); } result = collection; } } else if(trimmedStringValue.matches(SystemProperties.get(SystemProperties.HCJF_UUID_REGEX))) { result = UUID.fromString(trimmedStringValue); } else if(trimmedStringValue.matches(SystemProperties.get(SystemProperties.HCJF_INTEGER_NUMBER_REGEX))) { result = Long.parseLong(trimmedStringValue); } else if(trimmedStringValue.matches(SystemProperties.get(SystemProperties.HCJF_DECIMAL_NUMBER_REGEX))) { try { result = SystemProperties.getDecimalFormat(SystemProperties.Query.DECIMAL_FORMAT).parse(trimmedStringValue); } catch (ParseException e) { throw new HCJFRuntimeException("Unable to parse decimal number"); } } else if(trimmedStringValue.matches(SystemProperties.get(SystemProperties.HCJF_SCIENTIFIC_NUMBER_REGEX))) { try { result = SystemProperties.getDecimalFormat(SystemProperties.Query.SCIENTIFIC_NOTATION_FORMAT).parse(trimmedStringValue); } catch (ParseException e) { throw new HCJFRuntimeException("Unable to parse scientific number"); } } else if(trimmedStringValue.matches(SystemProperties.get(SystemProperties.HCJF_MATH_CONNECTOR_REGULAR_EXPRESSION)) && trimmedStringValue.matches(SystemProperties.get(SystemProperties.HCJF_MATH_REGULAR_EXPRESSION))) { String alias = null; String[] asParts = trimmedStringValue.split(SystemProperties.get(SystemProperties.Query.AS_REGULAR_EXPRESSION)); if(asParts.length == 3) { trimmedStringValue = asParts[0].trim(); alias = asParts[2].trim(); } //If the string matchs with a math expression then creates a function that resolves this math expression. String[] mathExpressionParts = trimmedStringValue.split(SystemProperties.get(SystemProperties.HCJF_MATH_SPLITTER_REGULAR_EXPRESSION)); List parameters = new ArrayList<>(); String currentValue; boolean desc = false; for (int i = 0; i < mathExpressionParts.length; i++) { currentValue = mathExpressionParts[i]; if(i == mathExpressionParts.length - 1){ //This code run only one time for the last part. if(parameterClass.equals(QueryReturnParameter.class)) { //Check if the last part contains the 'AS' word String[] parts = currentValue.split(SystemProperties.get(SystemProperties.Query.AS_REGULAR_EXPRESSION)); if (parts.length == 2) { currentValue = parts[0].trim(); alias = parts[1].trim(); } } else if(parameterClass.equals(QueryOrderParameter.class)) { //Check if the last part contains the 'DESC' word String[] parts = currentValue.split(SystemProperties.get(SystemProperties.Query.DESC_REGULAR_EXPRESSION)); if(parts.length == 3) { currentValue = parts[0].trim(); if(parts[2].trim().equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.DESC))) { desc = true; } } } } if(currentValue.matches(SystemProperties.get(SystemProperties.HCJF_MATH_CONNECTOR_REGULAR_EXPRESSION))) { //If the current value is a math connector (+-*/) the this connector is a function parameter. parameters.add(currentValue.trim()); } else { //If the current value is not a math connector then this string is evaluated recursively. parameters.add(processStringValue(query, groups, richTexts, currentValue, placesIndex, QueryParameter.class, presentFields)); } } if(parameterClass.equals(QueryParameter.class)) { result = new QueryFunction(query, Strings.reverseGrouping(trimmedStringValue, groups), SystemProperties.get(SystemProperties.Query.Function.MATH_EVAL_EXPRESSION_NAME), parameters); } else if(parameterClass.equals(QueryReturnParameter.class)) { result = new QueryReturnFunction(query, Strings.reverseGrouping(trimmedStringValue, groups), SystemProperties.get(SystemProperties.Query.Function.MATH_EVAL_EXPRESSION_NAME), parameters, alias); } else if(parameterClass.equals(QueryOrderParameter.class)) { result = new QueryOrderFunction(query, Strings.reverseGrouping(trimmedStringValue, groups), SystemProperties.get(SystemProperties.Query.Function.MATH_EVAL_EXPRESSION_NAME), parameters, desc); } } else { //Default case, only must be a query parameter. String functionName = null; String originalValue = null; String replaceValue = null; String group = null; List functionParameters = null; Boolean function = false; if(trimmedStringValue.contains(Strings.REPLACEABLE_GROUP)) { //If the string contains a replaceable group character then the parameter is a function. replaceValue = Strings.getGroupIndex(trimmedStringValue, Strings.REPLACEABLE_GROUP); group = groups.get(Integer.parseInt(replaceValue.replace(Strings.REPLACEABLE_GROUP,Strings.EMPTY_STRING))); functionName = trimmedStringValue.substring(0, trimmedStringValue.indexOf(Strings.REPLACEABLE_GROUP)); originalValue = trimmedStringValue.replace(replaceValue, Strings.START_GROUP + group + Strings.END_GROUP); functionParameters = new ArrayList<>(); for(String param : group.split(SystemProperties.get(SystemProperties.Query.ReservedWord.ARGUMENT_SEPARATOR))) { functionParameters.add(processStringValue(query, groups, richTexts, param, placesIndex, parameterClass, presentFields)); } originalValue = Strings.reverseRichTextGrouping(originalValue, richTexts); function = true; } else { originalValue = trimmedStringValue; } if(parameterClass.equals(QueryParameter.class)) { //If the parameter class is the default class then the result will be a //QueryFunction.class instance or QueryField.class instance. if(function) { result = new QueryFunction(query, originalValue, functionName, functionParameters); } else { result = new QueryField(query, trimmedStringValue); } } else if(parameterClass.equals(QueryReturnParameter.class)) { //If the parameter class is the QueryReturnParameter.class then the result will be a //QueryReturnFunction.class instance or QueryReturnField.class instance. String alias = null; String[] parts = originalValue.split(SystemProperties.get(SystemProperties.Query.AS_REGULAR_EXPRESSION)); if(parts.length == 3) { originalValue = parts[0].trim(); alias = parts[2].trim(); } if(function) { result = new QueryReturnFunction(query, originalValue, functionName, functionParameters, alias); } else { result = new QueryReturnField(query, originalValue, alias); } } else if(parameterClass.equals(QueryOrderParameter.class)) { //If the parameter class is the QueryOrderParameter.class then the result will be a //QueryOrderFunction.class instance or QueryOrderField.class instance. boolean desc = false; String[] parts = originalValue.split(SystemProperties.get(SystemProperties.Query.DESC_REGULAR_EXPRESSION)); if(parts.length == 2) { originalValue = parts[0].trim(); if(parts[1].trim().equalsIgnoreCase(SystemProperties.get(SystemProperties.Query.ReservedWord.DESC))) { desc = true; } } if(function) { result = new QueryOrderFunction(query, originalValue, functionName, functionParameters, desc) ; } else { result = new QueryOrderField(query, originalValue, desc); } } } if(result instanceof QueryField) { presentFields.add((QueryField) result); } return result; } @Override public boolean equals(Object obj) { return (obj instanceof Query) && obj.toString().equals(toString()); } }