Skip to content

Commit 9f8cc8e

Browse files
authored
Merge pull request graphql-java#1891 from graphql-java/execution-path-improvements
refactor ExecutionPath to improve performance and simplify it
2 parents 1a7c11c + 06d6bbc commit 9f8cc8e

2 files changed

Lines changed: 103 additions & 142 deletions

File tree

src/main/java/graphql/execution/ExecutionPath.java

Lines changed: 101 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
import graphql.AssertException;
55
import graphql.PublicApi;
66

7-
import java.util.ArrayList;
8-
import java.util.Collections;
7+
import java.util.LinkedList;
98
import java.util.List;
9+
import java.util.Objects;
1010
import java.util.StringTokenizer;
1111

1212
import 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

Comments
 (0)