• 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

61.46
/extensions/indexes/lucene/src/main/java/org/exist/indexing/lucene/AnalyzerConfig.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
 * NOTE: Parts of this file contain code from 'The eXist-db Authors'.
25
 *       The original license header is included below.
26
 *
27
 * =====================================================================
28
 *
29
 * eXist-db Open Source Native XML Database
30
 * Copyright (C) 2001 The eXist-db Authors
31
 *
32
 * info@exist-db.org
33
 * http://www.exist-db.org
34
 *
35
 * This library is free software; you can redistribute it and/or
36
 * modify it under the terms of the GNU Lesser General Public
37
 * License as published by the Free Software Foundation; either
38
 * version 2.1 of the License, or (at your option) any later version.
39
 *
40
 * This library is distributed in the hope that it will be useful,
41
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
42
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
43
 * Lesser General Public License for more details.
44
 *
45
 * You should have received a copy of the GNU Lesser General Public
46
 * License along with this library; if not, write to the Free Software
47
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
48
 */
49
package org.exist.indexing.lucene;
50

51
import java.io.Reader;
52
import java.lang.invoke.*;
53
import java.lang.reflect.Field;
54
import java.util.ArrayList;
55
import java.util.HashSet;
56
import java.util.List;
57
import java.util.Map;
58
import java.util.Set;
59
import java.util.TreeMap;
60

61
import org.apache.logging.log4j.LogManager;
62
import org.apache.logging.log4j.Logger;
63

64
import org.apache.lucene.analysis.Analyzer;
65
import org.apache.lucene.analysis.standard.StandardAnalyzer;
66
import org.apache.lucene.analysis.util.CharArraySet;
67
import org.apache.lucene.util.Version;
68

69
import org.exist.collections.CollectionConfiguration;
70
import org.exist.util.DatabaseConfigurationException;
71

72
import org.w3c.dom.Element;
73
import org.w3c.dom.NamedNodeMap;
74
import org.w3c.dom.Node;
75
import org.w3c.dom.NodeList;
76

77
import static java.lang.invoke.MethodType.methodType;
78
import static org.exist.util.StringUtil.isNullOrEmptyOrWs;
79

