blob: f78ad18481eeac433a83a7bd902f89339b2f1890 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2017 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
*******************************************************************************/
package org.eclipse.tracecompass.internal.tmf.core.markers;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.validation.SchemaFactory;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.SafeRunner;
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.markers.Marker.PeriodicMarker;
import org.eclipse.tracecompass.internal.tmf.core.markers.SubMarker.SplitMarker;
import org.eclipse.tracecompass.internal.tmf.core.markers.SubMarker.WeightedMarker;
import org.osgi.framework.Bundle;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import com.google.common.collect.ImmutableRangeSet;
import com.google.common.collect.Range;
import com.google.common.collect.RangeSet;
import com.google.common.collect.TreeRangeSet;
/**
* XML Parser for periodic marker configuration
*/
public class MarkerConfigXmlParser {
/** Default marker configuration file URL */
private static final URL DEFAULT_MARKER_CONFIG_URL = MarkerConfigXmlParser.class.getResource("/templates/markers.xml"); //$NON-NLS-1$
/** Marker configuration file name */
public static final IPath MARKER_CONFIG_PATH = Activator.getDefault().getStateLocation().addTrailingSeparator().append("markers.xml"); //$NON-NLS-1$
/** Marker configuration file */
private static final File MARKER_CONFIG_FILE = MARKER_CONFIG_PATH.toFile();
/** Marker configuration schema URL */
private static final URL MARKER_CONFIG_SCHEMA_URL = MarkerConfigXmlParser.class.getResource("/schema/markers.xsd"); //$NON-NLS-1$
/** Marker configuration schema path */
private static final IPath MARKER_CONFIG_SCHEMA_PATH = Activator.getDefault().getStateLocation().addTrailingSeparator().append("markers.xsd"); //$NON-NLS-1$
/** Marker configuration schema file */
private static final File MARKER_CONFIG_SCHEMA_FILE = MARKER_CONFIG_SCHEMA_PATH.toFile();
/** Default marker label */
private static final String DEFAULT_LABEL = "%d"; //$NON-NLS-1$
private static final @NonNull String ELLIPSIS = ".."; //$NON-NLS-1$
private static final @NonNull String TMF_CUSTOM_MARKERS_BUILTIN_EXTENSION_ID = "org.eclipse.tracecompass.tmf.core.custom.marker"; //$NON-NLS-1$
private static final @NonNull String ELEMENT_NAME_CUSTOM_MARKERS = "customMarker"; //$NON-NLS-1$
private static final @NonNull String ATTRIBUTE_NAME_FILE = "file"; //$NON-NLS-1$
private MarkerConfigXmlParser() {
// Do nothing, private constructor
}
/**
* Get the marker sets from the marker configuration file.
*
* @return the list of marker sets
*/
public static @NonNull List<MarkerSet> getMarkerSets() {
List<MarkerSet> markers = new ArrayList<>();
// add the MarkerSets from the editable marker config file
if (MARKER_CONFIG_FILE.exists()) {
markers.addAll(parse(MARKER_CONFIG_FILE.getAbsolutePath()));
}
// add the MarkerSets from the extension point
for (String extensionBookmark : getExtensionDefinitionsPaths()) {
markers.addAll(parse(extensionBookmark));
}
return markers;
}
/**
* Initialize the marker configuration file in the plug-in state location.
*/
public static void initMarkerSets() {
if (!MARKER_CONFIG_FILE.exists()) {
try {
File defaultConfigFile = new File(FileLocator.toFileURL(DEFAULT_MARKER_CONFIG_URL).toURI());
Files.copy(defaultConfigFile.toPath(), MARKER_CONFIG_FILE.toPath(), StandardCopyOption.REPLACE_EXISTING);
} catch (URISyntaxException | IOException e) {
Activator.logError("Error copying " + DEFAULT_MARKER_CONFIG_URL + " to " + MARKER_CONFIG_FILE.getAbsolutePath(), e); //$NON-NLS-1$ //$NON-NLS-2$
}
}
if (!MARKER_CONFIG_SCHEMA_FILE.exists()) {
try {
File schemaFile = new File(FileLocator.toFileURL(MARKER_CONFIG_SCHEMA_URL).toURI());
Files.copy(schemaFile.toPath(), MARKER_CONFIG_SCHEMA_FILE.toPath(), StandardCopyOption.REPLACE_EXISTING);
} catch (URISyntaxException | IOException e) {
Activator.logError("Error copying " + MARKER_CONFIG_SCHEMA_URL + " to " + MARKER_CONFIG_SCHEMA_FILE.getAbsolutePath(), e); //$NON-NLS-1$ //$NON-NLS-2$
}
}
}
/**
* Parse a periodic marker configuration file
*
* @param path
* the path to the configuration file
* @return the list of marker sets
*/
public static @NonNull List<MarkerSet> parse(String path) {
List<MarkerSet> markerSets = new ArrayList<>();
try {
DocumentBuilderFactory dbf = XmlUtils.newSafeDocumentBuilderFactory();
dbf.setValidating(false);
dbf.setNamespaceAware(true);
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
dbf.setSchema(schemaFactory.newSchema(MARKER_CONFIG_SCHEMA_URL));
DocumentBuilder db = dbf.newDocumentBuilder();
// The following catches xml parsing exceptions
db.setErrorHandler(new ErrorHandler() {
@Override
public void error(SAXParseException saxparseexception) throws SAXException {
throw saxparseexception;
}
@Override
public void warning(SAXParseException saxparseexception) throws SAXException {
throw saxparseexception;
}
@Override
public void fatalError(SAXParseException saxparseexception) throws SAXException {
throw saxparseexception;
}
});
File file = new File(path);
if (!file.canRead()) {
return markerSets;
}
Document doc = db.parse(file);
Element root = doc.getDocumentElement();
if (!root.getNodeName().equals(IMarkerConstants.MARKER_SETS)) {
return markerSets;
}
NodeList markerSetsList = root.getElementsByTagName(IMarkerConstants.MARKER_SET);
for (int i = 0; i < markerSetsList.getLength(); i++) {
try {
Element markerSetElem = (Element) markerSetsList.item(i);
String name = markerSetElem.getAttribute(IMarkerConstants.NAME);
String id = markerSetElem.getAttribute(IMarkerConstants.ID);
MarkerSet markerSet = new MarkerSet(name, id);
List<Marker> markers = getMarkers(markerSetElem);
for (Marker marker : markers) {
markerSet.addMarker(marker);
}
markerSets.add(markerSet);
} catch (IllegalArgumentException e) {
Activator.logError("Error parsing " + path, e); //$NON-NLS-1$
}
}
return markerSets;
} catch (ParserConfigurationException | SAXException | IOException e) {
Activator.logError("Error parsing " + path, e); //$NON-NLS-1$
}
return markerSets;
}
private static List<Marker> getMarkers(Element markerSet) {
List<Marker> markers = new ArrayList<>();
NodeList markerList = markerSet.getElementsByTagName(IMarkerConstants.MARKER);
for (int i = 0; i < markerList.getLength(); i++) {
Element markerElem = (Element) markerList.item(i);
String name = markerElem.getAttribute(IMarkerConstants.NAME);
String label = parseLabel(markerElem.getAttribute(IMarkerConstants.LABEL));
String id = markerElem.getAttribute(IMarkerConstants.ID);
String referenceId = markerElem.getAttribute(IMarkerConstants.REFERENCE_ID);
String color = markerElem.getAttribute(IMarkerConstants.COLOR);
double period = parsePeriod(markerElem.getAttribute(IMarkerConstants.PERIOD));
String unit = parseUnit(markerElem.getAttribute(IMarkerConstants.UNIT));
Range<Long> range = parseRange(markerElem.getAttribute(IMarkerConstants.RANGE));
long offset = parseOffset(markerElem.getAttribute(IMarkerConstants.OFFSET));
RangeSet<Long> indexRange = parseRangeSet(markerElem.getAttribute(IMarkerConstants.INDEX));
PeriodicMarker marker = new PeriodicMarker(name, label, id, referenceId, color, period, unit, range, offset, indexRange);
parseSubMarkers(markerElem, marker);
markers.add(marker);
}
return markers;
}
private static void parseSubMarkers(Element marker, Marker parent) {
NodeList nodeList = marker.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
if (node.getNodeName().equals(IMarkerConstants.SUBMARKER)) {
Element subMarkerElem = (Element) node;
String name = subMarkerElem.getAttribute(IMarkerConstants.NAME);
String label = parseLabel(subMarkerElem.getAttribute(IMarkerConstants.LABEL));
String id = subMarkerElem.getAttribute(IMarkerConstants.ID);
String color = subMarkerElem.getAttribute(IMarkerConstants.COLOR);
if (color.isEmpty()) {
color = parent.getColor();
}
String rangeAttr = subMarkerElem.getAttribute(IMarkerConstants.RANGE);
Range<Long> range = parseRange(rangeAttr);
if (!range.hasLowerBound() || !range.hasUpperBound()) {
throw new IllegalArgumentException("Unsupported unbound range: " + range); //$NON-NLS-1$
}
RangeSet<Long> indexRange = parseRangeSet(subMarkerElem.getAttribute(IMarkerConstants.INDEX));
SplitMarker subMarker = new SplitMarker(name, label, id, color, range, indexRange);
parent.addSubMarker(subMarker);
parseSubMarkers(subMarkerElem, subMarker);
} else if (node.getNodeName().equals(IMarkerConstants.SEGMENTS)) {
Element segmentsElem = (Element) node;
String name = segmentsElem.getAttribute(IMarkerConstants.NAME);
WeightedMarker subMarker = new WeightedMarker(name);
parent.addSubMarker(subMarker);
parseSegments(segmentsElem, subMarker);
parseSubMarkers(segmentsElem, subMarker);
}
}
}
}
private static void parseSegments(Element marker, WeightedMarker parent) {
NodeList nodeList = marker.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE && node.getNodeName().equals(IMarkerConstants.SEGMENT)) {
Element segmentElem = (Element) node;
String label = parseLabel(segmentElem.getAttribute(IMarkerConstants.LABEL));
String id = segmentElem.getAttribute(IMarkerConstants.ID);
String color = segmentElem.getAttribute(IMarkerConstants.COLOR);
String lengthAttr = segmentElem.getAttribute(IMarkerConstants.LENGTH);
int length = Integer.parseInt(lengthAttr);
if (length <= 0) {
throw new IllegalArgumentException("Unsupported length: " + lengthAttr); //$NON-NLS-1$
}
MarkerSegment segment = new MarkerSegment(label, id, color, length);
parent.addSegment(segment);
parseSubMarkers(segmentElem, segment);
}
}
}
private static String parseLabel(String labelAttr) {
if (labelAttr.isEmpty()) {
return DEFAULT_LABEL;
}
return labelAttr;
}
private static double parsePeriod(String periodAttr) {
double period = Double.parseDouble(periodAttr);
if (period <= 0) {
throw new IllegalArgumentException("Unsupported period: " + periodAttr); //$NON-NLS-1$
}
return period;
}
private static String parseUnit(String unitAttr) {
if (Arrays.asList(IMarkerConstants.MS, IMarkerConstants.US, IMarkerConstants.NS, IMarkerConstants.CYCLES).contains(unitAttr)) {
return unitAttr;
}
throw new IllegalArgumentException("Unsupported unit: " + unitAttr); //$NON-NLS-1$
}
private static Range<Long> parseRange(String rangeAttr) {
int index = rangeAttr.indexOf(ELLIPSIS);
if (index > 0) {
long min = Long.parseLong(rangeAttr.substring(0, index));
index += ELLIPSIS.length();
if (index < rangeAttr.length()) {
long max = Long.parseLong(rangeAttr.substring(index));
return Range.closed(min, max);
}
return Range.atLeast(min);
}
if (index == 0) {
index += ELLIPSIS.length();
if (index < rangeAttr.length()) {
long max = Long.parseLong(rangeAttr.substring(index));
return Range.atMost(max);
}
return Range.all();
}
if (!rangeAttr.isEmpty()) {
long val = Long.parseLong(rangeAttr);
return Range.singleton(val);
}
return Range.atLeast(0L);
}
private static RangeSet<Long> parseRangeSet(String rangeSetAttr) {
if (rangeSetAttr.isEmpty()) {
return ImmutableRangeSet.of(Range.all());
}
RangeSet<Long> rangeSet = TreeRangeSet.create();
String[] ranges = rangeSetAttr.split(","); //$NON-NLS-1$
if (ranges.length == 0) {
rangeSet.add(Range.all());
} else {
for (String range : ranges) {
rangeSet.add(parseRange(range));
}
}
return rangeSet;
}
private static long parseOffset(String offset) {
if (offset.isEmpty()) {
return 0L;
}
return Long.parseLong(offset);
}
/**
* Get all the custom marker definition paths contributed by extensions.
*
* @return the paths to the marker definition paths.
*/
private static final Collection<String> getExtensionDefinitionsPaths() {
List<String> extensionDefinitionsPaths = new ArrayList<>();
IConfigurationElement[] elements = Platform.getExtensionRegistry().getConfigurationElementsFor(TMF_CUSTOM_MARKERS_BUILTIN_EXTENSION_ID);
for (IConfigurationElement element : elements) {
if (!element.getName().equals(ELEMENT_NAME_CUSTOM_MARKERS)) {
continue;
}
final String filename = element.getAttribute(ATTRIBUTE_NAME_FILE);
final String name = element.getContributor().getName();
SafeRunner.run(new ISafeRunnable() {
@Override
public void run() throws IOException {
if (name != null) {
Bundle bundle = Platform.getBundle(name);
if (bundle != null) {
URL xmlUrl = bundle.getResource(filename);
URL locatedURL = FileLocator.toFileURL(xmlUrl);
extensionDefinitionsPaths.add(locatedURL.getPath());
}
}
}
@Override
public void handleException(Throwable exception) {
// Handled sufficiently in SafeRunner
}
});
}
return extensionDefinitionsPaths;
}
}