blob: d6d730c3c246fd0e29988e53cf459b8a3f3d1225 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2016 Ericsson
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License 2.0 which
* accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Patrick Tasse - Initial API and implementation
* Bernd Hufmann - Add trace type id handling
*******************************************************************************/
package org.eclipse.tracecompass.tmf.core.parsers.custom;
import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.util.Arrays;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.tracecompass.common.core.xml.XmlUtils;
import org.eclipse.tracecompass.internal.tmf.core.Activator;
import org.eclipse.tracecompass.internal.tmf.core.parsers.custom.CustomEventAspects;
import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
import org.eclipse.tracecompass.tmf.core.event.ITmfEventField;
import org.eclipse.tracecompass.tmf.core.event.aspect.ITmfEventAspect;
import org.eclipse.tracecompass.tmf.core.exceptions.TmfTraceException;
import org.eclipse.tracecompass.tmf.core.io.BufferedRandomAccessFile;
import org.eclipse.tracecompass.tmf.core.parsers.custom.CustomTraceDefinition.Tag;
import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
import org.eclipse.tracecompass.tmf.core.signal.TmfTraceRangeUpdatedSignal;
import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp;
import org.eclipse.tracecompass.tmf.core.trace.ITmfContext;
import org.eclipse.tracecompass.tmf.core.trace.TmfContext;
import org.eclipse.tracecompass.tmf.core.trace.TmfTrace;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils;
import org.eclipse.tracecompass.tmf.core.trace.TraceValidationStatus;
import org.eclipse.tracecompass.tmf.core.trace.indexer.ITmfPersistentlyIndexable;
import org.eclipse.tracecompass.tmf.core.trace.indexer.ITmfTraceIndexer;
import org.eclipse.tracecompass.tmf.core.trace.indexer.TmfBTreeTraceIndexer;
import org.eclipse.tracecompass.tmf.core.trace.indexer.checkpoint.ITmfCheckpoint;
import org.eclipse.tracecompass.tmf.core.trace.indexer.checkpoint.TmfCheckpoint;
import org.eclipse.tracecompass.tmf.core.trace.location.ITmfLocation;
import org.eclipse.tracecompass.tmf.core.trace.location.TmfLongLocation;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
/**
* Trace object for custom XML trace parsers.
*
* @author Patrick Tassé
*/
public class CustomXmlTrace extends TmfTrace implements ITmfPersistentlyIndexable {
private static final TmfLongLocation NULL_LOCATION = new TmfLongLocation(-1L);
private static final int DEFAULT_CACHE_SIZE = 100;
private static final int MAX_LINES = 100;
private static final int CONFIDENCE = 100;
private final CustomXmlTraceDefinition fDefinition;
private final ITmfEventField fRootField;
private final CustomXmlInputElement fRecordInputElement;
private BufferedRandomAccessFile fFile;
private final @NonNull String fTraceTypeId;
private static final char SEPARATOR = ':';
private static final String CUSTOM_XML_TRACE_TYPE_PREFIX = "custom.xml.trace" + SEPARATOR; //$NON-NLS-1$
private static final String LINUX_TOOLS_CUSTOM_XML_TRACE_TYPE_PREFIX = "org.eclipse.linuxtools.tmf.core.parsers.custom.CustomXmlTrace" + SEPARATOR; //$NON-NLS-1$
private static final String EARLY_TRACE_COMPASS_CUSTOM_XML_TRACE_TYPE_PREFIX = "org.eclipse.tracecompass.tmf.core.parsers.custom.CustomXmlTrace" + SEPARATOR; //$NON-NLS-1$
/**
* Basic constructor
*
* @param definition
* Trace definition
*/
public CustomXmlTrace(final CustomXmlTraceDefinition definition) {
fDefinition = definition;
fRootField = CustomEventType.getRootField(definition);
fRecordInputElement = getRecordInputElement(fDefinition.rootInputElement);
fTraceTypeId = buildTraceTypeId(definition.categoryName, definition.definitionName);
setCacheSize(DEFAULT_CACHE_SIZE);
}
/**
* Full constructor
*
* @param resource
* Trace resource
* @param definition
* Trace definition
* @param path
* Path to the trace/log file
* @param pageSize
* Page size to use
* @throws TmfTraceException
* If the trace/log couldn't be opened
*/
public CustomXmlTrace(final IResource resource,
final CustomXmlTraceDefinition definition, final String path,
final int pageSize) throws TmfTraceException {
this(definition);
setCacheSize((pageSize > 0) ? pageSize : DEFAULT_CACHE_SIZE);
initTrace(resource, path, CustomXmlEvent.class);
}
@Override
public void initTrace(final IResource resource, final String path, final Class<? extends ITmfEvent> eventType) throws TmfTraceException {
super.initTrace(resource, path, eventType);
initFile();
}
private void initFile() throws TmfTraceException {
closeFile();
try {
fFile = new BufferedRandomAccessFile(getPath(), "r"); //$NON-NLS-1$
} catch (IOException e) {
throw new TmfTraceException(e.getMessage(), e);
}
}
@Override
public synchronized void dispose() {
super.dispose();
closeFile();
}
private void closeFile() {
if (fFile != null) {
try {
fFile.close();
} catch (IOException e) {
} finally {
fFile = null;
}
}
}
@Override
public ITmfTraceIndexer getIndexer() {
return super.getIndexer();
}
@Override
public Iterable<ITmfEventAspect<?>> getEventAspects() {
return CustomEventAspects.generateAspects(fDefinition);
}
@Override
public synchronized TmfContext seekEvent(final ITmfLocation location) {
final CustomXmlTraceContext context = new CustomXmlTraceContext(NULL_LOCATION, ITmfContext.UNKNOWN_RANK);
if (NULL_LOCATION.equals(location) || fFile == null) {
return context;
}
try {
if (location == null) {
fFile.seek(0);
} else if (location.getLocationInfo() instanceof Long) {
fFile.seek((Long) location.getLocationInfo());
}
long rawPos = fFile.getFilePointer();
String line = fFile.getNextLine();
while (line != null) {
final int idx = indexOfElement(fRecordInputElement.getElementName(), line, 0);
if (idx != -1) {
context.setLocation(new TmfLongLocation(rawPos + idx));
return context;
}
rawPos = fFile.getFilePointer();
line = fFile.getNextLine();
}
return context;
} catch (final IOException e) {
Activator.logError("Error seeking event. File: " + getPath(), e); //$NON-NLS-1$
return context;
}
}
@Override
public synchronized TmfContext seekEvent(final double ratio) {
if (fFile == null) {
return new CustomTxtTraceContext(NULL_LOCATION, ITmfContext.UNKNOWN_RANK);
}
try {
long pos = Math.round(ratio * fFile.length());
while (pos > 0) {
fFile.seek(pos - 1);
if (fFile.read() == '\n') {
break;
}
pos--;
}
final ITmfLocation location = new TmfLongLocation(pos);
final TmfContext context = seekEvent(location);
context.setRank(ITmfContext.UNKNOWN_RANK);
return context;
} catch (final IOException e) {
Activator.logError("Error seeking event. File: " + getPath(), e); //$NON-NLS-1$
return new CustomXmlTraceContext(NULL_LOCATION, ITmfContext.UNKNOWN_RANK);
}
}
@Override
public synchronized double getLocationRatio(final ITmfLocation location) {
if (fFile == null) {
return 0;
}
try {
if (location.getLocationInfo() instanceof Long) {
return ((Long) location.getLocationInfo()).doubleValue() / fFile.length();
}
} catch (final IOException e) {
Activator.logError("Error getting location ration. File: " + getPath(), e); //$NON-NLS-1$
}
return 0;
}
@Override
public ITmfLocation getCurrentLocation() {
// TODO Auto-generated method stub
return null;
}
@Override
public synchronized CustomXmlEvent parseEvent(final ITmfContext tmfContext) {
ITmfContext context = seekEvent(tmfContext.getLocation());
return parse(context);
}
@Override
public synchronized CustomXmlEvent getNext(final ITmfContext context) {
final ITmfContext savedContext = new TmfContext(context.getLocation(), context.getRank());
final CustomXmlEvent event = parse(context);
if (event != null) {
updateAttributes(savedContext, event);
context.increaseRank();
}
return event;
}
private synchronized CustomXmlEvent parse(final ITmfContext tmfContext) {
if (fFile == null) {
return null;
}
if (!(tmfContext instanceof CustomXmlTraceContext)) {
return null;
}
final CustomXmlTraceContext context = (CustomXmlTraceContext) tmfContext;
ITmfLocation location = context.getLocation();
if (location == null || !(location.getLocationInfo() instanceof Long) || NULL_LOCATION.equals(location)) {
return null;
}
CustomXmlEvent event = null;
try {
// Below +1 for the <
if (fFile.getFilePointer() != (Long) location.getLocationInfo() + 1) {
fFile.seek((Long) location.getLocationInfo() + 1);
}
final StringBuffer elementBuffer = new StringBuffer("<"); //$NON-NLS-1$
readElement(elementBuffer, fFile);
final Element element = parseElementBuffer(elementBuffer);
event = extractEvent(element, fRecordInputElement);
((StringBuffer) event.getContentValue()).append(elementBuffer);
long rawPos = fFile.getFilePointer();
String line = fFile.getNextLine();
while (line != null) {
final int idx = indexOfElement(fRecordInputElement.getElementName(), line, 0);
if (idx != -1) {
context.setLocation(new TmfLongLocation(rawPos + idx));
return event;
}
rawPos = fFile.getFilePointer();
line = fFile.getNextLine();
}
} catch (final IOException e) {
Activator.logError("Error parsing event. File: " + getPath(), e); //$NON-NLS-1$
}
context.setLocation(NULL_LOCATION);
return event;
}
private Element parseElementBuffer(final StringBuffer elementBuffer) {
try {
final DocumentBuilder db = XmlUtils.newSafeDocumentBuilderFactory().newDocumentBuilder();
// The following allows xml parsing without access to the dtd
final EntityResolver resolver = (publicId, systemId) -> {
final String empty = ""; //$NON-NLS-1$
final ByteArrayInputStream bais = new ByteArrayInputStream(empty.getBytes());
return new InputSource(bais);
};
db.setEntityResolver(resolver);
// The following catches xml parsing exceptions
db.setErrorHandler(new ErrorHandler() {
@Override
public void error(final SAXParseException saxparseexception) throws SAXException {
// Do nothing
}
@Override
public void warning(final SAXParseException saxparseexception) throws SAXException {
// Do nothing
}
@Override
public void fatalError(final SAXParseException saxparseexception) throws SAXException {
throw saxparseexception;
}
});
final Document doc = db.parse(new ByteArrayInputStream(elementBuffer.toString().getBytes()));
return doc.getDocumentElement();
} catch (final ParserConfigurationException e) {
Activator.logError("Error parsing element buffer. File:" + getPath(), e); //$NON-NLS-1$
} catch (final SAXException e) {
Activator.logError("Error parsing element buffer. File:" + getPath(), e); //$NON-NLS-1$
} catch (final IOException e) {
Activator.logError("Error parsing element buffer. File: " + getPath(), e); //$NON-NLS-1$
}
return null;
}
private static int indexOfElement(String elementName, String line, int fromIndex) {
final String recordElementStart = '<' + elementName;
int index = line.indexOf(recordElementStart, fromIndex);
if (index == -1) {
return index;
}
int nextCharIndex = index + recordElementStart.length();
if (nextCharIndex < line.length()) {
char c = line.charAt(nextCharIndex);
// Check that the match is not just a substring of another element
if (Character.isLetterOrDigit(c)) {
return indexOfElement(elementName, line, nextCharIndex);
}
}
return index;
}
private void readElement(final StringBuffer buffer, final RandomAccessFile raFile) {
try {
int numRead = 0;
boolean startTagClosed = false;
int i;
while ((i = raFile.read()) != -1) {
numRead++;
final char c = (char) i;
buffer.append(c);
if (c == '"') {
readQuote(buffer, raFile, '"');
} else if (c == '\'') {
readQuote(buffer, raFile, '\'');
} else if (c == '<') {
readElement(buffer, raFile);
} else if (c == '/' && numRead == 1) {
break; // found "</"
} else if (c == '-' && numRead == 3 && buffer.substring(buffer.length() - 3, buffer.length() - 1).equals("!-")) { //$NON-NLS-1$
readComment(buffer, raFile); // found "<!--"
} else if (i == '>') {
if (buffer.charAt(buffer.length() - 2) == '/') {
break; // found "/>"
} else if (startTagClosed) {
break; // found "<...>...</...>"
}
else {
startTagClosed = true; // found "<...>"
}
}
}
return;
} catch (final IOException e) {
return;
}
}
private static void readQuote(final StringBuffer buffer,
final RandomAccessFile raFile, final char eq) {
try {
int i;
while ((i = raFile.read()) != -1) {
final char c = (char) i;
buffer.append(c);
if (c == eq)
{
break; // found matching end-quote
}
}
return;
} catch (final IOException e) {
return;
}
}
private static void readComment(final StringBuffer buffer,
final RandomAccessFile raFile) {
try {
int numRead = 0;
int i;
while ((i = raFile.read()) != -1) {
numRead++;
final char c = (char) i;
buffer.append(c);
if (c == '>' && numRead >= 2 && buffer.substring(buffer.length() - 3, buffer.length() - 1).equals("--")) //$NON-NLS-1$
{
break; // found "-->"
}
}
return;
} catch (final IOException e) {
return;
}
}
/**
* Parse an XML element.
*
* @param parentElement
* The parent element
* @param buffer
* The contents to parse
* @return The parsed content
*/
public static StringBuffer parseElement(final Element parentElement, final StringBuffer buffer) {
final NodeList nodeList = parentElement.getChildNodes();
String separator = null;
for (int i = 0; i < nodeList.getLength(); i++) {
final Node node = nodeList.item(i);
short nodeType = node.getNodeType();
if (nodeType == Node.ELEMENT_NODE) {
if (separator == null) {
separator = " | "; //$NON-NLS-1$
} else {
buffer.append(separator);
}
final Element element = (Element) node;
if (!element.hasChildNodes()) {
buffer.append(element.getNodeName());
} else if (element.getChildNodes().getLength() == 1 && element.getFirstChild().getNodeType() == Node.TEXT_NODE) {
buffer.append(element.getNodeName());
buffer.append(':');
buffer.append(element.getFirstChild().getNodeValue().trim());
} else {
buffer.append(element.getNodeName());
buffer.append(" [ "); //$NON-NLS-1$
parseElement(element, buffer);
buffer.append(" ]"); //$NON-NLS-1$
}
} else if ((nodeType == Node.TEXT_NODE) && (!node.getNodeValue().trim().isEmpty())) {
buffer.append(node.getNodeValue().trim());
}
}
return buffer;
}
/**
* Get an input element if it is a valid record input. If not, we will look
* into its children for valid inputs.
*
* @param inputElement
* The main element to check for.
* @return The record element
*/
public CustomXmlInputElement getRecordInputElement(final CustomXmlInputElement inputElement) {
if (inputElement.isLogEntry()) {
return inputElement;
} else if (inputElement.getChildElements() != null) {
for (final CustomXmlInputElement childInputElement : inputElement.getChildElements()) {
final CustomXmlInputElement recordInputElement = getRecordInputElement(childInputElement);
if (recordInputElement != null) {
return recordInputElement;
}
}
}
return null;
}
/**
* Extract a trace event from an XML element.
*
* @param element
* The element
* @param inputElement
* The input element
* @return The extracted event
*/
public CustomXmlEvent extractEvent(final Element element, final CustomXmlInputElement inputElement) {
CustomXmlEventType eventType = new CustomXmlEventType(checkNotNull(fDefinition.definitionName), fRootField);
final CustomXmlEvent event = new CustomXmlEvent(fDefinition, this, TmfTimestamp.ZERO, eventType);
event.setContent(new CustomEventContent(event, new StringBuffer()));
parseElement(element, event, inputElement);
return event;
}
private void parseElement(final Element element, final CustomXmlEvent event, final CustomXmlInputElement inputElement) {
String eventType = inputElement.getEventType();
if (eventType != null && event.getType() instanceof CustomEventType) {
((CustomEventType) event.getType()).setName(eventType);
}
if (!inputElement.getInputTag().equals(Tag.IGNORE)) {
event.parseInput(parseElement(element, new StringBuffer()).toString(), inputElement.getInputTag(), inputElement.getInputName(), inputElement.getInputAction(), inputElement.getInputFormat());
}
if (inputElement.getAttributes() != null) {
for (final CustomXmlInputAttribute attribute : inputElement.getAttributes()) {
event.parseInput(element.getAttribute(attribute.getAttributeName()), attribute.getInputTag(), attribute.getInputName(), attribute.getInputAction(), attribute.getInputFormat());
}
}
final NodeList childNodes = element.getChildNodes();
if (inputElement.getChildElements() != null) {
for (int i = 0; i < childNodes.getLength(); i++) {
final Node node = childNodes.item(i);
if (node instanceof Element) {
for (final CustomXmlInputElement child : inputElement.getChildElements()) {
if (node.getNodeName().equals(child.getElementName())) {
parseElement((Element) node, event, child);
break;
}
}
}
}
}
return;
}
/**
* Retrieve the trace definition.
*
* @return The trace definition
*/
public CustomTraceDefinition getDefinition() {
return fDefinition;
}
/**
* {@inheritDoc}
* <p>
* The default implementation sets the confidence to 100 if any of the first
* 100 lines of the file contains a valid record input element, and 0
* otherwise.
*/
@Override
public IStatus validate(IProject project, String path) {
File file = new File(path);
if (!file.exists() || !file.isFile() || !file.canRead()) {
return new Status(IStatus.ERROR, Activator.PLUGIN_ID, Messages.CustomTrace_FileNotFound + ": " + path); //$NON-NLS-1$
}
try {
if (!TmfTraceUtils.isText(file)) {
return new TraceValidationStatus(0, Activator.PLUGIN_ID);
}
} catch (IOException e) {
Activator.logError("Error validating file: " + path, e); //$NON-NLS-1$
return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "IOException validating file: " + path, e); //$NON-NLS-1$
}
try (BufferedRandomAccessFile rafile = new BufferedRandomAccessFile(path, "r")) { //$NON-NLS-1$
int lineCount = 0;
long rawPos = 0;
String line = rafile.getNextLine();
while ((line != null) && (lineCount++ < MAX_LINES)) {
final int idx = indexOfElement(fRecordInputElement.getElementName(), line, 0);
if (idx != -1) {
rafile.seek(rawPos + idx + 1); // +1 is for the <
final StringBuffer elementBuffer = new StringBuffer("<"); //$NON-NLS-1$
readElement(elementBuffer, rafile);
final Element element = parseElementBuffer(elementBuffer);
if (element != null) {
rafile.close();
return new TraceValidationStatus(CONFIDENCE, Activator.PLUGIN_ID);
}
}
rawPos = rafile.getFilePointer();
line = rafile.getNextLine();
}
} catch (IOException e) {
return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "IOException validating file: " + path, e); //$NON-NLS-1$
}
return new TraceValidationStatus(0, Activator.PLUGIN_ID);
}
private static int fCheckpointSize = -1;
@Override
public synchronized int getCheckpointSize() {
if (fCheckpointSize == -1) {
TmfCheckpoint c = new TmfCheckpoint(TmfTimestamp.ZERO, new TmfLongLocation(0L), 0);
ByteBuffer b = ByteBuffer.allocate(ITmfCheckpoint.MAX_SERIALIZE_SIZE);
b.clear();
c.serialize(b);
fCheckpointSize = b.position();
}
return fCheckpointSize;
}
@Override
public ITmfLocation restoreLocation(ByteBuffer bufferIn) {
return new TmfLongLocation(bufferIn);
}
@Override
protected ITmfTraceIndexer createIndexer(int interval) {
return new TmfBTreeTraceIndexer(this, interval);
}
@Override
public String getTraceTypeId() {
return fTraceTypeId;
}
/**
* Build the trace type id for a custom XML trace
*
* @param category
* the category
* @param definitionName
* the definition name
* @return the trace type id
*/
public static @NonNull String buildTraceTypeId(String category, String definitionName) {
return CUSTOM_XML_TRACE_TYPE_PREFIX + category + SEPARATOR + definitionName;
}
/**
* Checks whether the given trace type ID is a custom XML trace type ID
*
* @param traceTypeId
* the trace type ID to check
* @return <code>true</code> if it's a custom text trace type ID else <code>false</code>
*/
public static boolean isCustomTraceTypeId(@NonNull String traceTypeId) {
return traceTypeId.startsWith(CUSTOM_XML_TRACE_TYPE_PREFIX);
}
/**
* This methods builds a trace type ID from a given ID taking into
* consideration any format changes that were done for the IDs of custom
* XML traces. For example, such format change took place when moving to
* Trace Compass. Trace type IDs that are part of the plug-in extension for
* trace types won't be changed.
*
* This method is useful for IDs that were persisted in the workspace before
* the format changes (e.g. in the persistent properties of a trace
* resource).
*
* It ensures backwards compatibility of the workspace for custom XML
* traces.
*
* @param traceTypeId
* the legacy trace type ID
* @return the trace type id in Trace Compass format
*/
public static @NonNull String buildCompatibilityTraceTypeId(@NonNull String traceTypeId) {
// Handle early Trace Compass custom XML trace type IDs
if (traceTypeId.startsWith(EARLY_TRACE_COMPASS_CUSTOM_XML_TRACE_TYPE_PREFIX)) {
return CUSTOM_XML_TRACE_TYPE_PREFIX + traceTypeId.substring(EARLY_TRACE_COMPASS_CUSTOM_XML_TRACE_TYPE_PREFIX.length());
}
// Handle Linux Tools custom XML trace type IDs (with and without category)
int index = traceTypeId.lastIndexOf(SEPARATOR);
if ((index != -1) && (traceTypeId.startsWith(LINUX_TOOLS_CUSTOM_XML_TRACE_TYPE_PREFIX))) {
String definitionName = index < traceTypeId.length() ? traceTypeId.substring(index + 1) : ""; //$NON-NLS-1$
if (traceTypeId.contains(CustomXmlTrace.class.getSimpleName() + SEPARATOR) && traceTypeId.indexOf(SEPARATOR) == index) {
return buildTraceTypeId(CustomXmlTraceDefinition.CUSTOM_XML_CATEGORY, definitionName);
}
return CUSTOM_XML_TRACE_TYPE_PREFIX + traceTypeId.substring(LINUX_TOOLS_CUSTOM_XML_TRACE_TYPE_PREFIX.length());
}
return traceTypeId;
}
@TmfSignalHandler
@Override
public void traceRangeUpdated(TmfTraceRangeUpdatedSignal signal) {
if (signal.getTrace() == this) {
try {
synchronized (this) {
// Reset the file handle in case it has reached the end of the
// file already. Otherwise, it will not be able to read new data
// pass the previous end.
initFile();
}
} catch (TmfTraceException e) {
Activator.logError(e.getLocalizedMessage(), e);
}
}
super.traceRangeUpdated(signal);
}
/**
* @since 3.0
*/
@Override
public synchronized ITmfTimestamp readEnd() {
byte[] inputNameBytes = ("<" + fRecordInputElement.getElementName()).getBytes(); //$NON-NLS-1$
byte[] testBytes = new byte[inputNameBytes.length];
try {
Long pos = fFile.length() - inputNameBytes.length;
/* Outer loop to find the position of a matcher group. */
while (pos >= 0) {
/* Inner loop to find matching tag */
while (pos >= 0) {
fFile.seek(pos);
/* Make sure we have the right tag. */
fFile.read(testBytes, 0, testBytes.length);
if (Arrays.equals(inputNameBytes, testBytes)) {
break;
}
pos--;
}
ITmfLocation location = new TmfLongLocation(pos);
ITmfContext context = seekEvent(location);
ITmfEvent event = parseEvent(context);
context.dispose();
if (event != null) {
/* The last event in the trace was successfully parsed. */
return event.getTimestamp();
}
/*
* pos was after the beginning of the tag of the last event.
*/
pos -= inputNameBytes.length;
}
} catch (IOException e) {
Activator.logError("Error seeking last event. File: " + getPath(), e); //$NON-NLS-1$
}
/* Empty trace */
return null;
}
}