| /******************************************************************************* |
| * 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; |
| } |
| } |