File: PersistentMap.java

package info (click to toggle)
libglazedlists-java 1.9.0%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 3,024 kB
  • ctags: 4,252
  • sloc: java: 22,561; xml: 818; sh: 51; makefile: 5
file content (291 lines) | stat: -rw-r--r-- 8,647 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
/* Glazed Lists                                                 (c) 2003-2006 */
/* http://publicobject.com/glazedlists/                      publicobject.com,*/
/*                                                     O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.impl.pmap;

// NIO is used for CTP
import ca.odell.glazedlists.impl.nio.NIODaemon;
import ca.odell.glazedlists.io.ByteCoder;
import ca.odell.glazedlists.io.GlazedListsIO;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.Serializable;
import java.nio.channels.FileChannel;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * A Map whose entries are persisted to disk transactionally.
 *
 * <p>This class uses an extra thread to do all persistence. This means that all
 * operations will be immediate, but will return without having taken effect on disk.
 * To flush the disk, call {@link #flush()}.
 *
 * @author <a href="mailto:jesse@swank.ca">Jesse Wilson</a>
 */
public final class PersistentMap implements Map {
    // Implementation Notes
    //
    // Each entry is stored as a chunk in a file.
    // The chunks are allocated, modified and then turned on
    // All access is serialized

    /** logging */
    private static Logger logger = Logger.getLogger(PersistentMap.class.toString());

    /** read and write without ever flushing meta-data such as file-mod date */
    private static final String FILE_ACCESS_MODE = "rwd";

    /** the file where all the data is stored */
    private File file = null;
    private FileChannel fileChannel = null;

    /** the I/O event queue daemon */
    private NIODaemon nioDaemon = null;

    /** the underlying map stores the objects in RAM */
    private Map map = new HashMap();

    /** converts the key from an Object to bytes and back */
    private ByteCoder keyCoder = null;

    /** the next sequence id to return */
    private int nextSequenceId = 1500;

    /** just allocate bytes in order */
    private int nextAvailableByte = 8;

    /**
     * Creates a new PersistentMap for the specified file that uses the {@link Serializable}
     * interface to convert keys to bytes.
     */
    public PersistentMap(File file) throws IOException {
        this(file, GlazedListsIO.serializableByteCoder());
    }
    /**
     * Creates a new PersistentMap for the specified file that uses the specified
     * {@link ByteCoder} to convert keys to bytes.
     */
    public PersistentMap(File file, ByteCoder keyCoder) throws IOException {
        this.file = file;
        this.keyCoder = keyCoder;

        // set up file access
        fileChannel = new RandomAccessFile(file, FILE_ACCESS_MODE).getChannel();

        // start the nio daemon
        nioDaemon = new NIODaemon();
        nioDaemon.start();

        // load the initial file
        nioDaemon.invokeAndWait(new OpenFile(this));
    }

    /**
     * Closes the file used by this PersistentMap.
     */
    public void close() {
        // close the file
        nioDaemon.invokeAndWait(new CloseFile(this));

        // invalidate the local state
        map = null;
    }

    /**
     * Blocks until all pending writes to disk have completed.
     */
    public void flush() {
        // ensure all pending changes have been written
        nioDaemon.invokeAndWait(NoOp.instance());
    }

    /**
     * Removes all mappings from this map.
     */
    public void clear() {
        // This must merge all chunks into one massive chunk.
        throw new UnsupportedOperationException();
    }

    /**
     * Returns true if this map contains a mapping for the  specified key.
     */
    public boolean containsKey(Object key) {
        return map.containsKey(key);
    }

    /**
     * Returns true if this map maps one or more keys to the  specified value.
     */
    public boolean containsValue(Object value) {
        return map.containsValue(value);
    }

    /**
     * Returns a collection view of the mappings contained in this map.
     */
    public Set entrySet() {
        throw new UnsupportedOperationException();
    }

