• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

evolvedbinary / elemental / 982

29 Apr 2025 08:34PM UTC coverage: 56.409% (+0.007%) from 56.402%
982

push

circleci

adamretter
[feature] Improve README.md badges

28451 of 55847 branches covered (50.94%)

Branch coverage included in aggregate %.

77468 of 131924 relevant lines covered (58.72%)

0.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

53.13
/exist-core/src/main/java/org/exist/xquery/functions/map/MapType.java
1
/*
2
 * Elemental
3
 * Copyright (C) 2024, Evolved Binary Ltd
4
 *
5
 * admin@evolvedbinary.com
6
 * https://www.evolvedbinary.com | https://www.elemental.xyz
7
 *
8
 * Use of this software is governed by the Business Source License 1.1
9
 * included in the LICENSE file and at www.mariadb.com/bsl11.
10
 *
11
 * Change Date: 2028-04-27
12
 *
13
 * On the date above, in accordance with the Business Source License, use
14
 * of this software will be governed by the Apache License, Version 2.0.
15
 *
16
 * Additional Use Grant: Production use of the Licensed Work for a permitted
17
 * purpose. A Permitted Purpose is any purpose other than a Competing Use.
18
 * A Competing Use means making the Software available to others in a commercial
19
 * product or service that: substitutes for the Software; substitutes for any
20
 * other product or service we offer using the Software that exists as of the
21
 * date we make the Software available; or offers the same or substantially
22
 * similar functionality as the Software.
23
 */
24
package org.exist.xquery.functions.map;
25

26
import com.evolvedbinary.j8fu.tuple.Tuple2;
27
import com.ibm.icu.text.Collator;
28
import io.lacuna.bifurcan.IEntry;
29
import io.lacuna.bifurcan.IMap;
30
import io.lacuna.bifurcan.LinearMap;
31
import io.lacuna.bifurcan.Map;
32
import org.exist.xquery.*;
33
import org.exist.xquery.value.*;
34

35
import javax.annotation.Nullable;
36
import java.util.Iterator;
37
import java.util.Optional;
38
import java.util.function.BinaryOperator;
39
import java.util.function.ToLongFunction;
40

41
/**
42
 * Full implementation of the XDM map() type based on an
43
 * immutable hash-map.
44
 *
45
 * @author <a href="mailto:adam@evolvedbinary.com">Adam Retter</a>
46
 */
