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

oracle / opengrok / #3670

01 Nov 2023 10:10AM UTC coverage: 74.437% (-0.7%) from 75.16%
#3670

push

web-flow
Fix Sonar codesmell issues (#4460)

Signed-off-by: Gino Augustine <ginoaugustine@gmail.com>

308 of 308 new or added lines in 27 files covered. (100.0%)

43623 of 58604 relevant lines covered (74.44%)

0.74 hits per line

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

73.68
/opengrok-web/src/main/java/org/opengrok/web/api/v1/suggester/query/SuggesterQueryParser.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, 2021, Oracle and/or its affiliates. All rights reserved.
22
 */
23
package org.opengrok.web.api.v1.suggester.query;
24

25
import org.apache.lucene.analysis.Analyzer;
26
import org.apache.lucene.analysis.TokenStream;
27
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
28
import org.apache.lucene.index.Term;
29
import org.apache.lucene.queryparser.classic.ParseException;
30
import org.apache.lucene.search.BooleanClause;
31
import org.apache.lucene.search.BooleanQuery;
32
import org.apache.lucene.search.BoostQuery;
33
import org.apache.lucene.search.FuzzyQuery;
34
import org.apache.lucene.search.MatchAllDocsQuery;
35
import org.apache.lucene.search.Query;
36
import org.apache.lucene.util.BytesRef;
37
import org.opengrok.suggest.query.SuggesterFuzzyQuery;
38
import org.opengrok.suggest.query.SuggesterPhraseQuery;
39
import org.opengrok.suggest.query.SuggesterPrefixQuery;
40
import org.opengrok.suggest.query.SuggesterQuery;
41
import org.opengrok.suggest.query.SuggesterRangeQuery;
42
import org.opengrok.suggest.query.SuggesterRegexpQuery;
43
import org.opengrok.suggest.query.SuggesterWildcardQuery;
44
import org.opengrok.indexer.search.CustomQueryParser;
45

46
import java.io.IOException;
47
import java.util.HashSet;
48
import java.util.LinkedList;
49
import java.util.List;
50
import java.util.Set;
51
import java.util.logging.Level;
52
import java.util.logging.Logger;
53
import java.util.regex.Pattern;
54
import java.util.stream.Collectors;
55

56
import static org.apache.lucene.search.BoostAttribute.DEFAULT_BOOST;
57

58
/**
59
 * Used for parsing the text of a query for which suggestions should be retrieved. Decouples the query into 2 parts:
60
 * {@link SuggesterQuery} for suggestions and ordinary {@link Query} which serves as a dependency of
61
 * {@link SuggesterQuery}.
62
 */
63
class SuggesterQueryParser extends CustomQueryParser {
64

65
    private static final Logger logger = Logger.getLogger(SuggesterQueryParser.class.getName());
1✔
66

67
    private final Set<BooleanClause> suggesterClauses = new HashSet<>();
1✔
68

69
    private String identifier;
70

71
    private SuggesterQuery suggesterQuery;
72

73
    private String queryTextWithPlaceholder;
74

75
    /**
76
     * @param field field that is being parsed
77
     * @param identifier identifier that was inserted into the query to detect the {@link SuggesterQuery}
78
     */
79
    SuggesterQueryParser(final String field, final String identifier) {
80
        super(field);
1✔
81
        this.identifier = identifier;
1✔
82
        // always allow leading wildcard suggestions (even if they are disabled in configuration)
83
        setAllowLeadingWildcard(true);
1✔
84
    }
1✔
85

86
    public SuggesterQuery getSuggesterQuery() {
87
        return suggesterQuery;
1✔
88
    }
89

90
    public String getIdentifier() {
91
        return identifier;
1✔
92
    }
93

94
    public String getQueryTextWithPlaceholder() {
95
        return queryTextWithPlaceholder;
1✔
96
    }
97

98
    @Override
99
    protected Query newTermQuery(final Term term, float boost) {
100
        if (term.text().contains(identifier)) {
1✔
101
            Query q = new SuggesterPrefixQuery(replaceIdentifier(term, identifier));
1✔
102
            this.suggesterQuery = (SuggesterPrefixQuery) q;
1✔
103
            if (boost != DEFAULT_BOOST) {
1✔
104
                q = new BoostQuery(q, boost);
×
105
            }
106
            return q;
1✔
107
        }
108

109
        return super.newTermQuery(term, boost);
1✔
110
    }
111

112
    private Term replaceIdentifier(final Term term, final String identifier) {
113
        Term newTerm = new Term(term.field(), term.text().replace(identifier, ""));
1✔
114
        replaceIdentifier(term.field(), term.text());
1✔
115
        return newTerm;
1✔
116
    }
117

118
    private void replaceIdentifier(final String field, final String text) {
119
        this.identifier = text;
1✔
120
        if (!isCaseSensitive(field)) {
1✔
121
            // fixes problem when prefix contains upper case chars for case insensitive field
122
            queryTextWithPlaceholder = queryTextWithPlaceholder.replaceAll(
1✔
123
                    "(?i)" + Pattern.quote(identifier), identifier);
1✔
124
        }
125
    }
1✔
126

127
    @Override
128
    protected Query getBooleanQuery(final List<BooleanClause> clauses) throws ParseException {
129

130
        var inSuggesterClausesList = clauses.stream()
1✔
131
                .filter(this::isInSuggesterClauses)
1✔
132
                .collect(Collectors.toList());
1✔
133
        if (inSuggesterClausesList.isEmpty()) {
1✔
134
            return super.getBooleanQuery(clauses);
×
135
        }
136
        var reducedList = clauses.stream()
1✔
137
                .filter(booleanClause -> BooleanClause.Occur.SHOULD != booleanClause.getOccur())
1✔
138
                .collect(Collectors.toList());
1✔
139
        inSuggesterClausesList.stream()
1✔
140
                .filter(booleanClause -> BooleanClause.Occur.SHOULD == booleanClause.getOccur())
1✔
141
                .forEach(reducedList::add);
1✔
142
        return super.getBooleanQuery(reducedList);
1✔
143
    }
144

145
    private boolean isInSuggesterClauses(BooleanClause clause) {
146
        return suggesterClauses.stream()
1✔
147
                .anyMatch(itemClause ->
1✔
148
                        clause.getQuery().equals(itemClause.getQuery())
1✔
149
                );
150
    }
151

152
    @Override
153
    protected BooleanClause newBooleanClause(final Query q, final BooleanClause.Occur occur) {
154
        BooleanClause bc;
155

156
        if (q instanceof SuggesterPhraseQuery) {
1✔
157
            bc = super.newBooleanClause(((SuggesterPhraseQuery) q).getPhraseQuery(), BooleanClause.Occur.MUST);
1✔
158
            suggesterClauses.add(bc);
1✔
159
        } else if (q instanceof SuggesterQuery) {
1✔
160
            bc = super.newBooleanClause(new MatchAllDocsQuery(), BooleanClause.Occur.MUST);
1✔
161
            suggesterClauses.add(bc);
1✔
162
        } else if (q instanceof BooleanQuery) {
1✔
163
            bc = super.newBooleanClause(q, occur);
×
164
            for (BooleanClause clause : ((BooleanQuery) q).clauses()) {
×
165
                if (suggesterClauses.contains(clause)) {
×
166
                    suggesterClauses.add(bc);
×
167
                }
168
            }
×
169
        } else {
170
            bc = super.newBooleanClause(q, occur);
1✔
171
        }
172

173
        return bc;
1✔
174
    }
175

176
    @Override
177
    public Query parse(final String query) throws ParseException {
178
        this.queryTextWithPlaceholder = query;
1✔
179
        return super.parse(query);
1✔
180
    }
181

182
    @Override
183
    protected Query newPrefixQuery(final Term prefix) {
184
        if (prefix.text().contains(identifier)) {
×
185
            SuggesterPrefixQuery q = new SuggesterPrefixQuery(replaceIdentifier(prefix, identifier));
×
186
            this.suggesterQuery = q;
×
187
            return q;
×
188
        }
189

190
        return super.newPrefixQuery(prefix);
×
191
    }
192

193
    @Override
194
    protected Query newWildcardQuery(final Term t) {
195
        if (t.text().contains(identifier)) {
1✔
196
            String term = t.text().replace(identifier, "");
1✔
197
            if (term.endsWith("*") && !containsWildcardCharacter(term.substring(0, term.length() - 1))) {
1✔
198
                // the term ends with "*" but contains no other wildcard characters so faster method can be used
199
                replaceIdentifier(t.field(), t.text());
×
200
                term = term.substring(0, term.length() - 1);
×
201
                SuggesterPrefixQuery q = new SuggesterPrefixQuery(new Term(t.field(), term));
×
202
                this.suggesterQuery = q;
×
203
                return q;
×
204
            } else {
205
                SuggesterWildcardQuery q = new SuggesterWildcardQuery(replaceIdentifier(t, identifier));
1✔
206
                replaceIdentifier(t.field(), t.text());
1✔
207
                this.suggesterQuery = q;
1✔
208
                return q;
1✔
209
            }
210
        }
211

212
        return super.newWildcardQuery(t);
×
213
    }
214

215
    private boolean containsWildcardCharacter(final String s) {
216
        return s.contains("?") || s.contains("*");
1✔
217
    }
218

219
    @Override
220
    protected Query newFuzzyQuery(final Term term, final float minimumSimilarity, final int prefixLength) {
221
        if (term.text().contains(identifier)) {
×
222
            Term newTerm = replaceIdentifier(term, identifier);
×
223

224
            if (minimumSimilarity < 1) {
×
225
                replaceIdentifier(term.field(), term.text() + "~" + minimumSimilarity);
×
226
            } else { // similarity greater than 1 must be an integer
227
                replaceIdentifier(term.field(), term.text() + "~" + ((int) minimumSimilarity));
×
228
            }
229

230
            int numEdits = FuzzyQuery.floatToEdits(minimumSimilarity,
×
231
                    newTerm.text().codePointCount(0, newTerm.text().length()));
×
232

233
            SuggesterFuzzyQuery q = new SuggesterFuzzyQuery(newTerm, numEdits, prefixLength);
×
234
            this.suggesterQuery = q;
×
235
            return q;
×
236
        }
237

238
        return super.newFuzzyQuery(term, minimumSimilarity, prefixLength);
×
239
    }
240

241
    @Override
242
    protected Query newRegexpQuery(final Term regexp) {
243
        if (regexp.text().contains(identifier)) {
1✔
244
            Term newTerm = replaceIdentifier(regexp, identifier);
1✔
245

246
            replaceIdentifier(regexp.field(), "/" + regexp.text() + "/");
1✔
247

248
            SuggesterRegexpQuery q = new SuggesterRegexpQuery(newTerm);
1✔
249
            this.suggesterQuery = q;
1✔
250
            return q;
1✔
251
        }
252

253
        return super.newRegexpQuery(regexp);
×
254
    }
255

256
    @Override
257
    protected Query newFieldQuery(
258
            final Analyzer analyzer,
259
            final String field,
260
            final String queryText,
261
            final boolean quoted
262
    ) throws ParseException {
263

264
        if (quoted && queryText.contains(identifier)) {
1✔
265
            List<String> tokens = getAllTokens(analyzer, field, queryText);
1✔
266

267
            SuggesterPhraseQuery spq = new SuggesterPhraseQuery(field, identifier, tokens, this.getPhraseSlop());
1✔
268
            this.suggesterQuery = spq.getSuggesterQuery();
1✔
269
            replaceIdentifier(field, tokens.stream().filter(t -> t.contains(identifier)).findAny().orElseThrow());
1✔
270
            return spq;
1✔
271
        }
272

273
        return super.newFieldQuery(analyzer, field, queryText, quoted);
1✔
274
    }
275

276
    private static List<String> getAllTokens(final Analyzer analyzer, final String field, final String text) {
277
        List<String> tokens = new LinkedList<>();
1✔
278

279
        TokenStream ts = null;
1✔
280
        try {
281
            ts = analyzer.tokenStream(field, text);
1✔
282

283
            CharTermAttribute attr = ts.addAttribute(CharTermAttribute.class);
1✔
284

285
            ts.reset();
1✔
286
            while (ts.incrementToken()) {
1✔
287
                tokens.add(attr.toString());
1✔
288
            }
289
        } catch (IOException e) {
×
290
            logger.log(Level.WARNING, "Could not analyze query text", e);
×
291
        } finally {
292
            try {
293
                if (ts != null) {
1✔
294
                    ts.end();
1✔
295
                    ts.close();
1✔
296
                }
297
            } catch (IOException e) {
×
298
                logger.log(Level.WARNING, "Could not close token stream", e);
×
299
            }
1✔
300
        }
301

302
        return tokens;
1✔
303
    }
304

305
    @Override
306
    protected Query getFieldQuery(final String field, final String queryText, final int slop) throws ParseException {
307
        // this is a trick because we get slop here just as a parameter and it is not set to the field
308

309
        int oldPhraseSlope = getPhraseSlop();
1✔
310

311
        // set slop to correct value for field query evaluation
312
        setPhraseSlop(slop);
1✔
313

314
        Query query = super.getFieldQuery(field, queryText, slop);
1✔
315

316
        // set slop to the default value
317
        setPhraseSlop(oldPhraseSlope);
1✔
318

319
        return query;
1✔
320
    }
321

322
    @Override
323
    protected Query newRangeQuery(
324
            final String field,
325
            final String lowerTerm,
326
            final String upperTerm,
327
            final boolean startInclusive,
328
            final boolean endInclusive
329
    ) {
330
        if (lowerTerm.contains(identifier)) {
1✔
331
            String bareLowerTerm = lowerTerm.replace(identifier, "");
1✔
332
            replaceIdentifier(field, lowerTerm);
1✔
333

334
            SuggesterRangeQuery rangeQuery = new SuggesterRangeQuery(field, new BytesRef(bareLowerTerm),
1✔
335
                    new BytesRef(upperTerm), startInclusive, endInclusive, SuggesterRangeQuery.SuggestPosition.LOWER);
336

337
            this.suggesterQuery = rangeQuery;
1✔
338

339
            return rangeQuery;
1✔
340
        } else if (upperTerm.contains(identifier)) {
1✔
341
            String bareUpperTerm = upperTerm.replace(identifier, "");
1✔
342
            replaceIdentifier(field, upperTerm);
1✔
343

344
            SuggesterRangeQuery rangeQuery = new SuggesterRangeQuery(field, new BytesRef(lowerTerm),
1✔
345
                    new BytesRef(bareUpperTerm), startInclusive, endInclusive, SuggesterRangeQuery.SuggestPosition.UPPER);
346

347
            this.suggesterQuery = rangeQuery;
1✔
348

349
            return rangeQuery;
1✔
350
        }
351

352
        return super.newRangeQuery(field, lowerTerm, upperTerm, startInclusive, endInclusive);
×
353
    }
354

355
}
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