package org.springframework.roo.addon.layers.repository.jpa.addon.finder.parser;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.roo.model.JavaType;
/**
* The type of operation. Used to create query parts in various ways.
*
* @author Paula Navarro
* @since 2.0
*/
public enum Type {
BETWEEN(2, "IsBetween", "Between"), IS_NOT_NULL(0, "IsNotNull", "NotNull"), IS_NULL(0, "IsNull",
"Null"), LESS_THAN("IsLessThan", "LessThan"), LESS_THAN_EQUAL("IsLessThanEqual",
"LessThanEqual"), GREATER_THAN("IsGreaterThan", "GreaterThan"), GREATER_THAN_EQUAL(
"IsGreaterThanEqual", "GreaterThanEqual"), BEFORE("IsBefore", "Before"), AFTER("IsAfter",
"After"), NOT_LIKE("IsNotLike", "NotLike"), LIKE("IsLike", "Like"), STARTING_WITH(
"IsStartingWith", "StartingWith", "StartsWith"), ENDING_WITH("IsEndingWith", "EndingWith",
"EndsWith"), NOT_CONTAINING("IsNotContaining", "NotContaining", "NotContains"), CONTAINING(
"IsContaining", "Containing", "Contains"), NOT_IN("IsNotIn", "NotIn"), IN("IsIn", "In"), NEAR(
"IsNear", "Near"), WITHIN("IsWithin", "Within"), REGEX("MatchesRegex", "Matches", "Regex"), EXISTS(
0, "Exists"), TRUE(0, "IsTrue", "True"), FALSE(0, "IsFalse", "False"), NEGATING_SIMPLE_PROPERTY(
"IsNot", "Not"), SIMPLE_PROPERTY("Is", "Equals");
// Need to list them again explicitly as the order is important
// (esp. for IS_NULL, IS_NOT_NULL)
private static final List<Type> ALL = Arrays.asList(IS_NOT_NULL, IS_NULL, BETWEEN, LESS_THAN,
LESS_THAN_EQUAL, GREATER_THAN, GREATER_THAN_EQUAL, BEFORE, AFTER, NOT_LIKE, LIKE,
STARTING_WITH, ENDING_WITH, NOT_CONTAINING, CONTAINING, NOT_IN, IN, NEAR, WITHIN, REGEX,
EXISTS, TRUE, FALSE, NEGATING_SIMPLE_PROPERTY, SIMPLE_PROPERTY);
public static final Collection<String> ALL_KEYWORDS;
// Some operators are grouped by their prefix to limit the number of operators
public static final String[] PREFIX_GROUP = {"Is"};
static {
List<String> allKeywords = new ArrayList<String>();
for (Type type : ALL) {
allKeywords.addAll(type.keywords);
}
ALL_KEYWORDS = Collections.unmodifiableList(allKeywords);
}
private final List<String> keywords;
private final int numberOfArguments;
/**
* Creates a new {@link Type} using the given keyword, number of
* arguments to be bound and operator. Keyword and operator can be
* {@literal null}.
*
* @param numberOfArguments
* @param keywords
*/
private Type(int numberOfArguments, String... keywords) {
this.numberOfArguments = numberOfArguments;
this.keywords = Arrays.asList(keywords);
}
/**
* Creates a new {@link Type} using the given keyword, number of arguments to be bound and operator. Keyword and
* operator can be {@literal null}.
*
* @param numberOfArguments
* @param keywords
*/
private Type(String... keywords) {
this(1, keywords);
}
/**
* Returns the operator group from an operator.
* If it does not belong to any group or does not exist, returns an empty string
*
* @param operator
* @return group
*/
public static String extractOperatorGroup(String operator) {
if (operator == null) {
return "";
}
for (String prefix : PREFIX_GROUP) {
if (operator.startsWith(prefix))
return prefix;
}
return "";
}
/**
* Gets type operator types supported by Date or Calendar objects
* @return
*/
public static List<Type> getDateOperators() {
return Arrays.asList(BEFORE, AFTER, BETWEEN);
}
/**
* Gets type operator types supported by Numbers
* @return
*/
public static List<Type> getNumberOperators() {
return Arrays.asList(LESS_THAN, LESS_THAN_EQUAL, GREATER_THAN, GREATER_THAN_EQUAL, BETWEEN);
}
/**
* Gets type operator types supported by non-primitive java types
* @return
*/
public static List<Type> getObjectOperators() {
return Arrays.asList(IS_NOT_NULL, IS_NULL);
}
/**
* Gets type operator types supported by booleans
* @return
*/
public static List<Type> getBooleanOperators() {
return Arrays.asList(TRUE, FALSE);
}
/**
* Gets type operator types supported by Strings
* @return
*/
public static List<Type> getStringOperators() {
return Arrays.asList(NOT_LIKE, LIKE, STARTING_WITH, ENDING_WITH, NOT_CONTAINING, CONTAINING,
REGEX);
}
/**
* Gets operator types supported by a javaType
*
* @param javaType the {@link JavaType}
* @return
*/
public static List<Type> getOperators(JavaType javaType) {
List<Type> types = new ArrayList<Type>();
if (javaType == null) {
return types;
}
// All java types can perform these operations
types.add(Type.SIMPLE_PROPERTY);
types.add(Type.NEGATING_SIMPLE_PROPERTY);
types.add(Type.NOT_IN);
types.add(Type.IN);
// Only objects can use these operators
if (!javaType.isPrimitive()) {
types.addAll(Type.getObjectOperators());
}
if (javaType.isBoolean()) {
// Boolean operators
types.addAll(Type.getBooleanOperators());
} else if (javaType.equals(JavaType.STRING)) {
// String operators
types.addAll(Type.getStringOperators());
} else if (javaType.equals(new JavaType(Date.class))
|| javaType.equals(new JavaType(Calendar.class))) {
// Date operators
types.addAll(Type.getDateOperators());
} else {
// Number operators (int, double, long, etc)
try {
if (ClassUtils.getClass(javaType.getFullyQualifiedTypeName()).getSuperclass()
.equals(Number.class)) {
types.addAll(Type.getNumberOperators());
}
} catch (ClassNotFoundException e) {
// Type not supported
}
}
return types;
}
/**
* Returns the {@link Type} and the operator, that javaType supports, from the given operator source
* as a Pair(Type, keyword). Since operator source can contain information that is not referred to the operator,
* the operator will be the keyword that matches more letters with operator source.
* If any operator is found in operator source, returns Pair(SIMPLE_PROPERTY, null).
*
* @param operatorSource
* @return Pair of operator type and operator name
*/
public static Pair<Type, String> extractOperator(String operatorSource, JavaType javaType) {
Type lastType = SIMPLE_PROPERTY;
String lastKeyword = null;
if (javaType == null) {
return Pair.of(lastType, lastKeyword);
}
for (Type type : getOperators(javaType)) {
for (String keyword : type.keywords) {
if (operatorSource.equals(keyword)) {
return Pair.of(type, keyword);
}
if (operatorSource.startsWith(keyword)) {
if (lastKeyword == null || lastKeyword.length() < keyword.length()) {
lastKeyword = keyword;
lastType = type;
}
}
}
}
return Pair.of(lastType, lastKeyword);
}
/**
* Returns all keywords supported by the current {@link Type}.
*
* @return
*/
public Collection<String> getKeywords() {
return Collections.unmodifiableList(keywords);
}
/**
* Returns the number of arguments of the property operator. By default
* this exactly one argument.
*
* @return
*/
public int getNumberOfArguments() {
return numberOfArguments;
}
}