33
33
34
34
35
35
/**
36
+ * Represents a Node Path.
37
+ *
38
+ * Internally the node path is held as an array of {@link QName} components.
39
+ * Upon construction the array of `components` is sized such that it will be exactly
40
+ * what is required to represent the Node Path.
41
+ * Mutation operations however will over-allocate the array as an optmisation, so that
42
+ * we need not allocate/free memory on every mutation operation, but only
43
+ * every {@link #DEFAULT_NODE_PATH_SIZE} operations.
44
+ *
45
+ * @author <a href="mailto:[email protected] ">Adam Retter</a>
36
46
* @author wolf
37
- * @author Adam Retter
38
47
*/
39
48
public class NodePath implements Comparable <NodePath > {
40
49
41
- private static final int DEFAULT_NODE_PATH_SIZE = 5 ;
50
+ static final int DEFAULT_NODE_PATH_SIZE = 4 ;
51
+ static final int MAX_OVER_ALLOCATION_FACTOR = 2 ;
42
52
43
53
private static final Logger LOG = LogManager .getLogger (NodePath .class );
44
54
@@ -89,21 +99,33 @@ public boolean includeDescendants() {
89
99
}
90
100
91
101
public void append (final NodePath other ) {
92
- // expand the array
93
- final int newLength = pos + other .length ();
94
- this .components = Arrays .copyOf (components , newLength );
95
- System .arraycopy (other .components , 0 , components , pos , other .length ());
96
- this .pos = newLength ;
102
+ // do we have enough space to accommodate the components from `other`
103
+ final int numOtherComponentsToAppend = other .length ();
104
+ allocateIfNeeded (numOtherComponentsToAppend );
105
+
106
+ // at this point we have enough space, append the components from `other`
107
+ System .arraycopy (other .components , 0 , components , pos , numOtherComponentsToAppend );
108
+ this .pos += numOtherComponentsToAppend ;
97
109
}
98
110
99
111
public void addComponent (final QName component ) {
100
- if ( pos == components . length ) {
101
- // extend the array
102
- this . components = Arrays . copyOf ( components , pos + 1 );
103
- }
112
+ // do we have enough space to add the component
113
+ allocateIfNeeded ( 1 );
114
+
115
+ // at this point we have enough space, add the component
104
116
components [pos ++] = component ;
105
117
}
106
118
119
+ private void allocateIfNeeded (final int numOtherComponentsToAppend ) {
120
+ final int available = components .length - pos ;
121
+ if (available < numOtherComponentsToAppend ) {
122
+ // we need more space, allocate a multiple of DEFAULT_NODE_PATH_SIZE
123
+ final int allocationFactor = (int ) Math .ceil ((numOtherComponentsToAppend - available + components .length ) / ((float ) DEFAULT_NODE_PATH_SIZE ));
124
+ final int newSize = allocationFactor * DEFAULT_NODE_PATH_SIZE ;
125
+ this .components = Arrays .copyOf (components , newSize );
126
+ }
127
+ }
128
+
107
129
/**
108
130
* Remove the Last Component from the NodePath.
109
131
*
@@ -117,8 +139,8 @@ public void removeLastComponent() {
117
139
}
118
140
119
141
public void reset () {
120
- // when resetting if this object has twice the capacity of a new object, then set it back to the default capacity
121
- if (pos > DEFAULT_NODE_PATH_SIZE * 2 ) {
142
+ // when resetting if this object has more than twice the capacity of a new object, then set it back to the default capacity
143
+ if (pos > DEFAULT_NODE_PATH_SIZE * MAX_OVER_ALLOCATION_FACTOR ) {
122
144
components = new QName [DEFAULT_NODE_PATH_SIZE ];
123
145
} else {
124
146
Arrays .fill (components , null );
@@ -130,6 +152,18 @@ public int length() {
130
152
return pos ;
131
153
}
132
154
155
+ /**
156
+ * Return the size of the components array.
157
+ *
158
+ * This function is intentionally marked as package-private
159
+ * so that it may only be called for testing purposes!
160
+ *
161
+ * @return the size of the components array.
162
+ */
163
+ int componentsSize () {
164
+ return components .length ;
165
+ }
166
+
133
167
protected void reverseComponents () {
134
168
for (int i = 0 ; i < pos / 2 ; ++i ) {
135
169
QName tmp = components [i ];
@@ -290,10 +324,24 @@ private void init(@Nullable final Map<String, String> namespaces, final String p
290
324
291
325
@ Override
292
326
public boolean equals (final Object obj ) {
293
- if (obj != null && obj instanceof NodePath ) {
327
+ if (this == obj ) {
328
+ return true ;
329
+ }
330
+
331
+ if (obj instanceof NodePath ) {
294
332
final NodePath otherNodePath = (NodePath ) obj ;
295
- return Arrays .equals (components , otherNodePath .components );
333
+
334
+ // NOTE(AR) we cannot use Array.equals on the components of the NodePaths as they may be over-allocated!
335
+ if (pos == otherNodePath .pos ) {
336
+ for (int i = 0 ; i < pos ; i ++) {
337
+ if (!components [i ].equals (otherNodePath .components [i ])) {
338
+ return false ;
339
+ }
340
+ }
341
+ return true ;
342
+ }
296
343
}
344
+
297
345
return false ;
298
346
}
299
347
0 commit comments