Project

Profile

Help

Revision c9cbbbe4

Added by Michael Kay almost 3 years ago

Further fixes for bug 4060. Switch from virtual copy to fast bulk copy.

View differences:

latest9.9/hej/net/sf/saxon/event/ComplexContentOutputter.java
705 705
        afterBulkCopy();
706 706
    }
707 707

  
708
    public boolean isReadyForBulkCopy() {
709
        Receiver r2 = getReceiver();
710
        if (r2 instanceof NamespaceReducer) {
711
            if (!((NamespaceReducer) r2).isDisinheritingNamespaces()) {
712
                Receiver r3 = ((NamespaceReducer) r2).getNextReceiver();
713
                return r3 instanceof TinyBuilder &&
714
                        ((state == StartTag &&
715
                                  (startElementProperties & ReceiverOptions.DISINHERIT_NAMESPACES) == 0)
716
                                 || ((TinyBuilder) r3).isPositionedAtElement());
717
            }
718
        }
719
        return false;
720
    }
721

  
722
    public void bulkCopyElementNode(TinyElementImpl elementNode, int copyOptions) throws XPathException {
723
        NamespaceReducer r2 = (NamespaceReducer) getReceiver();
724
        TinyBuilder target = (TinyBuilder) r2.getNextReceiver();
725
        beforeBulkCopy();
726
        boolean copyNamespaces = CopyOptions.includes(copyOptions, CopyOptions.ALL_NAMESPACES);
727
        target.bulkCopy(elementNode, copyNamespaces);
728
        afterBulkCopy();
729
    }
