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

org.fryske_akademy.jpa.Param Maven / Gradle / Ivy

The newest version!
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package org.fryske_akademy.jpa;

/*-
 * #%L
 * ejbCrudApi
 * %%
 * Copyright (C) 2018 Fryske Akademy
 * %%
 * 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.
 * #L%
 */
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;

/**
 * Holder for parameter info that can be used when {@link JpqlBuilder building}
 * a jpql query. Uses {@link Builder#Builder() } with
 * syntax support by default.
 *
 * @author eduard
 */
public class Param {

    /**
     * call {@link #one(java.lang.String, java.lang.Object, boolean, org.fryske_akademy.ejb.Param.Builder.WildcardMapping)
     * } with true, {@link Builder#DEFAULT_MAPPING} and false
     *
     * @param key
     * @param value
     * @return
     */
    public static List one(String key, Object value) {
        return one(key, value, true, Builder.DEFAULT_MAPPING, false);
    }

    /**
     * @see Builder#Builder(boolean,
     * org.fryske_akademy.ejb.Param.Builder.WildcardMapping, boolean)
     * @param key
     * @param value
     * @param syntaxSupport
     * @param wildcardMapping
     * @param caseInsensitive
     * @return
     */
    public static List one(String key, Object value, boolean syntaxSupport, Builder.WildcardMapping wildcardMapping, boolean caseInsensitive) {
        return new Builder(syntaxSupport, wildcardMapping, caseInsensitive).add(key, value).build();
    }

    public enum AndOr {
        AND, OR;

        private static AndOr fromBool(boolean or) {
            return or ? OR : AND;
        }

        @Override
        public String toString() {
            return " " + name() + " ";
        }

    }

    private final String propertyPath;
    private final String paramKey;
    private final String operator;
    private final Object paramValue;
    private final String not;
    private final AndOr andOr;
    private final boolean skipSetValue;
    private final boolean caseInsensitive;

    /**
     * Bottleneck constructor
     *
     * @param propertyPath the path to the property in an entity i.e.
     * lemma.soart.id, or id
     * @param paramKey the key of the parameter i.e. :id
     * @param operator the operator i.e. like or =
     * @param paramValue may not be null
     * @param not when true use negation
     * @param or when true use or otherwise and
     * @param caseInsensitive when true query case insensitive
     * must be non null, when both set classes must be the same
     */
    private Param(String propertyPath, String paramKey, String operator, Object paramValue, boolean not, boolean or, boolean caseInsensitive) {
        if (paramValue == null) {
            throw new IllegalArgumentException("param value may not be null");
        }
        this.propertyPath = propertyPath + " ";
        this.skipSetValue = Builder.nullComp(operator) || Builder.emptyComp(operator);
        this.paramKey = paramKey;
        this.operator = " " + operator + " ";
        this.paramValue = paramValue;
        this.not = not ? " NOT " : " ";
        this.andOr = AndOr.fromBool(or);
        this.caseInsensitive = caseInsensitive;
    }

    public String getPropertyPath() {
        return propertyPath;
    }

    /**
     * for native queries this key should be a numeric positional parameter
     *
     * @return
     */
    public String getParamKey() {
        return paramKey;
    }

    public String getOperator() {
        return operator;
    }

    public Object getParamValue() {
        return paramValue;
    }

    public String getNot() {
        return not;
    }

    public AndOr getAndOr() {
        return andOr;
    }

    public boolean isSkipSetValue() {
        return skipSetValue;
    }

    public Class getParamType() {
        return paramValue.getClass();
    }

    public boolean isCaseInsensitive() {
        return caseInsensitive;
    }

    @Override
    public String toString() {
        return "Param{" + "propertyPath=" + propertyPath + ", paramKey=" + paramKey + ", operator=" + operator + ", paramValue=" + paramValue + ", not=" + not + ", andOr=" + andOr + ", skipSetValue=" + skipSetValue + '}';
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 47 * hash + Objects.hashCode(this.paramKey);
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Param other = (Param) obj;
        if (!Objects.equals(this.paramKey, other.paramKey)) {
            return false;
        }
        return true;
    }

    /**
     * helps building a list of Param, by default supports a bit of syntax in
     * String values: "is [not] null/empty" and ! at the beginning of a value.
     * You can also use {@link #Builder(boolean, WildcardMapping) wildcards}
     */
    public static class Builder {

        /**
         * translation table a wildcard for more characters and a wildcard for
         * one character
         */
        public interface WildcardMapping {

            char getMoreIn();

            char getMoreOut();

            char getOneIn();

            char getOneOut();
        }

        /**
         * * => %, ? => _
         */
        public static class DefaultWildcardMapping implements WildcardMapping {

            @Override
            public char getMoreIn() {
                return '*';
            }

            @Override
            public char getMoreOut() {
                return '%';
            }

            @Override
            public char getOneIn() {
                return '?';
            }

            @Override
            public char getOneOut() {
                return '_';
            }

        }

        private final List params = new ArrayList<>(3);
        public static final WildcardMapping DEFAULT_MAPPING = new DefaultWildcardMapping();
        public static final String ISNULL = "is null";
        public static final String ISNOTNULL = "is not null";
        public static final String ISEMPTY = "is empty";
        public static final String ISNOTEMPTY = "is not empty";
        public static final char NEGATION = '!';
        private final boolean syntaxInValue;
        private final WildcardMapping wildcardMapping;
        private final boolean caseInsensitive;

        public Builder(boolean syntaxInValue, WildcardMapping wildcardMapping, boolean caseInsensitive) {
            this.syntaxInValue = syntaxInValue;
            this.wildcardMapping = wildcardMapping;
            this.caseInsensitive = caseInsensitive;
        }

