/*
 * Copyright 2008-2020 the original author or authors.
 *
 * 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
 *
 *      https://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.springframework.data.repository.query.parser;

import lombok.EqualsAndHashCode;

import java.beans.Introspector;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.springframework.data.mapping.PropertyPath;
import org.springframework.util.Assert;

A single part of a method name that has to be transformed into a query part. The actual transformation is defined by a Type that is determined from inspecting the given part. The query part can then be looked up via getProperty().
Author:Oliver Gierke, Martin Baumgartner, Jens Schauder
/** * A single part of a method name that has to be transformed into a query part. The actual transformation is defined by * a {@link Type} that is determined from inspecting the given part. The query part can then be looked up via * {@link #getProperty()}. * * @author Oliver Gierke * @author Martin Baumgartner * @author Jens Schauder */
@EqualsAndHashCode public class Part { private static final Pattern IGNORE_CASE = Pattern.compile("Ignor(ing|e)Case"); private final PropertyPath propertyPath; private final Part.Type type; private IgnoreCaseType ignoreCase = IgnoreCaseType.NEVER;
Creates a new Part from the given method name part, the Class the part originates from and the start parameter index.
Params:
  • source – must not be null.
  • clazz – must not be null.
/** * Creates a new {@link Part} from the given method name part, the {@link Class} the part originates from and the * start parameter index. * * @param source must not be {@literal null}. * @param clazz must not be {@literal null}. */
public Part(String source, Class<?> clazz) { this(source, clazz, false); }
Creates a new Part from the given method name part, the Class the part originates from and the start parameter index.
Params:
  • source – must not be null.
  • clazz – must not be null.
  • alwaysIgnoreCase –
/** * Creates a new {@link Part} from the given method name part, the {@link Class} the part originates from and the * start parameter index. * * @param source must not be {@literal null}. * @param clazz must not be {@literal null}. * @param alwaysIgnoreCase */
public Part(String source, Class<?> clazz, boolean alwaysIgnoreCase) { Assert.hasText(source, "Part source must not be null or empty!"); Assert.notNull(clazz, "Type must not be null!"); String partToUse = detectAndSetIgnoreCase(source); if (alwaysIgnoreCase && ignoreCase != IgnoreCaseType.ALWAYS) { this.ignoreCase = IgnoreCaseType.WHEN_POSSIBLE; } this.type = Type.fromProperty(partToUse); this.propertyPath = PropertyPath.from(type.extractProperty(partToUse), clazz); } private String detectAndSetIgnoreCase(String part) { Matcher matcher = IGNORE_CASE.matcher(part); String result = part; if (matcher.find()) { ignoreCase = IgnoreCaseType.ALWAYS; result = part.substring(0, matcher.start()) + part.substring(matcher.end(), part.length()); } return result; } boolean isParameterRequired() { return getNumberOfArguments() > 0; }
Returns how many method parameters are bound by this part.
Returns:
/** * Returns how many method parameters are bound by this part. * * @return */
public int getNumberOfArguments() { return type.getNumberOfArguments(); }
Returns:the propertyPath
/** * @return the propertyPath */
public PropertyPath getProperty() { return propertyPath; }
Returns:the type
/** * @return the type */
public Part.Type getType() { return type; }
Returns whether the PropertyPath referenced should be matched ignoring case.
Returns:
/** * Returns whether the {@link PropertyPath} referenced should be matched ignoring case. * * @return */
public IgnoreCaseType shouldIgnoreCase() { return ignoreCase; } /* * (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return String.format("%s %s %s", propertyPath.getSegment(), type, ignoreCase); }
The type of a method name part. Used to create query parts in various ways.
Author:Oliver Gierke, Thomas Darimont, Michael Cramer
/** * The type of a method name part. Used to create query parts in various ways. * * @author Oliver Gierke * @author Thomas Darimont * @author Michael Cramer */
public static 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"), IS_NOT_EMPTY(0, "IsNotEmpty", "NotEmpty"), IS_EMPTY(0, "IsEmpty", "Empty"), 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<Part.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, IS_NOT_EMPTY, IS_EMPTY, NOT_CONTAINING, CONTAINING, NOT_IN, IN, NEAR, WITHIN, REGEX, EXISTS, TRUE, FALSE, NEGATING_SIMPLE_PROPERTY, SIMPLE_PROPERTY); public static final Collection<String> ALL_KEYWORDS; static { List<String> allKeywords = new ArrayList<>(); 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 Type using the given keyword, number of arguments to be bound and operator. Keyword and operator can be null.
Params:
  • numberOfArguments –
  • 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(int numberOfArguments, String... keywords) { this.numberOfArguments = numberOfArguments; this.keywords = Arrays.asList(keywords); } private Type(String... keywords) { this(1, keywords); }
Returns the Type of the Part for the given raw propertyPath. This will try to detect e.g. keywords contained in the raw propertyPath that trigger special query creation. Returns SIMPLE_PROPERTY by default.
Params:
  • rawProperty –
Returns:
/** * Returns the {@link Type} of the {@link Part} for the given raw propertyPath. This will try to detect e.g. * keywords contained in the raw propertyPath that trigger special query creation. Returns {@link #SIMPLE_PROPERTY} * by default. * * @param rawProperty * @return */
public static Part.Type fromProperty(String rawProperty) { for (Part.Type type : ALL) { if (type.supports(rawProperty)) { return type; } } return SIMPLE_PROPERTY; }
Returns all keywords supported by the current Type.
Returns:
/** * Returns all keywords supported by the current {@link Type}. * * @return */
public Collection<String> getKeywords() { return Collections.unmodifiableList(keywords); }
Returns whether the the type supports the given raw property. Default implementation checks whether the property ends with the registered keyword. Does not support the keyword if the property is a valid field as is.
Params:
  • property –
Returns:
/** * Returns whether the the type supports the given raw property. Default implementation checks whether the property * ends with the registered keyword. Does not support the keyword if the property is a valid field as is. * * @param property * @return */
protected boolean supports(String property) { for (String keyword : keywords) { if (property.endsWith(keyword)) { return true; } } return false; }
Returns the number of arguments the propertyPath binds. By default this exactly one argument.
Returns:
/** * Returns the number of arguments the propertyPath binds. By default this exactly one argument. * * @return */
public int getNumberOfArguments() { return numberOfArguments; }
Callback method to extract the actual propertyPath to be bound from the given part. Strips the keyword from the part's end if available.
Params:
  • part –
Returns:
/** * Callback method to extract the actual propertyPath to be bound from the given part. Strips the keyword from the * part's end if available. * * @param part * @return */
public String extractProperty(String part) { String candidate = Introspector.decapitalize(part); for (String keyword : keywords) { if (candidate.endsWith(keyword)) { return candidate.substring(0, candidate.length() - keyword.length()); } } return candidate; } /* * (non-Javadoc) * @see java.lang.Enum#toString() */ @Override public String toString() { return String.format("%s (%s): %s", name(), getNumberOfArguments(), getKeywords()); } }
The various types of ignore case that are supported.
Author:Phillip Webb
/** * The various types of ignore case that are supported. * * @author Phillip Webb */
public enum IgnoreCaseType {
Should not ignore the sentence case.
/** * Should not ignore the sentence case. */
NEVER,
Should ignore the sentence case, throwing an exception if this is not possible.
/** * Should ignore the sentence case, throwing an exception if this is not possible. */
ALWAYS,
Should ignore the sentence case when possible to do so, silently ignoring the option when not possible.
/** * Should ignore the sentence case when possible to do so, silently ignoring the option when not possible. */
WHEN_POSSIBLE } }