1 /*******************************************************************************
2  * Copyright (c) 2009, 2015 Mountainminds GmbH & Co. KG and Contributors
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  *    Marc R. Hoffmann - initial API and implementation
10  *
11  *******************************************************************************/
12 package org.jacoco.core.data;
13 
14 import static java.lang.String.format;
15 
16 import java.io.EOFException;
17 import java.io.IOException;
18 import java.io.InputStream;
19 
20 import org.jacoco.core.internal.data.CompactDataInput;
21 
22 /**
23  * Deserialization of execution data from binary streams.
24  */
25 public class ExecutionDataReader {
26 
27 	/** Underlying data input */
28 	protected final CompactDataInput in;
29 
30 	private ISessionInfoVisitor sessionInfoVisitor = null;
31 
32 	private IExecutionDataVisitor executionDataVisitor = null;
33 
34 	private boolean firstBlock = true;
35 
36 	/**
37 	 * Creates a new reader based on the given input stream input. Depending on
38 	 * the nature of the underlying stream input should be buffered as most data
39 	 * is read in single bytes.
40 	 *
41 	 * @param input
42 	 *            input stream to read execution data from
43 	 */
ExecutionDataReader(final InputStream input)44 	public ExecutionDataReader(final InputStream input) {
45 		this.in = new CompactDataInput(input);
46 	}
47 
48 	/**
49 	 * Sets an listener for session information.
50 	 *
51 	 * @param visitor
52 	 *            visitor to retrieve session info events
53 	 */
setSessionInfoVisitor(final ISessionInfoVisitor visitor)54 	public void setSessionInfoVisitor(final ISessionInfoVisitor visitor) {
55 		this.sessionInfoVisitor = visitor;
56 	}
57 
58 	/**
59 	 * Sets an listener for execution data.
60 	 *
61 	 * @param visitor
62 	 *            visitor to retrieve execution data events
63 	 */
setExecutionDataVisitor(final IExecutionDataVisitor visitor)64 	public void setExecutionDataVisitor(final IExecutionDataVisitor visitor) {
65 		this.executionDataVisitor = visitor;
66 	}
67 
68 	/**
69 	 * Reads all data and reports it to the corresponding visitors. The stream
70 	 * is read until its end or a command confirmation has been sent.
71 	 *
72 	 * @return <code>true</code> if additional data can be expected after a
73 	 *         command has been executed. <code>false</code> if the end of the
74 	 *         stream has been reached.
75 	 * @throws IOException
76 	 *             might be thrown by the underlying input stream
77 	 */
read()78 	public boolean read() throws IOException {
79 		try {
80 			byte type;
81 			do {
82 				type = in.readByte();
83 				if (firstBlock && type != ExecutionDataWriter.BLOCK_HEADER) {
84 					throw new IOException("Invalid execution data file.");
85 				}
86 				firstBlock = false;
87 			} while (readBlock(type));
88 			return true;
89 		} catch (final EOFException e) {
90 			return false;
91 		}
92 	}
93 
94 	/**
95 	 * Reads a block of data identified by the given id. Subclasses may
96 	 * overwrite this method to support additional block types.
97 	 *
98 	 * @param blocktype
99 	 *            block type
100 	 * @return <code>true</code> if there are more blocks to read
101 	 * @throws IOException
102 	 *             might be thrown by the underlying input stream
103 	 */
readBlock(final byte blocktype)104 	protected boolean readBlock(final byte blocktype) throws IOException {
105 		switch (blocktype) {
106 		case ExecutionDataWriter.BLOCK_HEADER:
107 			readHeader();
108 			return true;
109 		case ExecutionDataWriter.BLOCK_SESSIONINFO:
110 			readSessionInfo();
111 			return true;
112 		case ExecutionDataWriter.BLOCK_EXECUTIONDATA:
113 			readExecutionData();
114 			return true;
115 		default:
116 			throw new IOException(format("Unknown block type %x.",
117 					Byte.valueOf(blocktype)));
118 		}
119 	}
120 
readHeader()121 	private void readHeader() throws IOException {
122 		if (in.readChar() != ExecutionDataWriter.MAGIC_NUMBER) {
123 			throw new IOException("Invalid execution data file.");
124 		}
125 		final char version = in.readChar();
126 		if (version != ExecutionDataWriter.FORMAT_VERSION) {
127 			throw new IOException(format("Incompatible version %x.",
128 					Integer.valueOf(version)));
129 		}
130 	}
131 
readSessionInfo()132 	private void readSessionInfo() throws IOException {
133 		if (sessionInfoVisitor == null) {
134 			throw new IOException("No session info visitor.");
135 		}
136 		final String id = in.readUTF();
137 		final long start = in.readLong();
138 		final long dump = in.readLong();
139 		sessionInfoVisitor.visitSessionInfo(new SessionInfo(id, start, dump));
140 	}
141 
readExecutionData()142 	private void readExecutionData() throws IOException {
143 		if (executionDataVisitor == null) {
144 			throw new IOException("No execution data visitor.");
145 		}
146 		final long id = in.readLong();
147 		final String name = in.readUTF();
148 		final boolean[] probes = in.readBooleanArray();
149 		executionDataVisitor.visitClassExecution(new ExecutionData(id, name,
150 				probes));
151 	}
152 
153 }
154