25f7281518cc530592a8614beb2240ec20b50066
[deliverable/tracecompass.git] / statesystem / org.eclipse.tracecompass.statesystem.core / src / org / eclipse / tracecompass / internal / statesystem / core / backend / historytree / HT_IO.java
1 /*******************************************************************************
2 * Copyright (c) 2012, 2014 Ericsson
3 * Copyright (c) 2010, 2011 École Polytechnique de Montréal
4 * Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com>
5 *
6 * All rights reserved. This program and the accompanying materials are
7 * made available under the terms of the Eclipse Public License v1.0 which
8 * accompanies this distribution, and is available at
9 * http://www.eclipse.org/legal/epl-v10.html
10 *
11 *******************************************************************************/
12
13 package org.eclipse.tracecompass.internal.statesystem.core.backend.historytree;
14
15 import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
16
17 import java.io.File;
18 import java.io.FileInputStream;
19 import java.io.FileOutputStream;
20 import java.io.IOException;
21 import java.nio.channels.ClosedChannelException;
22 import java.nio.channels.FileChannel;
23 import java.util.logging.Logger;
24
25 import org.eclipse.jdt.annotation.NonNull;
26 import org.eclipse.tracecompass.common.core.log.TraceCompassLog;
27 import java.util.Objects;
28 import java.util.concurrent.ExecutionException;
29
30 import org.eclipse.jdt.annotation.Nullable;
31 import org.eclipse.tracecompass.internal.statesystem.core.Activator;
32
33 import com.google.common.cache.CacheBuilder;
34 import com.google.common.cache.CacheLoader;
35 import com.google.common.cache.LoadingCache;
36
37 /**
38 * This class abstracts inputs/outputs of the HistoryTree nodes.
39 *
40 * It contains all the methods and descriptors to handle reading/writing nodes
41 * to the tree-file on disk and all the caching mechanisms.
42 *
43 * This abstraction is mainly for code isolation/clarification purposes. Every
44 * HistoryTree must contain 1 and only 1 HT_IO element.
45 *
46 * @author Alexandre Montplaisir
47 */
48 public class HT_IO {
49
50 private static final Logger LOGGER = TraceCompassLog.getLogger(HT_IO.class);
51
52 // ------------------------------------------------------------------------
53 // Global cache of nodes
54 // ------------------------------------------------------------------------
55
56 private static final class CacheKey {
57
58 public final HT_IO fStateHistory;
59 public final int fSeqNumber;
60
61 public CacheKey(HT_IO stateHistory, int seqNumber) {
62 fStateHistory = stateHistory;
63 fSeqNumber = seqNumber;
64 }
65
66 @Override
67 public int hashCode() {
68 return Objects.hash(fStateHistory, fSeqNumber);
69 }
70
71 @Override
72 public boolean equals(@Nullable Object obj) {
73 if (this == obj) {
74 return true;
75 }
76 if (obj == null) {
77 return false;
78 }
79 if (getClass() != obj.getClass()) {
80 return false;
81 }
82 CacheKey other = (CacheKey) obj;
83 return (fStateHistory.equals(other.fStateHistory) &&
84 fSeqNumber == other.fSeqNumber);
85 }
86 }
87
88 private static final int CACHE_SIZE = 256;
89
90 private static final LoadingCache<CacheKey, HTNode> NODE_CACHE =
91 checkNotNull(CacheBuilder.newBuilder()
92 .maximumSize(CACHE_SIZE)
93 .build(new CacheLoader<CacheKey, HTNode>() {
94 @Override
95 public HTNode load(CacheKey key) throws IOException {
96 HT_IO io = key.fStateHistory;
97 int seqNb = key.fSeqNumber;
98
99 LOGGER.finest(() -> "[HtIo:CacheMiss] seqNum=" + seqNb); //$NON-NLS-1$
100
101 synchronized (io) {
102 io.seekFCToNodePos(io.fFileChannelIn, seqNb);
103 return HTNode.readNode(io.fConfig, io.fFileChannelIn);
104 }
105 }
106 }));
107
108
109 // ------------------------------------------------------------------------
110 // Instance fields
111 // ------------------------------------------------------------------------
112
113 /* Configuration of the History Tree */
114 private final HTConfig fConfig;
115
116 /* Fields related to the file I/O */
117 private final FileInputStream fFileInputStream;
118 private final FileOutputStream fFileOutputStream;
119 private final FileChannel fFileChannelIn;
120 private final FileChannel fFileChannelOut;
121
122 // ------------------------------------------------------------------------
123 // Methods
124 // ------------------------------------------------------------------------
125
126
127
128 /**
129 * Standard constructor
130 *
131 * @param config
132 * The configuration object for the StateHistoryTree
133 * @param newFile
134 * Flag indicating that the file must be created from scratch
135 *
136 * @throws IOException
137 * An exception can be thrown when file cannot be accessed
138 */
139 public HT_IO(HTConfig config, boolean newFile) throws IOException {
140 fConfig = config;
141
142 File historyTreeFile = config.getStateFile();
143 if (newFile) {
144 boolean success1 = true;
145 /* Create a new empty History Tree file */
146 if (historyTreeFile.exists()) {
147 success1 = historyTreeFile.delete();
148 }
149 boolean success2 = historyTreeFile.createNewFile();
150 if (!(success1 && success2)) {
151 /* It seems we do not have permission to create the new file */
152 throw new IOException("Cannot create new file at " + //$NON-NLS-1$
153 historyTreeFile.getName());
154 }
155 fFileInputStream = new FileInputStream(historyTreeFile);
156 fFileOutputStream = new FileOutputStream(historyTreeFile, false);
157 } else {
158 /*
159 * We want to open an existing file, make sure we don't squash the
160 * existing content when opening the fos!
161 */
162 fFileInputStream = new FileInputStream(historyTreeFile);
163 fFileOutputStream = new FileOutputStream(historyTreeFile, true);
164 }
165 fFileChannelIn = fFileInputStream.getChannel();
166 fFileChannelOut = fFileOutputStream.getChannel();
167 }
168
169 /**
170 * Read a node from the file on disk.
171 *
172 * @param seqNumber
173 * The sequence number of the node to read.
174 * @return The object representing the node
175 * @throws ClosedChannelException
176 * Usually happens because the file was closed while we were
177 * reading. Instead of using a big reader-writer lock, we'll
178 * just catch this exception.
179 */
180 public @NonNull HTNode readNode(int seqNumber) throws ClosedChannelException {
181 /* Do a cache lookup. If it's not present it will be loaded from disk */
182 LOGGER.finest(() -> "[HtIo:CacheLookup] seqNum=" + seqNumber); //$NON-NLS-1$
183 CacheKey key = new CacheKey(this, seqNumber);
184 try {
185 return checkNotNull(NODE_CACHE.get(key));
186
187 } catch (ExecutionException e) {
188 /* Get the inner exception that was generated */
189 Throwable cause = e.getCause();
190 if (cause instanceof ClosedChannelException) {
191 throw (ClosedChannelException) cause;
192 }
193 /*
194 * Other types of IOExceptions shouldn't happen at this point though.
195 */
196 Activator.getDefault().logError(e.getMessage(), e);
197 throw new IllegalStateException();
198 }
199 }
200
201 /**
202 * Write the given node to disk.
203 *
204 * @param node
205 * The node to write.
206 */
207 public void writeNode(HTNode node) {
208 try {
209 int seqNumber = node.getSequenceNumber();
210
211 /* "Write-back" the node into the cache */
212 CacheKey key = new CacheKey(this, seqNumber);
213 NODE_CACHE.put(key, node);
214
215 /* Position ourselves at the start of the node and write it */
216 synchronized (this) {
217 seekFCToNodePos(fFileChannelOut, seqNumber);
218 node.writeSelf(fFileChannelOut);
219 }
220 } catch (IOException e) {
221 /* If we were able to open the file, we should be fine now... */
222 Activator.getDefault().logError(e.getMessage(), e);
223 }
224 }
225
226 /**
227 * Get the output file channel, used for writing.
228 *
229 * @return The output file channel
230 */
231 public FileChannel getFcOut() {
232 return fFileChannelOut;
233 }
234
235 /**
236 * Retrieve the input stream with which to write the attribute tree.
237 *
238 * @param nodeOffset
239 * The offset in the file, in number of nodes. This should be
240 * after all the nodes.
241 * @return The correctly-seeked input stream
242 */
243 public FileInputStream supplyATReader(int nodeOffset) {
244 try {
245 /*
246 * Position ourselves at the start of the Mapping section in the
247 * file (which is right after the Blocks)
248 */
249 seekFCToNodePos(fFileChannelIn, nodeOffset);
250 } catch (IOException e) {
251 Activator.getDefault().logError(e.getMessage(), e);
252 }
253 return fFileInputStream;
254 }
255
256 /**
257 * Close all file channels and streams.
258 */
259 public synchronized void closeFile() {
260 try {
261 fFileInputStream.close();
262 fFileOutputStream.close();
263 } catch (IOException e) {
264 Activator.getDefault().logError(e.getMessage(), e);
265 }
266 }
267
268 /**
269 * Delete the history tree file
270 */
271 public synchronized void deleteFile() {
272 closeFile();
273
274 File historyTreeFile = fConfig.getStateFile();
275 if (!historyTreeFile.delete()) {
276 /* We didn't succeed in deleting the file */
277 Activator.getDefault().logError("Failed to delete" + historyTreeFile.getName()); //$NON-NLS-1$
278 }
279 }
280
281 /**
282 * Seek the given FileChannel to the position corresponding to the node that
283 * has seqNumber
284 *
285 * @param fc
286 * the channel to seek
287 * @param seqNumber
288 * the node sequence number to seek the channel to
289 * @throws IOException
290 * If some other I/O error occurs
291 */
292 private void seekFCToNodePos(FileChannel fc, int seqNumber)
293 throws IOException {
294 /*
295 * Cast to (long) is needed to make sure the result is a long too and
296 * doesn't get truncated
297 */
298 fc.position(IHistoryTree.TREE_HEADER_SIZE
299 + ((long) seqNumber) * fConfig.getBlockSize());
300 }
301
302 }
This page took 0.037182 seconds and 4 git commands to generate.