1
7 package org.hibernate.validator.internal.engine.path;
8
9 import static org.hibernate.validator.internal.util.logging.Messages.MESSAGES;
10
11 import java.io.Serializable;
12 import java.lang.invoke.MethodHandles;
13 import java.util.ArrayList;
14 import java.util.Collections;
15 import java.util.Iterator;
16 import java.util.List;
17 import java.util.regex.Matcher;
18 import java.util.regex.Pattern;
19
20 import javax.validation.ElementKind;
21 import javax.validation.Path;
22
23 import org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData;
24 import org.hibernate.validator.internal.util.Contracts;
25 import org.hibernate.validator.internal.util.logging.Log;
26 import org.hibernate.validator.internal.util.logging.LoggerFactory;
27
28
35 public final class PathImpl implements Path, Serializable {
36 private static final long serialVersionUID = 7564511574909882392L;
37 private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() );
38
39 private static final String PROPERTY_PATH_SEPARATOR = ".";
40
41
46 private static final String LEADING_PROPERTY_GROUP = "[^\\[\\.]+";
47 private static final String OPTIONAL_INDEX_GROUP = "\\[(\\w*)\\]";
48 private static final String REMAINING_PROPERTY_STRING = "\\.(.*)";
49
50 private static final Pattern PATH_PATTERN = Pattern.compile( "(" + LEADING_PROPERTY_GROUP + ")(" + OPTIONAL_INDEX_GROUP + ")?(" + REMAINING_PROPERTY_STRING + ")*" );
51 private static final int PROPERTY_NAME_GROUP = 1;
52 private static final int INDEXED_GROUP = 2;
53 private static final int INDEX_GROUP = 3;
54 private static final int REMAINING_STRING_GROUP = 5;
55
56 private List<Node> nodeList;
57 private boolean nodeListRequiresCopy;
58 private NodeImpl currentLeafNode;
59 private int hashCode;
60
61
73 public static PathImpl createPathFromString(String propertyPath) {
74 Contracts.assertNotNull( propertyPath, MESSAGES.propertyPathCannotBeNull() );
75
76 if ( propertyPath.length() == 0 ) {
77 return createRootPath();
78 }
79
80 return parseProperty( propertyPath );
81 }
82
83 public static PathImpl createPathForExecutable(ExecutableMetaData executable) {
84 Contracts.assertNotNull( executable, "A method is required to create a method return value path." );
85
86 PathImpl path = createRootPath();
87
88 if ( executable.getKind() == ElementKind.CONSTRUCTOR ) {
89 path.addConstructorNode( executable.getName(), executable.getParameterTypes() );
90 }
91 else {
92 path.addMethodNode( executable.getName(), executable.getParameterTypes() );
93 }
94
95 return path;
96 }
97
98 public static PathImpl createRootPath() {
99 PathImpl path = new PathImpl();
100 path.addBeanNode();
101 return path;
102 }
103
104 public static PathImpl createCopy(PathImpl path) {
105 return new PathImpl( path );
106 }
107
108 public static PathImpl createCopyWithoutLeafNode(PathImpl path) {
109 return new PathImpl( path.nodeList.subList( 0, path.nodeList.size() - 1 ) );
110 }
111
112
113 public boolean isRootPath() {
114 return nodeList.size() == 1 && nodeList.get( 0 ).getName() == null;
115 }
116
117 public NodeImpl addPropertyNode(String nodeName) {
118 requiresWriteableNodeList();
119
120 NodeImpl parent = currentLeafNode;
121 currentLeafNode = NodeImpl.createPropertyNode( nodeName, parent );
122 nodeList.add( currentLeafNode );
123 resetHashCode();
124 return currentLeafNode;
125 }
126
127 public NodeImpl addContainerElementNode(String nodeName) {
128 requiresWriteableNodeList();
129
130 NodeImpl parent = currentLeafNode;
131 currentLeafNode = NodeImpl.createContainerElementNode( nodeName, parent );
132 nodeList.add( currentLeafNode );
133 resetHashCode();
134 return currentLeafNode;
135 }
136
137 public NodeImpl addParameterNode(String nodeName, int index) {
138 requiresWriteableNodeList();
139
140 NodeImpl parent = currentLeafNode;
141 currentLeafNode = NodeImpl.createParameterNode( nodeName, parent, index );
142 nodeList.add( currentLeafNode );
143 resetHashCode();
144 return currentLeafNode;
145 }
146
147 public NodeImpl addCrossParameterNode() {
148 requiresWriteableNodeList();
149
150 NodeImpl parent = currentLeafNode;
151 currentLeafNode = NodeImpl.createCrossParameterNode( parent );
152 nodeList.add( currentLeafNode );
153 resetHashCode();
154 return currentLeafNode;
155 }
156
157 public NodeImpl addBeanNode() {
158 requiresWriteableNodeList();
159
160 NodeImpl parent = currentLeafNode;
161 currentLeafNode = NodeImpl.createBeanNode( parent );
162 nodeList.add( currentLeafNode );
163 resetHashCode();
164 return currentLeafNode;
165 }
166
167 public NodeImpl addReturnValueNode() {
168 requiresWriteableNodeList();
169
170 NodeImpl parent = currentLeafNode;
171 currentLeafNode = NodeImpl.createReturnValue( parent );
172 nodeList.add( currentLeafNode );
173 resetHashCode();
174 return currentLeafNode;
175 }
176
177 private NodeImpl addConstructorNode(String name, Class<?>[] parameterTypes) {
178 requiresWriteableNodeList();
179
180 NodeImpl parent = currentLeafNode;
181 currentLeafNode = NodeImpl.createConstructorNode( name, parent, parameterTypes );
182 nodeList.add( currentLeafNode );
183 resetHashCode();
184 return currentLeafNode;
185 }
186
187 private NodeImpl addMethodNode(String name, Class<?>[] parameterTypes) {
188 requiresWriteableNodeList();
189
190 NodeImpl parent = currentLeafNode;
191 currentLeafNode = NodeImpl.createMethodNode( name, parent, parameterTypes );
192 nodeList.add( currentLeafNode );
193 resetHashCode();
194 return currentLeafNode;
195 }
196
197 public NodeImpl makeLeafNodeIterable() {
198 requiresWriteableNodeList();
199
200 currentLeafNode = NodeImpl.makeIterable( currentLeafNode );
201
202 nodeList.set( nodeList.size() - 1, currentLeafNode );
203 resetHashCode();
204 return currentLeafNode;
205 }
206
207 public NodeImpl makeLeafNodeIterableAndSetIndex(Integer index) {
208 requiresWriteableNodeList();
209
210 currentLeafNode = NodeImpl.makeIterableAndSetIndex( currentLeafNode, index );
211
212 nodeList.set( nodeList.size() - 1, currentLeafNode );
213 resetHashCode();
214 return currentLeafNode;
215 }
216
217 public NodeImpl makeLeafNodeIterableAndSetMapKey(Object key) {
218 requiresWriteableNodeList();
219
220 currentLeafNode = NodeImpl.makeIterableAndSetMapKey( currentLeafNode, key );
221
222 nodeList.set( nodeList.size() - 1, currentLeafNode );
223 resetHashCode();
224 return currentLeafNode;
225 }
226
227 public NodeImpl setLeafNodeValueIfRequired(Object value) {
228
229 if ( currentLeafNode.getKind() == ElementKind.PROPERTY || currentLeafNode.getKind() == ElementKind.CONTAINER_ELEMENT ) {
230 requiresWriteableNodeList();
231
232 currentLeafNode = NodeImpl.setPropertyValue( currentLeafNode, value );
233
234 nodeList.set( nodeList.size() - 1, currentLeafNode );
235
236
237 }
238 return currentLeafNode;
239 }
240
241 public NodeImpl setLeafNodeTypeParameter(Class<?> containerClass, Integer typeArgumentIndex) {
242 requiresWriteableNodeList();
243
244 currentLeafNode = NodeImpl.setTypeParameter( currentLeafNode, containerClass, typeArgumentIndex );
245
246 nodeList.set( nodeList.size() - 1, currentLeafNode );
247 resetHashCode();
248 return currentLeafNode;
249 }
250
251 public void removeLeafNode() {
252 if ( !nodeList.isEmpty() ) {
253 requiresWriteableNodeList();
254
255 nodeList.remove( nodeList.size() - 1 );
256 currentLeafNode = nodeList.isEmpty() ? null : (NodeImpl) nodeList.get( nodeList.size() - 1 );
257 resetHashCode();
258 }
259 }
260
261 public NodeImpl getLeafNode() {
262 return currentLeafNode;
263 }
264
265 @Override
266 public Iterator<Path.Node> iterator() {
267 if ( nodeList.size() == 0 ) {
268 return Collections.<Path.Node>emptyList().iterator();
269 }
270 if ( nodeList.size() == 1 ) {
271 return nodeList.iterator();
272 }
273 return nodeList.subList( 1, nodeList.size() ).iterator();
274 }
275
276 public String asString() {
277 StringBuilder builder = new StringBuilder();
278 boolean first = true;
279 for ( int i = 1; i < nodeList.size(); i++ ) {
280 NodeImpl nodeImpl = (NodeImpl) nodeList.get( i );
281 String name = nodeImpl.asString();
282 if ( name.isEmpty() ) {
283
284 continue;
285 }
286
287 if ( !first ) {
288 builder.append( PROPERTY_PATH_SEPARATOR );
289 }
290
291 builder.append( nodeImpl.asString() );
292
293 first = false;
294 }
295 return builder.toString();
296 }
297
298 private void requiresWriteableNodeList() {
299 if ( !nodeListRequiresCopy ) {
300 return;
301 }
302
303
304 List<Node> newNodeList = new ArrayList<>( nodeList.size() + 1 );
305 newNodeList.addAll( nodeList );
306 nodeList = newNodeList;
307 nodeListRequiresCopy = false;
308 }
309
310 @Override
311 public String toString() {
312 return asString();
313 }
314
315 @Override
316 public boolean equals(Object obj) {
317 if ( this == obj ) {
318 return true;
319 }
320 if ( obj == null ) {
321 return false;
322 }
323 if ( getClass() != obj.getClass() ) {
324 return false;
325 }
326 PathImpl other = (PathImpl) obj;
327 if ( nodeList == null ) {
328 if ( other.nodeList != null ) {
329 return false;
330 }
331 }
332 else if ( !nodeList.equals( other.nodeList ) ) {
333 return false;
334 }
335 return true;
336 }
337
338 @Override
339
340 public int hashCode() {
341 if ( hashCode == -1 ) {
342 hashCode = buildHashCode();
343 }
344
345 return hashCode;
346 }
347
348 private int buildHashCode() {
349 final int prime = 31;
350 int result = 1;
351 result = prime * result
352 + ( ( nodeList == null ) ? 0 : nodeList.hashCode() );
353 return result;
354 }
355
356
361 private PathImpl(PathImpl path) {
362 nodeList = path.nodeList;
363 currentLeafNode = path.currentLeafNode;
364 hashCode = path.hashCode;
365 nodeListRequiresCopy = true;
366 }
367
368 private PathImpl() {
369 nodeList = new ArrayList<>( 1 );
370 hashCode = -1;
371 nodeListRequiresCopy = false;
372 }
373
374 private PathImpl(List<Node> nodeList) {
375 this.nodeList = nodeList;
376 currentLeafNode = (NodeImpl) nodeList.get( nodeList.size() - 1 );
377 hashCode = -1;
378 nodeListRequiresCopy = true;
379 }
380
381 private void resetHashCode() {
382 hashCode = -1;
383 }
384
385 private static PathImpl parseProperty(String propertyName) {
386 PathImpl path = createRootPath();
387 String tmp = propertyName;
388 do {
389 Matcher matcher = PATH_PATTERN.matcher( tmp );
390 if ( matcher.matches() ) {
391
392 String value = matcher.group( PROPERTY_NAME_GROUP );
393 if ( !isValidJavaIdentifier( value ) ) {
394 throw LOG.getInvalidJavaIdentifierException( value );
395 }
396
397
398 path.addPropertyNode( value );
399
400
401 if ( matcher.group( INDEXED_GROUP ) != null ) {
402 path.makeLeafNodeIterable();
403 }
404
405
406 String indexOrKey = matcher.group( INDEX_GROUP );
407 if ( indexOrKey != null && indexOrKey.length() > 0 ) {
408 try {
409 Integer i = Integer.parseInt( indexOrKey );
410 path.makeLeafNodeIterableAndSetIndex( i );
411 }
412 catch (NumberFormatException e) {
413 path.makeLeafNodeIterableAndSetMapKey( indexOrKey );
414 }
415 }
416
417
418 tmp = matcher.group( REMAINING_STRING_GROUP );
419 }
420 else {
421 throw LOG.getUnableToParsePropertyPathException( propertyName );
422 }
423 } while ( tmp != null );
424
425 if ( path.getLeafNode().isIterable() ) {
426 path.addBeanNode();
427 }
428
429 return path;
430 }
431
432
442 private static boolean isValidJavaIdentifier(String identifier) {
443 Contracts.assertNotNull( identifier, "identifier param cannot be null" );
444
445 if ( identifier.length() == 0 || !Character.isJavaIdentifierStart( (int) identifier.charAt( 0 ) ) ) {
446 return false;
447 }
448
449 for ( int i = 1; i < identifier.length(); i++ ) {
450 if ( !Character.isJavaIdentifierPart( (int) identifier.charAt( i ) ) ) {
451 return false;
452 }
453 }
454 return true;
455 }
456 }
457