80
public class AnalyzerConfig {
1✔
81

82
    /*
83

84
     Supported configurations
85

86
     <analyzer class="org.apache.lucene.analysis.standard.StandardAnalyzer"/>
87

88
     <analyzer id="ws" class="org.apache.lucene.analysis.core.WhitespaceAnalyzer"/>
89

90
     <analyzer id="stdstops" class="org.apache.lucene.analysis.standard.StandardAnalyzer">
91
     ..<param name="stopwords" type="java.io.File" value="/tmp/stop.txt"/>
92
     </analyzer>
93

94
     <analyzer id="stdstops" class="org.apache.lucene.analysis.standard.StandardAnalyzer">
95
     ..<param name="stopwords" type="java.util.Set">
96
     ....<value>the</value>
97
     ....<value>this</value>
98
     ....<value>and</value>
99
     ....<value>that</value>
100
     ..</param>
101
     </analyzer>
102

103
     <analyzer id="sbstops" class="org.apache.lucene.analysis.snowball.SnowballAnalyzer">
104
     ..<param name="name" value="English"/>
105
     ..<param name="stopwords" type="java.util.Set">
106
     ....<value>the</value>
107
     ....<value>this</value>
108
     ....<value>and</value>
109
     ....<value>that</value>
110
     ..</param>
111
     </analyzer>
112

113
     */
114
    private static final Logger LOG = LogManager.getLogger(AnalyzerConfig.class);
1✔
115
    private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
1✔
116

117
    private static final String ID_ATTRIBUTE = "id";
118

119
    private static final String NAME_ATTRIBUTE = "name";
120
    private static final String TYPE_ATTRIBUTE = "type";
121
    private static final String CLASS_ATTRIBUTE = "class";
122
    private static final String PARAM_VALUE_ENTRY = "value";
123
    private static final String PARAM_ELEMENT_NAME = "param";
124

125
    private Map<String, Analyzer> analyzers = new TreeMap<>();
1✔
126
    private Analyzer defaultAnalyzer = new StandardAnalyzer();
1✔
127

128
    public Analyzer getAnalyzerById(String id) {
129
        return analyzers.get(id);
1✔
130
    }
131

132
    public Analyzer getDefaultAnalyzer() {
133
        return defaultAnalyzer;
1✔
134
    }
135

136
    /**
137
     * Parse {@code <analyzer/>} element and register configured analyzer.
138
     *
139
     * @param config The analyzer element from .xconf file.
140
     *
141
     * @throws DatabaseConfigurationException Something unexpected happened.
142
     */
143
    public void addAnalyzer(Element config) throws DatabaseConfigurationException {
144

145
        // Configure lucene analuzer with configuration
146
        final Analyzer analyzer = configureAnalyzer(config);
1✔
147
        if (analyzer == null) {
1!
148
            return;
×
149
        }
150

151
        // Get (optional) id-attribute of analyzer
152
        final String id = config.getAttribute(ID_ATTRIBUTE).trim();
1✔
153

154
        // If no ID is provided, register as default analyzer
155
        // else register analyzer
156
        if (id.isEmpty()) {
1✔
157
            setDefaultAnalyzer(analyzer);
1✔
158
        } else {
159
            analyzers.put(id, analyzer);
1✔
160
        }
161
    }
1✔
162

163
    /**
164
     * Set default the analyzer.
165
     *
166
     * @param analyzer Lucene analyzer
167
     */
168
    public void setDefaultAnalyzer(Analyzer analyzer) {
169
        defaultAnalyzer = analyzer;
1✔
170
    }
1✔
171

172
    /**
173
     * Parse {@code <analyzer/>} element from xconf and initialize an analyzer with the
174
     * parameters.
175
     *
176
     * @param config The analyzer element
177
     * @return Initialized Analyzer object
178
     *
179
     * @throws DatabaseConfigurationException Something unexpected happened.
180
     */
181
    protected static Analyzer configureAnalyzer(Element config) throws DatabaseConfigurationException {
182

183
        // Get classname from attribute
184
        final String className = config.getAttribute(CLASS_ATTRIBUTE).trim();
1✔
185

186
        Analyzer newAnalyzer = null;
1✔
187

188
        if (className.isEmpty()) {
1!
189
            // No classname is defined.
190
            LOG.error("Missing class attribute or attribute is empty.");
×
191
            // DW: throw exception?
192

193
        } else {
194
            // Classname is defined.
195

196
            // Probe class
197
            final Class<?> untypedClazz;
198
            try {
199
                untypedClazz = Class.forName(className);
1✔
200

201
            } catch (ClassNotFoundException e) {
×
202
                LOG.error(String.format("Lucene index: analyzer class %s not found. (%s)", className, e.getMessage()));
×
203
                return null;
×
204
            }
1✔
205

206
            // CHeck if class is an Analyzer
207
            if (!Analyzer.class.isAssignableFrom(untypedClazz)) {
1!
208
                LOG.error(String.format("Lucene index: analyzer class has to be a subclass of %s", Analyzer.class.getName()));
×
209
                return null;
×
210
            }
211

212
            final Class<? extends Analyzer> clazz = (Class<? extends Analyzer>) untypedClazz;
1✔
213

214
            // Get list of parameters
215
            List<KeyTypedValue<?>> cParams;
216
            try {
217
                cParams = getAllConstructorParameters(config);
1✔
218

219
            } catch (ParameterException pe) {
×
220
                // Unable to parse parameters.
221
                LOG.error(String.format("Unable to get parameters for %s: %s", className, pe.getMessage()), pe);
×
222
                cParams = new ArrayList<>();
×
223
            }
1✔
224

225
            // Iterate over all parameters, convert data to two arrays
226
            // that can be used in the reflection code
227
            final Class<?> cParamClasses[] = new Class<?>[cParams.size()];
1✔
228
            final Object cParamValues[] = new Object[cParams.size()];
1✔
229
            for (int i = 0; i < cParams.size(); i++) {
1✔
230
                KeyTypedValue<?> ktv = cParams.get(i);
1✔
231
                cParamClasses[i] = ktv.valueClass();
1✔
232
                cParamValues[i] = ktv.value();
1✔
233
            }
234

235
            // Create new analyzer
236
            if (cParamClasses.length > 0 && cParamClasses[0] == Version.class) {
1!
237

238
                if (LOG.isDebugEnabled()) {
×
239
                    Version version = (Version) cParamValues[0];
×
240
                    LOG.debug(String.format("An explicit Version %s of lucene has been specified.", version.toString()));
×
241
                }
242

243
                // A lucene Version object has been provided, so it shall be used
244
                newAnalyzer = createInstance(clazz, cParamClasses, cParamValues, false);
×
245

246
            } else {
247
                // Either no parameters have been provided, or more than one parameter
248

249
                // Extend arrays with (default) Version object info, add to front.
250
                Class<?>[] vcParamClasses = addVersionToClasses(cParamClasses);
1✔
251
                Object[] vcParamValues = addVersionValueToValues(cParamValues);
1✔
252

253
                // Finally create Analyzer
254
                newAnalyzer = createInstance(clazz, vcParamClasses, vcParamValues, true);
1✔
255

256
                // Fallback scenario: a special (not standard type of) Analyzer has been specified without 
257
                // a 'Version' argument on purpose. For this (try) to create the Analyzer with 
258
                // the original parameters.
259
                if (newAnalyzer == null) {
1✔
260
                    newAnalyzer = createInstance(clazz, cParamClasses, cParamValues, false);
1✔
261
                }
262

263
            }
264

265
        }
266

267
        if (newAnalyzer == null) {
1!
268
            LOG.error(String.format("Unable to create analyzer '%s'", className));
×
269
        }
270

271
        return newAnalyzer;
1✔
272
    }
273

274
    /**
275
     * Create instance of the lucene analyzer with provided arguments
276
     *
277
     * @param clazz The analyzer class
278
     * @param vcParamClasses The parameter classes
279
     * @param vcParamValues The parameter values
280
     * @param warnOnError true if an error should be treated as a warning
281
     * @return The lucene analyzer
282
     */
283
    static <T extends Analyzer> T createInstance(final Class<T> clazz, final Class<?>[] vcParamClasses,
284
        final Object[] vcParamValues, final boolean warnOnError) {
285

286
        final String className = clazz.getName();
1✔
287

288
        MethodType constructorType = methodType(void.class);
1✔
289
        for (final Class<?> vcParamClazz : vcParamClasses) {
1✔
290
            constructorType = constructorType.appendParameterTypes(vcParamClazz);
1✔
291
        }
292

293
        try {
294
            final MethodHandle methodHandle = LOOKUP.findConstructor(clazz, constructorType);
1✔
295
            if (LOG.isDebugEnabled()) {
1!
296
                LOG.debug(String.format("Using analyzer %s", className));
×
297
            }
298
            return (T) methodHandle.invokeWithArguments(vcParamValues);
1✔
299
        } catch (final NoSuchMethodException e) {
1✔
300
            final String message = String.format("Could not find matching analyzer class constructor %s: %s", className, e.getMessage());
1✔
301
            if (warnOnError) {
1!
302
                LOG.warn("{}. Will retry...", message);
1✔
303
            } else {
304
                LOG.error(message, e);
×
305
            }
306
        } catch (final Throwable e) {
×
307
            if (e instanceof InterruptedException) {
×
308
                // NOTE: must set interrupted flag
309
                Thread.currentThread().interrupt();
×
310
            }
311

312
            final String message = String.format("Exception while instantiating analyzer class %s: %s", className, e.getMessage());
×
313
            if (warnOnError) {
×
314
                LOG.warn("{}. Will retry...", message);
×
315
            } else {
316
                LOG.error(message, e);
×
317
            }
318
        }
1✔
319
        return null;
1✔
320
    }
321

322
    /**
323
     * Extend list of values, add version-value to front
324
     */
325
    private static Object[] addVersionValueToValues(final Object[] cParamValues) {
326
        final Object vcParamValues[] = new Object[cParamValues.length + 1];
1✔
327
        vcParamValues[0] = LuceneIndex.LUCENE_VERSION_IN_USE;
1✔
328
        System.arraycopy(cParamValues, 0, vcParamValues, 1, cParamValues.length);
1✔
329
        return vcParamValues;
1✔
330
    }
331

332
    /**
333
     * Extend list of classes, add version-class to front
334
     */
335
    private static Class<?>[] addVersionToClasses(final Class<?>[] cParamClasses) {
336
        final Class<?> vcParamClasses[] = new Class<?>[cParamClasses.length + 1];
1✔
337
        vcParamClasses[0] = Version.class;
1✔
338
        System.arraycopy(cParamClasses, 0, vcParamClasses, 1, cParamClasses.length);
1✔
339
        return vcParamClasses;
1✔
340
    }
341

342
    /**
343
     * Retrieve parameter info from all <param/> elements.
344
     *
345
     * @param config The <analyzer/> element from the provided configuration
346
     * @return List of triples key-value-valueType
347
     * @throws org.exist.indexing.lucene.AnalyzerConfig.ParameterException
348
     */
349
    static List<KeyTypedValue<?>> getAllConstructorParameters(Element config) throws ParameterException {
350
        final List<KeyTypedValue<?>> parameters = new ArrayList<>();
1✔
351
        final NodeList params = config.getElementsByTagNameNS(CollectionConfiguration.NAMESPACE, PARAM_ELEMENT_NAME);
1✔
352

353
        // iterate over all <param/> elements
354
        for (int i = 0; i < params.getLength(); i++) {
1✔
355
            parameters.add(getConstructorParameter((Element) params.item(i)));
1✔
356
        }
357

358
        return parameters;
1✔
359
    }
360

361
    /**
362
     * Retrieve configuration information from one <param/> element. Type
363
     * information is used to construct actual data containing objects.
364
     *
365
     * @param param Element that represents <param/>
366
     * @return Triple key-value-value-type
367
     * @throws org.exist.indexing.lucene.AnalyzerConfig.ParameterException
368
     */
369
    static KeyTypedValue<?> getConstructorParameter(final Element param) throws ParameterException {
370

371
        // Get attributes
372
        final NamedNodeMap attrs = param.getAttributes();
1✔
373

374
        // Get name of parameter, NULL when no value is present
375
        Node namedItem = attrs.getNamedItem(NAME_ATTRIBUTE);
1✔
376
        final String name = (namedItem == null) ? null : namedItem.getNodeValue();
1!
377

378
        // Get value type information of parameter, NULL when not available
379
        namedItem = attrs.getNamedItem(TYPE_ATTRIBUTE);
1✔
380
        final String type = (namedItem == null) ? null : namedItem.getNodeValue();
1!
381

382
        // Get actual value from attribute, or NULL when not available.
383
        namedItem = attrs.getNamedItem(PARAM_VALUE_ENTRY);
1✔
384
        final String value = (namedItem == null) ? null : namedItem.getNodeValue();
1✔
385

386
        // Place holder return value
387
        KeyTypedValue<?> parameter = null;
1✔
388

389
        if (isNullOrEmptyOrWs(type) || "java.lang.String".equals(type)) {
1!
390
            // String or no type is provided, assume string.
391

392
            if (value == null) {
×
393
                throw new ParameterException("The 'value' attribute must exist and must contain String value.");
×
394
            }
395

396
            parameter = new KeyTypedValue<>(name, value, String.class);
×
397

398
        } else {
399
            switch (type) {
1!
400
                case "java.lang.reflect.Field":
401

402
                    if (value == null) {
×
403
                        throw new ParameterException("The 'value' attribute must exist and must contain a full classname.");
×
404
                    }
405

406
                    // Use reflection
407
                    // - retrieve classname from the value field
408
                    // - retrieve fieldname from the value field
409
                    final String clazzName = value.substring(0, value.lastIndexOf('.'));
×
410
                    final String fieldName = value.substring(value.lastIndexOf('.') + 1);
×
411
                    try {
412
                        // Retrieve value from Field
413
                        final Class<?> fieldClazz = Class.forName(clazzName);
×
414
                        final Field field = fieldClazz.getField(fieldName);
×
415
                        field.setAccessible(true);
×
416
                        final Object fValue = field.get(fieldClazz.newInstance());
×
417
                        parameter = new KeyTypedValue<>(name, fValue, Object.class);
×
418

419
                    } catch (final NoSuchFieldException | ClassNotFoundException | InstantiationException | IllegalAccessException nsfe) {
×
420
                        throw new ParameterException(nsfe.getMessage(), nsfe);
×
421
                    }
×
422
                    break;
423

424
                case "java.io.File": {
425

426
                    if (value == null) {
×
427
                        throw new ParameterException("The 'value' attribute must exist and must contain a file name.");
×
428
                    }
429

430
                    LOG.info(String.format("Type '%s' has been deprecated in recent Lucene versions, "
×
431
                            + "please use 'java.io.FileReader' (short 'file') instead.", type));
432

433
                    parameter = new KeyTypedValue<>(name, new java.io.File(value), java.io.File.class);
×
434
                    break;
×
435
                }
436

437
                case "java.io.FileReader":
438
                case "file": {
439

440
                    if (value == null) {
×
441
                        throw new ParameterException("The 'value' attribute must exist and must contain a file name.");
×
442
                    }
443

444
                    try {
445
                        // ToDo: check where to close reade to prevent resource leakage
446
                        Reader fileReader = new java.io.FileReader(value);
×
447
                        parameter = new KeyTypedValue<>(name, fileReader, Reader.class);
×
448

449
                    } catch (java.io.FileNotFoundException ex) {
×
450
                        LOG.error(String.format("File '%s' could not be found.", value), ex);
×
451
                    }
×
452
                    break;
×
453
                }
454

455
                case "java.util.Set": {
456

457
                    LOG.info(String.format("Type '%s' has been deprecated in recent Lucene versions, "
1✔
458
                            + "please use 'org.apache.lucene.analysis.util.CharArraySet' (short 'set') instead.", type));
459

460
                    final Set s = getConstructorParameterSetValues(param);
1✔
461
                    parameter = new KeyTypedValue<>(name, s, Set.class);
1✔
462
                    break;
1✔
463
                }
464

465
                case "java.lang.String[]": {
466
                    final String[] ary = getConstructorParameterStringArrayValues(param);
1✔
467
                    parameter = new KeyTypedValue<>(name, ary, String[].class);
1✔
468
                    break;
1✔
469
                }
470

471
                case "char[]": {
472
                    final char[] ary = getConstructorParameterCharArrayValues(param);
1✔
473
                    parameter = new KeyTypedValue<>(name, ary, char[].class);
1✔
474
                    break;
1✔
475
                }
476

477
                case "org.apache.lucene.analysis.util.CharArraySet":
478
                case "set": {
479
                    // This is mandatory to use iso a normal Set since Lucene 4
480
                    final CharArraySet s = getConstructorParameterCharArraySetValues(param);
1✔
481
                    parameter = new KeyTypedValue<>(name, s, CharArraySet.class);
1✔
482
                    break;
1✔
483
                }
484

485
                case "java.lang.Integer":
486
                    if (value == null) {
1!
487
                        throw new ParameterException("The 'value' attribute must exist and must contain an integer value.");
×
488
                    }
489
                    try {
490
                        final Integer n = Integer.parseInt(value);
1✔
491
                        parameter = new KeyTypedValue<>(name, n, Integer.class);
1✔
492
                    } catch (final NumberFormatException ex) {
×
493
                        LOG.error(String.format("Value %s could not be converted to an integer. %s", value, ex.getMessage()));
×
494
                    }
1✔
495
                    break;
×
496

497
                case "int":
498
                    if (value == null) {
1!
499
                        throw new ParameterException("The 'value' attribute must exist and must contain an int value.");
×
500
                    }
501
                    try {
502
                        final Integer n = Integer.parseInt(value);
1✔
503
                        parameter = new KeyTypedValue<>(name, n, int.class);
1✔
504
                    } catch (final NumberFormatException ex) {
×
505
                        LOG.error(String.format("Value %s could not be converted to an int. %s", value, ex.getMessage()));
×
506
                    }
1✔
507
                    break;
×
508

509
                case "java.lang.Boolean":
510
                    if (value == null) {
1!
511
                        throw new ParameterException("The 'value' attribute must exist and must contain a Boolean value.");
×
512
                    }
513
                    final Boolean b1 = Boolean.parseBoolean(value);
1✔
514
                    parameter = new KeyTypedValue<>(name, b1, Boolean.class);
1✔
515
                    break;
1✔
516

517
                case "boolean":
518
                    if (value == null) {
1!
519
                        throw new ParameterException("The 'value' attribute must exist and must contain a boolean value.");
×
520
                    }
521
                    final Boolean b2 = Boolean.parseBoolean(value);
1✔
522
                    parameter = new KeyTypedValue<>(name, b2, boolean.class);
1✔
523
                    break;
1✔
524

525
                default:
526
                    // FallBack there was no match
527

528
                    if (value == null) {
×
529
                        throw new ParameterException("The 'value' attribute must exist and must contain a value.");
×
530
                    }
531

532
                    try {
533
                        //if the type is an Enum then use valueOf()
534
                        final Class clazz = Class.forName(type);
×
535
                        if (clazz.isEnum()) {
×
536
                            parameter = new KeyTypedValue<>(name, Enum.valueOf(clazz, value), clazz);
×
537
                        } else {
538
                            //default, assume java.lang.String
539
                            parameter = new KeyTypedValue<>(name, value, String.class);
×
540
                        }
541

542
                    } catch (ClassNotFoundException cnfe) {
×
543
                        throw new ParameterException(String.format("Class for type: %s not found. %s", type, cnfe.getMessage()), cnfe);
×
544
                    }
×
545
                    break;
546
            }
547
        }
548

549
        return parameter;
1✔
550
    }
551

552
    /**
553
     * Get parameter configuration data as standard Java (Hash)Set.
554
     *
555
     * @param param The parameter-configuration element.
556
     * @return Set of parameter values
557
     */
558
    private static Set<String> getConstructorParameterSetValues(Element param) {
559
        final Set<String> set = new HashSet<>();
1✔
560
        final NodeList values = param.getElementsByTagNameNS(CollectionConfiguration.NAMESPACE, PARAM_VALUE_ENTRY);
1✔
561
        for (int i = 0; i < values.getLength(); i++) {
1✔
562
            final Element value = (Element) values.item(i);
1✔
563
            set.add(value.getTextContent());
1✔
564
        }
565

566
        return set;
1✔
567
    }
568

569
    /**
570
     * Get parameter configuration data as a String[].
571
     *
572
     * @param param The parameter-configuration element.
573
     * @return Parameter data as String[]
574
     */
575
    private static String[] getConstructorParameterStringArrayValues(final Element param) throws ParameterException {
576
        final NodeList values = param.getElementsByTagNameNS(CollectionConfiguration.NAMESPACE, PARAM_VALUE_ENTRY);
1✔
577
        final String[] ary = new String[values.getLength()];
1✔
578
        for (int i = 0; i < values.getLength(); i++) {
1✔
579
            final Element value = (Element) values.item(i);
1✔
580
            ary[i] = value.getTextContent();
1✔
581
        }
582
        return ary;
1✔
583
    }
584

585
    /**
586
     * Get parameter configuration data as a char[].
587
     *
588
     * @param param The parameter-configuration element.
589
     * @return Parameter data as char[]
590
     */
591
    private static char[] getConstructorParameterCharArrayValues(final Element param) throws ParameterException {
592
        final NodeList values = param.getElementsByTagNameNS(CollectionConfiguration.NAMESPACE, PARAM_VALUE_ENTRY);
1✔
593
        final char[] ary = new char[values.getLength()];
1✔
594
        for (int i = 0; i < values.getLength(); i++) {
1✔
595
            final Element value = (Element) values.item(i);
1✔
596
            final String s = value.getTextContent();
1✔
597
            if (s == null || s.isEmpty()) {
1!
598
                throw new ParameterException("The 'value[" + (i + 1) + "]' must be a single character.");
1✔
599
            }
600
            ary[i] = s.charAt(0);
1✔
601
        }
602
        return ary;
1✔
603
    }
604

605
    /**
606
     * Get parameter configuration data as a Lucene CharArraySet.
607
     *
608
     * @param param The parameter-configuration element.
609
     * @return Parameter data as Lucene CharArraySet
610
     */
611
    private static CharArraySet getConstructorParameterCharArraySetValues(Element param) {
612
        final Set<String> set = getConstructorParameterSetValues(param);
1✔
613
        return CharArraySet.copy(LuceneIndex.LUCENE_VERSION_IN_USE, set);
1✔
614
    }
615

616
    /**
617
         * CLass for containing the Triple : key (name), corresponding value and
618
         * class type of value.
619
         */
620
        record KeyTypedValue<T>(String key, T value, Class<T> valueClass) {
1✔
621

622
    }
623

624
    /**
625
     * Exception class to for reporting problems with the parameters.
626
     */
627
    static class ParameterException extends Exception {
628

629
        private static final long serialVersionUID = -4823392401966008877L;
630

631
        public ParameterException(String message) {
632
            super(message);
1✔
633
        }
1✔
634

635
        public ParameterException(String message, Throwable cause) {
636
            super(message, cause);
×
637
        }
×
638
    }
639
}
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

© 2026 Coveralls, Inc