xref: /OpenGrok/suggester/src/main/java/org/opengrok/suggest/popular/impl/chronicle/ChronicleMapAdapter.java (revision 523d6b7bd0b4e366ff4474971c592aaf036fff73)
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.suggest.popular.impl.chronicle;
24 
25 import net.openhft.chronicle.map.ChronicleMap;
26 import org.apache.lucene.util.BytesRef;
27 import org.opengrok.suggest.popular.PopularityMap;
28 
29 import java.io.File;
30 import java.io.IOException;
31 import java.nio.file.Files;
32 import java.nio.file.Path;
33 import java.util.ArrayList;
34 import java.util.Collections;
35 import java.util.List;
36 import java.util.Map.Entry;
37 import java.util.function.Predicate;
38 
39 /**
40  * Adapter for {@link ChronicleMap} to expose only the necessary operations needed for most popular completion.
41  */
42 public class ChronicleMapAdapter implements PopularityMap {
43 
44     private ChronicleMap<BytesRef, Integer> map;
45 
46     private final File chronicleMapFile;
47 
ChronicleMapAdapter(final String name, final double averageKeySize, final int entries, final File file)48     public ChronicleMapAdapter(final String name, final double averageKeySize, final int entries, final File file)
49             throws IOException {
50         map = ChronicleMap.of(BytesRef.class, Integer.class)
51                 .name(name)
52                 .averageKeySize(averageKeySize)
53                 .keyReaderAndDataAccess(BytesRefSizedReader.INSTANCE, new BytesRefDataAccess())
54                 .entries(entries)
55                 .createOrRecoverPersistedTo(file);
56         this.chronicleMapFile = file;
57     }
58 
59     /** {@inheritDoc} */
60     @Override
get(final BytesRef key)61     public int get(final BytesRef key) {
62         return map.getOrDefault(key, 0);
63     }
64 
65     /** {@inheritDoc} */
66     @Override
increment(final BytesRef key, final int value)67     public void increment(final BytesRef key, final int value) {
68         if (value < 0) {
69             throw new IllegalArgumentException("Cannot increment by negative value " + value);
70         }
71         map.merge(key, value, Integer::sum);
72     }
73 
74     /** {@inheritDoc} */
75     @Override
getPopularityData(final int page, final int pageSize)76     public List<Entry<BytesRef, Integer>> getPopularityData(final int page, final int pageSize) {
77         if (page < 0) {
78             throw new IllegalArgumentException("Cannot retrieve popularity data for negative page: " + page);
79         }
80         if (pageSize < 0) {
81             throw new IllegalArgumentException("Cannot retrieve negative number of results: " + pageSize);
82         }
83 
84         List<Entry<BytesRef, Integer>> list = new ArrayList<>(map.entrySet());
85         list.sort(Entry.<BytesRef, Integer>comparingByValue().reversed());
86 
87         int startIndex = page * pageSize;
88         if (startIndex >= list.size()) {
89             return Collections.emptyList();
90         }
91         int endIndex = startIndex + pageSize;
92         if (endIndex > list.size()) {
93             endIndex = list.size();
94         }
95 
96         return list.subList(startIndex, endIndex);
97     }
98 
99     /**
100      * Removes the entries with key that meets the predicate.
101      * @param predicate predicate which tests which entries should be removed
102      */
removeIf(final Predicate<BytesRef> predicate)103     public void removeIf(final Predicate<BytesRef> predicate) {
104         map.entrySet().removeIf(e -> predicate.test(e.getKey()));
105     }
106 
107     /**
108      * Resizes the underlying {@link ChronicleMap}.
109      * @param newMapSize new entries count
110      * @param newMapAvgKey new average key size
111      * @throws IOException if some error occurred
112      */
113     @SuppressWarnings("java:S2095")
resize(final int newMapSize, final double newMapAvgKey)114     public void resize(final int newMapSize, final double newMapAvgKey) throws IOException {
115         if (newMapSize < 0) {
116             throw new IllegalArgumentException("Cannot resize chronicle map to negative size");
117         }
118         if (newMapAvgKey < 0) {
119             throw new IllegalArgumentException("Cannot resize chronicle map to map with negative key size");
120         }
121 
122         Path tempFile = Files.createTempFile("opengrok", "chronicle");
123 
124         try {
125             map.getAll(tempFile.toFile());
126 
127             String field = map.name();
128 
129             map.close();
130 
131             Files.delete(chronicleMapFile.toPath());
132 
133             ChronicleMap<BytesRef, Integer> m = ChronicleMap.of(BytesRef.class, Integer.class)
134                     .name(field)
135                     .averageKeySize(newMapAvgKey)
136                     .entries(newMapSize)
137                     .keyReaderAndDataAccess(BytesRefSizedReader.INSTANCE, new BytesRefDataAccess())
138                     .createOrRecoverPersistedTo(chronicleMapFile);
139             m.putAll(tempFile.toFile());
140             map = m;
141         } finally {
142             Files.delete(tempFile);
143         }
144     }
145 
146     /**
147      * Closes the opened {@link ChronicleMap}.
148      */
149     @Override
close()150     public void close() {
151         map.close();
152     }
153 
154 }
155