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