org.cloudgraph.recognizer.GraphRecognizerSupport.java Source code

Java tutorial

Introduction

Here is the source code for org.cloudgraph.recognizer.GraphRecognizerSupport.java

Source

/**
 * Copyright 2017 TerraMeta Software, Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.cloudgraph.recognizer;

import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cloudgraph.store.service.GraphServiceException;
import org.plasma.query.model.AbstractPathElement;
import org.plasma.query.model.Function;
import org.plasma.query.model.FunctionName;
import org.plasma.query.model.Literal;
import org.plasma.query.model.NullLiteral;
import org.plasma.query.model.Path;
import org.plasma.query.model.PathElement;
import org.plasma.query.model.PredicateOperatorName;
import org.plasma.query.model.Property;
import org.plasma.query.model.RelationalOperatorName;
import org.plasma.query.model.WildcardPathElement;
import org.plasma.sdo.DataType;
import org.plasma.sdo.PlasmaDataObject;
import org.plasma.sdo.PlasmaProperty;
import org.plasma.sdo.PlasmaType;
import org.plasma.sdo.core.NullValue;
import org.plasma.sdo.helper.DataConverter;

import commonj.sdo.DataObject;

/**
 * Delegate for graph recognizer expression classes. This class is not thread
 * safe and should not be shared across multiple expression class instances.
 * 
 * @author Scott Cinnamond
 * @since 0.5.3
 */
public class GraphRecognizerSupport {
    private static Log log = LogFactory.getLog(GraphRecognizerSupport.class);
    @SuppressWarnings("rawtypes")
    private NumberComparator numberComparator;
    @SuppressWarnings("rawtypes")
    private BooleanComparator booleanComparator;
    private DataConverter dataConverter = DataConverter.INSTANCE;
    /** cached wildcard pattern */
    private Pattern wildcardLiteralPattern;
    private static NullValue NULL_OBJECT = new NullValue();

    /**
     * Collects and returns data values at the endpoint of the given path,
     * traversing objects along the the given traversal path if exists.
     * 
     * @param targetObject
     *          the current target
     * @param property
     *          the query property
     * @param path
     *          the query property path
     * @param pathIndex
     *          the current path element index
     * @param values
     *          the collection of result values
     */
    public void collect(DataObject targetObject, Property property, Path path, int pathIndex, List<Object> values) {
        PlasmaType targetType = (PlasmaType) targetObject.getType();
        if (path != null && pathIndex < path.getPathNodes().size()) {
            AbstractPathElement pathElem = path.getPathNodes().get(pathIndex).getPathElement();
            if (pathElem instanceof WildcardPathElement)
                throw new GraphServiceException(
                        "wildcard path elements applicable for 'Select' clause paths only, not 'Where' clause paths");
            String elem = ((PathElement) pathElem).getValue();
            PlasmaProperty prop = (PlasmaProperty) targetType.getProperty(elem);
            if (targetObject.isSet(prop)) {
                if (prop.isMany()) {
                    @SuppressWarnings("unchecked")
                    List<DataObject> list = targetObject.getList(prop);
                    for (DataObject next : list)
                        collect(next, property, path, pathIndex + 1, values);
                } else {
                    DataObject next = targetObject.getDataObject(prop);
                    collect(next, property, path, pathIndex + 1, values);
                }
            }
        } else {
            PlasmaProperty endpointProp = (PlasmaProperty) targetType.getProperty(property.getName());
            if (!endpointProp.getType().isDataType())
                throw new GraphServiceException("expected datatype property for, " + endpointProp);
            if (property.getFunctions().size() == 0) {
                if (endpointProp.isMany()) {
                    @SuppressWarnings("unchecked")
                    List<Object> list = targetObject.getList(endpointProp);
                    if (list != null)
                        for (Object value : list)
                            values.add(value);
                } else {
                    Object value = targetObject.get(endpointProp);
                    if (value != null)
                        values.add(value);
                    else
                        values.add(NULL_OBJECT);
                }
            } else {
                if (property.getFunctions().size() > 1)
                    log.warn("ignoring all but first scalar function of total " + property.getFunctions().size());
                Function func = property.getFunctions().get(0);
                if (endpointProp.isMany()) {
                    throw new GraphServiceException("expected datatype property for, " + endpointProp);
                } else {
                    PlasmaDataObject target = (PlasmaDataObject) targetObject;
                    Object value = target.get(func.getName(), endpointProp);
                    if (value != null)
                        values.add(value);
                    else
                        values.add(NULL_OBJECT);
                }

            }
        }
    }

