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

querymethods.springdata.query.parser.PartTree Maven / Gradle / Ivy

There is a newer version: 0.1.4-RELEASE
Show newest version
/*
 * Copyright 2008-2019 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 querymethods.springdata.query.parser;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import querymethods.springdata.query.domain.Sort;
import querymethods.springdata.query.parser.Part.Type;
import querymethods.springdata.query.parser.PartTree.OrPart;
import querymethods.springdata.util.Assert;
import querymethods.springdata.util.StringUtils;

/**
 * Class to parse a {@link String} into a tree or {@link OrPart}s consisting of simple {@link Part} instances in turn.
 * Takes a domain class as well to validate that each of the {@link Part}s are referring to a property of the domain
 * class. The {@link PartTree} can then be used to build queries based on its API instead of parsing the method name for
 * each query execution.
 * 
 * @author Oliver Gierke
 * @author Thomas Darimont
 * @author Christoph Strobl
 * @author Mark Paluch
 */
public class PartTree implements Iterable {

	/*
	 * We look for a pattern of: keyword followed by
	 *
	 *  an upper-case letter that has a lower-case variant \p{Lu}
	 * OR
	 *  any other letter NOT in the BASIC_LATIN Uni-code Block \\P{InBASIC_LATIN} (like Chinese, Korean, Japanese, etc.).
	 *
	 * @see http://www.regular-expressions.info/unicode.html
	 * @see Pattern
	 */
	private static final String KEYWORD_TEMPLATE = "(%s)(?=(\\p{Lu}|\\P{InBASIC_LATIN}))";
	private static final String QUERY_PATTERN = "find|read|get|query|stream";
	private static final String COUNT_PATTERN = "count";
	private static final String EXISTS_PATTERN = "exists";
	private static final String DELETE_PATTERN = "delete|remove";
	private static final Pattern PREFIX_TEMPLATE = Pattern.compile( //
			"^(" + QUERY_PATTERN + "|" + COUNT_PATTERN + "|" + EXISTS_PATTERN + "|" + DELETE_PATTERN + ")((\\p{Lu}.*?))??By");

	/**
	 * The subject, for example "findDistinctUserByNameOrderByAge" would have the subject "DistinctUser".
	 */
	private final Subject subject;

	/**
	 * The subject, for example "findDistinctUserByNameOrderByAge" would have the predicate "NameOrderByAge".
	 */
	private final Predicate predicate;

	/**
	 * Creates a new {@link PartTree} by parsing the given {@link String}.
	 * 
	 * @param source the {@link String} to parse
	 * @param domainClass the domain class to check individual parts against to ensure they refer to a property of the
	 *          class
	 */
	public PartTree(String source) {

		Assert.notNull(source, "Source must not be null");

		Matcher matcher = PREFIX_TEMPLATE.matcher(source);
		if (!matcher.find()) {
			this.subject = new Subject(null);
			this.predicate = new Predicate(source);
		} else {
			this.subject = new Subject(matcher.group(0));
			this.predicate = new Predicate(source.substring(matcher.group().length()));
		}
	}

	/*
	 * (non-Javadoc)
	 * @see java.lang.Iterable#iterator()
	 */
	@Override
	public Iterator iterator() {
		return predicate.iterator();
	}

	/**
	 * Returns the {@link Sort} specification parsed from the source or null.
	 * 
	 * @return the sort
	 */
	public Sort getSort() {

		OrderBySource orderBySource = predicate.getOrderBySource();
		return orderBySource == null ? null : orderBySource.toSort();
	}

	/**
	 * Returns whether we indicate distinct lookup of entities.
	 * 
	 * @return {@literal true} if distinct
	 */
	public boolean isDistinct() {
		return subject.isDistinct();
	}

	/**
	 * Returns whether a count projection shall be applied.
	 * 
	 * @return
	 */
	public Boolean isCountProjection() {
		return subject.isCountProjection();
	}

	/**
	 * Returns whether an exists projection shall be applied.
	 *
	 * @return
	 * @since 1.13
	 */
	public Boolean isExistsProjection() {
		return subject.isExistsProjection();
	}

	/**
	 * return true if the created {@link PartTree} is meant to be used for delete operation.
	 * 
	 * @return
	 * @since 1.8
	 */
	public Boolean isDelete() {
		return subject.isDelete();
	}