730

  
708 731
    private void beforeBulkCopy() throws XPathException {
709 732
        level++;
710 733
        if (state == StartTag) {
latest9.9/hej/net/sf/saxon/event/SequenceWriter.java
303 303
    }
304 304

  
305 305

  
306
    public boolean isReadyForBulkCopy() {
307
        return level > 0 && outputter instanceof ComplexContentOutputter &&
308
                ((ComplexContentOutputter)outputter).isReadyForBulkCopy();
309
    }
310

  
311
    public void bulkCopyElementNode(TinyElementImpl node, int copyOptions) throws XPathException {
312
        if (inStartTag) {
313
            startContent();
314
        }
315
        ((ComplexContentOutputter) outputter).bulkCopyElementNode(node, copyOptions);
316
    }
317

  
306 318
    public boolean isReadyForGrafting() {
307 319
        return level > 0 && outputter instanceof ComplexContentOutputter &&
308
                ((ComplexContentOutputter)outputter).isReadyForGrafting();
320
                ((ComplexContentOutputter) outputter).isReadyForGrafting();
309 321
    }
310 322

  
311 323
    public void graftElementNode(TinyElementImpl node, int copyOptions) throws XPathException {
latest9.9/hej/net/sf/saxon/expr/parser/XPathParser.java
3132 3132
            default:
3133 3133
                Expression[] entriesArray = new Expression[entries.size()];
3134 3134
                Block block = new Block(entries.toArray(entriesArray));
3135
                Dictionary options = new Dictionary();
3135
                DictionaryMap options = new DictionaryMap();
3136 3136
                options.initialPut("duplicates", new StringValue("reject"));
3137 3137
                options.initialPut("duplicates-error-code", new StringValue("XQDY0137"));
3138 3138
                result = MapFunctionSet.getInstance().makeFunction("merge", 2).makeFunctionCall(block, Literal.makeLiteral(options));
latest9.9/hej/net/sf/saxon/ma/json/JsonHandlerMap.java
10 10
import net.sf.saxon.expr.XPathContext;
11 11
import net.sf.saxon.ma.arrays.ArrayItem;
12 12
import net.sf.saxon.ma.arrays.SimpleArrayItem;
13
import net.sf.saxon.ma.map.Dictionary;
13
import net.sf.saxon.ma.map.DictionaryMap;
14 14
import net.sf.saxon.ma.map.MapItem;
15 15
import net.sf.saxon.om.GroundedValue;
16 16
import net.sf.saxon.om.Sequence;
......
85 85
     * Start a new object/map
86 86
     */
87 87
    public void startMap() {
88
        Dictionary map = new Dictionary();
88
        DictionaryMap map = new DictionaryMap();
89 89
        stack.push(map);
90 90
    }
91 91

  
......
93 93
     * Close the current object/map
94 94
     */
95 95
    public void endMap() {
96
        Dictionary map = (Dictionary) stack.pop();
96
        DictionaryMap map = (DictionaryMap) stack.pop();
97 97
        if (stack.empty()) {
98 98
            stack.push(map); // the end
99 99
        } else {
......
112 112
            SimpleArrayItem array = (SimpleArrayItem) stack.peek();
113 113
            array.getMembers().add(val.materialize());
114 114
        } else {
115
            Dictionary map = (Dictionary) stack.peek();
115
            DictionaryMap map = (DictionaryMap) stack.peek();
116 116
            //StringValue key = new StringValue(reEscape(keys.pop(), true, false, false));
117 117
            //StringValue key = new StringValue(keys.pop());
118 118
            map.initialPut(keys.pop(), val);
latest9.9/hej/net/sf/saxon/ma/map/Dictionary.java
1
package net.sf.saxon.ma.map;
2

  
3
import net.sf.saxon.om.GroundedValue;
4
import net.sf.saxon.om.SequenceTool;
5
import net.sf.saxon.trans.XPathException;
6
import net.sf.saxon.tree.iter.AtomicIterator;
7
import net.sf.saxon.type.*;
8
import net.sf.saxon.value.AtomicValue;
9
import net.sf.saxon.value.Cardinality;
10
import net.sf.saxon.value.SequenceType;
11
import net.sf.saxon.value.StringValue;
12

  
13
import java.util.*;
14

  
15
/**
16
 * A simple implementation of MapItem where the strings are keys, and modification is unlikely.
17
 * This implementation is used in a number of cases where it can be determined that it is suitable,
18
 * for example when parsing JSON input, or when creating a fixed map to use in an options argument.
19
 */
20

  
21
public class Dictionary implements MapItem {
22

  
23
    private HashMap<String, GroundedValue<?>> hashMap;
24

  
25
    /**
26
     * Create an empty dictionary, to which entries can be added using {@link #initialPut(String, GroundedValue)},
27
     * provided this is done before the map is exposed to the outside world.
28
     */
29

  
30
    public Dictionary() {
31
        hashMap = new HashMap<>();
32
    }
33

  
34
    /**
35
     * During initial construction of the map, add a key-value pair
36
     */
37

  
38
    public void initialPut(String key, GroundedValue<?> value) {
39
        hashMap.put(key, value);
40
    }
41

  
42
    /**
43
     * Get an entry from the Map
44
     *
45
     * @param key the value of the key
46
     * @return the value associated with the given key, or null if the key is not present in the map.
47
     */
48
    @Override
49
    public GroundedValue<?> get(AtomicValue key) {
50
        if (key instanceof StringValue) {
51
            return hashMap.get(key.getStringValue());
52
        } else {
53
            return null;
54
        }
55
    }
56

  
57
    /**
58
     * Get the size of the map
59
     *
60
     * @return the number of keys/entries present in this map
61
     */
62
    @Override
63
    public int size() {
64
        return hashMap.size();
65
    }
66

  
67
    /**
68
     * Ask whether the map is empty
69
     *
70
     * @return true if and only if the size of the map is zero
71
     */
72
    @Override
73
    public boolean isEmpty() {
74
        return hashMap.isEmpty();
75
    }
76

  
77
    /**
78
     * Get the set of all key values in the map.
79
     *
80
     * @return a set containing all the key values present in the map, in unpredictable order
81
     */
82
    @Override
83
    public AtomicIterator keys() {
84
        Iterator<String> base = hashMap.keySet().iterator();
85
        return () -> base.hasNext() ? new StringValue(base.next()) : null;
86
    }
87

  
88
    /**
89
     * Get the set of all key-value pairs in the map
90
     *
91
     * @return an iterable containing all the key-value pairs
92
     */
93
    @Override
94
    public Iterable<KeyValuePair> keyValuePairs() {
95
        List<KeyValuePair> pairs = new ArrayList<>();
96
        hashMap.forEach((k, v) -> pairs.add(new KeyValuePair(new StringValue(k), v)));
97
        return pairs;
98
    }
99

  
100
    /**
101
     * Create a new map containing the existing entries in the map plus an additional entry,
102
     * without modifying the original. If there is already an entry with the specified key,
103
     * this entry is replaced by the new entry.
104
     *
105
     * @param key   the key of the new entry
106
     * @param value the value associated with the new entry
107
     * @return the new map containing the additional entry
108
     */
109
    @Override
110
    public MapItem addEntry(AtomicValue key, GroundedValue<?> value) {
111
        return toHashTrieMap().addEntry(key, value);
112
    }
113

  
114
    /**
115
     * Remove an entry from the map
116
     *
117
     * @param key the key of the entry to be removed
118
     * @return a new map in which the requested entry has been removed; or this map
119
     * unchanged if the specified key was not present
120
     */
121
    @Override
122
    public MapItem remove(AtomicValue key) {
123
        return toHashTrieMap().remove(key);
124
    }
125

  
126
    /**
127
     * Ask whether the map conforms to a given map type
128
     *
129
     * @param keyType   the required keyType
130
     * @param valueType the required valueType
131
     * @param th        the type hierarchy cache for the configuration
132
     * @return true if the map conforms to the required type
133
     */
134
    @Override
135
    public boolean conforms(AtomicType keyType, SequenceType valueType, TypeHierarchy th) {
136
        if (isEmpty()) {
137
            return true;
138
        }
139
        if (!(keyType == BuiltInAtomicType.STRING || keyType == BuiltInAtomicType.ANY_ATOMIC)) {
140
            return false;
141
        }
142
        if (valueType.equals(SequenceType.ANY_SEQUENCE)) {
143
            return true;
144
        }
145
        for (GroundedValue<?> val : hashMap.values()) {
146
            try {
147
                if (!valueType.matches(val, th)) {
148
                    return false;
149
                }
150
            } catch (XPathException e) {
151
                throw new AssertionError(e); // cannot happen when value is grounded
152
            }
153
        }
154
        return true;
155
    }
156

  
157
    /**
158
     * Get the type of the map. This method is used largely for diagnostics, to report
159
     * the type of a map when it differs from the required type.
160
     *
161
     * @param th the type hierarchy cache
162
     * @return the type of this map
163
     */
164
    @Override
165
    public ItemType getItemType(TypeHierarchy th) {
166
        ItemType valueType = null;
167
        int valueCard = 0;
168
        // we need to test the entries individually
169
        AtomicIterator keyIter = keys();
170
        AtomicValue key;
171
        for (Map.Entry<String, GroundedValue<?>> entry : hashMap.entrySet()) {
172
            GroundedValue<?> val = entry.getValue();
173
            if (valueType == null) {
174
                valueType = SequenceTool.getItemType(val, th);
175
                valueCard = SequenceTool.getCardinality(val);
176
            } else {
177
                valueType = Type.getCommonSuperType(valueType, SequenceTool.getItemType(val, th), th);
178
                valueCard = Cardinality.union(valueCard, SequenceTool.getCardinality(val));
179
            }
180
        }
181
        if (valueType == null) {
182
            // empty map
183
            return MapType.EMPTY_MAP_TYPE;
184
        } else {
185
            return new MapType(BuiltInAtomicType.STRING, SequenceType.makeSequenceType(valueType, valueCard));
186
        }
187
    }
188

  
189
    /**
190
     * Get the lowest common item type of the keys in the map
191
     *
192
     * @return the most specific type to which all the keys belong. If the map is
193
     * empty, return UType.VOID
194
     */
195
    @Override
196
    public UType getKeyUType() {
197
        return hashMap.isEmpty() ? UType.VOID : UType.STRING;
198
    }
199

  
200
    /**
201
     * Convert to a HashTrieMap
202
     */
203

  
204
    private HashTrieMap toHashTrieMap() {
205
        //System.err.println("Dictionary rewrite!!!!");
206
        HashTrieMap target = new HashTrieMap();
207
        hashMap.forEach((k, v) -> target.initialPut(new StringValue(k), v));
208
        return target;
209
    }
210
}
211

  
latest9.9/hej/net/sf/saxon/ma/map/DictionaryMap.java
1
package net.sf.saxon.ma.map;
2

  
3
import net.sf.saxon.om.GroundedValue;
4
import net.sf.saxon.om.SequenceTool;
5
import net.sf.saxon.trans.XPathException;
6
import net.sf.saxon.tree.iter.AtomicIterator;
7
import net.sf.saxon.type.*;
8
import net.sf.saxon.value.AtomicValue;
9
import net.sf.saxon.value.Cardinality;
10
import net.sf.saxon.value.SequenceType;
11
import net.sf.saxon.value.StringValue;
12

  
13
import java.util.*;
14

  
15
/**
16
 * A simple implementation of MapItem where the strings are keys, and modification is unlikely.
17
 * This implementation is used in a number of cases where it can be determined that it is suitable,
18
 * for example when parsing JSON input, or when creating a fixed map to use in an options argument.
19
 */
20

  
21
public class DictionaryMap implements MapItem {
22

  
23
    private HashMap<String, GroundedValue<?>> hashMap;
24

  
25
    /**
26
     * Create an empty dictionary, to which entries can be added using {@link #initialPut(String, GroundedValue)},
27
     * provided this is done before the map is exposed to the outside world.
28
     */
29

  
30
    public DictionaryMap() {
31
        hashMap = new HashMap<>();
32
    }
33

  
34
    /**
35
     * During initial construction of the map, add a key-value pair
36
     */
37

  
38
    public void initialPut(String key, GroundedValue<?> value) {
39
        hashMap.put(key, value);
40
    }
41

  
42
    /**
43
     * Get an entry from the Map
44
     *
45
     * @param key the value of the key
46
     * @return the value associated with the given key, or null if the key is not present in the map.
47
     */
48
    @Override
49
    public GroundedValue<?> get(AtomicValue key) {
50
        if (key instanceof StringValue) {
51
            return hashMap.get(key.getStringValue());
52
        } else {
53
            return null;
54
        }
55
    }
56

  
57
    /**
58
     * Get the size of the map
59
     *
60
     * @return the number of keys/entries present in this map
61
     */
62
    @Override
63
    public int size() {
64
        return hashMap.size();
65
    }
66

  
67
    /**
68
     * Ask whether the map is empty
69
     *
70
     * @return true if and only if the size of the map is zero
71
     */
72
    @Override
73
    public boolean isEmpty() {
74
        return hashMap.isEmpty();
75
    }
76

  
77
    /**
78
     * Get the set of all key values in the map.
79
     *
80
     * @return a set containing all the key values present in the map, in unpredictable order
81
     */
82
    @Override
83
    public AtomicIterator keys() {
84
        Iterator<String> base = hashMap.keySet().iterator();
85
        return () -> base.hasNext() ? new StringValue(base.next()) : null;
86
    }
87

  
88
    /**
89
     * Get the set of all key-value pairs in the map
90
     *
91
     * @return an iterable containing all the key-value pairs
92
     */
93
    @Override
94
    public Iterable<KeyValuePair> keyValuePairs() {
95
        List<KeyValuePair> pairs = new ArrayList<>();
96
        hashMap.forEach((k, v) -> pairs.add(new KeyValuePair(new StringValue(k), v)));
97
        return pairs;
98
    }
99

  
100
    /**
101
     * Create a new map containing the existing entries in the map plus an additional entry,
102
     * without modifying the original. If there is already an entry with the specified key,
103
     * this entry is replaced by the new entry.
104
     *
105
     * @param key   the key of the new entry
106
     * @param value the value associated with the new entry
107
     * @return the new map containing the additional entry
108
     */
109
    @Override
110
    public MapItem addEntry(AtomicValue key, GroundedValue<?> value) {
111
        return toHashTrieMap().addEntry(key, value);
112
    }
113

  
114
    /**
115
     * Remove an entry from the map
116
     *
117
     * @param key the key of the entry to be removed
118
     * @return a new map in which the requested entry has been removed; or this map
119
     * unchanged if the specified key was not present
120
     */
121
    @Override
122
    public MapItem remove(AtomicValue key) {
123
        return toHashTrieMap().remove(key);
124
    }
125

  
126
    /**
127
     * Ask whether the map conforms to a given map type
128
     *
129
     * @param keyType   the required keyType
130
     * @param valueType the required valueType
131
     * @param th        the type hierarchy cache for the configuration
132
     * @return true if the map conforms to the required type
133
     */
134
    @Override
135
    public boolean conforms(AtomicType keyType, SequenceType valueType, TypeHierarchy th) {
136
        if (isEmpty()) {
137
            return true;
138
        }
139
        if (!(keyType == BuiltInAtomicType.STRING || keyType == BuiltInAtomicType.ANY_ATOMIC)) {
140
            return false;
141
        }
142
        if (valueType.equals(SequenceType.ANY_SEQUENCE)) {
143
            return true;
144
        }
145
        for (GroundedValue<?> val : hashMap.values()) {
146
            try {
147
                if (!valueType.matches(val, th)) {
148
                    return false;
149
                }
150
            } catch (XPathException e) {
151
                throw new AssertionError(e); // cannot happen when value is grounded
152
            }
153
        }
154
        return true;
155
    }
156

  
157
    /**
158
     * Get the type of the map. This method is used largely for diagnostics, to report
159
     * the type of a map when it differs from the required type.
160
     *
161
     * @param th the type hierarchy cache
162
     * @return the type of this map
163
     */
164
    @Override
165
    public ItemType getItemType(TypeHierarchy th) {
166
        ItemType valueType = null;
167
        int valueCard = 0;
168
        // we need to test the entries individually
169
        AtomicIterator keyIter = keys();
170
        AtomicValue key;
171
        for (Map.Entry<String, GroundedValue<?>> entry : hashMap.entrySet()) {
172
            GroundedValue<?> val = entry.getValue();
173
            if (valueType == null) {
174
                valueType = SequenceTool.getItemType(val, th);
175
                valueCard = SequenceTool.getCardinality(val);
176
            } else {
177
                valueType = Type.getCommonSuperType(valueType, SequenceTool.getItemType(val, th), th);
178
                valueCard = Cardinality.union(valueCard, SequenceTool.getCardinality(val));
179
            }
180
        }
181
        if (valueType == null) {
182
            // empty map
183
            return MapType.EMPTY_MAP_TYPE;
184
        } else {
185
            return new MapType(BuiltInAtomicType.STRING, SequenceType.makeSequenceType(valueType, valueCard));
186
        }
187
    }
188

  
189
    /**
190
     * Get the lowest common item type of the keys in the map
191
     *
192
     * @return the most specific type to which all the keys belong. If the map is
193
     * empty, return UType.VOID
194
     */
195
    @Override
196
    public UType getKeyUType() {
197
        return hashMap.isEmpty() ? UType.VOID : UType.STRING;
198
    }
199

  
200
    /**
201
     * Convert to a HashTrieMap
202
     */
203

  
204
    private HashTrieMap toHashTrieMap() {
205
        //System.err.println("Dictionary rewrite!!!!");
206
        HashTrieMap target = new HashTrieMap();
207
        hashMap.forEach((k, v) -> target.initialPut(new StringValue(k), v));
208
        return target;
209
    }
210
}
211

  
latest9.9/hej/net/sf/saxon/ma/map/MapFunctionSet.java
460 460
            if (treatAsFinal && allStringKeys) {
461 461
                // Optimize for a map with string-valued keys that's unlikely to be modified
462 462
                SequenceIterator iter = arguments[0].iterate();
463
                Dictionary baseMap = new Dictionary();
463
                DictionaryMap baseMap = new DictionaryMap();
464 464
                MapItem next;
465 465
                switch (duplicates) {
466 466
                    // Code is structured (a) to avoid testing "duplicates" within the loop unnecessarily,
latest9.9/hej/net/sf/saxon/om/PrefixPool.java
34 34

  
35 35
    /**
36 36
     * Get the prefix code corresponding to a given prefix, allocating a new code if necessary
37
     * @param prefix
37
     * @param prefix the namespace prefix. If empty, the prefix code is always zero.
38 38
     * @return the integer prefix code (always fits in 10 bits)
39 39
     */
40 40

  
......
72 72
    }
73 73

  
74 74
    private void makeIndex() {
75
        index = new HashMap<String, Integer>(used);
75
        index = new HashMap<>(used);
76 76
        for (int i=0; i<used; i++) {
77 77
            index.put(prefixes[i], i);
78 78
        }
latest9.9/hej/net/sf/saxon/resource/MetadataResource.java
11 11
import net.sf.saxon.expr.XPathContext;
12 12
import net.sf.saxon.functions.CallableFunction;
13 13
import net.sf.saxon.lib.Resource;
14
import net.sf.saxon.ma.map.Dictionary;
14
import net.sf.saxon.ma.map.DictionaryMap;
15 15
import net.sf.saxon.om.GroundedValue;
16 16
import net.sf.saxon.om.Item;
17 17
import net.sf.saxon.type.FunctionItemType;
......
47 47
    public Item getItem(XPathContext context)  {
48 48

  
49 49
        // Create a map for the result
50
        Dictionary map = new Dictionary();
50
        DictionaryMap map = new DictionaryMap();
51 51

  
52 52
        // Add the custom properties of the resource
53 53
        for (Map.Entry<String, GroundedValue<?>> entry : properties.entrySet()) {
latest9.9/hej/net/sf/saxon/trace/Instrumentation.java
10 10

  
11 11
public class Instrumentation {
12 12

  
13
    public static final boolean ACTIVE = false;
13
    public static final boolean ACTIVE = true;  // KILROY
14 14

  
15 15
    public static HashMap<String, Integer> counters = new HashMap<>();
16 16

  
......
22 22
        }
23 23
    }
24 24

  
25
    public static void count(String counter, int increment) {
26
        if (counters.containsKey(counter)) {
27
            counters.put(counter, counters.get(counter) + increment);
28
        } else {
29
            counters.put(counter, increment);
30
        }
31
    }
32

  
25 33
    public static void report() {
26 34
        if (ACTIVE && !counters.isEmpty()) {
27 35
            System.err.println("COUNTERS");
latest9.9/hej/net/sf/saxon/tree/tiny/TinyBuilder.java
529 529

  
530 530
    /**
531 531
     * Copy an element node and its subtree from another TinyTree instance
532
     * @param source the TinyTree from which a subtree is to be copied
533
     * @param nodeNr the node number of the element node to be copied
532
     * @param sourceNode the element at the root of the subtree to be copied
534 533
     */
535 534

  
536
    public void bulkCopy(TinyTree source, int nodeNr) {
535
    public void bulkCopy(TinyElementImpl sourceNode, boolean copyNamespaces) {
536
        TinyTree sourceTree = sourceNode.tree;
537
        int oldNodeNr = sourceNode.nodeNr;
537 538
        int newNodeNr = tree.numberOfNodes;
538
        tree.bulkCopy(source, nodeNr, currentDepth);
539
        tree.bulkCopy(sourceTree, oldNodeNr, currentDepth);
539 540
        int prev = prevAtDepth[currentDepth];
540 541
        if (prev > 0) {
541 542
            tree.next[prev] = newNodeNr;
latest9.9/hej/net/sf/saxon/tree/tiny/TinyElementImpl.java
206 206
        return null;
207 207
    }
208 208

  
209
    private int subtreeSize() {
210
        int next = tree.next[nodeNr];
211
        while (next < nodeNr) {
212
            if (next < 0) {
213
                return tree.numberOfNodes - nodeNr;
214
            }
215
            next = tree.next[next];
216
        }
217
        return nodeNr - next;
218
    }
219

  
209 220
    /**
210 221
     * Copy this node to a given receiver
211 222
     *
......
217 228

  
218 229
        boolean copyTypes = CopyOptions.includes(copyOptions, CopyOptions.TYPE_ANNOTATIONS);
219 230

  
220
        // Fast path for copying to another TinyTree
221
//        if (TinyTree.useFastCopy) {
222
//            Receiver r1 = receiver;
223
//            if (isSkipValidator(r1)) {
224
//                copyTypes = false;
225
//                r1 = ((ProxyReceiver) r1).getNextReceiver();
226
//            }
227
//            if (r1 instanceof ComplexContentOutputter && !copyTypes) {
228
//                Receiver r2 = ((ComplexContentOutputter) r1).getReceiver();
229
//                if (r2 instanceof NamespaceReducer) {
230
//                    Receiver r3 = ((NamespaceReducer) r2).getNextReceiver();
231
//                    if (r3 instanceof TinyBuilder) {
232
//                        ((ComplexContentOutputter) r1).beforeBulkCopy();
233
//                        TinyBuilder target = (TinyBuilder) r3;
234
//                        target.bulkCopy(getTree(), nodeNr);
235
//                        ((ComplexContentOutputter) r1).afterBulkCopy();
236
//                        return;
237
//                    }
238
//                }
239
//            }
240
//        } else
241
        boolean grafted = tryGraft(copyOptions, receiver);
242
        if (grafted) {
231
        boolean fastCopied = tryBulkCopy(copyOptions, receiver);
232
        if (fastCopied) {
243 233
            return;
244 234
        }
245 235

  
236
//        boolean grafted = tryGraft(copyOptions, receiver);
237
//        if (grafted) {
238
//            Instrumentation.count("GRAFT");
239
//            Instrumentation.count("GRAFT NODES ", Count.count(iterateAxis(AxisInfo.DESCENDANT_OR_SELF, NodeKindTest.ELEMENT)));
240
//            return;
241
//        }
242
//        Instrumentation.count("NON-GRAFT");
243

  
244

  
246 245
        short level = -1;
247 246
        boolean closePending = false;
248 247
        short startLevel = tree.depth[nodeNr];
......
327 326
                    } else {
328 327
                        // there is an element to close
329 328
                        closePending = true;
330
                        
329

  
331 330
                        // output namespaces
332 331
                        if ((copyOptions & CopyOptions.SOME_NAMESPACES) != 0 && tree.usesNamespaces) {
333 332
                            String defaultNS = null;
......
466 465
    }
467 466

  
468 467
    private boolean tryGraft(int copyOptions, Receiver out) throws XPathException {
468
        if (subtreeSize() < 100) {
469
            return false;
470
        }
469 471
        if (TinyTree.useGraft &&
470 472
                (copyOptions & CopyOptions.FOR_UPDATE) == 0 &&
471 473
                (copyOptions & CopyOptions.ALL_NAMESPACES) != 0 &&
......
493 495
        return false;
494 496
    }
495 497

  
498
    private boolean tryBulkCopy(int copyOptions, Receiver out) throws XPathException {
499
        // Fast path for copying to another TinyTree
500
        if (TinyTree.useBulkCopy &&
501
                    (copyOptions & CopyOptions.FOR_UPDATE) == 0 &&
502
                    (copyOptions & CopyOptions.ALL_NAMESPACES) != 0 &&
503
                    // don't allow a subtree that already contains external nodes to be grafted to another tree
504
                    tree.externalNodes == null) {
505
            if (isSkipValidator(out)) {
506
                return false;
507
                // Can't currently use grafting copy with validation="strip" because of the
508
                // complications of ID and IDREF attributes (test case copy-5034)
509
                //r1 = ((ProxyReceiver) r1).getNextReceiver();
510
            }
511
            if (tree.isTyped()) {
512
                return false;
513
            }
514
            if (out instanceof SequenceWriter && ((SequenceWriter) out).isReadyForBulkCopy()) {
515
                ((SequenceWriter) out).bulkCopyElementNode(this, copyOptions);
516
                return true;
517
            } else if (out instanceof ComplexContentOutputter) {
518
                if (((ComplexContentOutputter) out).isReadyForBulkCopy()) {
519
                    ((ComplexContentOutputter) out).bulkCopyElementNode(this, copyOptions);
520
                    return true;
521
                }
522
            }
523
        }
524
        return false;
525
    }
526

  
496 527
    /**
497 528
     * Check whether the content of an element is namespace-sensitive
498 529
     *
latest9.9/hej/net/sf/saxon/tree/tiny/TinyTree.java
179 179
     * element nodes from one TinyTree to another. This code is currently disabled by default until
180 180
     * it has been more thoroughly tested.
181 181
     */
182
    public final static boolean useFastCopy = false;
183
    public static boolean useGraft = true;
182
    public final static boolean useBulkCopy = true;
183
    public static boolean useGraft = false; // KILROY
184 184

  
185 185
    /**
186 186
     * Create a tree with a specified initial size
......
1695 1695
            end = source.numberOfNodes - 1;
1696 1696
        }
1697 1697
        int length = end - nodeNr;
1698
        assert length > 0;
1698 1699
        ensureNodeCapacity(Type.ELEMENT, length);
1699 1700
        System.arraycopy(source.nodeKind, nodeNr, nodeKind, numberOfNodes, length);
1700 1701
        int depthDiff = currentDepth - source.depth[nodeNr];
......
1715 1716
                        }
1716 1717
                        int atts = lastAtt - firstAtt;
1717 1718
                        ensureAttributeCapacity(atts);
1718
                        int aFrom = source.alpha[from];
1719
                        int aFrom = firstAtt;
1719 1720
                        int aTo = numberOfAttributes;
1720 1721
                        alpha[to] = aTo;
1721 1722
                        System.arraycopy(source.attValue, firstAtt, attValue, aTo, atts);
......
1724 1725
                            int attNameCode = attCode[aTo] = source.attCode[aFrom];
1725 1726
                            if (NamePool.isPrefixed(attNameCode)) {
1726 1727
                                String prefix = source.prefixPool.getPrefix(attNameCode >> 20);
1727
                                attCode[aTo] |= prefixPool.obtainPrefixCode(prefix) << 20;
1728
                                attCode[aTo] = (attNameCode & 0xfffff) | (prefixPool.obtainPrefixCode(prefix) << 20);
1729
                            } else {
1730
                                attCode[aTo] = attNameCode;
1728 1731
                            }
1729 1732
                            if (source.isIdAttribute(aFrom)) {
1730 1733
                                registerID(getNode(to), source.attValue[aFrom].toString());
......
1740 1743
                    } else {
1741 1744
                        alpha[to] = -1;
1742 1745
                    }
1743
                    int firstNS = source.beta[from];
1744
                    if (firstNS >= 0) {
1745
                        int lastNS = firstNS;
1746
                        while (lastNS < source.numberOfNamespaces && source.namespaceParent[lastNS] == from) {
1747
                            lastNS++;
1746
                    // Copy the namespaces. For the top-level element node, this needs special
1747
                    // attention: we need to ensure that all the in-scope namespaces of the
1748
                    // source node are declared in the new tree. We may also need to undeclare
1749
                    // a default namespace that is present in the target tree, but unwanted in the
1750
                    // new element node.
1751
                    if (i==0) {
1752
                        List<NamespaceBinding> inScopeNamespaces = new ArrayList<>();
1753
                        int anc = from;
1754
                        boolean foundDefaultNamespace = false;
1755
                        while (anc >= 0 && source.nodeKind[anc] == Type.ELEMENT) {
1756
                            int ns = source.beta[anc];
1757
                            if (ns >= 0) {
1758
                                while (ns < source.numberOfNamespaces && source.namespaceParent[ns] == anc) {
1759
                                    NamespaceBinding sourceNs = source.namespaceBinding[ns];
1760
                                    String prefix = sourceNs.getPrefix();
1761
                                    if (prefix.isEmpty()) {
1762
                                        foundDefaultNamespace = true;
1763
                                    }
1764
                                    if (inScopeNamespaces.stream().noneMatch(
1765
                                            binding -> binding.getPrefix().equals(prefix))) {
1766
                                        inScopeNamespaces.add(sourceNs);
1767
                                    }
1768
                                    ns++;
1769
                                }
1770
                            }
1771
                            anc = TinyNodeImpl.getParentNodeNr(source, anc);
1772
                        }
1773
                        if (!foundDefaultNamespace) {
1774
                            // if there is no default namespace in force for the copied subtree,
1775
                            // but there is a default namespace for the target tree, then we need
1776
                            // to insert an xmlns="" undeclaration to ensure that the default namespace
1777
                            // does not leak into the subtree. (Arguably we should do this only if
1778
                            // the subtree contains elements that use the default namespace??)
1779
                            // TODO: search only ancestors of the insertion position
1780
                            boolean targetDeclaresDefaultNamespace = false;
1781
                            for (int n = 0; n < numberOfNamespaces; n++) {
1782
                                if (namespaceBinding[n].getPrefix().isEmpty()) {
1783
                                    targetDeclaresDefaultNamespace = true;
1784
                                }
1785
                            }
1786
                            if (targetDeclaresDefaultNamespace) {
1787
                                inScopeNamespaces.add(NamespaceBinding.DEFAULT_UNDECLARATION);
1788
                            }
1748 1789
                        }
1749
                        int nrOfNamespaces = lastNS - firstNS;
1750
                        ensureNamespaceCapacity(nrOfNamespaces);
1751 1790
                        int nTo = numberOfNamespaces;
1791
                        ensureNamespaceCapacity(inScopeNamespaces.size());
1752 1792
                        beta[to] = nTo;
1753
                        System.arraycopy(source.namespaceBinding, firstNS, namespaceBinding, nTo, nrOfNamespaces);
1754
                        Arrays.fill(namespaceParent, nTo, nTo + nrOfNamespaces, to);
1755
                        numberOfNamespaces += nrOfNamespaces;
1793
                        for (NamespaceBinding nb : inScopeNamespaces) {
1794
                            namespaceBinding[nTo++] = nb;
1795
                        }
1796
                        Arrays.fill(namespaceParent, numberOfNamespaces, nTo, to);
1797
                        numberOfNamespaces = nTo;
1798

  
1756 1799
                    } else {
1757
                        beta[to] = -1;
1800
                        int firstNS = source.beta[from];
1801
                        if (firstNS >= 0) {
1802
                            int lastNS = firstNS;
1803
                            while (lastNS < source.numberOfNamespaces && source.namespaceParent[lastNS] == from) {
1804
                                lastNS++;
1805
                            }
1806
                            int nrOfNamespaces = lastNS - firstNS;
1807
                            ensureNamespaceCapacity(nrOfNamespaces);
1808
                            int nTo = numberOfNamespaces;
1809
                            beta[to] = nTo;
1810
                            System.arraycopy(source.namespaceBinding, firstNS, namespaceBinding, nTo, nrOfNamespaces);
1811
                            Arrays.fill(namespaceParent, nTo, nTo + nrOfNamespaces, to);
1812
                            numberOfNamespaces += nrOfNamespaces;
1813
                        } else {
1814
                            beta[to] = -1;
1815
                        }
1758 1816
                    }
1759 1817
                    break;
1760 1818
                }
1819
                case Type.TEXTUAL_ELEMENT: {
1820
                    int start = source.alpha[from];
1821
                    int len = source.beta[from];
1822
                    nameCode[to] = (source.nameCode[from] & NamePool.FP_MASK) |
1823
                            (prefixPool.obtainPrefixCode(source.getPrefix(from)) << 20);
1824
                    alpha[to] = charBuffer.length();
1825
                    appendChars(source.charBuffer.subSequence(start, start + len));
1826
                    beta[to] = len;
1827
                    break;
1828
                }
1761 1829
                case Type.TEXT: {
1762 1830
                    int start = source.alpha[from];
1763 1831
                    int len = source.beta[from];

Also available in: Unified diff