    /**
     * Returns the SDO property endpoint for the given query property traversal
     * path
     * 
     * @param property
     *          the query property
     * @param rootType
     *          the graph root type
     * @return the SDO property endpoint
     */
    public Endpoint getEndpoint(Property property, PlasmaType rootType) {
        return new RecognizerEndpoint(property, rootType);
    }

    /**
     * Determines the property datatype and evaluates the given property value
     * against the given literal and relational operator.
     * 
     * @param endpoint
     *          the endpoint
     * @param propertyValue
     *          the property data value
     * @param operator
     *          the relational operator
     * @param literal
     *          the query literal
     * @return whether the data property value evaluates true against given
     *         literal and relational operator
     */
    public boolean evaluate(Endpoint endpoint, Object propertyValue, RelationalOperatorName operator,
            Literal literal) {
        if (propertyValue == null)
            throw new IllegalArgumentException("expected non-null value");
        DataType dataType = DataType.valueOf(endpoint.getProperty().getType().getName());
        boolean result = true;

        switch (endpoint.getProperty().getDataFlavor()) {
        case integral:
        case real:
            if (Number.class.isAssignableFrom(propertyValue.getClass())) {
                if (!literal.isNullLiteral()) {
                    Number propertyNumberValue = (Number) propertyValue;
                    Number literalNumberValue = null;
                    if (!endpoint.hasFunctions()) {
                        literalNumberValue = (Number) this.dataConverter.convert(endpoint.getProperty().getType(),
                                literal.getValue());
                    } else {
                        Function func = endpoint.getSingleFunction();
                        FunctionName funcName = func.getName();
                        DataType scalarDataType = funcName.getScalarDatatype(dataType);
                        literalNumberValue = (Number) this.dataConverter.fromString(scalarDataType,
                                literal.getValue());
                    }
                    result = evaluate(propertyNumberValue, operator, literalNumberValue);
                } else {
                    result = evaluate(propertyValue, operator, NullLiteral.class.cast(literal));
                }
            } else if (Boolean.class.isAssignableFrom(propertyValue.getClass())) {
                if (!literal.isNullLiteral()) {
                    Boolean propertyBooleanValue = (Boolean) propertyValue;
                    Boolean literalBooleanValue = (Boolean) this.dataConverter
                            .convert(endpoint.getProperty().getType(), literal.getValue());
                    result = evaluate(propertyBooleanValue, operator, literalBooleanValue);
                } else {
                    result = evaluate(propertyValue, operator, NullLiteral.class.cast(literal));
                }
            } else if (NullValue.class.isInstance(propertyValue)) {
                if (!literal.isNullLiteral()) {
                    return false;
                } else {
                    result = evaluate(propertyValue, operator, NullLiteral.class.cast(literal));
                }
            } else
                throw new GraphServiceException("unexpected instanceof " + propertyValue.getClass().getName()
                        + " for property, " + endpoint.getProperty() + " with data flavor "
                        + endpoint.getProperty().getDataFlavor());
            break;
        case string:
            if (!literal.isNullLiteral()) {
                if (!NullValue.class.isInstance(propertyValue)) {
                    String propertyStringValue = (String) propertyValue;
                    String literalStringValue = (String) this.dataConverter
                            .convert(endpoint.getProperty().getType(), literal.getValue());
                    result = evaluate(propertyStringValue, operator, literalStringValue);
                } else {
                    result = false;
                }
            } else {
                result = evaluate(propertyValue, operator, NullLiteral.class.cast(literal));
            }
            break;
        case temporal:
            switch (dataType) {
            case Date:
                if (!literal.isNullLiteral()) {
                    if (!NullValue.class.isInstance(propertyValue)) {
                        Date propertyDateValue = (Date) propertyValue;
                        Date literalDateValue = (Date) this.dataConverter.convert(endpoint.getProperty().getType(),
                                literal.getValue());
                        result = evaluate(propertyDateValue, operator, literalDateValue);
                    } else {
                        result = false;
                    }
                } else {
                    result = evaluate(propertyValue, operator, NullLiteral.class.cast(literal));
                }
                break;
            default:
                if (!literal.isNullLiteral()) {
                    if (!NullValue.class.isInstance(propertyValue)) {
                        String propertyStringValue = (String) propertyValue;
                        String literalStringValue = (String) this.dataConverter
                                .convert(endpoint.getProperty().getType(), literal.getValue());
                        result = evaluate(propertyStringValue, operator, literalStringValue);
                    } else {
                        result = false;
                    }
                } else {
                    result = evaluate(propertyValue, operator, NullLiteral.class.cast(literal));
                }
                break;
            }
            break;
        case other:
            throw new GraphServiceException("data flavor '" + endpoint.getProperty().getDataFlavor()
                    + "' not supported for relational operator '" + operator + "'");
        }
        return result;
    }

