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>
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
11 *******************************************************************************/
13 package org
.eclipse
.tracecompass
.internal
.statesystem
.core
.backend
.historytree
;
15 import static org
.eclipse
.tracecompass
.common
.core
.NonNullUtils
.checkNotNull
;
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
;
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
;
30 import org
.eclipse
.jdt
.annotation
.Nullable
;
31 import org
.eclipse
.tracecompass
.internal
.statesystem
.core
.Activator
;
33 import com
.google
.common
.cache
.CacheBuilder
;
34 import com
.google
.common
.cache
.CacheLoader
;
35 import com
.google
.common
.cache
.LoadingCache
;
38 * This class abstracts inputs/outputs of the HistoryTree nodes.
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.
43 * This abstraction is mainly for code isolation/clarification purposes. Every
44 * HistoryTree must contain 1 and only 1 HT_IO element.
46 * @author Alexandre Montplaisir
50 private static final Logger LOGGER
= TraceCompassLog
.getLogger(HT_IO
.class);
52 // ------------------------------------------------------------------------
53 // Global cache of nodes
54 // ------------------------------------------------------------------------
56 private static final class CacheKey
{
58 public final HT_IO fStateHistory
;
59 public final int fSeqNumber
;
61 public CacheKey(HT_IO stateHistory
, int seqNumber
) {
62 fStateHistory
= stateHistory
;
63 fSeqNumber
= seqNumber
;
67 public int hashCode() {
68 return Objects
.hash(fStateHistory
, fSeqNumber
);
72 public boolean equals(@Nullable Object obj
) {
79 if (getClass() != obj
.getClass()) {
82 CacheKey other
= (CacheKey
) obj
;
83 return (fStateHistory
.equals(other
.fStateHistory
) &&
84 fSeqNumber
== other
.fSeqNumber
);
88 private static final int CACHE_SIZE
= 256;
90 private static final LoadingCache
<CacheKey
, HTNode
> NODE_CACHE
=
91 checkNotNull(CacheBuilder
.newBuilder()
92 .maximumSize(CACHE_SIZE
)
93 .build(new CacheLoader
<CacheKey
, HTNode
>() {
95 public HTNode
load(CacheKey key
) throws IOException
{
96 HT_IO io
= key
.fStateHistory
;
97 int seqNb
= key
.fSeqNumber
;
99 LOGGER
.finest(() -> "[HtIo:CacheMiss] seqNum=" + seqNb
); //$NON-NLS-1$
102 io
.seekFCToNodePos(io
.fFileChannelIn
, seqNb
);
103 return HTNode
.readNode(io
.fConfig
, io
.fFileChannelIn
);
109 // ------------------------------------------------------------------------
111 // ------------------------------------------------------------------------
113 /* Configuration of the History Tree */
114 private final HTConfig fConfig
;
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
;
122 // ------------------------------------------------------------------------
124 // ------------------------------------------------------------------------
129 * Standard constructor
132 * The configuration object for the StateHistoryTree
134 * Flag indicating that the file must be created from scratch
136 * @throws IOException
137 * An exception can be thrown when file cannot be accessed
139 public HT_IO(HTConfig config
, boolean newFile
) throws IOException
{
142 File historyTreeFile
= config
.getStateFile();
144 boolean success1
= true;
145 /* Create a new empty History Tree file */
146 if (historyTreeFile
.exists()) {
147 success1
= historyTreeFile
.delete();
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());
155 fFileInputStream
= new FileInputStream(historyTreeFile
);
156 fFileOutputStream
= new FileOutputStream(historyTreeFile
, false);
159 * We want to open an existing file, make sure we don't squash the
160 * existing content when opening the fos!
162 fFileInputStream
= new FileInputStream(historyTreeFile
);
163 fFileOutputStream
= new FileOutputStream(historyTreeFile
, true);
165 fFileChannelIn
= fFileInputStream
.getChannel();
166 fFileChannelOut
= fFileOutputStream
.getChannel();
170 * Read a node from the file on disk.
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.
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
);
185 return checkNotNull(NODE_CACHE
.get(key
));
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
;
194 * Other types of IOExceptions shouldn't happen at this point though.
196 Activator
.getDefault().logError(e
.getMessage(), e
);
197 throw new IllegalStateException();
202 * Write the given node to disk.
207 public void writeNode(HTNode node
) {
209 int seqNumber
= node
.getSequenceNumber();
211 /* "Write-back" the node into the cache */
212 CacheKey key
= new CacheKey(this, seqNumber
);
213 NODE_CACHE
.put(key
, node
);
215 /* Position ourselves at the start of the node and write it */
216 synchronized (this) {
217 seekFCToNodePos(fFileChannelOut
, seqNumber
);
218 node
.writeSelf(fFileChannelOut
);
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
);
227 * Get the output file channel, used for writing.
229 * @return The output file channel
231 public FileChannel
getFcOut() {
232 return fFileChannelOut
;
236 * Retrieve the input stream with which to write the attribute tree.
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
243 public FileInputStream
supplyATReader(int nodeOffset
) {
246 * Position ourselves at the start of the Mapping section in the
247 * file (which is right after the Blocks)
249 seekFCToNodePos(fFileChannelIn
, nodeOffset
);
250 } catch (IOException e
) {
251 Activator
.getDefault().logError(e
.getMessage(), e
);
253 return fFileInputStream
;
257 * Close all file channels and streams.
259 public synchronized void closeFile() {
261 fFileInputStream
.close();
262 fFileOutputStream
.close();
263 } catch (IOException e
) {
264 Activator
.getDefault().logError(e
.getMessage(), e
);
269 * Delete the history tree file
271 public synchronized void deleteFile() {
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$
282 * Seek the given FileChannel to the position corresponding to the node that
286 * the channel to seek
288 * the node sequence number to seek the channel to
289 * @throws IOException
290 * If some other I/O error occurs
292 private void seekFCToNodePos(FileChannel fc
, int seqNumber
)
295 * Cast to (long) is needed to make sure the result is a long too and
296 * doesn't get truncated
298 fc
.position(IHistoryTree
.TREE_HEADER_SIZE
299 + ((long) seqNumber
) * fConfig
.getBlockSize());