/*
* Hibernate Search, full-text search for your domain model
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.search.elasticsearch.util.impl;
import org.hibernate.search.exception.AssertionFailure;
/**
* A stateful helper to extract path components from a string, one
* component at a time.
*
* @author Yoann Rodiere
*/
public class PathComponentExtractor implements Cloneable {
private static final String PATH_COMPONENT_SEPARATOR = ".";
/*
* Non-final for cloning only.
*/
private StringBuilder path = new StringBuilder();
private int currentIndexInPath = 0;
/**
* Append a string to the path for later consumption through {@link #next(ConsumptionLimit)}.
* @param pathPart A string that may or may not include path component
* separators (dots).
*/
public void append(String pathPart) {
path.append( pathPart );
}
/**
* Append to the path the part of {@code otherPath} that is relative to the current path.
* <p>In other words, replace the current path with {@code otherPath} provided {@code otherPath}
* denotes a child element of the current path, while preserving the memory of the previously
* consumed path components.
* @param otherPath A path that must start with the current path.
* @throws ParentPathMismatchException If {@code otherPath} is not contained with the current path.
*/
public void appendRelativePart(String otherPath) throws ParentPathMismatchException {
String pathAsString = path.toString();
if ( !otherPath.startsWith( pathAsString ) ) {
throw new ParentPathMismatchException( pathAsString, otherPath );
}
path.append( otherPath, path.length(), otherPath.length() );
}
public enum ConsumptionLimit {
/**
* Consume up to the second-but-last path component, i.e. up to the last path separator
* in the path string.
* <p>Don't consume anything if there is no path separator in the path string.
*/
SECOND_BUT_LAST,
/**
* Consume up to the last path component, i.e. up to the end of the path string.
*/
LAST;
}
/**
* Consume one more component in the current path (provided it's not beyond the given limit) and returns this component.
*
*<p>If this method reaches a incompletely qualified path component, i.e one that is not
*followed by a dot but by the end of the path, it will return it only if {@code consumptionLimit}
*is {@link ConsumptionLimit#LAST}.
*
* @param consumeLimit The consumption limit, i.e. the definition of the last component
* to consume in the path.
* @return The next path component, or {@code null} if the consumption limit has been reached.
*/
public String next(ConsumptionLimit consumeLimit) {
int nextSeparatorIndex = path.indexOf( PATH_COMPONENT_SEPARATOR, currentIndexInPath );
if ( nextSeparatorIndex >= 0 ) {
String childName = path.substring( currentIndexInPath, nextSeparatorIndex );
currentIndexInPath = nextSeparatorIndex + 1 /* skip the dot */;
return childName;
}
else if ( ConsumptionLimit.LAST.equals( consumeLimit ) && currentIndexInPath < path.length() ) {
String lastComponent = path.substring( currentIndexInPath );
currentIndexInPath = path.length();
return lastComponent;
}
else {
return null;
}
}
/**
* Consume all components in the current path up to the given limit.
*
*<p>This is equivalent to calling {@code next(consumeLimit)} repeatedly until {@code null} is returned.
*
* @param consumeLimit The consumption limit, i.e. the definition of the last component
* to consume in the path.
*/
public void flushTo(ConsumptionLimit consumeLimit) {
if ( ConsumptionLimit.LAST.equals( consumeLimit ) ) {
currentIndexInPath = path.length();
}
else {
int nextSeparatorIndex = path.lastIndexOf( PATH_COMPONENT_SEPARATOR );
if ( nextSeparatorIndex >= 0 ) {
currentIndexInPath = nextSeparatorIndex + 1;
}
}
}
public void reset() {
path.delete( 0, path.length() );
currentIndexInPath = 0;
}
/**
* @return The absolute path of the last non-null path component returned by {@link #next(ConsumptionLimit)},
* or {@code null} if {@link #next(ConsumptionLimit)} hasn't returned such a component yet.
*/
public String getLastComponentAbsolutePath() {
return currentIndexInPath == 0 ? null : path.substring( 0, currentIndexInPath - 1 );
}
@Override
public PathComponentExtractor clone() {
try {
PathComponentExtractor clone = (PathComponentExtractor) super.clone();
clone.path = new StringBuilder( path );
return clone;
}
catch (CloneNotSupportedException e) {
throw new AssertionFailure( "Unexpected clone() failure", e );
}
}
@Override
public String toString() {
return new StringBuilder( path ).insert( currentIndexInPath, "[]" ).toString();
}
}