    /**
     * Determines the property datatype and evaluates the given property value
     * against the given literal and wildcard operator.
     * 
     * @param endpoint
     *          the endpoint
     * @param propertyValue
     *          the property data value
     * @param operator
     *          the wildcard operator
     * @param literal
     *          the query literal
     * @return whether the data property value evaluates true against given
     *         literal and wildcard operator
     */
    public boolean evaluate(Endpoint endpoint, Object propertyValue, PredicateOperatorName operator,
            Literal literal) {
        if (propertyValue == null)
            throw new IllegalArgumentException("expected non-null value");
        boolean result = true;

        switch (operator) {
        case LIKE:
            switch (endpoint.getProperty().getDataFlavor()) {
            case string:
                if (!NullValue.class.isInstance(propertyValue)) {
                    String propertyStringValue = (String) propertyValue;
                    // as trailing newlines confuse regexp greatly
                    propertyStringValue = propertyStringValue.trim();
                    String literalStringValue = (String) this.dataConverter
                            .convert(endpoint.getProperty().getType(), literal.getValue());
                    result = evaluate(propertyStringValue, operator, literalStringValue);
                } else {
                    result = false;
                }
                break;
            case integral:
            case real:
            case temporal:
            case other:
                throw new GraphServiceException("data flavor '" + endpoint.getProperty().getDataFlavor()
                        + "' not supported for wildcard operator '" + operator + "'");
            }
            break;
        case IN:
            if (!NullValue.class.isInstance(propertyValue)) {
                // evals true if property value equals any or given literals
                String[] literals = null;
                if (literal != null)
                    literals = literal.getValue().split(" ");
                boolean anySuccess = false;
                for (String lit : literals) {
                    if (evaluate(endpoint.getProperty(), propertyValue, lit)) {
                        anySuccess = true;
                        break;
                    }
                }
                result = anySuccess;
            } else {
                result = false;
            }
            break;
        default:
            throw new GraphServiceException("operator '" + operator + "' not supported for context");
        }
        return result;
    }