    /**
     * Associates the specified value with the specified key in this map.
     *
     * @throws IllegalArgumentException if value is not a chunk.
     */
    public Object put(Object key, Object value) {
        if(!(value instanceof Chunk)) throw new IllegalArgumentException("value must be a chunk");
        Chunk newValue = (Chunk)value;

        // prepare the chunk for persistence
        newValue.initializeForPersistence(this, key);

        // save the new chunk and update the memory-data
        Chunk oldValue = (Chunk)map.put(key, newValue);

        // write the chunk
        nioDaemon.invokeLater(new AddChunk(this, newValue, oldValue));

        // return the previous value
        return oldValue;
    }

    /**
     * Returns the value to which the specified key is mapped in this weak  hash map, or null if the map contains no mapping for  this key.
     */
    public Object get(Object key) {
        return map.get(key);
    }

    /**
     * Removes the mapping for this key from this map if present.
     */
    public Object remove(Object key) {
        // remove from the memory-map
        Chunk removed = (Chunk)map.remove(key);

        // if there was nothing to remove
        if(removed == null) return null;

        // remove from disk
        nioDaemon.invokeLater(new RemoveChunk(this, removed));

        // return the removed value
        return removed;
    }

    /**
     * Returns a set view of the keys contained in this map.
     */
    public Set keySet() {
        return Collections.unmodifiableSet(map.keySet());
    }

    /**
     * Returns a collection view of the values contained in this map.
     */
    public Collection values() {
        return Collections.unmodifiableCollection(map.values());
    }

    /**
     * Returns true if this map contains no key-value mappings.
     */
    public boolean isEmpty() {
        return map.isEmpty();
    }

    /**
     * Copies all of the mappings from the specified map to this map These  mappings will replace any mappings that this map had for any of the  keys currently in the specified map.
     */
    public void putAll(Map m) {
        // this allocates a big chunk that is off and puts all the little chunks inside
        // then the big chunk is split into small chunks that are all on
        throw new UnsupportedOperationException();
    }

    /**
     * Returns the number of key-value mappings in this map.
     */
    public int size() {
        return map.size();
    }

    /**
     * Gets the next logical sequence ID for a new chunk and increments the value
     * for the next caller.
     */
    int nextSequenceId() {
        return nextSequenceId++;
    }

    /**
     * Get the daemon that does all the threading and selection.
     */
    NIODaemon getNIODaemon() {
        return nioDaemon;
    }

    /**
     * Gets the file being written.
     */
    File getFile() {
        return file;
    }

    /**
     * Gets the channel for writing to this file.
     */
    FileChannel getFileChannel() {
        return fileChannel;
    }

    /**
     * Handles a write failure.
     */
    void fail(IOException e, String message) {
        logger.log(Level.SEVERE, message, e);
    }

    /**
     * Handles the specified chunk having been loaded from file.
     */
    void loadedChunk(Chunk chunk) {
        // if this chunk contains active data
        if(chunk.isOn()) {
            map.put(chunk.getKey(), chunk);
            nextSequenceId = Math.max(nextSequenceId, chunk.getSequenceId() + 1);
        }

        // update allocation
        nextAvailableByte = Math.max(nextAvailableByte, chunk.getOffset() + chunk.size());
    }

    /**
     * Get the {@link ByteCoder} used to convert the key Objects to bytes and back.
     */
    ByteCoder getKeyCoder() {
        return keyCoder;
    }

    /**
     * Allocate some space for this chunk. The returned value is an array of two
     * integers. Result[0] == offset into file, Result[1] == number of bytes allocated.
     * Result[2] == the size index being used for this chunk, either 1 or 2, or 0
     * for new space.
     *
     * <p>More bytes may be allocated than necessary, and it is absolutely mandatory
     * that chunks consume the full number of bytes allocated to them.
     */
    void allocate(Chunk value) throws IOException {
        int offset = nextAvailableByte;
        int size = value.bytesRequired();
        nextAvailableByte += size;

        value.allocateAsNew(offset, size);
     }
}