	/**
	 * Return {@literal true} if the create {@link PartTree} is meant to be used for a query with limited maximal results.
	 * 
	 * @return
	 * @since 1.9
	 */
	public boolean isLimiting() {
		return getMaxResults() != null;
	}

	/**
	 * Return the number of maximal results to return or {@literal null} if not restricted.
	 * 
	 * @return
	 * @since 1.9
	 */
	public Integer getMaxResults() {
		return subject.getMaxResults();
	}
	
	public String getQueryProperty() {
	    return subject.getQueryProperty();
	}

	/**
	 * Returns an {@link Iterable} of all parts contained in the {@link PartTree}.
	 * 
	 * @return the iterable {@link Part}s
	 */
	public Iterable getParts() {

		List result = new ArrayList();
		for (OrPart orPart : this) {
			for (Part part : orPart) {
				result.add(part);
			}
		}
		return result;
	}

	/**
	 * Returns all {@link Part}s of the {@link PartTree} of the given {@link Type}.
	 * 
	 * @param type
	 * @return
	 */
	public Iterable getParts(Type type) {

		List result = new ArrayList();

		for (Part part : getParts()) {
			if (part.getType().equals(type)) {
				result.add(part);
			}
		}

		return result;
	}

	@Override
	public String toString() {

		OrderBySource orderBySource = predicate.getOrderBySource();
		return String.format("%s%s", StringUtils.collectionToDelimitedString(predicate.nodes, " or "),
				orderBySource == null ? "" : " " + orderBySource);
	}

	/**
	 * Splits the given text at the given keywords. Expects camel-case style to only match concrete keywords and not
	 * derivatives of it.
	 * 
	 * @param text the text to split
	 * @param keyword the keyword to split around
	 * @return an array of split items
	 */
	private static String[] split(String text, String keyword) {

		Pattern pattern = Pattern.compile(String.format(KEYWORD_TEMPLATE, keyword));
		return pattern.split(text);
	}

	/**
	 * A part of the parsed source that results from splitting up the resource around {@literal Or} keywords. Consists of
	 * {@link Part}s that have to be concatenated by {@literal And}.
	 */
	public static class OrPart implements Iterable {

		private final List children = new ArrayList();

		/**
		 * Creates a new {@link OrPart}.
		 * 
		 * @param source the source to split up into {@literal And} parts in turn.
		 * @param domainClass the domain class to check the resulting {@link Part}s against.
		 * @param alwaysIgnoreCase if always ignoring case
		 */
		OrPart(String source, boolean alwaysIgnoreCase) {

			String[] split = split(source, "And");
			for (String part : split) {
				if (StringUtils.hasText(part)) {
					children.add(new Part(part, alwaysIgnoreCase));
				}
			}
		}

		@Override
		public Iterator iterator() {

			return children.iterator();
		}

		@Override
		public String toString() {

			return StringUtils.collectionToDelimitedString(children, " and ");
		}
	}

	/**
	 * Represents the subject part of the query. E.g. {@code findDistinctUserByNameOrderByAge} would have the subject
	 * {@code DistinctUser}.
	 * 
	 * @author Phil Webb
	 * @author Oliver Gierke
	 * @author Christoph Strobl
	 * @author Thomas Darimont
	 */
	private static class Subject {

		private static final String DISTINCT = "Distinct";
		private static final Pattern COUNT_BY_TEMPLATE = Pattern.compile("^count(\\p{Lu}.*?)??By");
		private static final Pattern EXISTS_BY_TEMPLATE = Pattern.compile("^(" + EXISTS_PATTERN + ")(\\p{Lu}.*?)??By");
		private static final Pattern DELETE_BY_TEMPLATE = Pattern.compile("^(" + DELETE_PATTERN + ")(\\p{Lu}.*?)??By");
		private static final String LIMITING_QUERY_PATTERN = "(First|Top)(\\d*)?";
		private static final Pattern LIMITED_QUERY_TEMPLATE = Pattern.compile("^(" + QUERY_PATTERN + ")(" + DISTINCT + ")?"
				+ LIMITING_QUERY_PATTERN + "(\\p{Lu}.*?)??By");
		