    private boolean evaluate(PlasmaProperty property, Object propertyValue, String literal) {
        boolean result = true;
        switch (property.getDataFlavor()) {
        case string:
            String propertyStringValue = (String) propertyValue;
            propertyStringValue = propertyStringValue.trim();
            String literalStringValue = (String) this.dataConverter.convert(property.getType(), literal);
            result = evaluate(propertyStringValue, RelationalOperatorName.EQUALS, literalStringValue);
            break;
        case integral:
        case real:
            Number propertyNumberValue = (Number) propertyValue;
            Number literalNumberValue = (Number) this.dataConverter.convert(property.getType(), literal);
            result = evaluate(propertyNumberValue, RelationalOperatorName.EQUALS, literalNumberValue);
            break;
        case temporal:
        case other:
        default:
            throw new GraphServiceException(
                    "data flavor '" + property.getDataFlavor() + "' not supported for context");
        }
        return result;
    }

    private boolean evaluate(Date propertyValue, RelationalOperatorName operator, Date literalValue) {
        int comp = propertyValue.compareTo(literalValue);
        return evaluate(operator, comp);
    }

    @SuppressWarnings("rawtypes")
    private boolean evaluate(Number propertyValue, RelationalOperatorName operator, Number literalValue) {
        if (this.numberComparator == null)
            this.numberComparator = new NumberComparator();
        @SuppressWarnings("unchecked")
        int comp = this.numberComparator.compare(propertyValue, literalValue);
        return evaluate(operator, comp);
    }

    private boolean evaluate(Boolean propertyValue, RelationalOperatorName operator, Boolean literalValue) {
        if (this.booleanComparator == null)
            this.booleanComparator = new BooleanComparator();
        int comp = this.booleanComparator.compare(propertyValue, literalValue);
        return evaluate(operator, comp);
    }

    private boolean evaluate(String propertyValue, RelationalOperatorName operator, String literalValue) {
        int comp = propertyValue.compareTo(literalValue);
        return evaluate(operator, comp);
    }

    private boolean evaluate(Object propertyValue, RelationalOperatorName operator, NullLiteral literalValue) {
        if (NullValue.class.isInstance(propertyValue))
            return true;
        else
            return false;
    }

    private boolean evaluate(String propertyValue, PredicateOperatorName operator, String literalValue) {
        if (this.wildcardLiteralPattern == null) {
            String pattern = wildcardToRegex(literalValue);
            this.wildcardLiteralPattern = Pattern.compile(pattern);
        }
        Matcher matcher = this.wildcardLiteralPattern.matcher(propertyValue);
        return matcher.matches();
    }

    private String wildcardToRegex(String wildcard) {
        StringBuffer s = new StringBuffer(wildcard.length());
        s.append('^');
        for (int i = 0, is = wildcard.length(); i < is; i++) {
            char c = wildcard.charAt(i);
            switch (c) {
            case '*':
                s.append(".*");
                break;
            case '?':
                s.append(".");
                break;
            // escape special regexp-characters
            case '(':
            case ')':
            case '[':
            case ']':
            case '$':
            case '^':
            case '.':
            case '{':
            case '}':
            case '|':
            case '\\':
                s.append("\\");
                s.append(c);
                break;
            default:
                s.append(c);
                break;
            }
        }
        s.append('$');
        return (s.toString());
    }

    private boolean evaluate(RelationalOperatorName operator, int comp) {
        switch (operator) {
        case EQUALS:
            return comp == 0;
        case NOT_EQUALS:
            return comp != 0;
        case GREATER_THAN:
            return comp > 0;
        case GREATER_THAN_EQUALS:
            return comp >= 0;
        case LESS_THAN:
            return comp < 0;
        case LESS_THAN_EQUALS:
            return comp <= 0;
        default:
            throw new GraphServiceException("unknown relational operator, " + operator);
        }
    }

    @SuppressWarnings("rawtypes")
    class NumberComparator<T extends Number & Comparable> implements Comparator<T> {

        @SuppressWarnings("unchecked")
        public int compare(T a, T b) throws ClassCastException {
            return a.compareTo(b);
        }
    }

    class BooleanComparator implements Comparator<Boolean> {

        @Override
        public int compare(Boolean a, Boolean b) {
            return a.compareTo(b);
        }
    }
}