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