/******************************************************************************* * Copyright (c) 2010, 2014 Ericsson * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v1.0 which * accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Patrick Tasse - Initial API and implementation *******************************************************************************/ package fr.inria.linuxtools.tmf.core.parsers.custom; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; import java.util.regex.Matcher; 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 fr.inria.linuxtools.internal.tmf.core.Activator; import fr.inria.linuxtools.tmf.core.event.ITmfEvent; import fr.inria.linuxtools.tmf.core.exceptions.TmfTraceException; import fr.inria.linuxtools.tmf.core.io.BufferedRandomAccessFile; import fr.inria.linuxtools.tmf.core.parsers.custom.CustomTxtTraceDefinition.InputLine; import fr.inria.linuxtools.tmf.core.timestamp.TmfTimestamp; import fr.inria.linuxtools.tmf.core.trace.ITmfContext; import fr.inria.linuxtools.tmf.core.trace.ITmfEventParser; import fr.inria.linuxtools.tmf.core.trace.TmfContext; import fr.inria.linuxtools.tmf.core.trace.TmfTrace; import fr.inria.linuxtools.tmf.core.trace.TraceValidationStatus; import fr.inria.linuxtools.tmf.core.trace.indexer.ITmfPersistentlyIndexable; import fr.inria.linuxtools.tmf.core.trace.indexer.ITmfTraceIndexer; import fr.inria.linuxtools.tmf.core.trace.indexer.TmfBTreeTraceIndexer; import fr.inria.linuxtools.tmf.core.trace.indexer.checkpoint.ITmfCheckpoint; import fr.inria.linuxtools.tmf.core.trace.indexer.checkpoint.TmfCheckpoint; import fr.inria.linuxtools.tmf.core.trace.location.ITmfLocation; import fr.inria.linuxtools.tmf.core.trace.location.TmfLongLocation; /** * Base class for custom plain text traces. * * @author Patrick Tassé * @since 3.0 */ public class CustomTxtTrace extends TmfTrace implements ITmfEventParser, 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 MAX_CONFIDENCE = 100; private final CustomTxtTraceDefinition fDefinition; private final CustomTxtEventType fEventType; private BufferedRandomAccessFile fFile; /** * Basic constructor. * * @param definition * Text trace definition */ public CustomTxtTrace(final CustomTxtTraceDefinition definition) { fDefinition = definition; fEventType = new CustomTxtEventType(fDefinition); setCacheSize(DEFAULT_CACHE_SIZE); } /** * Full constructor. * * @param resource * Trace's resource. * @param definition * Text trace definition * @param path * Path to the trace file * @param cacheSize * Cache size to use * @throws TmfTraceException * If we couldn't open the trace at 'path' */ public CustomTxtTrace(final IResource resource, final CustomTxtTraceDefinition definition, final String path, final int cacheSize) throws TmfTraceException { this(definition); setCacheSize((cacheSize > 0) ? cacheSize : DEFAULT_CACHE_SIZE); initTrace(resource, path, CustomTxtEvent.class); } @Override public void initTrace(final IResource resource, final String path, final Class<? extends ITmfEvent> eventType) throws TmfTraceException { super.initTrace(resource, path, eventType); 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(); if (fFile != null) { try { fFile.close(); } catch (IOException e) { } finally { fFile = null; } } } @Override public ITmfTraceIndexer getIndexer() { return super.getIndexer(); } @Override public synchronized TmfContext seekEvent(final ITmfLocation location) { final CustomTxtTraceContext context = new CustomTxtTraceContext(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) { for (final InputLine input : getFirstLines()) { final Matcher matcher = input.getPattern().matcher(line); if (matcher.matches()) { context.setLocation(new TmfLongLocation(rawPos)); context.firstLineMatcher = matcher; context.firstLine = line; context.nextLineLocation = fFile.getFilePointer(); context.inputLine = input; return context; } } rawPos = fFile.getFilePointer(); line = fFile.getNextLine(); } return context; } catch (final FileNotFoundException e) { Activator.logError("Error seeking event. File not found: " + getPath(), e); //$NON-NLS-1$ 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 CustomTxtTraceContext(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 seeking event. File: " + getPath(), e); //$NON-NLS-1$ } return 0; } @Override public ITmfLocation getCurrentLocation() { // TODO Auto-generated method stub return null; } @Override public synchronized CustomTxtEvent parseEvent(final ITmfContext tmfContext) { ITmfContext context = seekEvent(tmfContext.getLocation()); return parse(context); } @Override public synchronized CustomTxtEvent getNext(final ITmfContext context) { final ITmfContext savedContext = new TmfContext(context.getLocation(), context.getRank()); final CustomTxtEvent event = parse(context); if (event != null) { updateAttributes(savedContext, event.getTimestamp()); context.increaseRank(); } return event; } private synchronized CustomTxtEvent parse(final ITmfContext tmfContext) { if (fFile == null) { return null; } if (!(tmfContext instanceof CustomTxtTraceContext)) { return null; } final CustomTxtTraceContext context = (CustomTxtTraceContext) tmfContext; if (context.getLocation() == null || !(context.getLocation().getLocationInfo() instanceof Long) || NULL_LOCATION.equals(context.getLocation())) { return null; } CustomTxtEvent event = parseFirstLine(context); final HashMap<InputLine, Integer> countMap = new HashMap<>(); InputLine currentInput = null; if (context.inputLine.childrenInputs != null && context.inputLine.childrenInputs.size() > 0) { currentInput = context.inputLine.childrenInputs.get(0); countMap.put(currentInput, 0); } try { if (fFile.getFilePointer() != context.nextLineLocation) { fFile.seek(context.nextLineLocation); } long rawPos = fFile.getFilePointer(); String line = fFile.getNextLine(); while (line != null) { boolean processed = false; if (currentInput == null) { for (final InputLine input : getFirstLines()) { final Matcher matcher = input.getPattern().matcher(line); if (matcher.matches()) { context.setLocation(new TmfLongLocation(rawPos)); context.firstLineMatcher = matcher; context.firstLine = line; context.nextLineLocation = fFile.getFilePointer(); context.inputLine = input; return event; } } } else { if (countMap.get(currentInput) >= currentInput.getMinCount()) { final List<InputLine> nextInputs = currentInput.getNextInputs(countMap); if (nextInputs.size() == 0 || nextInputs.get(nextInputs.size() - 1).getMinCount() == 0) { for (final InputLine input : getFirstLines()) { final Matcher matcher = input.getPattern().matcher(line); if (matcher.matches()) { context.setLocation(new TmfLongLocation(rawPos)); context.firstLineMatcher = matcher; context.firstLine = line; context.nextLineLocation = fFile.getFilePointer(); context.inputLine = input; return event; } } } for (final InputLine input : nextInputs) { final Matcher matcher = input.getPattern().matcher(line); if (matcher.matches()) { event.processGroups(input, matcher); currentInput = input; if (countMap.get(currentInput) == null) { countMap.put(currentInput, 1); } else { countMap.put(currentInput, countMap.get(currentInput) + 1); } Iterator<InputLine> iter = countMap.keySet().iterator(); while (iter.hasNext()) { final InputLine inputLine = iter.next(); if (inputLine.level > currentInput.level) { iter.remove(); } } if (currentInput.childrenInputs != null && currentInput.childrenInputs.size() > 0) { currentInput = currentInput.childrenInputs.get(0); countMap.put(currentInput, 0); } else if (countMap.get(currentInput) >= currentInput.getMaxCount()) { if (currentInput.getNextInputs(countMap).size() > 0) { currentInput = currentInput.getNextInputs(countMap).get(0); if (countMap.get(currentInput) == null) { countMap.put(currentInput, 0); } iter = countMap.keySet().iterator(); while (iter.hasNext()) { final InputLine inputLine = iter.next(); if (inputLine.level > currentInput.level) { iter.remove(); } } } else { currentInput = null; } } processed = true; break; } } } if (!processed && currentInput != null) { final Matcher matcher = currentInput.getPattern().matcher(line); if (matcher.matches()) { event.processGroups(currentInput, matcher); countMap.put(currentInput, countMap.get(currentInput) + 1); if (currentInput.childrenInputs != null && currentInput.childrenInputs.size() > 0) { currentInput = currentInput.childrenInputs.get(0); countMap.put(currentInput, 0); } else if (countMap.get(currentInput) >= currentInput.getMaxCount()) { if (currentInput.getNextInputs(countMap).size() > 0) { currentInput = currentInput.getNextInputs(countMap).get(0); if (countMap.get(currentInput) == null) { countMap.put(currentInput, 0); } final Iterator<InputLine> iter = countMap.keySet().iterator(); while (iter.hasNext()) { final InputLine inputLine = iter.next(); if (inputLine.level > currentInput.level) { iter.remove(); } } } else { currentInput = null; } } } ((StringBuffer) event.getContent().getValue()).append("\n").append(line); //$NON-NLS-1$ } } rawPos = fFile.getFilePointer(); line = fFile.getNextLine(); } } catch (final IOException e) { Activator.logError("Error seeking event. File: " + getPath(), e); //$NON-NLS-1$ } for (final Entry<InputLine, Integer> entry : countMap.entrySet()) { if (entry.getValue() < entry.getKey().getMinCount()) { event = null; } } context.setLocation(NULL_LOCATION); return event; } /** * @return The first few lines of the text file */ public List<InputLine> getFirstLines() { return fDefinition.inputs; } /** * Parse the first line of the trace (to recognize the type). * * @param context * Trace context * @return The first event */ public CustomTxtEvent parseFirstLine(final CustomTxtTraceContext context) { final CustomTxtEvent event = new CustomTxtEvent(fDefinition, this, TmfTimestamp.ZERO, "", fEventType, ""); //$NON-NLS-1$ //$NON-NLS-2$ event.processGroups(context.inputLine, context.firstLineMatcher); event.setContent(new CustomEventContent(event, new StringBuffer(context.firstLine))); return event; } /** * Get the trace definition. * * @return The trace definition */ public CustomTraceDefinition getDefinition() { return fDefinition; } /** * {@inheritDoc} * <p> * The default implementation computes the confidence as the percentage of * lines in the first 100 lines of the file which match any of the root * input line patterns. */ @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$ } int confidence = 0; try (BufferedRandomAccessFile rafile = new BufferedRandomAccessFile(path, "r")) { //$NON-NLS-1$ int lineCount = 0; int matches = 0; String line = rafile.getNextLine(); while ((line != null) && (lineCount++ < MAX_LINES)) { for (InputLine inputLine : fDefinition.inputs) { Matcher matcher = inputLine.getPattern().matcher(line); if (matcher.matches()) { matches++; break; } } confidence = MAX_CONFIDENCE * matches / lineCount; 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(confidence, 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); } }