Commit | Line | Data |
---|---|---|
866e5b51 | 1 | /******************************************************************************* |
60ae41e1 | 2 | * Copyright (c) 2011, 2014 Ericsson, Ecole Polytechnique de Montreal and others |
866e5b51 FC |
3 | * |
4 | * All rights reserved. This program and the accompanying materials are made | |
5 | * available under the terms of the Eclipse Public License v1.0 which | |
6 | * accompanies this distribution, and is available at | |
7 | * http://www.eclipse.org/legal/epl-v10.html | |
8 | * | |
9 | * Contributors: Matthew Khouzam - Initial API and implementation | |
10 | * Contributors: Simon Marchi - Initial API and implementation | |
11 | *******************************************************************************/ | |
12 | ||
486efb2e | 13 | package org.eclipse.linuxtools.ctf.core.trace; |
866e5b51 FC |
14 | |
15 | import java.io.File; | |
aa3b05ef | 16 | import java.io.IOException; |
526c823c | 17 | import java.nio.ByteBuffer; |
866e5b51 | 18 | import java.nio.channels.FileChannel; |
aa3b05ef | 19 | import java.nio.channels.FileChannel.MapMode; |
866e5b51 FC |
20 | import java.util.UUID; |
21 | ||
486efb2e | 22 | import org.eclipse.linuxtools.ctf.core.event.io.BitBuffer; |
866e5b51 FC |
23 | import org.eclipse.linuxtools.ctf.core.event.types.ArrayDefinition; |
24 | import org.eclipse.linuxtools.ctf.core.event.types.Definition; | |
21fb02fa MK |
25 | import org.eclipse.linuxtools.ctf.core.event.types.EnumDefinition; |
26 | import org.eclipse.linuxtools.ctf.core.event.types.FloatDefinition; | |
866e5b51 FC |
27 | import org.eclipse.linuxtools.ctf.core.event.types.IDefinitionScope; |
28 | import org.eclipse.linuxtools.ctf.core.event.types.IntegerDefinition; | |
21fb02fa | 29 | import org.eclipse.linuxtools.ctf.core.event.types.StringDefinition; |
866e5b51 | 30 | import org.eclipse.linuxtools.ctf.core.event.types.StructDefinition; |
486efb2e AM |
31 | import org.eclipse.linuxtools.internal.ctf.core.trace.StreamInputPacketIndex; |
32 | import org.eclipse.linuxtools.internal.ctf.core.trace.StreamInputPacketIndexEntry; | |
866e5b51 FC |
33 | |
34 | /** | |
35 | * <b><u>StreamInput</u></b> | |
36 | * <p> | |
37 | * Represents a trace file that belongs to a certain stream. | |
cf9a28da | 38 | * |
486efb2e | 39 | * @since 2.0 |
866e5b51 FC |
40 | */ |
41 | public class StreamInput implements IDefinitionScope { | |
42 | ||
43 | // ------------------------------------------------------------------------ | |
44 | // Attributes | |
45 | // ------------------------------------------------------------------------ | |
46 | ||
47 | /** | |
48 | * The associated Stream | |
49 | */ | |
f224bebc | 50 | private final Stream fStream; |
866e5b51 FC |
51 | |
52 | /** | |
53 | * FileChannel to the trace file | |
54 | */ | |
f224bebc | 55 | private final FileChannel fFileChannel; |
866e5b51 FC |
56 | |
57 | /** | |
58 | * Information on the file (used for debugging) | |
59 | */ | |
f224bebc | 60 | private final File fFile; |
866e5b51 FC |
61 | |
62 | /** | |
63 | * The packet index of this input | |
64 | */ | |
f224bebc | 65 | private final StreamInputPacketIndex fIndex; |
866e5b51 | 66 | |
f224bebc | 67 | private long fTimestampEnd; |
866e5b51 | 68 | |
bfe038ff MK |
69 | /* |
70 | * Definition of trace packet header | |
71 | */ | |
f224bebc | 72 | private StructDefinition fTracePacketHeaderDef = null; |
bfe038ff MK |
73 | |
74 | /* | |
75 | * Definition of trace stream packet context | |
76 | */ | |
f224bebc | 77 | private StructDefinition fStreamPacketContextDef = null; |
bfe038ff | 78 | |
132a02b0 MK |
79 | /* |
80 | * Total number of lost events in this stream | |
81 | */ | |
f224bebc | 82 | private long fLostSoFar = 0; |
132a02b0 | 83 | |
866e5b51 FC |
84 | // ------------------------------------------------------------------------ |
85 | // Constructors | |
86 | // ------------------------------------------------------------------------ | |
87 | ||
88 | /** | |
89 | * Constructs a StreamInput. | |
90 | * | |
91 | * @param stream | |
92 | * The stream to which this StreamInput belongs to. | |
93 | * @param fileChannel | |
94 | * The FileChannel to the trace file. | |
95 | * @param file | |
96 | * Information about the trace file (for debugging purposes). | |
97 | */ | |
98 | public StreamInput(Stream stream, FileChannel fileChannel, File file) { | |
f224bebc MK |
99 | fStream = stream; |
100 | fFileChannel = fileChannel; | |
101 | fFile = file; | |
102 | fIndex = new StreamInputPacketIndex(); | |
866e5b51 FC |
103 | } |
104 | ||
105 | // ------------------------------------------------------------------------ | |
106 | // Getters/Setters/Predicates | |
107 | // ------------------------------------------------------------------------ | |
108 | ||
9ac2eb62 MK |
109 | /** |
110 | * Gets the stream the streamInput wrapper is wrapping | |
21fb02fa | 111 | * |
9ac2eb62 MK |
112 | * @return the stream the streamInput wrapper is wrapping |
113 | */ | |
866e5b51 | 114 | public Stream getStream() { |
f224bebc | 115 | return fStream; |
866e5b51 FC |
116 | } |
117 | ||
9ac2eb62 | 118 | /** |
ecb12461 | 119 | * The common streamInput Index |
21fb02fa | 120 | * |
9ac2eb62 MK |
121 | * @return the stream input Index |
122 | */ | |
486efb2e | 123 | StreamInputPacketIndex getIndex() { |
f224bebc | 124 | return fIndex; |
866e5b51 FC |
125 | } |
126 | ||
9ac2eb62 MK |
127 | /** |
128 | * Gets the filename of the streamInput file. | |
21fb02fa | 129 | * |
9ac2eb62 MK |
130 | * @return the filename of the streaminput file. |
131 | */ | |
866e5b51 | 132 | public String getFilename() { |
f224bebc | 133 | return fFile.getName(); |
866e5b51 FC |
134 | } |
135 | ||
9ac2eb62 | 136 | /** |
ecb12461 | 137 | * Gets the last read timestamp of a stream. (this is not necessarily the |
21fb02fa MK |
138 | * last time in the stream.) |
139 | * | |
9ac2eb62 MK |
140 | * @return the last read timestamp |
141 | */ | |
866e5b51 | 142 | public long getTimestampEnd() { |
f224bebc | 143 | return fTimestampEnd; |
866e5b51 FC |
144 | } |
145 | ||
9ac2eb62 | 146 | /** |
21fb02fa MK |
147 | * Sets the last read timestamp of a stream. (this is not necessarily the |
148 | * last time in the stream.) | |
149 | * | |
150 | * @param timestampEnd | |
151 | * the last read timestamp | |
9ac2eb62 | 152 | */ |
866e5b51 | 153 | public void setTimestampEnd(long timestampEnd) { |
f224bebc | 154 | fTimestampEnd = timestampEnd; |
866e5b51 FC |
155 | } |
156 | ||
9ac2eb62 | 157 | /** |
ecb12461 | 158 | * Useless for streaminputs |
9ac2eb62 | 159 | */ |
866e5b51 FC |
160 | @Override |
161 | public String getPath() { | |
162 | return ""; //$NON-NLS-1$ | |
163 | } | |
164 | ||
165 | // ------------------------------------------------------------------------ | |
166 | // Operations | |
167 | // ------------------------------------------------------------------------ | |
168 | ||
169 | @Override | |
170 | public Definition lookupDefinition(String lookupPath) { | |
171 | /* TODO: lookup in different dynamic scopes is not supported yet. */ | |
172 | return null; | |
173 | } | |
174 | ||
175 | /** | |
176 | * Create the index for this trace file. | |
bfe038ff MK |
177 | */ |
178 | public void setupIndex() { | |
179 | ||
bfe038ff MK |
180 | /* |
181 | * The BitBuffer to extract data from the StreamInput | |
182 | */ | |
183 | BitBuffer bitBuffer = new BitBuffer(); | |
f224bebc | 184 | bitBuffer.setByteOrder(getStream().getTrace().getByteOrder()); |
bfe038ff MK |
185 | |
186 | /* | |
187 | * Create the definitions we need to read the packet headers + contexts | |
188 | */ | |
189 | if (getStream().getTrace().getPacketHeader() != null) { | |
f224bebc | 190 | fTracePacketHeaderDef = getStream().getTrace().getPacketHeader() |
bfe038ff MK |
191 | .createDefinition(this, "trace.packet.header"); //$NON-NLS-1$ |
192 | } | |
193 | ||
194 | if (getStream().getPacketContextDecl() != null) { | |
f224bebc | 195 | fStreamPacketContextDef = getStream().getPacketContextDecl() |
bfe038ff MK |
196 | .createDefinition(this, "stream.packet.context"); //$NON-NLS-1$ |
197 | } | |
198 | ||
199 | } | |
200 | ||
9ac2eb62 MK |
201 | /** |
202 | * Adds the next packet header index entry to the index of a stream input. | |
be6df2d8 | 203 | * |
ab04fc6b | 204 | * <strong>This method is slow and can corrupt data if not used properly</strong> |
9ac2eb62 MK |
205 | * @return true if there are more packets to add |
206 | * @throws CTFReaderException | |
be6df2d8 | 207 | * If there was a problem reading the packed header |
9ac2eb62 | 208 | */ |
bfe038ff MK |
209 | public boolean addPacketHeaderIndex() throws CTFReaderException { |
210 | long currentPos = 0L; | |
f224bebc MK |
211 | if (!fIndex.getEntries().isEmpty()) { |
212 | StreamInputPacketIndexEntry pos = fIndex.getEntries().lastElement(); | |
bfe038ff MK |
213 | currentPos = computeNextOffset(pos); |
214 | } | |
215 | long fileSize = getStreamSize(); | |
216 | if (currentPos < fileSize) { | |
217 | BitBuffer bitBuffer = new BitBuffer(); | |
f224bebc | 218 | bitBuffer.setByteOrder(getStream().getTrace().getByteOrder()); |
bfe038ff MK |
219 | StreamInputPacketIndexEntry packetIndex = new StreamInputPacketIndexEntry( |
220 | currentPos); | |
221 | createPacketIndexEntry(fileSize, currentPos, packetIndex, | |
f224bebc MK |
222 | fTracePacketHeaderDef, fStreamPacketContextDef, bitBuffer); |
223 | fIndex.addEntry(packetIndex); | |
bfe038ff MK |
224 | return true; |
225 | } | |
226 | return false; | |
227 | } | |
228 | ||
bfe038ff | 229 | private long getStreamSize() { |
f224bebc | 230 | return fFile.length(); |
bfe038ff MK |
231 | } |
232 | ||
bfe038ff MK |
233 | private long createPacketIndexEntry(long fileSizeBytes, |
234 | long packetOffsetBytes, StreamInputPacketIndexEntry packetIndex, | |
235 | StructDefinition tracePacketHeaderDef, | |
236 | StructDefinition streamPacketContextDef, BitBuffer bitBuffer) | |
237 | throws CTFReaderException { | |
0594c61c | 238 | |
cf9a28da MK |
239 | /* |
240 | * Ignoring the return value, but this call is needed to initialize the | |
241 | * input | |
242 | */ | |
0594c61c | 243 | createPacketBitBuffer(fileSizeBytes, packetOffsetBytes, packetIndex, bitBuffer); |
bfe038ff | 244 | |
866e5b51 | 245 | /* |
bfe038ff | 246 | * Read the trace packet header if it exists. |
866e5b51 | 247 | */ |
bfe038ff MK |
248 | if (tracePacketHeaderDef != null) { |
249 | parseTracePacketHeader(tracePacketHeaderDef, bitBuffer); | |
866e5b51 FC |
250 | } |
251 | ||
252 | /* | |
bfe038ff | 253 | * Read the stream packet context if it exists. |
866e5b51 | 254 | */ |
bfe038ff MK |
255 | if (streamPacketContextDef != null) { |
256 | parsePacketContext(fileSizeBytes, streamPacketContextDef, | |
257 | bitBuffer, packetIndex); | |
258 | } else { | |
259 | setPacketContextNull(fileSizeBytes, packetIndex); | |
260 | } | |
261 | ||
262 | /* Basic validation */ | |
263 | if (packetIndex.getContentSizeBits() > packetIndex.getPacketSizeBits()) { | |
264 | throw new CTFReaderException("Content size > packet size"); //$NON-NLS-1$ | |
265 | } | |
866e5b51 | 266 | |
bfe038ff MK |
267 | if (packetIndex.getPacketSizeBits() > ((fileSizeBytes - packetIndex |
268 | .getOffsetBytes()) * 8)) { | |
cf9a28da | 269 | throw new CTFReaderException("Not enough data remaining in the file for the size of this packet"); //$NON-NLS-1$ |
bfe038ff MK |
270 | } |
271 | ||
272 | /* | |
273 | * Offset in the file, in bits | |
274 | */ | |
275 | packetIndex.setDataOffsetBits(bitBuffer.position()); | |
276 | ||
277 | /* | |
278 | * Update the counting packet offset | |
279 | */ | |
21fb02fa | 280 | return computeNextOffset(packetIndex); |
bfe038ff MK |
281 | } |
282 | ||
283 | /** | |
284 | * @param packetIndex | |
285 | * @return | |
286 | */ | |
287 | private static long computeNextOffset( | |
288 | StreamInputPacketIndexEntry packetIndex) { | |
289 | return packetIndex.getOffsetBytes() | |
290 | + ((packetIndex.getPacketSizeBits() + 7) / 8); | |
291 | } | |
292 | ||
526c823c | 293 | ByteBuffer getByteBufferAt(long position, long size) throws IOException { |
f224bebc | 294 | return fFileChannel.map(MapMode.READ_ONLY, position, size); |
526c823c EB |
295 | } |
296 | ||
aa3b05ef FC |
297 | /** |
298 | * @param fileSizeBytes | |
299 | * @param packetOffsetBytes | |
300 | * @param packetIndex | |
301 | * @param bitBuffer | |
302 | * @return | |
303 | * @throws CTFReaderException | |
304 | */ | |
526c823c | 305 | private ByteBuffer createPacketBitBuffer(long fileSizeBytes, |
aa3b05ef FC |
306 | long packetOffsetBytes, StreamInputPacketIndexEntry packetIndex, |
307 | BitBuffer bitBuffer) throws CTFReaderException { | |
308 | /* | |
309 | * Initial size, it should map at least the packet header + context | |
310 | * size. | |
311 | * | |
312 | * TODO: use a less arbitrary size. | |
313 | */ | |
314 | long mapSize = 4096; | |
315 | /* | |
316 | * If there is less data remaining than what we want to map, reduce the | |
317 | * map size. | |
318 | */ | |
319 | if ((fileSizeBytes - packetIndex.getOffsetBytes()) < mapSize) { | |
320 | mapSize = fileSizeBytes - packetIndex.getOffsetBytes(); | |
321 | } | |
322 | ||
323 | /* | |
324 | * Map the packet. | |
325 | */ | |
526c823c | 326 | ByteBuffer bb; |
aa3b05ef FC |
327 | |
328 | try { | |
526c823c | 329 | bb = getByteBufferAt(packetOffsetBytes, mapSize); |
aa3b05ef FC |
330 | } catch (IOException e) { |
331 | throw new CTFReaderException(e); | |
332 | } | |
333 | bitBuffer.setByteBuffer(bb); | |
334 | return bb; | |
335 | } | |
bfe038ff | 336 | |
bfe038ff MK |
337 | private void parseTracePacketHeader(StructDefinition tracePacketHeaderDef, |
338 | BitBuffer bitBuffer) throws CTFReaderException { | |
339 | tracePacketHeaderDef.read(bitBuffer); | |
866e5b51 FC |
340 | |
341 | /* | |
bfe038ff | 342 | * Check the CTF magic number |
866e5b51 | 343 | */ |
bfe038ff MK |
344 | IntegerDefinition magicDef = (IntegerDefinition) tracePacketHeaderDef |
345 | .lookupDefinition("magic"); //$NON-NLS-1$ | |
346 | if (magicDef != null) { | |
347 | int magic = (int) magicDef.getValue(); | |
348 | if (magic != Utils.CTF_MAGIC) { | |
349 | throw new CTFReaderException( | |
350 | "CTF magic mismatch " + Integer.toHexString(magic) + " vs " + Integer.toHexString(Utils.CTF_MAGIC)); //$NON-NLS-1$//$NON-NLS-2$ | |
351 | } | |
866e5b51 FC |
352 | } |
353 | ||
354 | /* | |
bfe038ff | 355 | * Check the trace UUID |
866e5b51 | 356 | */ |
0594c61c AM |
357 | ArrayDefinition uuidDef = |
358 | (ArrayDefinition) tracePacketHeaderDef.lookupDefinition("uuid"); //$NON-NLS-1$ | |
bfe038ff MK |
359 | if (uuidDef != null) { |
360 | byte[] uuidArray = new byte[16]; | |
361 | ||
0594c61c AM |
362 | for (int i = 0; i < uuidArray.length; i++) { |
363 | IntegerDefinition uuidByteDef = (IntegerDefinition) uuidDef.getElem(i); | |
bfe038ff | 364 | uuidArray[i] = (byte) uuidByteDef.getValue(); |
866e5b51 | 365 | } |
866e5b51 | 366 | |
bfe038ff | 367 | UUID uuid = Utils.makeUUID(uuidArray); |
866e5b51 | 368 | |
bfe038ff MK |
369 | if (!getStream().getTrace().getUUID().equals(uuid)) { |
370 | throw new CTFReaderException("UUID mismatch"); //$NON-NLS-1$ | |
866e5b51 | 371 | } |
bfe038ff | 372 | } |
866e5b51 | 373 | |
bfe038ff MK |
374 | /* |
375 | * Check that the stream id did not change | |
376 | */ | |
377 | IntegerDefinition streamIDDef = (IntegerDefinition) tracePacketHeaderDef | |
378 | .lookupDefinition("stream_id"); //$NON-NLS-1$ | |
379 | if (streamIDDef != null) { | |
380 | long streamID = streamIDDef.getValue(); | |
866e5b51 | 381 | |
bfe038ff | 382 | if (streamID != getStream().getId()) { |
cf9a28da | 383 | throw new CTFReaderException("Stream ID changing within a StreamInput"); //$NON-NLS-1$ |
866e5b51 | 384 | } |
bfe038ff MK |
385 | } |
386 | } | |
866e5b51 | 387 | |
bfe038ff MK |
388 | private static void setPacketContextNull(long fileSizeBytes, |
389 | StreamInputPacketIndexEntry packetIndex) { | |
390 | /* | |
391 | * If there is no packet context, infer the content and packet size from | |
392 | * the file size (assume that there is only one packet and no padding) | |
393 | */ | |
47ca6c05 SM |
394 | packetIndex.setContentSizeBits(fileSizeBytes * 8); |
395 | packetIndex.setPacketSizeBits(fileSizeBytes * 8); | |
bfe038ff | 396 | } |
866e5b51 | 397 | |
bfe038ff MK |
398 | private void parsePacketContext(long fileSizeBytes, |
399 | StructDefinition streamPacketContextDef, BitBuffer bitBuffer, | |
db8e8f7d | 400 | StreamInputPacketIndexEntry packetIndex) throws CTFReaderException { |
bfe038ff | 401 | streamPacketContextDef.read(bitBuffer); |
866e5b51 | 402 | |
21fb02fa MK |
403 | for (String field : streamPacketContextDef.getDeclaration() |
404 | .getFieldsList()) { | |
405 | Definition id = streamPacketContextDef.lookupDefinition(field); | |
406 | if (id instanceof IntegerDefinition) { | |
407 | packetIndex.addAttribute(field, | |
408 | ((IntegerDefinition) id).getValue()); | |
409 | } else if (id instanceof FloatDefinition) { | |
410 | packetIndex.addAttribute(field, | |
411 | ((FloatDefinition) id).getValue()); | |
412 | } else if (id instanceof EnumDefinition) { | |
413 | packetIndex.addAttribute(field, | |
414 | ((EnumDefinition) id).getValue()); | |
415 | } else if (id instanceof StringDefinition) { | |
416 | packetIndex.addAttribute(field, | |
417 | ((StringDefinition) id).getValue()); | |
418 | } | |
21fb02fa MK |
419 | } |
420 | ||
421 | Long contentSize = (Long) packetIndex.lookupAttribute("content_size"); //$NON-NLS-1$ | |
422 | Long packetSize = (Long) packetIndex.lookupAttribute("packet_size"); //$NON-NLS-1$ | |
0594c61c AM |
423 | Long tsBegin = (Long) packetIndex.lookupAttribute("timestamp_begin"); //$NON-NLS-1$ |
424 | Long tsEnd = (Long) packetIndex.lookupAttribute("timestamp_end"); //$NON-NLS-1$ | |
21fb02fa MK |
425 | String device = (String) packetIndex.lookupAttribute("device"); //$NON-NLS-1$ |
426 | // LTTng Specific | |
0594c61c | 427 | Long cpuId = (Long) packetIndex.lookupAttribute("cpu_id"); //$NON-NLS-1$ |
cf9a28da | 428 | Long lostEvents = (Long) packetIndex.lookupAttribute("events_discarded"); //$NON-NLS-1$ |
132a02b0 MK |
429 | |
430 | /* Read the content size in bits */ | |
21fb02fa MK |
431 | if (contentSize != null) { |
432 | packetIndex.setContentSizeBits(contentSize.intValue()); | |
cf9a28da MK |
433 | } else if (packetSize != null) { |
434 | packetIndex.setContentSizeBits(packetSize.longValue()); | |
bfe038ff MK |
435 | } else { |
436 | packetIndex.setContentSizeBits((int) (fileSizeBytes * 8)); | |
437 | } | |
866e5b51 | 438 | |
cf9a28da | 439 | |
132a02b0 | 440 | /* Read the packet size in bits */ |
21fb02fa MK |
441 | if (packetSize != null) { |
442 | packetIndex.setPacketSizeBits(packetSize.intValue()); | |
51598b21 MK |
443 | } else if (packetIndex.getContentSizeBits() != 0) { |
444 | packetIndex.setPacketSizeBits(packetIndex.getContentSizeBits()); | |
bfe038ff | 445 | } else { |
51598b21 | 446 | packetIndex.setPacketSizeBits((int) (fileSizeBytes * 8)); |
bfe038ff MK |
447 | } |
448 | ||
51598b21 | 449 | |
132a02b0 | 450 | /* Read the begin timestamp */ |
0594c61c AM |
451 | if (tsBegin != null) { |
452 | packetIndex.setTimestampBegin(tsBegin.longValue()); | |
bfe038ff MK |
453 | } |
454 | ||
132a02b0 | 455 | /* Read the end timestamp */ |
0594c61c | 456 | if (tsEnd != null) { |
ecb12461 | 457 | if (tsEnd == -1) { |
0594c61c | 458 | tsEnd = Long.MAX_VALUE; |
21fb02fa | 459 | } |
0594c61c | 460 | packetIndex.setTimestampEnd(tsEnd.longValue()); |
bfe038ff | 461 | setTimestampEnd(packetIndex.getTimestampEnd()); |
866e5b51 | 462 | } |
21fb02fa MK |
463 | |
464 | if (device != null) { | |
465 | packetIndex.setTarget(device); | |
466 | } | |
467 | ||
0594c61c AM |
468 | if (cpuId != null) { |
469 | packetIndex.setTarget("CPU" + cpuId.toString()); //$NON-NLS-1$ | |
21fb02fa | 470 | } |
132a02b0 MK |
471 | |
472 | if (lostEvents != null) { | |
f224bebc MK |
473 | packetIndex.setLostEvents(lostEvents - fLostSoFar); |
474 | fLostSoFar = lostEvents; | |
132a02b0 | 475 | } |
866e5b51 FC |
476 | } |
477 | ||
81c8e6f7 MK |
478 | @Override |
479 | public int hashCode() { | |
480 | final int prime = 31; | |
481 | int result = 1; | |
f224bebc | 482 | result = (prime * result) + ((fFile == null) ? 0 : fFile.hashCode()); |
81c8e6f7 MK |
483 | return result; |
484 | } | |
485 | ||
81c8e6f7 MK |
486 | @Override |
487 | public boolean equals(Object obj) { | |
488 | if (this == obj) { | |
489 | return true; | |
490 | } | |
491 | if (obj == null) { | |
492 | return false; | |
493 | } | |
494 | if (!(obj instanceof StreamInput)) { | |
495 | return false; | |
496 | } | |
497 | StreamInput other = (StreamInput) obj; | |
f224bebc MK |
498 | if (fFile == null) { |
499 | if (other.fFile != null) { | |
81c8e6f7 MK |
500 | return false; |
501 | } | |
f224bebc | 502 | } else if (!fFile.equals(other.fFile)) { |
81c8e6f7 MK |
503 | return false; |
504 | } | |
505 | return true; | |
506 | } | |
507 | ||
866e5b51 | 508 | } |