1 /*******************************************************************************
2 * Copyright (c) 2014, 2016 École Polytechnique de Montréal
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
10 * Geneviève Bastien - Initial API and implementation
11 *******************************************************************************/
13 package org
.eclipse
.tracecompass
.internal
.tmf
.analysis
.xml
.core
.module
;
15 import static org
.eclipse
.tracecompass
.common
.core
.NonNullUtils
.nullToEmptyString
;
18 import java
.io
.FileInputStream
;
19 import java
.io
.FileNotFoundException
;
20 import java
.io
.FileOutputStream
;
21 import java
.io
.IOException
;
23 import java
.nio
.channels
.FileChannel
;
24 import java
.util
.ArrayList
;
25 import java
.util
.Collections
;
26 import java
.util
.HashMap
;
27 import java
.util
.List
;
30 import javax
.xml
.XMLConstants
;
31 import javax
.xml
.parsers
.DocumentBuilderFactory
;
32 import javax
.xml
.parsers
.ParserConfigurationException
;
33 import javax
.xml
.transform
.Source
;
34 import javax
.xml
.transform
.stream
.StreamSource
;
35 import javax
.xml
.validation
.Schema
;
36 import javax
.xml
.validation
.SchemaFactory
;
37 import javax
.xml
.validation
.Validator
;
39 import org
.eclipse
.core
.runtime
.FileLocator
;
40 import org
.eclipse
.core
.runtime
.IConfigurationElement
;
41 import org
.eclipse
.core
.runtime
.IPath
;
42 import org
.eclipse
.core
.runtime
.ISafeRunnable
;
43 import org
.eclipse
.core
.runtime
.IStatus
;
44 import org
.eclipse
.core
.runtime
.Path
;
45 import org
.eclipse
.core
.runtime
.Platform
;
46 import org
.eclipse
.core
.runtime
.SafeRunner
;
47 import org
.eclipse
.core
.runtime
.Status
;
48 import org
.eclipse
.jdt
.annotation
.NonNull
;
49 import org
.eclipse
.jdt
.annotation
.Nullable
;
50 import org
.eclipse
.osgi
.util
.NLS
;
51 import org
.eclipse
.tracecompass
.internal
.tmf
.analysis
.xml
.core
.Activator
;
52 import org
.eclipse
.tracecompass
.internal
.tmf
.analysis
.xml
.core
.stateprovider
.TmfXmlStrings
;
53 import org
.osgi
.framework
.Bundle
;
54 import org
.w3c
.dom
.Document
;
55 import org
.w3c
.dom
.Element
;
56 import org
.w3c
.dom
.Node
;
57 import org
.w3c
.dom
.NodeList
;
58 import org
.xml
.sax
.SAXException
;
59 import org
.xml
.sax
.SAXParseException
;
62 * Class containing some utilities for the XML plug-in packages: for example, it
63 * manages the XML files and validates them
65 * @author Geneviève Bastien
67 public class XmlUtils
{
69 /** Sub-directory of the plug-in where XML files are stored */
70 private static final String XML_DIRECTORY
= "xml_files"; //$NON-NLS-1$
72 /** Name of the XSD schema file */
73 private static final String XSD
= "xmlDefinition.xsd"; //$NON-NLS-1$
75 /** Extension point ID and attributes */
76 private static final String TMF_XML_BUILTIN_ID
= "org.eclipse.linuxtools.tmf.analysis.xml.core.files"; //$NON-NLS-1$
77 private static final String XML_FILE_ELEMENT
= "xmlfile"; //$NON-NLS-1$
78 private static final String XML_FILE_ATTRIB
= "file"; //$NON-NLS-1$
81 * Extension for XML files
83 public static final String XML_EXTENSION
= "xml"; //$NON-NLS-1$
85 /** Make this class non-instantiable */
91 * Get the path where the XML files are stored. Create it if it does not
94 * @return path to XML files
96 public static IPath
getXmlFilesPath() {
97 IPath path
= Activator
.getDefault().getStateLocation();
98 path
= path
.addTrailingSeparator().append(XML_DIRECTORY
);
100 /* Check if directory exists, otherwise create it */
101 File dir
= path
.toFile();
102 if (!dir
.exists() || !dir
.isDirectory()) {
110 * Validate the XML file input with the XSD schema
113 * XML file to validate
114 * @return True if the XML validates
116 public static IStatus
xmlValidate(File xmlFile
) {
117 URL url
= XmlUtils
.class.getResource(XSD
);
118 SchemaFactory schemaFactory
= SchemaFactory
.newInstance(XMLConstants
.W3C_XML_SCHEMA_NS_URI
);
119 Source xmlSource
= new StreamSource(xmlFile
);
121 Schema schema
= schemaFactory
.newSchema(url
);
122 Validator validator
= schema
.newValidator();
123 validator
.validate(xmlSource
);
124 } catch (SAXParseException e
) {
125 String error
= NLS
.bind(Messages
.XmlUtils_XmlParseError
, e
.getLineNumber(), e
.getLocalizedMessage());
126 Activator
.logError(error
);
127 return new Status(IStatus
.ERROR
, Activator
.PLUGIN_ID
, error
, e
);
128 } catch (SAXException e
) {
129 String error
= NLS
.bind(Messages
.XmlUtils_XmlValidationError
, e
.getLocalizedMessage());
130 Activator
.logError(error
);
131 return new Status(IStatus
.ERROR
, Activator
.PLUGIN_ID
, error
, e
);
132 } catch (IOException e
) {
133 String error
= Messages
.XmlUtils_XmlValidateError
;
134 Activator
.logError("IO exception occurred", e
); //$NON-NLS-1$
135 return new Status(IStatus
.ERROR
, Activator
.PLUGIN_ID
, error
, e
);
137 return Status
.OK_STATUS
;
141 * Adds an XML file to the plugin's path. The XML file should have been
142 * validated using the {@link XmlUtils#xmlValidate(File)} method before
143 * calling this method.
146 * The XML file to add
147 * @return Whether the file was successfully added
149 public static IStatus
addXmlFile(File fromFile
) {
151 /* Copy file to path */
152 File toFile
= getXmlFilesPath().addTrailingSeparator().append(fromFile
.getName()).toFile();
154 return copyXmlFile(fromFile
, toFile
);
158 * List all files under the XML analysis files path. It returns a map where
159 * the key is the file name.
161 * @return A map with all the XML analysis files
163 public static synchronized @NonNull Map
<String
, File
> listFiles() {
164 IPath pathToFiles
= XmlUtils
.getXmlFilesPath();
165 File folder
= pathToFiles
.toFile();
167 Map
<String
, File
> fileMap
= new HashMap
<>();
168 if ((folder
.isDirectory() && folder
.exists())) {
169 File
[] listOfFiles
= folder
.listFiles();
170 if (listOfFiles
!= null) {
171 for (File file
: listOfFiles
) {
172 IPath path
= new Path(file
.getName());
173 if (path
.getFileExtension().equals(XML_EXTENSION
)) {
174 fileMap
.put(file
.getName(), file
);
178 Activator
.logError("I/O error occured while accessing files in folder " + folder
.getPath()); //$NON-NLS-1$
181 return Collections
.unmodifiableMap(fileMap
);
185 * List all files advertised through the builtin extension point. It returns a map where the key is the file name.
187 * @return A map with all the XMl analysis builtin files
189 public static synchronized @NonNull Map
<String
, IPath
> listBuiltinFiles() {
190 /* Get the XML files advertised through the extension point */
191 IConfigurationElement
[] elements
= Platform
.getExtensionRegistry().getConfigurationElementsFor(TMF_XML_BUILTIN_ID
);
192 Map
<String
, IPath
> map
= new HashMap
<>();
193 for (IConfigurationElement element
: elements
) {
194 if (element
.getName().equals(XML_FILE_ELEMENT
)) {
195 final String filename
= element
.getAttribute(XML_FILE_ATTRIB
);
196 final String name
= element
.getContributor().getName();
197 // Run this in a safe runner in case there is an exception
198 // (IOException, FileNotFoundException, NPE, etc).
199 // This makes sure other extensions are not prevented from
200 // working if one is faulty.
201 SafeRunner
.run(new ISafeRunnable() {
204 public void run() throws IOException
{
206 Bundle bundle
= Platform
.getBundle(name
);
207 if (bundle
!= null) {
208 URL xmlUrl
= bundle
.getResource(filename
);
209 if (xmlUrl
== null) {
210 throw new FileNotFoundException(filename
);
212 URL locatedURL
= FileLocator
.toFileURL(xmlUrl
);
213 map
.put(filename
, new Path(locatedURL
.getPath()));
219 public void handleException(Throwable exception
) {
220 // Handled sufficiently in SafeRunner
229 * Delete an XML analysis file
232 * The XML file to delete
234 public static void deleteFile(String name
) {
235 Map
<String
, File
> files
= listFiles();
236 File file
= files
.get(name
);
244 * Export an XML analysis file to an external path
247 * The name of the file to export
249 * The full path of the file to write to
250 * @return Whether the file was successfully exported
252 public static IStatus
exportXmlFile(String from
, String to
) {
254 /* Copy file to path */
255 File fromFile
= getXmlFilesPath().addTrailingSeparator().append(from
).toFile();
257 if (!fromFile
.exists()) {
258 Activator
.logError("Failed to find XML analysis file " + fromFile
.getName()); //$NON-NLS-1$
259 return Status
.CANCEL_STATUS
;
262 File toFile
= new File(to
);
264 return copyXmlFile(fromFile
, toFile
);
267 private static IStatus
copyXmlFile(File fromFile
, File toFile
) {
269 if (!toFile
.exists()) {
270 toFile
.createNewFile();
272 } catch (IOException e
) {
273 String error
= Messages
.XmlUtils_ErrorCopyingFile
;
274 Activator
.logError(error
, e
);
275 return new Status(IStatus
.ERROR
, Activator
.PLUGIN_ID
, error
, e
);
278 try (FileInputStream fis
= new FileInputStream(fromFile
);
279 FileOutputStream fos
= new FileOutputStream(toFile
);
280 FileChannel source
= fis
.getChannel();
281 FileChannel destination
= fos
.getChannel();) {
282 destination
.transferFrom(source
, 0, source
.size());
283 } catch (IOException e
) {
284 String error
= Messages
.XmlUtils_ErrorCopyingFile
;
285 Activator
.logError(error
, e
);
286 return new Status(IStatus
.ERROR
, Activator
.PLUGIN_ID
, error
, e
);
288 return Status
.OK_STATUS
;
292 * Get the IDs of all the analysis described in a single file
296 * @return The list of IDs
298 public static List
<String
> getAnalysisIdsFromFile(String fileName
) {
299 List
<String
> ids
= new ArrayList
<>();
300 File file
= getXmlFilesPath().addTrailingSeparator().append(fileName
).toFile();
303 Document doc
= getDocumentFromFile(file
);
305 /* get State Providers modules */
306 NodeList stateproviderNodes
= doc
.getElementsByTagName(TmfXmlStrings
.STATE_PROVIDER
);
307 for (int i
= 0; i
< stateproviderNodes
.getLength(); i
++) {
308 ids
.add(nullToEmptyString(((Element
) stateproviderNodes
.item(i
)).getAttribute(TmfXmlStrings
.ID
)));
311 /* get patterns modules */
312 NodeList patternNodes
= doc
.getElementsByTagName(TmfXmlStrings
.PATTERN
);
313 for (int i
= 0; i
< patternNodes
.getLength(); i
++) {
314 ids
.add(nullToEmptyString(((Element
) patternNodes
.item(i
)).getAttribute(TmfXmlStrings
.ID
)));
316 } catch (ParserConfigurationException
| SAXException
| IOException e
) {
317 Activator
.logError("Failed to get analyses IDs from " + fileName
); //$NON-NLS-1$
328 * @return The document representing the XML file
329 * @throws ParserConfigurationException
330 * if a DocumentBuilder cannot be created
331 * @throws SAXException
332 * If any parse errors occur.
333 * @throws IOException
334 * If any IO errors occur.
336 public static Document
getDocumentFromFile(File file
) throws ParserConfigurationException
, SAXException
, IOException
{
337 DocumentBuilderFactory dbFactory
= DocumentBuilderFactory
.newInstance();
338 Document doc
= dbFactory
.newDocumentBuilder().parse(file
);
339 doc
.getDocumentElement().normalize();
344 * Get only the XML element children of an XML element.
347 * The parent element to get children from
348 * @return The list of children Element of the parent
350 public static @NonNull List
<@Nullable Element
> getChildElements(Element parent
) {
351 NodeList childNodes
= parent
.getChildNodes();
352 List
<@Nullable Element
> childElements
= new ArrayList
<>();
353 for (int index
= 0; index
< childNodes
.getLength(); index
++) {
354 if (childNodes
.item(index
).getNodeType() == Node
.ELEMENT_NODE
) {
355 childElements
.add((Element
) childNodes
.item(index
));
358 return childElements
;
362 * Get the XML children element of an XML element, but only those of a
366 * The parent element to get the children from
368 * The tag of the elements to return
369 * @return The list of children {@link Element} of the parent
371 public static List
<@NonNull Element
> getChildElements(Element parent
, String elementTag
) {
372 /* get the state providers and find the corresponding one */
373 NodeList nodes
= parent
.getElementsByTagName(elementTag
);
374 List
<@NonNull Element
> childElements
= new ArrayList
<>();
376 for (int i
= 0; i
< nodes
.getLength(); i
++) {
377 Element node
= (Element
) nodes
.item(i
);
378 if (node
.getParentNode().equals(parent
)) {
379 childElements
.add(node
);
382 return childElements
;
386 * Return the node element corresponding to the requested type in the file.
388 * TODO: Nothing prevents from having duplicate type -> id in a same file.
389 * That should not be allowed. If you want an element with the same ID as
390 * another one, it should be in a different file and we should check it at
394 * The absolute path to the XML file
396 * The type of top level element to search for
398 * The ID of the desired element
399 * @return The XML element or <code>null</code> if not found
401 public static Element
getElementInFile(String filePath
, @NonNull String elementType
, @NonNull String elementId
) {
403 if (filePath
== null) {
407 IPath path
= new Path(filePath
);
408 File file
= path
.toFile();
409 if (file
== null || !file
.exists() || !file
.isFile() || !xmlValidate(file
).isOK()) {
414 Document doc
= getDocumentFromFile(file
);
416 /* get the state providers and find the corresponding one */
417 NodeList nodes
= doc
.getElementsByTagName(elementType
);
418 Element foundNode
= null;
420 for (int i
= 0; i
< nodes
.getLength(); i
++) {
421 Element node
= (Element
) nodes
.item(i
);
422 String id
= node
.getAttribute(TmfXmlStrings
.ID
);
423 if (id
.equals(elementId
)) {
428 } catch (ParserConfigurationException
| SAXException
| IOException e
) {