47
public class MapType extends AbstractMapType {
48

49
    private static final ToLongFunction<AtomicValue> KEY_HASH_FN = AtomicValue::hashCode;
1✔
50

51
    // TODO(AR) future potential optimisation... could the class member `map` remain `linear` ?
52
    private IMap<AtomicValue, Sequence> map;
53

54
    /**
55
     * The type of the keys in the map,
56
     * if not all keys have the same type
57
     * then this is set to {@link #MIXED_KEY_TYPES}.
58
     *
59
     * Uses integer values from {@link org.exist.xquery.value.Type}.
60
     */
61
    private int keyType = UNKNOWN_KEY_TYPE;
1✔
62

63
    private static IMap<AtomicValue, Sequence> newMap(@Nullable final Collator collator) {
64
        return new Map<>(KEY_HASH_FN, (k1, k2) -> sameKey(collator, k1, k2));
1✔
65
    }
66

67
    /**
68
     * Construct a new Bifurcan mutable-map for use with AtomicValue keys.
69
     * 
70
     * This function is predominantly for pre-building a Map of key/values
71
     * for passing to {@link #MapType(XQueryContext, IMap, Integer)}.
72
     *
73
     * @param <V> the value type of the linear map
74
     * @param collator The collator if a collation is in effect for comparing keys.
75
     * 
76
     * @return A mutable-map on which {@link IMap#forked()} can be called to produce an immutable map. 
77
     */
78
    public static <V> IMap<AtomicValue, V> newLinearMap(@Nullable final Collator collator) {
79
        return new LinearMap<>(KEY_HASH_FN, (k1, k2) -> sameKey(collator, k1, k2));
1✔
80
    }
81

82
    public MapType(final XQueryContext context) {
83
        this(null, context);
1✔
84
    }
1✔
85

86
    public MapType(final Expression expression, final XQueryContext context) {
87
        this(expression, context,null);
1✔
88
    }
1✔
89

90
    public MapType(final XQueryContext context, @Nullable final Collator collator) {
91
        this(null, context, collator);
×
92
    }
×
93

94
    public MapType(final Expression expression, final XQueryContext context, @Nullable final Collator collator) {
95
        super(expression, context);
1✔
96
        // if there's no collation, we'll use a hash map for better performance
97
        this.map = newMap(collator);
1✔
98
    }
1✔
99

100
    public MapType(final XQueryContext context, @Nullable final Collator collator, final AtomicValue key, final Sequence value) {
101
        this(null, context, collator, key, value);
×
102
    }
×
103

104
    public MapType(final Expression expression, final XQueryContext context, @Nullable final Collator collator, final AtomicValue key, final Sequence value) {
105
        super(expression, context);
1✔
106
        this.map = newMap(collator).put(key, value);
1✔
107
        this.keyType = key.getType();
1✔
108
    }
1✔
109

110
    public MapType(final XQueryContext context, @Nullable final Collator collator, final Iterable<Tuple2<AtomicValue, Sequence>> keyValues) {
111
        this(null, context, collator, keyValues);
×
112
    }
×
113

114
    public MapType(final Expression expression, final XQueryContext context, @Nullable final Collator collator, final Iterable<Tuple2<AtomicValue, Sequence>> keyValues) {
115
        this(expression, context, collator, keyValues.iterator());
×
116
    }
×
117

118
    public MapType(final XQueryContext context, @Nullable final Collator collator, final Iterator<Tuple2<AtomicValue, Sequence>> keyValues) {
119
        this(null, context, collator, keyValues);
×
120
    }
×
121

122
    public MapType(final Expression expression, final XQueryContext context, @Nullable final Collator collator, final Iterator<Tuple2<AtomicValue, Sequence>> keyValues) {
123
        super(expression, context);
×
124

125
        // bulk put
126
        final IMap<AtomicValue, Sequence> map = newMap(collator).linear();
×
127
        keyValues.forEachRemaining(kv -> map.put(kv._1, kv._2));
×
128
        this.map = map.forked();
×
129

130
        setKeyType(map);
×
131
    }
×
132

133
    public MapType(final XQueryContext context, final IMap<AtomicValue, Sequence> other, @Nullable final Integer keyType) {
134
        this(null, context, other, keyType);
1✔
135
    }
1✔
136

137
    public MapType(final Expression expression, final XQueryContext context, final IMap<AtomicValue, Sequence> other, @Nullable final Integer keyType) {
138
        super(expression, context);
1✔
139

140
        if (other.isLinear()) {
1!
141
            throw new IllegalArgumentException("Map must be immutable, but linear Map was provided");
×
142
        }
143

144
        this.map = other;
1✔
145

146
        if (keyType != null) {
1!
147
            this.keyType = keyType;
1✔
148
        } else {
1✔
149
            setKeyType(map);
×
150
        }
151
    }
1✔
152

153
    public void add(final AbstractMapType other) {
154
        setKeyType(other.key() != null ? other.key().getType() : UNKNOWN_KEY_TYPE);
×
155

156
        if(other instanceof MapType) {
×
157
            map = map.union(((MapType)other).map);
×
158
        } else {
×
159

160
            // create a transient map
161
            final IMap<AtomicValue, Sequence> newMap = map.linear();
×
162

163
            for (final IEntry<AtomicValue, Sequence> entry : other) {
×
164
                newMap.put(entry.key(), entry.value());
×
165
            }
166

167
            // return to immutable map
168
            map = newMap.forked();
×
169
        }
170
    }
×
171

172
    @Override
173
    public AbstractMapType merge(final Iterable<AbstractMapType> others) {
174

175
        // create a transient map
176
        IMap<AtomicValue, Sequence> newMap = map.linear();
1✔
177

178
        int prevType = keyType;
1✔
179
        for (final AbstractMapType other: others) {
1✔
180
            if (other instanceof MapType otherMap) {
1✔
181
                // MapType - optimise merge
182
                newMap = newMap.union(otherMap.map);
1✔
183

184
                if (prevType != otherMap.keyType) {
1✔
185
                    prevType = MIXED_KEY_TYPES;
1✔
186
                }
187
            } else {
1✔
188
                // non MapType
189
                for (final IEntry<AtomicValue, Sequence> entry : other) {
1✔
190
                    final AtomicValue key = entry.key();
1✔
191
                    newMap = newMap.put(key, entry.value());
1✔
192
                    if (prevType != key.getType()) {
1!
193
                        prevType = MIXED_KEY_TYPES;
×
194
                    }
195
                }
196
            }
197
        }
198

199
        // return an immutable map
200
        return new MapType(getExpression(), context, newMap.forked(), prevType);
1✔
201
    }
202

203
    @Override
204
    public AbstractMapType merge(final Iterable<AbstractMapType> others, final BinaryOperator<Sequence> mergeFn) {
205

206
        // create a transient map
207
        IMap<AtomicValue, Sequence> newMap = map.linear();
1✔
208

209
        int prevType = keyType;
1✔
210
        for (final AbstractMapType other: others) {
1✔
211
            if (other instanceof MapType otherMap) {
1!
212
                // MapType - optimise merge
213
                newMap = newMap.merge(otherMap.map, mergeFn);
1✔
214

215
                if (prevType != otherMap.keyType) {
1!
216
                    prevType = MIXED_KEY_TYPES;
×
217
                }
218
            } else {
×
219
                // non MapType
220
                for (final IEntry<AtomicValue, Sequence> entry : other) {
×
221
                    final AtomicValue key = entry.key();
×
222
                    final Optional<Sequence> headEntry = newMap.get(key);
×
223
                    if (headEntry.isPresent()) {
×
224
                        newMap = newMap.put(key, mergeFn.apply(headEntry.get(), entry.value()));
×
225
                    } else {
×
226
                        newMap = newMap.put(key, entry.value());
×
227
                    }
228
                    if (prevType != key.getType()) {
×
229
                        prevType = MIXED_KEY_TYPES;
×
230
                    }
231
                }
232
            }
233
        }
234

235
        // return an immutable map
236
        return new MapType(context, newMap.forked(), prevType);
1✔
237
    }
238

239
    public void add(final AtomicValue key, final Sequence value) {
240
        setKeyType(key.getType());
1✔
241
        map = map.put(key, value);
1✔
242
    }
1✔
243

244
    @Override
245
    public Sequence get(AtomicValue key) {
246
        key = convert(key);
1✔
247
        if (key == null) {
1✔
248
            return Sequence.EMPTY_SEQUENCE;
1✔
249
        }
250

251
        final Sequence result = map.get(key, null);
1✔
252
        return result == null ? Sequence.EMPTY_SEQUENCE : result;
1✔
253
    }
254

255
    @Override
256
    public AbstractMapType put(final AtomicValue key, final Sequence value) {
257
        final IMap<AtomicValue, Sequence> newMap = map.put(key, value);
1✔
258
        return new MapType(getExpression(), this.context, newMap, keyType == key.getType() ? keyType : MIXED_KEY_TYPES);
1✔
259
    }
260

261
    @Override
262
    public boolean contains(AtomicValue key) {
263
        key = convert(key);
1✔
264
        if (key == null) {
1!
265
            return false;
×
266
        }
267

268
        return map.contains(key);
1✔
269
    }
270

271
    @Override
272
    public boolean containsReference(final Item item) {
273
        for (final Iterator<Sequence> it = map.values().iterator(); it.hasNext();) {
1!
274
            final Sequence value = it.next();
1✔
275
            if (value == item || value.containsReference(item)) {
1!
276
                return true;
1✔
277
            }
278
        }
279
        return false;
×
280
    }
281

282
    @Override
283
    public boolean contains(final Item item) {
284
        for (final Iterator<Sequence> it = map.values().iterator(); it.hasNext();) {
×
285
            final Sequence value = it.next();
×
286
            if (value.equals(item) || value.contains(item)) {
×
287
                return true;
×
288
            }
289
        }
290
        return false;
×
291
    }
292

293
    @Override
294
    public Sequence keys() {
295
        final ArrayListValueSequence seq = new ArrayListValueSequence((int)map.size());
1✔
296
        for (final AtomicValue key: map.keys()) {
1✔
297
            seq.add(key);
1✔
298
        }
299
        return seq;
1✔
300
    }
301

302
    public AbstractMapType remove(final AtomicValue[] keysAtomicValues) {
303

304
        // create a transient map
305
        IMap<AtomicValue, Sequence> newMap = map.linear();
1✔
306

307
        for (final AtomicValue key: keysAtomicValues) {
1✔
308
            newMap = newMap.remove(key);
1✔
309
        }
310

311
        // return an immutable map
312
        return new MapType(getExpression(), context, newMap.forked(), keyType);
1✔
313
    }
314

315
    @Override
316
    public int size() {
317
        return (int)map.size();
1✔
318
    }
319

320
    @Override
321
    public Iterator<IEntry<AtomicValue, Sequence>> iterator() {
322
        return map.iterator();
1✔
323
    }
324

325
    @Override
326
    public AtomicValue key() {
327
        if (map.size() > 0) {
×
328
            final IEntry<AtomicValue, Sequence> entry = map.nth(0);
×
329
            if (entry != null) {
×
330
                return entry.key();
×
331
            }
332
        }
333

334
        return null;
×
335
    }
336

337
    @Override
338
    public Sequence value() {
339
        if (map.size() > 0) {
×
340
            final IEntry<AtomicValue, Sequence> entry = map.nth(0);
×
341
            if (entry != null) {
×
342
                return entry.value();
×
343
            }
344
        }
345

346
        return null;
×
347
    }
348

349
    private void setKeyType(final int newType) {
350
        if (keyType == UNKNOWN_KEY_TYPE) {
1✔
351
            keyType = newType;
1✔
352

353
        } else if (keyType != newType) {
1✔
354
            keyType = MIXED_KEY_TYPES;
1✔
355
        }
356
    }
1✔
357

358
    private void setKeyType(final IMap<AtomicValue, Sequence> newMap) {
359
        for (final AtomicValue newKey : newMap.keys()) {
×
360
            final int newType = newKey.getType();
×
361

362
            if (keyType == UNKNOWN_KEY_TYPE) {
×
363
                keyType = newType;
×
364

365
            } else if (keyType != newType) {
×
366
                keyType = MIXED_KEY_TYPES;
×
367
                break; // done, we only have to detect this once!
×
368
            }
369
        }
370
    }
×
371

372
    private AtomicValue convert(final AtomicValue key) {
373
        if (keyType != UNKNOWN_KEY_TYPE && keyType != MIXED_KEY_TYPES) {
1✔
374
            try {
375
                return key.convertTo(keyType);
1✔
376
            } catch (final XPathException e) {
1✔
377
                return null;
1✔
378
            }
379
        }
380
        return key;
1✔
381
    }
382

383
    @Override
384
    public int getKeyType() {
385
        return keyType;
×
386
    }
387
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc