tmf: Remove the ITmfEventTableColumns extension point
[deliverable/tracecompass.git] / org.eclipse.tracecompass.tmf.core / src / org / eclipse / tracecompass / tmf / core / parsers / custom / CustomXmlTrace.java
1 /*******************************************************************************
2 * Copyright (c) 2010, 2014 Ericsson
3 *
4 * All rights reserved. This program and the accompanying materials are
5 * made 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:
10 * Patrick Tasse - Initial API and implementation
11 *******************************************************************************/
12
13 package org.eclipse.tracecompass.tmf.core.parsers.custom;
14
15 import java.io.ByteArrayInputStream;
16 import java.io.File;
17 import java.io.IOException;
18 import java.io.RandomAccessFile;
19 import java.nio.ByteBuffer;
20
21 import javax.xml.parsers.DocumentBuilder;
22 import javax.xml.parsers.DocumentBuilderFactory;
23 import javax.xml.parsers.ParserConfigurationException;
24
25 import org.eclipse.core.resources.IProject;
26 import org.eclipse.core.resources.IResource;
27 import org.eclipse.core.runtime.IStatus;
28 import org.eclipse.core.runtime.Status;
29 import org.eclipse.tracecompass.internal.tmf.core.Activator;
30 import org.eclipse.tracecompass.internal.tmf.core.parsers.custom.CustomEventAspects;
31 import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
32 import org.eclipse.tracecompass.tmf.core.event.aspect.ITmfEventAspect;
33 import org.eclipse.tracecompass.tmf.core.exceptions.TmfTraceException;
34 import org.eclipse.tracecompass.tmf.core.io.BufferedRandomAccessFile;
35 import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp;
36 import org.eclipse.tracecompass.tmf.core.trace.ITmfContext;
37 import org.eclipse.tracecompass.tmf.core.trace.ITmfEventParser;
38 import org.eclipse.tracecompass.tmf.core.trace.TmfContext;
39 import org.eclipse.tracecompass.tmf.core.trace.TmfTrace;
40 import org.eclipse.tracecompass.tmf.core.trace.TraceValidationStatus;
41 import org.eclipse.tracecompass.tmf.core.trace.indexer.ITmfPersistentlyIndexable;
42 import org.eclipse.tracecompass.tmf.core.trace.indexer.ITmfTraceIndexer;
43 import org.eclipse.tracecompass.tmf.core.trace.indexer.TmfBTreeTraceIndexer;
44 import org.eclipse.tracecompass.tmf.core.trace.indexer.checkpoint.ITmfCheckpoint;
45 import org.eclipse.tracecompass.tmf.core.trace.indexer.checkpoint.TmfCheckpoint;
46 import org.eclipse.tracecompass.tmf.core.trace.location.ITmfLocation;
47 import org.eclipse.tracecompass.tmf.core.trace.location.TmfLongLocation;
48 import org.w3c.dom.Document;
49 import org.w3c.dom.Element;
50 import org.w3c.dom.Node;
51 import org.w3c.dom.NodeList;
52 import org.xml.sax.EntityResolver;
53 import org.xml.sax.ErrorHandler;
54 import org.xml.sax.InputSource;
55 import org.xml.sax.SAXException;
56 import org.xml.sax.SAXParseException;
57
58 /**
59 * Trace object for custom XML trace parsers.
60 *
61 * @author Patrick Tassé
62 * @since 3.0
63 */
64 public class CustomXmlTrace extends TmfTrace implements ITmfEventParser, ITmfPersistentlyIndexable {
65
66 private static final TmfLongLocation NULL_LOCATION = new TmfLongLocation(-1L);
67 private static final int DEFAULT_CACHE_SIZE = 100;
68 private static final int MAX_LINES = 100;
69 private static final int CONFIDENCE = 100;
70
71 private final CustomXmlTraceDefinition fDefinition;
72 private final CustomXmlEventType fEventType;
73 private final CustomXmlInputElement fRecordInputElement;
74 private BufferedRandomAccessFile fFile;
75
76 /**
77 * Basic constructor
78 *
79 * @param definition
80 * Trace definition
81 */
82 public CustomXmlTrace(final CustomXmlTraceDefinition definition) {
83 fDefinition = definition;
84 fEventType = new CustomXmlEventType(fDefinition);
85 fRecordInputElement = getRecordInputElement(fDefinition.rootInputElement);
86 setCacheSize(DEFAULT_CACHE_SIZE);
87 }
88
89 /**
90 * Full constructor
91 *
92 * @param resource
93 * Trace resource
94 * @param definition
95 * Trace definition
96 * @param path
97 * Path to the trace/log file
98 * @param pageSize
99 * Page size to use
100 * @throws TmfTraceException
101 * If the trace/log couldn't be opened
102 */
103 public CustomXmlTrace(final IResource resource,
104 final CustomXmlTraceDefinition definition, final String path,
105 final int pageSize) throws TmfTraceException {
106 this(definition);
107 setCacheSize((pageSize > 0) ? pageSize : DEFAULT_CACHE_SIZE);
108 initTrace(resource, path, CustomXmlEvent.class);
109 }
110
111 @Override
112 public void initTrace(final IResource resource, final String path, final Class<? extends ITmfEvent> eventType) throws TmfTraceException {
113 super.initTrace(resource, path, eventType);
114 try {
115 fFile = new BufferedRandomAccessFile(getPath(), "r"); //$NON-NLS-1$
116 } catch (IOException e) {
117 throw new TmfTraceException(e.getMessage(), e);
118 }
119 }
120
121 @Override
122 public synchronized void dispose() {
123 super.dispose();
124 if (fFile != null) {
125 try {
126 fFile.close();
127 } catch (IOException e) {
128 } finally {
129 fFile = null;
130 }
131 }
132 }
133
134 @Override
135 public ITmfTraceIndexer getIndexer() {
136 return super.getIndexer();
137 }
138
139 @Override
140 public Iterable<ITmfEventAspect> getEventAspects() {
141 return CustomEventAspects.generateAspects(fDefinition);
142 }
143
144 @Override
145 public synchronized TmfContext seekEvent(final ITmfLocation location) {
146 final CustomXmlTraceContext context = new CustomXmlTraceContext(NULL_LOCATION, ITmfContext.UNKNOWN_RANK);
147 if (NULL_LOCATION.equals(location) || fFile == null) {
148 return context;
149 }
150 try {
151 if (location == null) {
152 fFile.seek(0);
153 } else if (location.getLocationInfo() instanceof Long) {
154 fFile.seek((Long) location.getLocationInfo());
155 }
156 long rawPos = fFile.getFilePointer();
157 String line = fFile.getNextLine();
158 while (line != null) {
159 final int idx = indexOfElement(fRecordInputElement.getElementName(), line, 0);
160 if (idx != -1) {
161 context.setLocation(new TmfLongLocation(rawPos + idx));
162 return context;
163 }
164 rawPos = fFile.getFilePointer();
165 line = fFile.getNextLine();
166 }
167 return context;
168 } catch (final IOException e) {
169 Activator.logError("Error seeking event. File: " + getPath(), e); //$NON-NLS-1$
170 return context;
171 }
172
173 }
174
175 @Override
176 public synchronized TmfContext seekEvent(final double ratio) {
177 if (fFile == null) {
178 return new CustomTxtTraceContext(NULL_LOCATION, ITmfContext.UNKNOWN_RANK);
179 }
180 try {
181 long pos = Math.round(ratio * fFile.length());
182 while (pos > 0) {
183 fFile.seek(pos - 1);
184 if (fFile.read() == '\n') {
185 break;
186 }
187 pos--;
188 }
189 final ITmfLocation location = new TmfLongLocation(pos);
190 final TmfContext context = seekEvent(location);
191 context.setRank(ITmfContext.UNKNOWN_RANK);
192 return context;
193 } catch (final IOException e) {
194 Activator.logError("Error seeking event. File: " + getPath(), e); //$NON-NLS-1$
195 return new CustomXmlTraceContext(NULL_LOCATION, ITmfContext.UNKNOWN_RANK);
196 }
197 }
198
199 @Override
200 public synchronized double getLocationRatio(final ITmfLocation location) {
201 if (fFile == null) {
202 return 0;
203 }
204 try {
205 if (location.getLocationInfo() instanceof Long) {
206 return ((Long) location.getLocationInfo()).doubleValue() / fFile.length();
207 }
208 } catch (final IOException e) {
209 Activator.logError("Error getting location ration. File: " + getPath(), e); //$NON-NLS-1$
210 }
211 return 0;
212 }
213
214 @Override
215 public ITmfLocation getCurrentLocation() {
216 // TODO Auto-generated method stub
217 return null;
218 }
219
220 @Override
221 public synchronized CustomXmlEvent parseEvent(final ITmfContext tmfContext) {
222 ITmfContext context = seekEvent(tmfContext.getLocation());
223 return parse(context);
224 }
225
226 @Override
227 public synchronized CustomXmlEvent getNext(final ITmfContext context) {
228 final ITmfContext savedContext = new TmfContext(context.getLocation(), context.getRank());
229 final CustomXmlEvent event = parse(context);
230 if (event != null) {
231 updateAttributes(savedContext, event.getTimestamp());
232 context.increaseRank();
233 }
234 return event;
235 }
236
237 private synchronized CustomXmlEvent parse(final ITmfContext tmfContext) {
238 if (fFile == null) {
239 return null;
240 }
241 if (!(tmfContext instanceof CustomXmlTraceContext)) {
242 return null;
243 }
244
245 final CustomXmlTraceContext context = (CustomXmlTraceContext) tmfContext;
246 if (context.getLocation() == null || !(context.getLocation().getLocationInfo() instanceof Long) || NULL_LOCATION.equals(context.getLocation())) {
247 return null;
248 }
249
250 CustomXmlEvent event = null;
251 try {
252 // Below +1 for the <
253 if (fFile.getFilePointer() != (Long) context.getLocation().getLocationInfo() + 1) {
254 fFile.seek((Long) context.getLocation().getLocationInfo() + 1);
255 }
256 final StringBuffer elementBuffer = new StringBuffer("<"); //$NON-NLS-1$
257 readElement(elementBuffer, fFile);
258 final Element element = parseElementBuffer(elementBuffer);
259
260 event = extractEvent(element, fRecordInputElement);
261 ((StringBuffer) event.getContent().getValue()).append(elementBuffer);
262
263 long rawPos = fFile.getFilePointer();
264 String line = fFile.getNextLine();
265 while (line != null) {
266 final int idx = indexOfElement(fRecordInputElement.getElementName(), line, 0);
267 if (idx != -1) {
268 context.setLocation(new TmfLongLocation(rawPos + idx));
269 return event;
270 }
271 rawPos = fFile.getFilePointer();
272 line = fFile.getNextLine();
273 }
274 } catch (final IOException e) {
275 Activator.logError("Error parsing event. File: " + getPath(), e); //$NON-NLS-1$
276
277 }
278 context.setLocation(NULL_LOCATION);
279 return event;
280 }
281
282 private Element parseElementBuffer(final StringBuffer elementBuffer) {
283 try {
284 final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
285 final DocumentBuilder db = dbf.newDocumentBuilder();
286
287 // The following allows xml parsing without access to the dtd
288 final EntityResolver resolver = new EntityResolver() {
289 @Override
290 public InputSource resolveEntity(final String publicId, final String systemId) {
291 final String empty = ""; //$NON-NLS-1$
292 final ByteArrayInputStream bais = new ByteArrayInputStream(empty.getBytes());
293 return new InputSource(bais);
294 }
295 };
296 db.setEntityResolver(resolver);
297
298 // The following catches xml parsing exceptions
299 db.setErrorHandler(new ErrorHandler() {
300 @Override
301 public void error(final SAXParseException saxparseexception) throws SAXException {
302 }
303
304 @Override
305 public void warning(final SAXParseException saxparseexception) throws SAXException {
306 }
307
308 @Override
309 public void fatalError(final SAXParseException saxparseexception) throws SAXException {
310 throw saxparseexception;
311 }
312 });
313
314 final Document doc = db.parse(new ByteArrayInputStream(elementBuffer.toString().getBytes()));
315 return doc.getDocumentElement();
316 } catch (final ParserConfigurationException e) {
317 Activator.logError("Error parsing element buffer. File:" + getPath(), e); //$NON-NLS-1$
318 } catch (final SAXException e) {
319 Activator.logError("Error parsing element buffer. File:" + getPath(), e); //$NON-NLS-1$
320 } catch (final IOException e) {
321 Activator.logError("Error parsing element buffer. File: " + getPath(), e); //$NON-NLS-1$
322 }
323 return null;
324 }
325
326 private static int indexOfElement(String elementName, String line, int fromIndex) {
327 final String recordElementStart = '<' + elementName;
328 int index = line.indexOf(recordElementStart, fromIndex);
329 if (index == -1) {
330 return index;
331 }
332 int nextCharIndex = index + recordElementStart.length();
333 if (nextCharIndex < line.length()) {
334 char c = line.charAt(nextCharIndex);
335 // Check that the match is not just a substring of another element
336 if (Character.isLetterOrDigit(c)) {
337 return indexOfElement(elementName, line, nextCharIndex);
338 }
339 }
340 return index;
341 }
342
343 private void readElement(final StringBuffer buffer, final RandomAccessFile raFile) {
344 try {
345 int numRead = 0;
346 boolean startTagClosed = false;
347 int i;
348 while ((i = raFile.read()) != -1) {
349 numRead++;
350 final char c = (char) i;
351 buffer.append(c);
352 if (c == '"') {
353 readQuote(buffer, raFile, '"');
354 } else if (c == '\'') {
355 readQuote(buffer, raFile, '\'');
356 } else if (c == '<') {
357 readElement(buffer, raFile);
358 } else if (c == '/' && numRead == 1) {
359 break; // found "</"
360 } else if (c == '-' && numRead == 3 && buffer.substring(buffer.length() - 3, buffer.length() - 1).equals("!-")) { //$NON-NLS-1$
361 readComment(buffer, raFile); // found "<!--"
362 } else if (i == '>') {
363 if (buffer.charAt(buffer.length() - 2) == '/') {
364 break; // found "/>"
365 } else if (startTagClosed) {
366 break; // found "<...>...</...>"
367 }
368 else {
369 startTagClosed = true; // found "<...>"
370 }
371 }
372 }
373 return;
374 } catch (final IOException e) {
375 return;
376 }
377 }
378
379 private static void readQuote(final StringBuffer buffer,
380 final RandomAccessFile raFile, final char eq) {
381 try {
382 int i;
383 while ((i = raFile.read()) != -1) {
384 final char c = (char) i;
385 buffer.append(c);
386 if (c == eq)
387 {
388 break; // found matching end-quote
389 }
390 }
391 return;
392 } catch (final IOException e) {
393 return;
394 }
395 }
396
397 private static void readComment(final StringBuffer buffer,
398 final RandomAccessFile raFile) {
399 try {
400 int numRead = 0;
401 int i;
402 while ((i = raFile.read()) != -1) {
403 numRead++;
404 final char c = (char) i;
405 buffer.append(c);
406 if (c == '>' && numRead >= 2 && buffer.substring(buffer.length() - 3, buffer.length() - 1).equals("--")) //$NON-NLS-1$
407 {
408 break; // found "-->"
409 }
410 }
411 return;
412 } catch (final IOException e) {
413 return;
414 }
415 }
416
417 /**
418 * Parse an XML element.
419 *
420 * @param parentElement
421 * The parent element
422 * @param buffer
423 * The contents to parse
424 * @return The parsed content
425 */
426 public static StringBuffer parseElement(final Element parentElement, final StringBuffer buffer) {
427 final NodeList nodeList = parentElement.getChildNodes();
428 String separator = null;
429 for (int i = 0; i < nodeList.getLength(); i++) {
430 final Node node = nodeList.item(i);
431 if (node.getNodeType() == Node.ELEMENT_NODE) {
432 if (separator == null) {
433 separator = " | "; //$NON-NLS-1$
434 } else {
435 buffer.append(separator);
436 }
437 final Element element = (Element) node;
438 if (!element.hasChildNodes()) {
439 buffer.append(element.getNodeName());
440 } else if (element.getChildNodes().getLength() == 1 && element.getFirstChild().getNodeType() == Node.TEXT_NODE) {
441 buffer.append(element.getNodeName() + ":" + element.getFirstChild().getNodeValue().trim()); //$NON-NLS-1$
442 } else {
443 buffer.append(element.getNodeName());
444 buffer.append(" [ "); //$NON-NLS-1$
445 parseElement(element, buffer);
446 buffer.append(" ]"); //$NON-NLS-1$
447 }
448 } else if (node.getNodeType() == Node.TEXT_NODE) {
449 if (node.getNodeValue().trim().length() != 0) {
450 buffer.append(node.getNodeValue().trim());
451 }
452 }
453 }
454 return buffer;
455 }
456
457 /**
458 * Get an input element if it is a valid record input. If not, we will look
459 * into its children for valid inputs.
460 *
461 * @param inputElement
462 * The main element to check for.
463 * @return The record element
464 */
465 public CustomXmlInputElement getRecordInputElement(final CustomXmlInputElement inputElement) {
466 if (inputElement.isLogEntry()) {
467 return inputElement;
468 } else if (inputElement.getChildElements() != null) {
469 for (final CustomXmlInputElement childInputElement : inputElement.getChildElements()) {
470 final CustomXmlInputElement recordInputElement = getRecordInputElement(childInputElement);
471 if (recordInputElement != null) {
472 return recordInputElement;
473 }
474 }
475 }
476 return null;
477 }
478
479 /**
480 * Extract a trace event from an XML element.
481 *
482 * @param element
483 * The element
484 * @param inputElement
485 * The input element
486 * @return The extracted event
487 */
488 public CustomXmlEvent extractEvent(final Element element, final CustomXmlInputElement inputElement) {
489 final CustomXmlEvent event = new CustomXmlEvent(fDefinition, this, TmfTimestamp.ZERO, fEventType);
490 event.setContent(new CustomEventContent(event, new StringBuffer()));
491 parseElement(element, event, inputElement);
492 return event;
493 }
494
495 private void parseElement(final Element element, final CustomXmlEvent event, final CustomXmlInputElement inputElement) {
496 if (inputElement.getInputName() != null && !inputElement.getInputName().equals(CustomXmlTraceDefinition.TAG_IGNORE)) {
497 event.parseInput(parseElement(element, new StringBuffer()).toString(), inputElement.getInputName(), inputElement.getInputAction(), inputElement.getInputFormat());
498 }
499 if (inputElement.getAttributes() != null) {
500 for (final CustomXmlInputAttribute attribute : inputElement.getAttributes()) {
501 event.parseInput(element.getAttribute(attribute.getAttributeName()), attribute.getInputName(), attribute.getInputAction(), attribute.getInputFormat());
502 }
503 }
504 final NodeList childNodes = element.getChildNodes();
505 if (inputElement.getChildElements() != null) {
506 for (int i = 0; i < childNodes.getLength(); i++) {
507 final Node node = childNodes.item(i);
508 if (node instanceof Element) {
509 for (final CustomXmlInputElement child : inputElement.getChildElements()) {
510 if (node.getNodeName().equals(child.getElementName())) {
511 parseElement((Element) node, event, child);
512 break;
513 }
514 }
515 }
516 }
517 }
518 return;
519 }
520
521 /**
522 * Retrieve the trace definition.
523 *
524 * @return The trace definition
525 */
526 public CustomTraceDefinition getDefinition() {
527 return fDefinition;
528 }
529
530 /**
531 * {@inheritDoc}
532 * <p>
533 * The default implementation sets the confidence to 100 if any of the first
534 * 100 lines of the file contains a valid record input element, and 0
535 * otherwise.
536 */
537 @Override
538 public IStatus validate(IProject project, String path) {
539 File file = new File(path);
540 if (!file.exists() || !file.isFile() || !file.canRead()) {
541 return new Status(IStatus.ERROR, Activator.PLUGIN_ID, Messages.CustomTrace_FileNotFound + ": " + path); //$NON-NLS-1$
542 }
543 try (BufferedRandomAccessFile rafile = new BufferedRandomAccessFile(path, "r")) { //$NON-NLS-1$
544 int lineCount = 0;
545 long rawPos = 0;
546 String line = rafile.getNextLine();
547 while ((line != null) && (lineCount++ < MAX_LINES)) {
548 final int idx = indexOfElement(fRecordInputElement.getElementName(), line, 0);
549 if (idx != -1) {
550 rafile.seek(rawPos + idx + 1); // +1 is for the <
551 final StringBuffer elementBuffer = new StringBuffer("<"); //$NON-NLS-1$
552 readElement(elementBuffer, rafile);
553 final Element element = parseElementBuffer(elementBuffer);
554 if (element != null) {
555 rafile.close();
556 return new TraceValidationStatus(CONFIDENCE, Activator.PLUGIN_ID);
557 }
558 }
559 rawPos = rafile.getFilePointer();
560 line = rafile.getNextLine();
561 }
562 } catch (IOException e) {
563 return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "IOException validating file: " + path, e); //$NON-NLS-1$
564 }
565 return new TraceValidationStatus(0, Activator.PLUGIN_ID);
566 }
567
568 private static int fCheckpointSize = -1;
569
570 @Override
571 public synchronized int getCheckpointSize() {
572 if (fCheckpointSize == -1) {
573 TmfCheckpoint c = new TmfCheckpoint(TmfTimestamp.ZERO, new TmfLongLocation(0L), 0);
574 ByteBuffer b = ByteBuffer.allocate(ITmfCheckpoint.MAX_SERIALIZE_SIZE);
575 b.clear();
576 c.serialize(b);
577 fCheckpointSize = b.position();
578 }
579
580 return fCheckpointSize;
581 }
582
583 @Override
584 public ITmfLocation restoreLocation(ByteBuffer bufferIn) {
585 return new TmfLongLocation(bufferIn);
586 }
587
588 @Override
589 protected ITmfTraceIndexer createIndexer(int interval) {
590 return new TmfBTreeTraceIndexer(this, interval);
591 }
592 }
This page took 0.049569 seconds and 5 git commands to generate.