44import graphql .AssertException ;
55import graphql .PublicApi ;
66
7- import java .util .ArrayList ;
8- import java .util .Collections ;
7+ import java .util .LinkedList ;
98import java .util .List ;
9+ import java .util .Objects ;
1010import java .util .StringTokenizer ;
1111
1212import static graphql .Assert .assertNotNull ;
@@ -32,26 +32,31 @@ public static ExecutionPath rootPath() {
3232 }
3333
3434 private final ExecutionPath parent ;
35- private final PathSegment segment ;
36- private final List <Object > pathList ;
35+ private final Object segment ;
36+
37+ // hash is effective immutable but lazily initialized similar to the hash code of java.lang.String
38+ private int hash ;
3739
3840 private ExecutionPath () {
3941 parent = null ;
4042 segment = null ;
41- pathList = toListImpl ();
4243 }
4344
44- private ExecutionPath (ExecutionPath parent , PathSegment segment ) {
45+ private ExecutionPath (ExecutionPath parent , String segment ) {
4546 this .parent = assertNotNull (parent , "Must provide a parent path" );
4647 this .segment = assertNotNull (segment , "Must provide a sub path" );
47- pathList = toListImpl ();
48+ }
49+
50+ private ExecutionPath (ExecutionPath parent , int segment ) {
51+ this .parent = assertNotNull (parent , "Must provide a parent path" );
52+ this .segment = segment ;
4853 }
4954
5055 public int getLevel () {
5156 int counter = 0 ;
5257 ExecutionPath currentPath = this ;
5358 while (currentPath != null ) {
54- if (currentPath .segment instanceof StringPathSegment ) {
59+ if (currentPath .segment instanceof String ) {
5560 counter ++;
5661 }
5762 currentPath = currentPath .parent ;
@@ -63,27 +68,48 @@ public ExecutionPath getPathWithoutListEnd() {
6368 if (ROOT_PATH .equals (this )) {
6469 return ROOT_PATH ;
6570 }
66- if (segment instanceof StringPathSegment ) {
71+ if (segment instanceof String ) {
6772 return this ;
6873 }
6974 return parent ;
7075 }
7176
77+ /**
78+ * @return true if the end of the path has a list style segment eg 'a/b[2]'
79+ */
80+ public boolean isListSegment () {
81+ return segment instanceof Integer ;
82+ }
83+
84+ /**
85+ * @return true if the end of the path has a named style segment eg 'a/b[2]/c'
86+ */
87+ public boolean isNamedSegment () {
88+ return segment instanceof String ;
89+ }
90+
91+
7292 public String getSegmentName () {
73- if (segment instanceof StringPathSegment ) {
74- return ((StringPathSegment ) segment ).getValue ();
75- } else {
76- if (parent == null ) {
77- return null ;
78- }
79- return ((StringPathSegment ) parent .segment ).getValue ();
80- }
93+ return (String ) segment ;
94+ }
95+
96+ public int getSegmentIndex () {
97+ return (int ) segment ;
98+ }
99+
100+ public Object getSegmentValue () {
101+ return segment ;
102+ }
103+
104+ public ExecutionPath getParent () {
105+ return parent ;
81106 }
82107
83108 /**
84109 * Parses an execution path from the provided path string in the format /segment1/segment2[index]/segmentN
85110 *
86111 * @param pathString the path string
112+ *
87113 * @return a parsed execution path
88114 */
89115 public static ExecutionPath parse (String pathString ) {
@@ -112,16 +138,17 @@ public static ExecutionPath parse(String pathString) {
112138 * This will create an execution path from the list of objects
113139 *
114140 * @param objects the path objects
141+ *
115142 * @return a new execution path
116143 */
117144 public static ExecutionPath fromList (List <?> objects ) {
118145 assertNotNull (objects );
119146 ExecutionPath path = ExecutionPath .rootPath ();
120147 for (Object object : objects ) {
121- if (object instanceof Number ) {
122- path = path .segment (((Number ) object ). intValue ( ));
148+ if (object instanceof String ) {
149+ path = path .segment (((String ) object ));
123150 } else {
124- path = path .segment (String . valueOf ( object ) );
151+ path = path .segment (( int ) object );
125152 }
126153 }
127154 return path ;
@@ -135,20 +162,22 @@ private static String mkErrMsg() {
135162 * Takes the current path and adds a new segment to it, returning a new path
136163 *
137164 * @param segment the string path segment to add
165+ *
138166 * @return a new path containing that segment
139167 */
140168 public ExecutionPath segment (String segment ) {
141- return new ExecutionPath (this , new StringPathSegment ( segment ) );
169+ return new ExecutionPath (this , segment );
142170 }
143171
144172 /**
145173 * Takes the current path and adds a new segment to it, returning a new path
146174 *
147175 * @param segment the int path segment to add
176+ *
148177 * @return a new path containing that segment
149178 */
150179 public ExecutionPath segment (int segment ) {
151- return new ExecutionPath (this , new IntPathSegment ( segment ) );
180+ return new ExecutionPath (this , segment );
152181 }
153182
154183 /**
@@ -168,45 +197,27 @@ public ExecutionPath dropSegment() {
168197 * equals "/a/b[9]"
169198 *
170199 * @param segment the integer segment to use
200+ *
171201 * @return a new path with the last segment replaced
172202 */
173203 public ExecutionPath replaceSegment (int segment ) {
174204 Assert .assertTrue (!ROOT_PATH .equals (this ), "You MUST not call this with the root path" );
175-
176- List <Object > objects = this .toList ();
177- objects .set (objects .size () - 1 , new IntPathSegment (segment ).getValue ());
178- return fromList (objects );
205+ return new ExecutionPath (parent , segment );
179206 }
180207
181208 /**
182209 * Replaces the last segment on the path eg ExecutionPath.parse("/a/b[1]").replaceSegment("x")
183210 * equals "/a/b/x"
184211 *
185212 * @param segment the string segment to use
213+ *
186214 * @return a new path with the last segment replaced
187215 */
188216 public ExecutionPath replaceSegment (String segment ) {
189217 Assert .assertTrue (!ROOT_PATH .equals (this ), "You MUST not call this with the root path" );
190-
191- List <Object > objects = this .toList ();
192- objects .set (objects .size () - 1 , new StringPathSegment (segment ).getValue ());
193- return fromList (objects );
194- }
195-
196-
197- /**
198- * @return true if the end of the path has a list style segment eg 'a/b[2]'
199- */
200- public boolean isListSegment () {
201- return segment instanceof IntPathSegment ;
218+ return new ExecutionPath (parent , segment );
202219 }
203220
204- /**
205- * @return true if the end of the path has a named style segment eg 'a/b[2]/c'
206- */
207- public boolean isNamedSegment () {
208- return segment instanceof StringPathSegment ;
209- }
210221
211222 /**
212223 * @return true if the path is the {@link #rootPath()}
@@ -219,6 +230,7 @@ public boolean isRootPath() {
219230 * Appends the provided path to the current one
220231 *
221232 * @param path the path to append
233+ *
222234 * @return a new path
223235 */
224236 public ExecutionPath append (ExecutionPath path ) {
@@ -230,27 +242,27 @@ public ExecutionPath append(ExecutionPath path) {
230242
231243 public ExecutionPath sibling (String siblingField ) {
232244 Assert .assertTrue (!ROOT_PATH .equals (this ), "You MUST not call this with the root path" );
233- return new ExecutionPath (this .parent , new StringPathSegment (siblingField ));
245+ return new ExecutionPath (this .parent , siblingField );
246+ }
247+
248+ public ExecutionPath sibling (int siblingField ) {
249+ Assert .assertTrue (!ROOT_PATH .equals (this ), "You MUST not call this with the root path" );
250+ return new ExecutionPath (this .parent , siblingField );
234251 }
235252
236253 /**
237254 * @return converts the path into a list of segments
238255 */
239256 public List <Object > toList () {
240- return new ArrayList <>(pathList );
241- }
242-
243- private List <Object > toListImpl () {
244257 if (parent == null ) {
245- return Collections . emptyList ();
258+ return new LinkedList <> ();
246259 }
247- List <Object > list = new ArrayList <>();
260+ LinkedList <Object > list = new LinkedList <>();
248261 ExecutionPath p = this ;
249262 while (p .segment != null ) {
250- list .add (p .segment . getValue () );
263+ list .addFirst (p .segment );
251264 p = p .parent ;
252265 }
253- Collections .reverse (list );
254266 return list ;
255267 }
256268
@@ -265,66 +277,57 @@ public String toString() {
265277 }
266278
267279 if (ROOT_PATH .equals (parent )) {
268- return segment . toString ();
280+ return segmentToString ();
269281 }
270282
271- return parent .toString () + segment .toString ();
283+ return parent .toString () + segmentToString ();
284+ }
285+
286+ public String segmentToString () {
287+ if (segment instanceof String ) {
288+ return "/" + segment ;
289+ } else {
290+ return "[" + segment + "]" ;
291+ }
272292 }
273293
274294 @ Override
275295 public boolean equals (Object o ) {
276- if (this == o ) return true ;
277- if (o == null || getClass () != o .getClass ()) return false ;
296+ if (this == o ) {
297+ return true ;
298+ }
299+ if (o == null || getClass () != o .getClass ()) {
300+ return false ;
301+ }
278302
303+ ExecutionPath self = this ;
279304 ExecutionPath that = (ExecutionPath ) o ;
305+ while (self .segment != null && that .segment != null ) {
306+ if (!Objects .equals (self .segment , that .segment )) {
307+ return false ;
308+ }
309+ self = self .parent ;
310+ that = that .parent ;
311+ }
280312
281- return pathList . equals ( that .pathList );
313+ return self . isRootPath () && that .isRootPath ( );
282314 }
283315
284316 @ Override
285317 public int hashCode () {
286- return pathList .hashCode ();
287- }
288-
289-
290- private interface PathSegment <T > {
291- T getValue ();
292- }
293-
294- private static class StringPathSegment implements PathSegment <String > {
295- private final String value ;
296-
297- StringPathSegment (String value ) {
298- assertTrue (value != null && !value .isEmpty (), "empty path component" );
299- this .value = value ;
300- }
301-
302- @ Override
303- public String getValue () {
304- return value ;
305- }
306-
307- @ Override
308- public String toString () {
309- return '/' + value ;
318+ int h = hash ;
319+ if (h == 0 ) {
320+ h = 1 ;
321+ ExecutionPath self = this ;
322+ while (self != null ) {
323+ Object value = self .segment ;
324+ h = 31 * h + (value == null ? 0 : value .hashCode ());
325+ self = self .parent ;
326+ }
327+ hash = h ;
310328 }
329+ return h ;
311330 }
312331
313- private static class IntPathSegment implements PathSegment <Integer > {
314- private final int value ;
315-
316- IntPathSegment (int value ) {
317- this .value = value ;
318- }
319-
320- @ Override
321- public Integer getValue () {
322- return value ;
323- }
324332
325- @ Override
326- public String toString () {
327- return "[" + value + ']' ;
328- }
329- }
330333}
0 commit comments