        /**
         * Builder with syntax support in value (!, is [not] null). Calls
         * {@link #Builder(boolean, WildcardMapping, boolean)
         * } with true, null and false.
         */
        public Builder() {
            this(true, null, false);
        }

        /**
         * Calls {@link #Builder(boolean, org.fryske_akademy.ejb.Param.Builder.WildcardMapping, boolean)
         * } with true, null and the caseInsensitive argument.
         *
         * @param caseInsensitive
         */
        public Builder(boolean caseInsensitive) {
            this(true, null, caseInsensitive);
        }

        /**
         * When paramValue is a String call {@link #add(java.lang.String, java.lang.String, java.lang.String, java.lang.String, boolean)
         * } with false otherwise call {@link #add(java.lang.String, java.lang.String, java.lang.String, java.lang.Object, boolean, boolean)
         * } with false, false.
         *
         * @param propertyPath
         * @param paramKey
         * @param operator
         * @param paramValue
         * @return
         */
        public Builder add(String propertyPath, String paramKey, String operator, Object paramValue) {
            if (paramValue instanceof String) {
                return add(propertyPath, paramKey, operator, (String)paramValue, false);
            } else {
                return add(propertyPath, paramKey, operator, paramValue, false, false);
            }
        }

        /**
         * When paramValue starts with a ! {@link #getNot() negation} is used,
         * when trimmed paramValue is "is [not] null", operator is set to value
         * and value will be ignored.
         *
         * @param propertyPath
         * @param paramKey
         * @param operator
         * @param paramValue
         * @param or
         * @return
         */
        public Builder add(String propertyPath, String paramKey, String operator, String paramValue, boolean or) {
            return add(propertyPath, paramKey, determineOperator(operator, paramValue), replaceWildcards(stripNegation(paramValue)), isNegation(paramValue), or);
        }

        /**
         * Bottleneck function, adds a new Param
         *
         * @param propertyPath
         * @param paramKey
         * @param operator
         * @param paramValue
         * @param not
         * @param or
         * @throws IllegalArgumentException when paramKey is already present or when value is null
         * @return
         */
        public Builder add(String propertyPath, String paramKey, String operator, Object paramValue, boolean not, boolean or) {
            if (params.stream().anyMatch((p) -> {
                return p.paramKey.equals(paramKey);
            })) {
                throw new IllegalArgumentException(String.format("builder already contains %s", paramKey));
            }
            params.add(new Param(propertyPath, paramKey, operator, paramValue, not, or, caseInsensitive));
            return this;
        }

        public Builder remove(String paramKey) {
            if (params.stream().anyMatch((p) -> {
                return p.paramKey.equals(paramKey);
            })) {
                for (Iterator iterator = params.iterator(); iterator.hasNext();) {
                    Param next = iterator.next();
                    if (next.paramKey.equals(paramKey)) {
                        iterator.remove();
                        break;
                    }
                }
            }
            return this;
        }

        /**
         * check if a value indicates a negation
         *
         * @param value
         * @return
         */
        public boolean isNegation(String value) {
            return syntaxInValue && value != null && value.indexOf(NEGATION) == 0;
        }

        private String stripNegation(String value) {
            return isNegation(value) ? value.substring(1) : value;
        }

        private String replaceWildcards(String value) {
            return wildcardMapping == null ? value : value
                    .replace(wildcardMapping.getMoreIn(), wildcardMapping.getMoreOut())
                    .replace(wildcardMapping.getOneIn(), wildcardMapping.getOneOut());
        }

        private String determineOperator(String operator, String value) {
            if (syntaxInValue && nullComp(value)) {
                return value;
            }
            return operator;
        }

        /**
         * check if a value is a null comparison
         *
         * @param s
         * @return
         */
        public static boolean nullComp(String s) {
            if (s == null) {
                return false;
            }
            String t = s.trim().toLowerCase();
            return t.equals(ISNULL) || t.equals(ISNOTNULL);
        }

        /**
         * check if a value is a empty comparison
         *
         * @param s
         * @return
         */
        public static boolean emptyComp(String s) {
            if (s == null) {
                return false;
            }
            String t = s.trim().toLowerCase();
            return t.equals(ISEMPTY) || t.equals(ISNOTEMPTY);
        }

        /**
         * Calls {@link #add(java.lang.String, java.lang.Object, boolean) } with
         * false
         *
         * @param paramKey
         * @param paramValue
         * @return
         */
        public Builder add(String paramKey, Object paramValue) {
            return add(paramKey, paramValue, false);
        }


        /**
         * When paramValue is a String use like as operator otherwise = and call
         * {@link #add(java.lang.String, java.lang.String, java.lang.String, java.lang.String, boolean) },
         * otherwise call {@link #add(java.lang.String, java.lang.String, java.lang.String, java.lang.Object, boolean, boolean)
         * }
         * with false for not.
         *
         * @param paramKey
         * @param paramValue
         * @param or when true use or in queries
         * @return
         */
        public Builder add(String paramKey, Object paramValue, boolean or) {
            if (paramValue instanceof String) {
                return add(paramKey, paramKey, "like", (String) paramValue, or);
            } else {
                return add(paramKey, paramKey, "=", paramValue, false, or);
            }
        }

        public List build() {
            return params;
        }

        public boolean isSyntaxInValue() {
            return syntaxInValue;
        }

        public boolean containsKey(String key) {
            return params.stream().anyMatch((t) -> {
                return t.getParamKey().equals(key);
            });
        }

    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy