/*
 * Hibernate Validator, declare and validate application constraints
 *
 * License: Apache License, Version 2.0
 * See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
 */
package org.hibernate.validator.internal.engine.path;

import static org.hibernate.validator.internal.util.logging.Messages.MESSAGES;

import java.io.Serializable;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.validation.ElementKind;
import javax.validation.Path;

import org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData;
import org.hibernate.validator.internal.util.Contracts;
import org.hibernate.validator.internal.util.logging.Log;
import org.hibernate.validator.internal.util.logging.LoggerFactory;

Default implementation of javax.validation.Path.
Author:Hardy Ferentschik, Gunnar Morling, Kevin Pollet <kevin.pollet@serli.com> (C) 2011 SERLI
/** * Default implementation of {@code javax.validation.Path}. * * @author Hardy Ferentschik * @author Gunnar Morling * @author Kevin Pollet &lt;kevin.pollet@serli.com&gt; (C) 2011 SERLI */
public final class PathImpl implements Path, Serializable { private static final long serialVersionUID = 7564511574909882392L; private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() ); private static final String PROPERTY_PATH_SEPARATOR = ".";
Regular expression used to split a string path into its elements.
See Also:
/** * Regular expression used to split a string path into its elements. * * @see <a href="http://www.regexplanet.com/simple/index.jsp">Regular expression tester</a> */
private static final String LEADING_PROPERTY_GROUP = "[^\\[\\.]+"; // everything up to a [ or . private static final String OPTIONAL_INDEX_GROUP = "\\[(\\w*)\\]"; private static final String REMAINING_PROPERTY_STRING = "\\.(.*)"; // processed recursively private static final Pattern PATH_PATTERN = Pattern.compile( "(" + LEADING_PROPERTY_GROUP + ")(" + OPTIONAL_INDEX_GROUP + ")?(" + REMAINING_PROPERTY_STRING + ")*" ); private static final int PROPERTY_NAME_GROUP = 1; private static final int INDEXED_GROUP = 2; private static final int INDEX_GROUP = 3; private static final int REMAINING_STRING_GROUP = 5; private List<Node> nodeList; private boolean nodeListRequiresCopy; private NodeImpl currentLeafNode; private int hashCode;
Returns a Path instance representing the path described by the given string. To create a root node the empty string should be passed.
Params:
  • propertyPath – the path as string representation.