		private final boolean distinct;
		private final boolean count;
		private final boolean exists;
		private final boolean delete;
		private final Integer maxResults;
		
		// 判断是否为查询
		private static final Pattern QUERY_BY_TEMPLATE = Pattern.compile("^(" + QUERY_PATTERN + ")(\\p{Lu}.*?)??By");
		// 需要查询的字段 findNameById -> 指定只查询name
		private final String queryProperty;

		public Subject(String subject) {

			this.distinct = subject == null ? false : subject.contains(DISTINCT);
			this.count = matches(subject, COUNT_BY_TEMPLATE);
			this.exists = matches(subject, EXISTS_BY_TEMPLATE);
			this.delete = matches(subject, DELETE_BY_TEMPLATE);
			this.maxResults = returnMaxResultsIfFirstKSubjectOrNull(subject);
			this.queryProperty = findQueryProperty(subject);
		}
		
		/**
		 * 获取需要查询的属性
		 * @param subject
		 * @return
		 */
		private String findQueryProperty(String subject) {
			if (null == subject) {
				return null;
			}
			if (matches(subject, QUERY_BY_TEMPLATE)) {
				String property = Pattern.compile("^("+QUERY_PATTERN+")|By$", Pattern.DOTALL).matcher(subject).replaceAll("");
				if (StringUtils.isEmpty(property)) return null;
				return StringUtils.uncapitalize(property);
			}
			return null;
		}

		/**
		 * @param subject
		 * @return
		 * @since 1.9
		 */
		private Integer returnMaxResultsIfFirstKSubjectOrNull(String subject) {

			if (subject == null) {
				return null;
			}

			Matcher grp = LIMITED_QUERY_TEMPLATE.matcher(subject);

			if (!grp.find()) {
				return null;
			}

			return StringUtils.hasText(grp.group(4)) ? Integer.valueOf(grp.group(4)) : 1;
		}

		/**
		 * Returns {@literal true} if {@link Subject} matches {@link #DELETE_BY_TEMPLATE}.
		 * 
		 * @return
		 * @since 1.8
		 */
		public Boolean isDelete() {
			return delete;
		}

		public boolean isCountProjection() {
			return count;
		}

		/**
		 * Returns {@literal true} if {@link Subject} matches {@link #EXISTS_BY_TEMPLATE}.
		 *
		 * @return
		 * @since 1.13
		 */
		public boolean isExistsProjection() {
			return exists;
		}

		public boolean isDistinct() {
			return distinct;
		}

		public Integer getMaxResults() {
			return maxResults;
		}
		// 需要查询的字段
		public String getQueryProperty() {
			return queryProperty;
		}

		private final boolean matches(String subject, Pattern pattern) {
			return subject == null ? false : pattern.matcher(subject).find();
		}
	}

	/**
	 * Represents the predicate part of the query.
	 * 
	 * @author Oliver Gierke
	 * @author Phil Webb
	 */
	private static class Predicate {

		private static final Pattern ALL_IGNORE_CASE = Pattern.compile("AllIgnor(ing|e)Case");
		private static final String ORDER_BY = "OrderBy";

		private final List nodes = new ArrayList();
		private final OrderBySource orderBySource;
		private boolean alwaysIgnoreCase;

		public Predicate(String predicate) {

			String[] parts = split(detectAndSetAllIgnoreCase(predicate), ORDER_BY);

			if (parts.length > 2) {
				throw new IllegalArgumentException("OrderBy must not be used more than once in a method name!");
			}

			buildTree(parts[0]);
			this.orderBySource = parts.length == 2 ? new OrderBySource(parts[1]) : null;
		}

		private String detectAndSetAllIgnoreCase(String predicate) {

			Matcher matcher = ALL_IGNORE_CASE.matcher(predicate);

			if (matcher.find()) {
				alwaysIgnoreCase = true;
				predicate = predicate.substring(0, matcher.start()) + predicate.substring(matcher.end(), predicate.length());
			}

			return predicate;
		}

		private void buildTree(String source) {

			String[] split = split(source, "Or");
			for (String part : split) {
				nodes.add(new OrPart(part, alwaysIgnoreCase));
			}
		}

		public Iterator iterator() {
			return nodes.iterator();
		}

		public OrderBySource getOrderBySource() {
			return orderBySource;
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy