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

oracle / opengrok / #3715

30 Nov 2023 08:55AM UTC coverage: 75.937% (+9.8%) from 66.106%
#3715

push

web-flow
Refactoring to reduce sonar code smell fixes (#4485)

---------

Signed-off-by: Gino Augustine <ginoaugustine@gmail.com>
Co-authored-by: Vladimir Kotal <vlada@kotalovi.cz>

397 of 478 new or added lines in 51 files covered. (83.05%)

50 existing lines in 22 files now uncovered.

44494 of 58593 relevant lines covered (75.94%)

0.76 hits per line

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

77.24
/suggester/src/main/java/org/opengrok/suggest/SuggesterProjectData.java
1
/*
2
 * CDDL HEADER START
3
 *
4
 * The contents of this file are subject to the terms of the
5
 * Common Development and Distribution License (the "License").
6
 * You may not use this file except in compliance with the License.
7
 *
8
 * See LICENSE.txt included in this distribution for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing Covered Code, include this CDDL HEADER in each
12
 * file and include the License file at LICENSE.txt.
13
 * If applicable, add the following below this CDDL HEADER, with the
14
 * fields enclosed by brackets "[]" replaced with your own identifying
15
 * information: Portions Copyright [yyyy] [name of copyright owner]
16
 *
17
 * CDDL HEADER END
18
 */
19

20
/*
21
 * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved.
22
 */
23
package org.opengrok.suggest;
24

25
import org.apache.commons.io.FileUtils;
26
import org.apache.lucene.index.DirectoryReader;
27
import org.apache.lucene.index.FieldInfos;
28
import org.apache.lucene.index.IndexCommit;
29
import org.apache.lucene.index.IndexReader;
30
import org.apache.lucene.index.Term;
31
import org.apache.lucene.search.spell.LuceneDictionary;
32
import org.apache.lucene.search.suggest.InputIterator;
33
import org.apache.lucene.search.suggest.Lookup;
34
import org.apache.lucene.search.suggest.fst.WFSTCompletionLookup;
35
import org.apache.lucene.store.Directory;
36
import org.apache.lucene.store.FSDirectory;
37
import org.apache.lucene.util.BytesRef;
38
import org.opengrok.suggest.popular.PopularityCounter;
39
import org.opengrok.suggest.popular.PopularityMap;
40
import org.opengrok.suggest.popular.impl.chronicle.ChronicleMapAdapter;
41
import org.opengrok.suggest.popular.impl.chronicle.ChronicleMapConfiguration;
42

43
import java.io.Closeable;
44
import java.io.File;
45
import java.io.FileInputStream;
46
import java.io.FileOutputStream;
47
import java.io.IOException;
48
import java.nio.charset.StandardCharsets;
49
import java.nio.file.Path;
50
import java.nio.file.Paths;
51
import java.util.Collection;
52
import java.util.Collections;
53
import java.util.HashMap;
54
import java.util.HashSet;
55
import java.util.List;
56
import java.util.Map;
57
import java.util.Map.Entry;
58
import java.util.Objects;
59
import java.util.Set;
60
import java.util.concurrent.locks.ReadWriteLock;
61
import java.util.concurrent.locks.ReentrantReadWriteLock;
62
import java.util.logging.Level;
63
import java.util.logging.Logger;
64

65
/**
66
 * Holds all the necessary data for one index directory. In the context of OpenGrok it is one project.
67
 */
68
class SuggesterProjectData implements Closeable {
69

70
    private static final String TMP_DIR_PROPERTY = "java.io.tmpdir";
71

72
    private static final Logger logger = Logger.getLogger(SuggesterProjectData.class.getName());
1✔
73

74
    private static final int MAX_TERM_SIZE = Short.MAX_VALUE - 3;
75

76
    private static final String WFST_TEMP_FILE_PREFIX = "opengrok_suggester_wfst";
77

78
    private static final String WFST_FILE_SUFFIX = ".wfst";
79

80
    private static final String SEARCH_COUNT_MAP_NAME = "search_count.db";
81

82
    private static final String VERSION_FILE_NAME = "version.txt";
83

84
    private static final int DEFAULT_WEIGHT = 0;
85

86
    private static final double AVERAGE_LENGTH_DEFAULT = 22;
87

88
    private final Directory indexDir;
89

90
    private final Path suggesterDir;
91

92
    private final Map<String, WFSTCompletionLookup> lookups = new HashMap<>();
1✔
93

94
    private final Map<String, PopularityMap> searchCountMaps = new HashMap<>();
1✔
95

96
    private final Map<String, Double> averageLengths = new HashMap<>();
1✔
97

98
    private final boolean allowMostPopular;
99

100
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
1✔
101

102
    private final Set<String> allowedFields;
103

104
    private Set<String> fields;
105

106
    private final Directory tempDir;
107

108
    SuggesterProjectData(
109
            final Directory indexDir,
110
            final Path suggesterDir,
111
            final boolean allowMostPopular,
112
            final Set<String> allowedFields
113
    ) throws IOException {
1✔
114
        this.indexDir = indexDir;
1✔
115
        this.suggesterDir = suggesterDir;
1✔
116
        this.allowMostPopular = allowMostPopular;
1✔
117
        this.allowedFields = allowedFields;
1✔
118

119
        tempDir = FSDirectory.open(Paths.get(System.getProperty(TMP_DIR_PROPERTY)));
1✔
120

121
        initFields();
1✔
122
    }
1✔
123

124
    private void initFields() throws IOException {
125
        try (IndexReader indexReader = DirectoryReader.open(indexDir)) {
1✔
126
            Collection<String> indexedFields = FieldInfos.getIndexedFields(indexReader);
1✔
127
            if (allowedFields == null) {
1✔
128
                this.fields = new HashSet<>(indexedFields);
×
129
            } else if (!indexedFields.containsAll(allowedFields)) {
1✔
130
                Set<String> copy = new HashSet<>(allowedFields);
1✔
131
                copy.removeAll(indexedFields);
1✔
132
                logger.log(Level.WARNING,
1✔
133
                        "Fields {0} will be ignored because they were not found in index directory {1}",
134
                        new Object[] {copy, indexDir});
135

136
                copy = new HashSet<>(allowedFields);
1✔
137
                copy.retainAll(indexedFields);
1✔
138
                this.fields = copy;
1✔
139
            } else {
1✔
140
                this.fields = new HashSet<>(allowedFields);
1✔
141
            }
142
        }
143
    }
1✔
144

145
    /**
146
     * Initializes the data structure. Rebuild is launched only if necessary.
147
     * @throws IOException if initialization was not successful
148
     */
149
    public void init() throws IOException {
150
        lock.writeLock().lock();
1✔
151
        try {
152
            long commitVersion = getCommitVersion();
1✔
153

154
            if (hasStoredData() && commitVersion == getDataVersion()) {
1✔
155
                loadStoredWFSTs();
×
156
            } else {
157
                createSuggesterDir();
1✔
158
                build();
1✔
159
            }
160

161
            if (allowMostPopular) {
1✔
162
                initSearchCountMap();
1✔
163
            }
164

165
            storeDataVersion(commitVersion);
1✔
166
        } finally {
167
            lock.writeLock().unlock();
1✔
168
        }
169
    }
1✔
170

171
    private long getCommitVersion() throws IOException {
172
        List<IndexCommit> commits = DirectoryReader.listCommits(indexDir);
1✔
173
        if (commits.size() > 1) {
1✔
174
            throw new IllegalStateException(String.format("IndexDeletionPolicy changed, normally only one commit "
×
175
                    + "should be stored. There are %d commits in '%s'", commits.size(), indexDir));
×
176
        }
177
        IndexCommit commit = commits.get(0);
1✔
178

179
        return commit.getGeneration();
1✔
180
    }
181

182
    private boolean hasStoredData() {
183
        if (!suggesterDir.toFile().exists()) {
1✔
184
            return false;
1✔
185
        }
186

187
        File[] children = suggesterDir.toFile().listFiles();
1✔
188
        return children != null && children.length > 0;
1✔
189
    }
190

191
    private void loadStoredWFSTs() throws IOException {
192
        try (IndexReader indexReader = DirectoryReader.open(indexDir)) {
×
193
            for (String field : fields) {
×
194

195
                var wfstFile = getWFSTFile(field);
×
196
                if (wfstFile.exists()) {
×
197
                    var wfst = loadStoredWFST(wfstFile);
×
198
                    lookups.put(field, wfst);
×
199
                } else {
×
200
                    logger.log(Level.INFO, "Missing WFST file for {0} field in {1}, creating a new one",
×
201
                            new Object[] {field, suggesterDir});
202

203
                    WFSTCompletionLookup lookup = build(indexReader, field);
×
204
                    store(lookup, field);
×
205

206
                    lookups.put(field, lookup);
×
207
                }
208
            }
×
209
        }
210
    }
×
211

212
    private WFSTCompletionLookup loadStoredWFST(final File file) throws IOException {
213
        try (FileInputStream fis = new FileInputStream(file)) {
×
214
            WFSTCompletionLookup lookup = createWFST();
×
215
            lookup.load(fis);
×
216
            return lookup;
×
217
        }
218
    }
219

220
    private WFSTCompletionLookup createWFST() {
221
        return new WFSTCompletionLookup(tempDir, WFST_TEMP_FILE_PREFIX);
1✔
222
    }
223

224
    private File getWFSTFile(final String field) {
225
        return getFile(field + WFST_FILE_SUFFIX);
1✔
226
    }
227

228
    private File getFile(final String fileName) {
229
        return suggesterDir.resolve(fileName).toFile();
1✔
230
    }
231

232
    /**
233
     * Forces the rebuild of the data structure.
234
     * @throws IOException if some error occurred
235
     */
236
    public void rebuild() throws IOException {
237
        lock.writeLock().lock();
1✔
238
        try {
239
            initFields();
1✔
240
            build();
1✔
241

242
            if (allowMostPopular) {
1✔
243
                initSearchCountMap();
1✔
244
            }
245

246
            storeDataVersion(getCommitVersion());
1✔
247
        } finally {
248
            lock.writeLock().unlock();
1✔
249
        }
250
    }
1✔
251

252
    private void build() throws IOException {
253
        try (IndexReader indexReader = DirectoryReader.open(indexDir)) {
1✔
254
            for (String field : fields) {
1✔
255
                WFSTCompletionLookup lookup = build(indexReader, field);
1✔
256
                store(lookup, field);
1✔
257

258
                lookups.put(field, lookup);
1✔
259
            }
1✔
260
        }
261
    }
1✔
262

263
    private WFSTCompletionLookup build(final IndexReader indexReader, final String field) throws IOException {
264
        WFSTInputIterator iterator = new WFSTInputIterator(
1✔
265
                new LuceneDictionary(indexReader, field).getEntryIterator(), indexReader, field, getSearchCounts(field));
1✔
266

267
        WFSTCompletionLookup lookup = createWFST();
1✔
268
        lookup.build(iterator);
1✔
269

270
        if (lookup.getCount() > 0) {
1✔
271
            double averageLength = (double) iterator.termLengthAccumulator / lookup.getCount();
1✔
272
            averageLengths.put(field, averageLength);
1✔
273
        }
274

275
        return lookup;
1✔
276
    }
277

278
    private void store(final WFSTCompletionLookup wfst, final String field) throws IOException {
279
        try (FileOutputStream fos = new FileOutputStream(getWFSTFile(field))) {
1✔
280
            wfst.store(fos);
1✔
281
        }
282
    }
1✔
283

284
    private void createSuggesterDir() throws IOException {
285
        if (!suggesterDir.toFile().exists()) {
1✔
286
            boolean directoryCreated = suggesterDir.toFile().mkdirs();
1✔
287
            if (!directoryCreated) {
1✔
288
                throw new IOException(String.format("Could not create suggester directory '%s'", suggesterDir));
×
289
            }
290
        }
291
    }
1✔
292

293
    @SuppressWarnings("{java:S2095,java:S1181}")
294
    private void initSearchCountMap() throws IOException {
295
        searchCountMaps.values().forEach(PopularityMap::close);
1✔
296
        searchCountMaps.clear();
1✔
297

298
        for (String field : fields) {
1✔
299
            int numEntries = (int) lookups.get(field).getCount();
1✔
300
            if (numEntries == 0) {
1✔
301
                logger.log(Level.FINE, () -> String.format("Skipping creation of ChronicleMap for field %s " +
×
302
                        "in directory '%s' due to zero number of entries", field, suggesterDir));
303
                continue;
×
304
            }
305

306
            ChronicleMapConfiguration conf = ChronicleMapConfiguration.load(suggesterDir, field);
1✔
307
            if (conf == null) { // it was not yet initialized
1✔
308
                conf = new ChronicleMapConfiguration(numEntries, getAverageLength(field));
1✔
309
                conf.save(suggesterDir, field);
1✔
310
            }
311

312
            File f = getChronicleMapFile(field);
1✔
313

314
            var chronicleMapAdapter = createChronicleMapAdapter(field, conf, f);
1✔
315
            if (Objects.isNull(chronicleMapAdapter)) {
1✔
UNCOV
316
                return;
×
317
            }
318
            if (getCommitVersion() != getDataVersion()) {
1✔
319
                removeOldTerms(chronicleMapAdapter, lookups.get(field));
1✔
320

321
                if (conf.getEntries() < lookups.get(field).getCount()) {
1✔
322
                    int newEntriesCount = (int) lookups.get(field).getCount();
1✔
323
                    double newKeyAvgLength = getAverageLength(field);
1✔
324

325
                    conf.setEntries(newEntriesCount);
1✔
326
                    conf.setAverageKeySize(newKeyAvgLength);
1✔
327
                    conf.save(suggesterDir, field);
1✔
328

329
                    chronicleMapAdapter.resize(newEntriesCount, newKeyAvgLength);
1✔
330
                }
331
            }
332
            searchCountMaps.put(field, chronicleMapAdapter);
1✔
333

334
        }
1✔
335
    }
1✔
336

337
    @SuppressWarnings("java:S1181")
338
    private ChronicleMapAdapter createChronicleMapAdapter(final String name,
339
                                                          final ChronicleMapConfiguration conf,
340
                                                          final File file) {
341
        ChronicleMapAdapter mapAdapter = null;
1✔
342
        try {
343
            mapAdapter = new ChronicleMapAdapter(name, conf.getAverageKeySize(), conf.getEntries(), file);
1✔
NEW
344
        } catch (IllegalArgumentException e) {
×
NEW
345
            logger.log(Level.SEVERE, String.format("Could not create ChronicleMap for field %s in directory " +
×
346
                            "'%s' due to invalid key size (%f) or number of entries: (%d):",
NEW
347
                    name, suggesterDir,  conf.getAverageKeySize(), conf.getEntries()), e);
×
NEW
348
        } catch (Throwable t) {
×
NEW
349
            logger.log(Level.SEVERE,
×
NEW
350
                    String.format("Could not create ChronicleMap for field %s in directory '%s'"
×
351
                            +  " , most popular completion disabled, if you are using "
352
                            + "JDK9+ make sure to specify: "
353
                            + "--add-exports java.base/jdk.internal.ref=ALL-UNNAMED "
354
                            + "--add-exports java.base/sun.nio.ch=ALL-UNNAMED "
355
                            + "--add-exports jdk.unsupported/sun.misc=ALL-UNNAMED "
356
                            + "--add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED "
357
                            + "--add-opens jdk.compiler/com.sun.tools.javac=ALL-UNNAMED "
358
                            + "--add-opens java.base/java.lang=ALL-UNNAMED "
359
                            + "--add-opens java.base/java.lang.reflect=ALL-UNNAMED "
360
                            + "--add-opens java.base/java.io=ALL-UNNAMED "
361
                            + "--add-opens java.base/java.util=ALL-UNNAMED", name, suggesterDir), t);
362
        }
1✔
363
        return mapAdapter;
1✔
364
    }
365

366
    private File getChronicleMapFile(final String field) {
367
        return suggesterDir.resolve(field + "_" + SEARCH_COUNT_MAP_NAME).toFile();
1✔
368
    }
369

370
    private double getAverageLength(final String field) {
371
        if (averageLengths.containsKey(field)) {
1✔
372
            return averageLengths.get(field);
1✔
373
        }
374
        logger.log(Level.FINE, "Could not determine average length for field {0}, using default one", field);
×
375
        return AVERAGE_LENGTH_DEFAULT;
×
376
    }
377

378
    private void removeOldTerms(final ChronicleMapAdapter adapter, final WFSTCompletionLookup lookup) {
379
        adapter.removeIf(key -> lookup.get(key.toString()) == null);
1✔
380
    }
1✔
381

382
    /**
383
     * Looks up the terms in the WFST data structure.
384
     * @param field term field
385
     * @param prefix prefix the returned terms must contain
386
     * @param resultSize number of terms to return
387
     * @return terms with highest score
388
     */
389
    public List<Lookup.LookupResult> lookup(final String field, final String prefix, final int resultSize) {
390
        lock.readLock().lock();
1✔
391
        try {
392
            WFSTCompletionLookup lookup = lookups.get(field);
1✔
393
            if (lookup == null) {
1✔
394
                logger.log(Level.WARNING, "No WFST for field {0} in {1}", new Object[] {field, suggesterDir});
1✔
395
                return Collections.emptyList();
1✔
396
            }
397
            return lookup.lookup(prefix, false, resultSize);
1✔
398
        } catch (IOException e) {
×
399
            logger.log(Level.WARNING, "Could not perform lookup in {0} for {1}:{2}",
×
400
                    new Object[] {suggesterDir, field, prefix});
401
        } finally {
402
            lock.readLock().unlock();
1✔
403
        }
404
        return Collections.emptyList();
×
405
    }
406

407
    /**
408
     * Removes all stored data structures.
409
     */
410
    public void remove() {
411
        lock.writeLock().lock();
1✔
412
        try {
413
            try {
414
                close();
1✔
415
            } catch (IOException e) {
×
416
                logger.log(Level.WARNING, "Could not close opened index directory ''{0}''", indexDir);
×
417
            }
1✔
418

419
            try {
420
                FileUtils.deleteDirectory(suggesterDir.toFile());
1✔
421
            } catch (IOException e) {
×
422
                logger.log(Level.WARNING, "Cannot remove suggester data in ''{0}''", suggesterDir);
×
423
            }
1✔
424
        } finally {
425
            lock.writeLock().unlock();
1✔
426
        }
427
    }
1✔
428

429
    /**
430
     * Increments search count for {@code term} by 1.
431
     * @param term term for which to increment search count
432
     */
433
    public void incrementSearchCount(final Term term) {
434
        incrementSearchCount(term, 1);
1✔
435
    }
1✔
436

437
    /**
438
     * Increments search count for {@code term} by {@code value}.
439
     * @param term term for which to increment search count
440
     * @param value value to increment by
441
     * @return false if update failed, otherwise true
442
     */
443
    public boolean incrementSearchCount(final Term term, final int value) {
444
        return incrementSearchCount(term, value, false);
1✔
445
    }
446

447
    boolean incrementSearchCount(final Term term, final int value, boolean waitForLock) {
448
        if (term == null) {
1✔
449
            throw new IllegalArgumentException("Cannot increment search count for null");
1✔
450
        }
451

452
        boolean ret = false;
1✔
453
        boolean gotLock;
454
        if (waitForLock) {
1✔
455
            lock.readLock().lock();
1✔
456
        } else {
457
            gotLock = lock.readLock().tryLock();
1✔
458
            if (!gotLock) {
1✔
459
                logger.log(Level.INFO, "Cannot increment search count for term {0} in ''{1}'', rebuild in progress",
×
460
                        new Object[]{term, suggesterDir});
461
                return false;
×
462
            }
463
        }
464

465
        try {
466
            WFSTCompletionLookup lookup = lookups.get(term.field());
1✔
467
            if (lookup == null || lookup.get(term.text()) == null) {
1✔
468
                logger.log(Level.FINE, "Cannot increment search count for unknown term {0} in ''{1}''",
×
469
                        new Object[]{term, suggesterDir});
470
                return false; // unknown term
×
471
            }
472

473
            PopularityMap map = searchCountMaps.get(term.field());
1✔
474
            if (map != null) {
1✔
475
                map.increment(term.bytes(), value);
1✔
476
                ret = true;
1✔
477
            }
478
        } finally {
479
            lock.readLock().unlock();
1✔
480
        }
481
        return ret;
1✔
482
    }
483

484
    /**
485
     * Returns search counts for term field. For the time the returned data structure is used this object needs to be
486
     * locked by {@link #tryLock()}.
487
     * @param field term field
488
     * @return search counts object
489
     */
490
    public PopularityCounter getSearchCounts(final String field) {
491
        if (!searchCountMaps.containsKey(field)) {
1✔
492
            return key -> 0;
1✔
493
        }
494

495
        return key -> searchCountMaps.get(field).get(key);
1✔
496
    }
497

498
    /**
499
     * Closes the open data structures.
500
     * @throws IOException if the index directory could not be closed
501
     */
502
    @Override
503
    public void close() throws IOException {
504
        lock.writeLock().lock();
1✔
505
        try {
506
            searchCountMaps.values().forEach(val -> {
1✔
507
                try {
508
                    val.close();
1✔
509
                } catch (Exception e) {
×
510
                    logger.log(Level.WARNING, "Could not properly close most popular completion data", e);
×
511
                }
1✔
512
            });
1✔
513
            indexDir.close();
1✔
514

515
            tempDir.close();
1✔
516
        } finally {
517
            lock.writeLock().unlock();
1✔
518
        }
519
    }
1✔
520

521
    private long getDataVersion() {
522
        File versionFile = getFile(VERSION_FILE_NAME);
1✔
523
        if (!versionFile.exists()) {
1✔
524
            return -1;
1✔
525
        }
526

527
        try {
528
            String str = FileUtils.readFileToString(versionFile, StandardCharsets.UTF_8.toString());
1✔
529
            return Long.parseLong(str);
1✔
530
        } catch (IOException e) {
×
531
            logger.log(Level.WARNING, "Could not read suggester data version", e);
×
532
        }
533
        return -1;
×
534
    }
535

536
    private void storeDataVersion(final long version) {
537
        try {
538
            FileUtils.writeStringToFile(getFile(VERSION_FILE_NAME), "" + version,
1✔
539
                    StandardCharsets.UTF_8.toString());
1✔
540
        } catch (IOException e) {
×
541
            logger.log(Level.WARNING, "Could not store version", e);
×
542
        }
1✔
543
    }
1✔
544

545
    /**
546
     * Tries to lock the inner data structures for reading, so far only for {@link #getSearchCounts(String)}.
547
     * @return {@code true} if lock was acquired, {@code false} otherwise
548
     */
549
    public boolean tryLock() {
550
        return lock.readLock().tryLock();
1✔
551
    }
552

553
    /**
554
     * Unlocks the inner data structures for reading.
555
     */
556
    public void unlock() {
557
        lock.readLock().unlock();
1✔
558
    }
1✔
559

560
    /**
561
     * Returns the searched terms sorted according to their popularity.
562
     * @param field field for which to return the data
563
     * @param page which page of data to retrieve
564
     * @param pageSize number of results to return
565
     * @return list of terms with their popularity
566
     */
567
    public List<Entry<BytesRef, Integer>> getSearchCountsSorted(final String field, int page, int pageSize) {
568
        lock.readLock().lock();
1✔
569
        try {
570
            PopularityMap map = searchCountMaps.get(field);
1✔
571
            if (map == null) {
1✔
572
                logger.log(Level.FINE, "No search count map initialized for field {0}", field);
×
573
                return Collections.emptyList();
×
574
            }
575

576
            return map.getPopularityData(page, pageSize);
1✔
577
        } finally {
578
            lock.readLock().unlock();
1✔
579
        }
580
    }
581

582
    @Override
583
    public String toString() {
584
        return "SuggesterProjectData{" +
×
585
                "indexDir=" + indexDir +
586
                ", suggesterDir=" + suggesterDir +
587
                ", allowMostPopular=" + allowMostPopular +
588
                '}';
589
    }
590

591
    /**
592
     * An {@link InputIterator} for WFST data structure with most popular completion support.
593
     */
594
    private static class WFSTInputIterator implements InputIterator {
595

596
        private final InputIterator wrapped;
597

598
        private final IndexReader indexReader;
599

600
        private final String field;
601

602
        private long termLengthAccumulator = 0;
1✔
603

604
        private final PopularityCounter searchCounts;
605

606
        WFSTInputIterator(
607
                final InputIterator wrapped,
608
                final IndexReader indexReader,
609
                final String field,
610
                final PopularityCounter searchCounts
611
        ) {
1✔
612
            this.wrapped = wrapped;
1✔
613
            this.indexReader = indexReader;
1✔
614
            this.field = field;
1✔
615
            this.searchCounts = searchCounts;
1✔
616
        }
1✔
617

618
        private BytesRef last;
619

620
        @Override
621
        public long weight() {
622
            if (last != null) {
1✔
623
                int add = searchCounts.get(last);
1✔
624

625
                return SuggesterUtils.computeScore(indexReader, field, last)
1✔
626
                        + (long) add * SuggesterSearcher.TERM_ALREADY_SEARCHED_MULTIPLIER;
627
            }
628

629
            return DEFAULT_WEIGHT;
×
630
        }
631

632
        @Override
633
        public BytesRef payload() {
634
            return wrapped.payload();
1✔
635
        }
636

637
        @Override
638
        public boolean hasPayloads() {
639
            return wrapped.hasPayloads();
1✔
640
        }
641

642
        @Override
643
        public Set<BytesRef> contexts() {
644
            return wrapped.contexts();
1✔
645
        }
646

647
        @Override
648
        public boolean hasContexts() {
649
            return wrapped.hasContexts();
1✔
650
        }
651

652
        @Override
653
        public BytesRef next() throws IOException {
654
            last = wrapped.next();
1✔
655

656
            // skip very large terms because of the buffer exception
657
            while (last != null && last.length > MAX_TERM_SIZE) {
1✔
658
                last = wrapped.next();
×
659
            }
660

661
            if (last != null) {
1✔
662
                termLengthAccumulator += last.length;
1✔
663
            }
664

665
            return last;
1✔
666
        }
667
    }
668
}
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