001/*
002 * The contents of this file are subject to the license and copyright
003 * detailed in the LICENSE and NOTICE files at the root of the source
004 * tree.
005 */
006package org.fcrepo.search.api;
007
008import java.util.regex.Pattern;
009
010/**
011 * A data structure representing a search condition.
012 *
013 * @author dbernstein
014 */
015public class Condition {
016    public enum Operator {
017        LTE("<="),
018        GTE(">="),
019        EQ("="),
020        GT(">"),
021        LT("<");
022
023        private String value;
024
025        Operator(final String value) {
026            this.value = value;
027        }
028
029        public String getStringValue() {
030            return this.value;
031        }
032
033        public static Operator fromString(final String str) {
034            for (final Operator o : Operator.values()) {
035                if (o.value.equals(str)) {
036                    return o;
037                }
038            }
039
040            throw new IllegalArgumentException("Value " + str + " not recognized.");
041        }
042
043    }
044
045    public enum Field {
046        FEDORA_ID,
047        MODIFIED,
048        CREATED,
049        CONTENT_SIZE,
050        MIME_TYPE,
051        RDF_TYPE;
052
053        @Override
054        public String toString() {
055            return super.toString().toLowerCase();
056        }
057
058        public static Field fromString(final String fieldStr) throws InvalidConditionExpressionException {
059            for (final var field : values()) {
060                if (field.toString().equalsIgnoreCase(fieldStr)) {
061                   return field;
062                }
063            }
064
065            throw new InvalidConditionExpressionException(fieldStr + " is not a valid search field");
066        }
067    }
068
069    /* A regex for parsing the value of a "condition" query  parameter which follows the format
070     * [field_name][operation][object]
071     * The field name is composed of at least one character and can contain alpha number characters and underscores.
072     * The operation can equal "=", "<", ">", "<=" or ">="
073     * The object can be anything but cannot start with >, <, and =.
074     */
075    final static Pattern CONDITION_REGEX = Pattern.compile("([a-zA-Z0-9_]+)([><=]|<=|>=)([^><=].*)");
076
077
078    private Field field;
079    private Operator operator;
080    private String object;
081
082    /**
083     * Internal constructor
084     *
085     * @param field    The search field (condition subject)
086     * @param operator The operator (condition predicate)
087     * @param object   The object (condition object)
088     */
089    private Condition(final Field field, final Operator operator, final String object) {
090        this.field = field;
091        this.operator = operator;
092        this.object = object;
093    }
094
095    /**
096     * Field accessor
097     *
098     * @return the field
099     */
100    public Field getField() {
101        return field;
102    }
103
104    /**
105     * Operator accessor
106     * @return the operator
107     */
108    public Operator getOperator() {
109        return operator;
110    }
111
112    /**
113     * @return the object portion of the condition
114     */
115    public String getObject() {
116        return object;
117    }
118
119    @Override
120    public String toString() {
121        return this.field.toString().toLowerCase() + operator + object;
122    }
123
124    /**
125     * Parses a string expression into a Condition object.
126     * @param expression The condition as a string expression.
127     * @return The condition
128     * @throws InvalidConditionExpressionException if we can't parse the string into a Condition.
129     */
130    public static Condition fromExpression(final String expression) throws InvalidConditionExpressionException {
131        final var m = CONDITION_REGEX.matcher(expression);
132        if (m.matches()) {
133            final var field = Field.fromString(m.group(1));
134            final var operation = Operator.fromString(m.group(2));
135            final var object = m.group(3);
136            return fromEnums(field, operation, object);
137        }
138
139        throw new InvalidConditionExpressionException(expression);
140    }
141
142    public static Condition fromEnums(final Field field, final Operator operator, final String expression) {
143        return new Condition(field, operator, expression);
144    }
145}