Throws:
Returns:a Path instance representing the path described by the given string.
/** * Returns a {@code Path} instance representing the path described by the * given string. To create a root node the empty string should be passed. * * @param propertyPath the path as string representation. * * @return a {@code Path} instance representing the path described by the * given string. * * @throws IllegalArgumentException in case {@code property == null} or * {@code property} cannot be parsed. */
public static PathImpl createPathFromString(String propertyPath) { Contracts.assertNotNull( propertyPath, MESSAGES.propertyPathCannotBeNull() ); if ( propertyPath.length() == 0 ) { return createRootPath(); } return parseProperty( propertyPath ); } public static PathImpl createPathForExecutable(ExecutableMetaData executable) { Contracts.assertNotNull( executable, "A method is required to create a method return value path." ); PathImpl path = createRootPath(); if ( executable.getKind() == ElementKind.CONSTRUCTOR ) { path.addConstructorNode( executable.getName(), executable.getParameterTypes() ); } else { path.addMethodNode( executable.getName(), executable.getParameterTypes() ); } return path; } public static PathImpl createRootPath() { PathImpl path = new PathImpl(); path.addBeanNode(); return path; } public static PathImpl createCopy(PathImpl path) { return new PathImpl( path ); } public boolean isRootPath() { return nodeList.size() == 1 && nodeList.get( 0 ).getName() == null; } public PathImpl getPathWithoutLeafNode() { return new PathImpl( nodeList.subList( 0, nodeList.size() - 1 ) ); } public NodeImpl addPropertyNode(String nodeName) { requiresWriteableNodeList(); NodeImpl parent = currentLeafNode; currentLeafNode = NodeImpl.createPropertyNode( nodeName, parent ); nodeList.add( currentLeafNode ); resetHashCode(); return currentLeafNode; } public NodeImpl addContainerElementNode(String nodeName) { requiresWriteableNodeList(); NodeImpl parent = currentLeafNode; currentLeafNode = NodeImpl.createContainerElementNode( nodeName, parent ); nodeList.add( currentLeafNode ); resetHashCode(); return currentLeafNode; } public NodeImpl addParameterNode(String nodeName, int index) { requiresWriteableNodeList(); NodeImpl parent = currentLeafNode; currentLeafNode = NodeImpl.createParameterNode( nodeName, parent, index ); nodeList.add( currentLeafNode ); resetHashCode(); return currentLeafNode; } public NodeImpl addCrossParameterNode() { requiresWriteableNodeList(); NodeImpl parent = currentLeafNode; currentLeafNode = NodeImpl.createCrossParameterNode( parent ); nodeList.add( currentLeafNode ); resetHashCode(); return currentLeafNode; } public NodeImpl addBeanNode() { requiresWriteableNodeList(); NodeImpl parent = currentLeafNode; currentLeafNode = NodeImpl.createBeanNode( parent ); nodeList.add( currentLeafNode ); resetHashCode(); return currentLeafNode; } public NodeImpl addReturnValueNode() { requiresWriteableNodeList(); NodeImpl parent = currentLeafNode; currentLeafNode = NodeImpl.createReturnValue( parent ); nodeList.add( currentLeafNode ); resetHashCode(); return currentLeafNode; } private NodeImpl addConstructorNode(String name, Class<?>[] parameterTypes) { requiresWriteableNodeList(); NodeImpl parent = currentLeafNode; currentLeafNode = NodeImpl.createConstructorNode( name, parent, parameterTypes ); nodeList.add( currentLeafNode ); resetHashCode(); return currentLeafNode; } private NodeImpl addMethodNode(String name, Class<?>[] parameterTypes) { requiresWriteableNodeList(); NodeImpl parent = currentLeafNode; currentLeafNode = NodeImpl.createMethodNode( name, parent, parameterTypes ); nodeList.add( currentLeafNode ); resetHashCode(); return currentLeafNode; } public NodeImpl makeLeafNodeIterable() { requiresWriteableNodeList(); currentLeafNode = NodeImpl.makeIterable( currentLeafNode ); nodeList.set( nodeList.size() - 1, currentLeafNode ); resetHashCode(); return currentLeafNode; } public NodeImpl makeLeafNodeIterableAndSetIndex(Integer index) { requiresWriteableNodeList(); currentLeafNode = NodeImpl.makeIterableAndSetIndex( currentLeafNode, index ); nodeList.set( nodeList.size() - 1, currentLeafNode ); resetHashCode(); return currentLeafNode; } public NodeImpl makeLeafNodeIterableAndSetMapKey(Object key) { requiresWriteableNodeList(); currentLeafNode = NodeImpl.makeIterableAndSetMapKey( currentLeafNode, key ); nodeList.set( nodeList.size() - 1, currentLeafNode ); resetHashCode(); return currentLeafNode; } public NodeImpl setLeafNodeValueIfRequired(Object value) { // The value is only exposed for property and container element nodes if ( currentLeafNode.getKind() == ElementKind.PROPERTY || currentLeafNode.getKind() == ElementKind.CONTAINER_ELEMENT ) { requiresWriteableNodeList(); currentLeafNode = NodeImpl.setPropertyValue( currentLeafNode, value ); nodeList.set( nodeList.size() - 1, currentLeafNode ); // the property value is not part of the NodeImpl hashCode so we don't need to reset the PathImpl hashCode } return currentLeafNode; } public NodeImpl setLeafNodeTypeParameter(Class<?> containerClass, Integer typeArgumentIndex) { requiresWriteableNodeList(); currentLeafNode = NodeImpl.setTypeParameter( currentLeafNode, containerClass, typeArgumentIndex ); nodeList.set( nodeList.size() - 1, currentLeafNode ); resetHashCode(); return currentLeafNode; } public void removeLeafNode() { if ( !nodeList.isEmpty() ) { requiresWriteableNodeList(); nodeList.remove( nodeList.size() - 1 ); currentLeafNode = nodeList.isEmpty() ? null : (NodeImpl) nodeList.get( nodeList.size() - 1 ); resetHashCode(); } } public NodeImpl getLeafNode() { return currentLeafNode; } @Override public Iterator<Path.Node> iterator() { if ( nodeList.size() == 0 ) { return Collections.<Path.Node>emptyList().iterator(); } if ( nodeList.size() == 1 ) { return nodeList.iterator(); } return nodeList.subList( 1, nodeList.size() ).iterator(); } public String asString() { StringBuilder builder = new StringBuilder(); boolean first = true; for ( int i = 1; i < nodeList.size(); i++ ) { NodeImpl nodeImpl = (NodeImpl) nodeList.get( i ); String name = nodeImpl.asString(); if ( name.isEmpty() ) { // skip the node if it does not contribute to the string representation of the path, eg class level constraints continue; } if ( !first ) { builder.append( PROPERTY_PATH_SEPARATOR ); } builder.append( nodeImpl.asString() ); first = false; } return builder.toString(); } private void requiresWriteableNodeList() { if ( !nodeListRequiresCopy ) { return; } // Usually, the write operation is about adding one more node, so let's make the list one element larger. List<Node> newNodeList = new ArrayList<>( nodeList.size() + 1 ); newNodeList.addAll( nodeList ); nodeList = newNodeList; nodeListRequiresCopy = false; } @Override public String toString() { return asString(); } @Override public boolean equals(Object obj) { if ( this == obj ) { return true; } if ( obj == null ) { return false; } if ( getClass() != obj.getClass() ) { return false; } PathImpl other = (PathImpl) obj; if ( nodeList == null ) { if ( other.nodeList != null ) { return false; } } else if ( !nodeList.equals( other.nodeList ) ) { return false; } return true; } @Override // deferred hash code building public int hashCode() { if ( hashCode == -1 ) { hashCode = buildHashCode(); } return hashCode; } private int buildHashCode() { final int prime = 31; int result = 1; result = prime * result + ( ( nodeList == null ) ? 0 : nodeList.hashCode() ); return result; }
Copy constructor.
Params:
  • path – the path to make a copy of.
/** * Copy constructor. * * @param path the path to make a copy of. */
private PathImpl(PathImpl path) { nodeList = path.nodeList; currentLeafNode = path.currentLeafNode; hashCode = path.hashCode; nodeListRequiresCopy = true; } private PathImpl() { nodeList = new ArrayList<>( 1 ); hashCode = -1; nodeListRequiresCopy = false; } private PathImpl(List<Node> nodeList) { this.nodeList = nodeList; currentLeafNode = (NodeImpl) nodeList.get( nodeList.size() - 1 ); hashCode = -1; nodeListRequiresCopy = true; } private void resetHashCode() { hashCode = -1; } private static PathImpl parseProperty(String propertyName) { PathImpl path = createRootPath(); String tmp = propertyName; do { Matcher matcher = PATH_PATTERN.matcher( tmp ); if ( matcher.matches() ) { String value = matcher.group( PROPERTY_NAME_GROUP ); if ( !isValidJavaIdentifier( value ) ) { throw LOG.getInvalidJavaIdentifierException( value ); } // create the node path.addPropertyNode( value ); // is the node indexable if ( matcher.group( INDEXED_GROUP ) != null ) { path.makeLeafNodeIterable(); } // take care of the index/key if one exists String indexOrKey = matcher.group( INDEX_GROUP ); if ( indexOrKey != null && indexOrKey.length() > 0 ) { try { Integer i = Integer.parseInt( indexOrKey ); path.makeLeafNodeIterableAndSetIndex( i ); } catch (NumberFormatException e) { path.makeLeafNodeIterableAndSetMapKey( indexOrKey ); } } // match the remaining string tmp = matcher.group( REMAINING_STRING_GROUP ); } else { throw LOG.getUnableToParsePropertyPathException( propertyName ); } } while ( tmp != null ); if ( path.getLeafNode().isIterable() ) { path.addBeanNode(); } return path; }
Validate that the given identifier is a valid Java identifier according to the Java Language Specification, chapter 3.8
Params:
  • identifier – string identifier to validate
Throws:
Returns:true if the given identifier is a valid Java Identifier
/** * Validate that the given identifier is a valid Java identifier according to the Java Language Specification, * <a href="http://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.8">chapter 3.8</a> * * @param identifier string identifier to validate * * @return true if the given identifier is a valid Java Identifier * * @throws IllegalArgumentException if the given identifier is {@code null} */
private static boolean isValidJavaIdentifier(String identifier) { Contracts.assertNotNull( identifier, "identifier param cannot be null" ); if ( identifier.length() == 0 || !Character.isJavaIdentifierStart( (int) identifier.charAt( 0 ) ) ) { return false; } for ( int i = 1; i < identifier.length(); i++ ) { if ( !Character.isJavaIdentifierPart( (int) identifier.charAt( i ) ) ) { return false; } } return true; } }