diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/jfc.xsd b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/jfc.xsd
index b09a5769c6f..205877cc67e 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/jfc.xsd
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/jfc.xsd
@@ -6,29 +6,19 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
+
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/AbortException.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/AbortException.java
new file mode 100644
index 00000000000..26685dcacb7
--- /dev/null
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/AbortException.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jfr.internal.jfc.model;
+
+public final class AbortException extends Exception {
+ private static final long serialVersionUID = -2501519883611363246L;
+}
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/Constraint.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/Constraint.java
new file mode 100644
index 00000000000..342b31d43ea
--- /dev/null
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/Constraint.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jfr.internal.jfc.model;
+
+record Constraint(Class extends XmlElement> type, int min, int max) {
+
+ public static Constraint any(Class extends XmlElement> type) {
+ return new Constraint(type, 0, Integer.MAX_VALUE);
+ }
+
+ public static Constraint atLeast(Class extends XmlElement> type, int min) {
+ return new Constraint(type, min, Integer.MAX_VALUE);
+ }
+}
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/ControlElement.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/ControlElement.java
new file mode 100644
index 00000000000..a91ddb025a9
--- /dev/null
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/ControlElement.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jfr.internal.jfc.model;
+
+// Interface for elements that can control a setting,
+// , , and
+interface ControlElement {
+ String getName();
+}
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/JFCModel.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/JFCModel.java
new file mode 100644
index 00000000000..a2ee14ad343
--- /dev/null
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/JFCModel.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jfr.internal.jfc.model;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.charset.Charset;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import jdk.jfr.internal.SecuritySupport.SafePath;
+
+// Holds the structure of a .jfc file similar to an XML DOM.
+public final class JFCModel {
+ private final Map> controls = new LinkedHashMap<>();
+ private final XmlConfiguration configuration;
+
+ public JFCModel(SafePath file) throws ParseException, IOException {
+ this.configuration = createConfiguration(file);
+ this.configuration.validate();
+ addControls();
+ wireConditions();
+ wireSettings();
+ }
+
+ public JFCModel(List files) throws IOException, ParseException {
+ this.configuration = new XmlConfiguration();
+ this.configuration.setAttribute("version", "2.0");
+ for (SafePath file : files) {
+ JFCModel model = new JFCModel(file);
+ for (var entry : model.controls.entrySet()) {
+ String name = entry.getKey();
+ // Fail-fast checks that prevents an ambiguous file to be written later
+ if (controls.containsKey(name)) {
+ throw new ParseException("Control with '" + name + "' is declared in multiple files", 0);
+ }
+ controls.put(name, entry.getValue());
+ }
+ for (XmlElement child : model.configuration.getChildren()) {
+ this.configuration.addChild(child);
+ }
+ }
+ }
+
+ public void setLabel(String label) {
+ configuration.setAttribute("label", label);
+ }
+
+ public void configure(String name, String value) {
+ for (XmlInput i : getInputs()) {
+ if (i.getName().equals(name)) {
+ i.configure(value);
+ return;
+ }
+ }
+ boolean add = name.startsWith("+");
+ if (add) {
+ name = name.substring(1);
+ }
+ int index = name.indexOf("#");
+ if (index < 1 || index == name.length() - 1) {
+ throw new IllegalArgumentException("Option '" + name + "' doesn't exist in configuration");
+ }
+ XmlEvent event = configuration.getEvent(name.substring(0, index), add);
+ String settingName = name.substring(index + 1);
+ XmlSetting setting = event.getSetting(settingName, add);
+
+ if (settingName.equals("period") || settingName.equals("threshold")) {
+ try {
+ value = Utilities.parseTimespan(value);
+ } catch (IllegalArgumentException iae) {
+ // OK, no validation to allow forward compatibility.
+ }
+ }
+
+ setting.setContent(value);
+ }
+
+ public void configure(UserInterface ui) throws AbortException {
+ for (XmlInput input : getInputs()) {
+ input.configure(ui);
+ }
+ }
+
+ public List getInputs() {
+ List inputs = new ArrayList<>();
+ for (XmlControl control : configuration.getControls()) {
+ inputs.addAll(control.getInputs());
+ }
+ return inputs;
+ }
+
+ public XmlConfiguration getConfiguration() {
+ return configuration;
+ }
+
+ public void saveToFile(SafePath path) throws IOException {
+ try (PrintWriter p = new PrintWriter(path.toFile(), Charset.forName("UTF-8"))) {
+ PrettyPrinter pp = new PrettyPrinter(p);
+ pp.print(configuration);
+ if (p.checkError()) {
+ throw new IOException("Error writing " + path);
+ }
+ }
+ }
+
+ private List getControlElements(String name) {
+ return controls.getOrDefault(name, Collections.emptyList());
+ }
+
+ private void addControls() {
+ for (var controls : configuration.getControls()) {
+ for (var control : controls.getControlElements()) {
+ add(control);
+ }
+ }
+ }
+
+ private void wireConditions() {
+ for (XmlControl control : configuration.getControls()) {
+ for (XmlCondition condition : control.getConditions()) {
+ for (XmlElement element : condition.getChildren()) {
+ wireExpression(condition, element);
+ }
+ }
+ }
+ }
+
+ private void wireExpression(XmlElement parent, XmlElement element) {
+ element.addListener(parent);
+ if (element instanceof XmlTest test) {
+ wireTest(test);
+ }
+ if (element instanceof XmlExpression expression) {
+ for (XmlExpression child : expression.getExpressions()) {
+ wireExpression(expression, child);
+ }
+ }
+ }
+
+ private void wireTest(XmlTest test) {
+ String name = test.getName();
+ for (ControlElement ce : getControlElements(name)) {
+ XmlElement control = (XmlElement) ce;
+ control.addListener(test);
+ }
+ }
+
+ private void wireSettings() {
+ for (XmlEvent event : configuration.getEvents()) {
+ for (XmlSetting setting : event.getSettings()) {
+ var controlName = setting.getControl();
+ if (controlName.isPresent()) {
+ List controls = getControlElements(controlName.get());
+ if (controls.isEmpty()) {
+ System.out.println("Warning! Setting '" + setting.getFullName() + "' refers to missing control '" + controlName.get() + "'");
+ }
+ for (ControlElement ce : controls) {
+ XmlElement control = (XmlElement) ce;
+ control.addListener(setting);
+ }
+ }
+ }
+ }
+ }
+
+ private void add(ControlElement control) {
+ controls.computeIfAbsent(control.getName(), x -> new ArrayList<>()).add(control);
+ }
+
+ private XmlConfiguration createConfiguration(SafePath file) throws ParseException, IOException {
+ if (file.toString().equals("none")) {
+ XmlConfiguration configuration = new XmlConfiguration();
+ configuration.setAttribute("version", "2.0");
+ configuration.setAttribute("label", "None");
+ return configuration;
+ }
+ return Parser.parse(file.toPath());
+ }
+}
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/Parser.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/Parser.java
new file mode 100644
index 00000000000..ad5dae8f896
--- /dev/null
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/Parser.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jfr.internal.jfc.model;
+
+import java.io.FileReader;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Path;
+import java.text.ParseException;
+import java.util.ArrayDeque;
+import java.util.Deque;
+
+import jdk.internal.org.xml.sax.Attributes;
+import jdk.internal.org.xml.sax.InputSource;
+import jdk.internal.org.xml.sax.SAXException;
+import jdk.internal.org.xml.sax.helpers.DefaultHandler;
+import jdk.internal.util.xml.SAXParser;
+import jdk.internal.util.xml.impl.SAXParserImpl;
+
+final class Parser {
+
+ static XmlConfiguration parse(Path path) throws ParseException, IOException {
+ try (FileReader r = new FileReader(path.toFile(), Charset.forName("UTF-8"))) {
+ SAXParser saxParser = new SAXParserImpl();
+ ConfigurationHandler handler = new ConfigurationHandler();
+ saxParser.parse(new InputSource(r), handler);
+ return handler.configuration;
+ } catch (SAXException sp) {
+ ParseException pe = new ParseException(sp.getMessage(), -1);
+ pe.initCause(sp);
+ throw pe;
+ }
+ }
+
+ private static final class ConfigurationHandler extends DefaultHandler {
+ private final Deque stack = new ArrayDeque<>();
+ private final StringBuilder buffer = new StringBuilder();
+ private XmlConfiguration configuration;
+
+ @Override
+ public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+ if (configuration == null) {
+ if (!qName.equalsIgnoreCase("configuration")) {
+ throw new SAXException("Expected root element to be named 'configuration'");
+ }
+ configuration = new XmlConfiguration();
+ addAttributes(configuration, attributes);
+ stack.push(configuration);
+ return;
+ }
+ XmlElement current = stack.peek();
+ XmlElement child = current.createChild(qName);
+ addAttributes(child, attributes);
+ stack.push(child);
+ }
+
+ @Override
+ public void characters(char ch[], int start, int length) throws SAXException {
+ buffer.append(ch, start, length);
+ }
+
+ @Override
+ public void endElement(String uri, String localName, String qName) {
+ String content = buffer.toString().strip();
+ if (!content.isEmpty()) {
+ stack.peek().setContent(content);
+ buffer.setLength(0);
+ }
+
+ XmlElement current = stack.peek();
+ if (current.getElementName().equalsIgnoreCase(qName)) {
+ stack.pop();
+ } else {
+ throw new IllegalStateException("Unexpected <" + qName + "/>");
+ }
+ }
+
+ private void addAttributes(XmlElement element, Attributes attributes) {
+ for (int i = 0; i < attributes.getLength(); i++) {
+ element.setAttribute(attributes.getQName(i), attributes.getValue(i));
+ }
+ }
+ }
+}
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/PrettyPrinter.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/PrettyPrinter.java
new file mode 100644
index 00000000000..087337bd219
--- /dev/null
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/PrettyPrinter.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jfr.internal.jfc.model;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+final class PrettyPrinter {
+ private final PrintWriter out;
+
+ PrettyPrinter(PrintWriter out) {
+ this.out = out;
+ }
+
+ void print(XmlConfiguration configuration) {
+ printHeader();
+ prettyPrint("", configuration);
+ }
+
+ private void printHeader() {
+ out.println("");
+ }
+
+ private void prettyPrint(String indent, XmlElement element) {
+ printComment(indent, element);
+ String elementName = element.getElementName();
+ out.print(indent + '<' + elementName);
+ printAttributes(element.getAttributes());
+ if (element.getChildren().isEmpty() && !element.hasContent()) {
+ out.println("/>");
+ return;
+ }
+ out.print('>');
+ out.print(Utilities.escapeAll(element.getContent().trim()));
+ if (element.getChildren().isEmpty()) {
+ out.println("" + elementName + '>');
+ return;
+ }
+ out.println();
+ boolean first = true;
+ for (XmlElement child : element.getChildren()) {
+ if (first && child.isEntity()) {
+ out.println();
+ }
+ prettyPrint(indent + " ", child);
+ if (child.isEntity()) {
+ out.println();
+ }
+ first = false;
+ }
+ out.println(indent + "" + elementName + '>');
+ }
+
+ private void printComment(String indent, XmlElement element) {
+ String comment = element.comment();
+ if (!comment.isEmpty()) {
+ String text = comment.indent(indent.length());
+ out.println(indent + "");
+ }
+ }
+
+ private void printAttributes(Map attributes) {
+ for (var entry : attributes.entrySet()) {
+ out.print(' ');
+ out.print(entry.getKey());
+ out.print("=\"");
+ out.print(Utilities.escapeAll(entry.getValue()));
+ out.print('\"');
+ }
+ }
+}
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/Result.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/Result.java
new file mode 100644
index 00000000000..ae4e4b6cd2a
--- /dev/null
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/Result.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jfr.internal.jfc.model;
+
+record Result(String value) {
+ public static final Result FALSE = new Result("false");
+ public static final Result TRUE = new Result("true");
+ public static final Result NULL = new Result(null);
+
+ public boolean isTrue() {
+ return "true".equalsIgnoreCase(value);
+ }
+
+ public boolean isNull() {
+ return value == null;
+ }
+
+ public boolean isFalse() {
+ return "false".equalsIgnoreCase(value);
+ }
+
+ public static Result of(String value) {
+ if ("true".equalsIgnoreCase(value)) {
+ return TRUE;
+ }
+ if ("false".equalsIgnoreCase(value)) {
+ return FALSE;
+ }
+ return new Result(value);
+ }
+}
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/SettingsLog.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/SettingsLog.java
new file mode 100644
index 00000000000..11568664984
--- /dev/null
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/SettingsLog.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.jfr.internal.jfc.model;
+
+import java.util.Map;
+import java.util.TreeMap;
+
+public final class SettingsLog {
+ private static final Map settings = new TreeMap<>();
+ private static boolean enabled;
+
+ public static void enable() {
+ enabled = true;
+ }
+
+ public static void flush() {
+ if (!settings.isEmpty()) {
+ System.out.println();
+ System.out.println("Setting:");
+ for (var s : settings.entrySet()) {
+ System.out.println("\"" + s.getKey() + "=" + s.getValue() + "\"");
+ }
+ settings.clear();
+ }
+ }
+
+ static void log(XmlSetting setting, String value) {
+ if (enabled) {
+ settings.put(setting.getFullName(), value);
+ }
+ }
+}
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/UserInterface.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/UserInterface.java
new file mode 100644
index 00000000000..7d9fc2478ba
--- /dev/null
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/UserInterface.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jfr.internal.jfc.model;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+public final class UserInterface {
+
+ public void println() {
+ System.out.println();
+ }
+
+ public void println(String text) {
+ System.out.println(text);
+ }
+
+ public String readLine() throws AbortException {
+ try {
+ BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
+ String line = br.readLine();
+ if (line == null || line.equalsIgnoreCase("Q")) {
+ println();
+ throw new AbortException();
+ }
+ return line;
+ } catch (IOException e) {
+ throw new Error("Unable to read input", e);
+ }
+ }
+}
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/Utilities.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/Utilities.java
new file mode 100644
index 00000000000..76e834fe6eb
--- /dev/null
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/Utilities.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jfr.internal.jfc.model;
+
+import java.util.StringJoiner;
+
+public final class Utilities {
+ private static final String[] UNITS = new String[] {
+ "ns", "us", "ns", "ms", "s", "m", "h", "d" // order matters
+ };
+
+ static XmlElement instantiate(Class extends XmlElement> type) {
+ try {
+ return type.getDeclaredConstructor().newInstance();
+ } catch (Exception e) {
+ throw new InternalError("Unable to instantiate " + type, e);
+ }
+ }
+
+ static String elementName(Class extends XmlElement> type) {
+ String name = type.getSimpleName();
+ if (name.startsWith("Xml") && name.length() > 3) {
+ return name.substring(3).toLowerCase();
+ }
+ throw new InternalError("Unexpected class " + type);
+ }
+
+ static String escapeAll(String text) {
+ StringBuilder s = new StringBuilder();
+ for (int i = 0; i < text.length(); i++) {
+ addCharacter(s, text.charAt(i));
+ }
+ return s.toString();
+ }
+
+ private static void addCharacter(StringBuilder s, char c) {
+ if (c == 34) {
+ s.append(""");
+ return;
+ }
+ if (c == 38) {
+ s.append("&");
+ return;
+ }
+ if (c == 39) {
+ s.append("'");
+ return;
+ }
+ if (c == 60) {
+ s.append("<");
+ return;
+ }
+ if (c == 62) {
+ s.append(">");
+ return;
+ }
+ if (c > 0x7F) {
+ s.append("");
+ s.append((int) c);
+ s.append(';');
+ return;
+ }
+ s.append(c);
+ }
+
+ static void checkValid(String value, Object... valid) {
+ StringJoiner sj = new StringJoiner(", ");
+ for (Object v : valid) {
+ if (v.equals(value)) {
+ return;
+ }
+ sj.add("'" + v + "'");
+ }
+ String msg = "Incorrect value '" + value + "'. Valid values are " + sj.toString() + ".";
+ int index = msg.lastIndexOf(",");
+ if (index != -1) {
+ msg = msg.substring(0, index) + " and" + msg.substring(index + 1);
+ }
+ throw new IllegalArgumentException(msg);
+ }
+
+ static String parseTimespan(String s) {
+ StringBuilder sb = new StringBuilder();
+ try {
+ for (String unit : UNITS) {
+ if (s.endsWith(unit)) {
+ return parseForUnit(s, unit);
+ }
+ }
+ Long.parseLong(s);
+ sb.append("Timespan '" + s + "' is missing unit.");
+ } catch (NumberFormatException nfe) {
+ sb.append("'" + s + "' is not a valid timespan." + System.lineSeparator());
+ sb.append("Should be numeric value followed by a unit, i.e. 20 ms.");
+ }
+ sb.append(" Valid units are ns, us, ms, s, m, h and d.");
+ throw new IllegalArgumentException(sb.toString());
+ }
+
+ private static String parseForUnit(String s, String unit) {
+ String number = s.trim().substring(0, s.length() - unit.length());
+ return Long.parseLong(number.trim()) + " " + unit;
+ }
+}
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlAnd.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlAnd.java
new file mode 100644
index 00000000000..e7c7d89d294
--- /dev/null
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlAnd.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jfr.internal.jfc.model;
+
+// Corresponds to
+final class XmlAnd extends XmlExpression {
+
+ @Override
+ boolean isEntity() {
+ return false;
+ }
+
+ @Override
+ protected Result evaluate() {
+ Result result = Result.NULL;
+ for (XmlElement e : getProducers()) {
+ Result r = e.evaluate();
+ if (r.isFalse()) {
+ return Result.FALSE;
+ }
+ if (r.isTrue()) {
+ result = Result.TRUE;
+ }
+ }
+ return result;
+ }
+}
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlCondition.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlCondition.java
new file mode 100644
index 00000000000..21cd990f0c8
--- /dev/null
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlCondition.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jfr.internal.jfc.model;
+
+import java.text.ParseException;
+import java.util.List;
+import java.util.Optional;
+
+// Corresponds
+final class XmlCondition extends XmlExpression implements ControlElement {
+
+ @Override
+ public String getName() {
+ return attribute("name");
+ }
+
+ public Result getTrueValue() {
+ return Result.of(attribute("true"));
+ }
+
+ public Result getFalseValue() {
+ return Result.of(attribute("false"));
+ }
+
+ @Override
+ protected void validateChildConstraints() throws ParseException {
+ if (getExpressions().size() > 1) {
+ throw new ParseException("Expected to not have more than one child", -1);
+ }
+ }
+
+ @Override
+ protected List attributes() {
+ return List.of("name");
+ }
+
+ @Override
+ protected Result evaluate() {
+ Optional trueValue = optional("true");
+ Optional falseValue = optional("false");
+ for (XmlElement producer : getProducers()) {
+ Result r = producer.evaluate();
+ if (trueValue.isPresent() && r.isTrue()) {
+ return getTrueValue();
+ }
+ if (falseValue.isPresent() && r.isFalse()) {
+ return getFalseValue();
+ }
+ }
+ return Result.NULL;
+ }
+}
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlConfiguration.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlConfiguration.java
new file mode 100644
index 00000000000..6a6c96dc597
--- /dev/null
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlConfiguration.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jfr.internal.jfc.model;
+
+import java.text.ParseException;
+import java.util.List;
+import java.util.Optional;
+
+// Corresponds to
+final class XmlConfiguration extends XmlElement {
+
+ public List getEvents() {
+ return elements(XmlEvent.class);
+ }
+
+ public Optional getDescription() {
+ return optional("description");
+ }
+
+ public Optional getLabel() {
+ return optional("label");
+ }
+
+ public Optional getProvider() {
+ return optional("provider");
+ }
+
+ public Optional getVersion() {
+ return optional("version");
+ }
+
+ public List getControls() {
+ return elements(XmlControl.class);
+ }
+
+ @Override
+ String comment() {
+ return """
+ Recommended way to edit .jfc files is to use the configure command of
+ the 'jfr' tool, i.e. jfr configure, or JDK Mission Control
+ see Window -> Flight Recorder Template Manager
+ """;
+ }
+
+ XmlEvent getEvent(String eventName, boolean add) {
+ for (XmlEvent event : getEvents()) {
+ if (eventName.equals(event.getName())) {
+ return event;
+ }
+ }
+ if (!add) {
+ throw new IllegalArgumentException("Could not find event '" + eventName + "'");
+ }
+ XmlEvent event = new XmlEvent();
+ event.setAttribute("name", eventName);
+ addChild(event);
+ return event;
+ }
+
+ @Override
+ protected List attributes() {
+ return List.of("version", "label");
+ }
+
+ @Override
+ protected void validateAttributes() throws ParseException {
+ super.validateAttributes();
+ if (!attribute("version").equals("2.0")) {
+ throw new ParseException("Only .jfc files of version 2.0 is supported", -1);
+ }
+ }
+
+ @Override
+ protected List constraints() {
+ return List.of(
+ Constraint.any(XmlEvent.class),
+ Constraint.any(XmlControl.class)
+ );
+ }
+}
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlControl.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlControl.java
new file mode 100644
index 00000000000..f2574de35e5
--- /dev/null
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlControl.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jfr.internal.jfc.model;
+
+import java.util.List;
+
+// Corresponds to
+final class XmlControl extends XmlElement {
+
+ public List getInputs() {
+ return elements(XmlInput.class);
+ }
+
+ public List getConditions() {
+ return elements(XmlCondition.class);
+ }
+
+ // Returns list of all , , and
+ public List getControlElements() {
+ return elements(ControlElement.class);
+ }
+
+ @Override
+ String comment() {
+ return """
+ Contents of the control element is not read by the JVM, it's used
+ by JDK Mission Control and the 'jfr'-tool to change settings that
+ carry the control attribute.
+ """;
+ }
+
+ @Override
+ protected List constraints() {
+ return List.of(
+ Constraint.any(XmlCondition.class),
+ Constraint.any(XmlText.class),
+ Constraint.any(XmlSelection.class),
+ Constraint.any(XmlFlag.class)
+ );
+ }
+}
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlElement.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlElement.java
new file mode 100644
index 00000000000..a128a645d24
--- /dev/null
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlElement.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jfr.internal.jfc.model;
+
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+// Base class for XML-elements
+class XmlElement {
+ private final Map attributes = new HashMap<>();
+ private final List elements = new ArrayList<>();
+ private final List listeners = new ArrayList<>(2);
+ private final List producers = new ArrayList<>(2);
+ private final String elementName;
+
+ private XmlElement parent;
+ private String content = "";
+
+ XmlElement() {
+ this.elementName = Utilities.elementName(this.getClass());
+ }
+
+ private XmlElement(String elementName) {
+ this.elementName = elementName;
+ }
+
+ final String getElementName() {
+ return elementName;
+ }
+
+ final boolean hasContent() {
+ return content != null && !content.isEmpty();
+ }
+
+ final Map getAttributes() {
+ return attributes;
+ }
+
+ final void validate() throws ParseException {
+ validateAttributes();
+ validateChildConstraints();
+ validateChildren();
+ }
+
+ final void setAttribute(String key, String value) {
+ Objects.requireNonNull(key);
+ Objects.requireNonNull(key);
+ attributes.put(key, value);
+ }
+
+ final XmlElement createChild(String name) {
+ XmlElement child = instantiate(name);
+ child.parent = this;
+ addChild(child);
+ return child;
+ }
+
+ private XmlElement instantiate(String name) {
+ for (var e : constraints()) {
+ String elementName = Utilities.elementName(e.type());
+ if (elementName.equalsIgnoreCase(name)) {
+ return Utilities.instantiate(e.type());
+ }
+ }
+ return new XmlElement(name);
+ }
+
+ final void addChild(XmlElement element) {
+ elements.add(element);
+ }
+
+ final List getChildren() {
+ return elements;
+ }
+
+ void setContent(String content) {
+ this.content = content;
+ }
+
+ final String getContent() {
+ return content;
+ }
+
+ final void addListener(XmlElement listener) {
+ listeners.add(listener);
+ listener.addProducer(this);
+ }
+
+ // If the element should be surrounded with an empty
+ // line when printed to file, for example, but not
+ boolean isEntity() {
+ return true;
+ }
+
+ String comment() {
+ return "";
+ }
+
+ protected final void notifyListeners() {
+ onChange();
+ SettingsLog.flush();
+ }
+
+ protected void onChange() {
+ for (XmlElement listener : listeners) {
+ listener.onChange();
+ }
+ }
+
+ protected final XmlElement getParent() {
+ return parent;
+ }
+
+ protected List constraints() {
+ return List.of();
+ }
+
+ protected List attributes() {
+ return List.of();
+ }
+
+ protected final List getProducers() {
+ return producers;
+ }
+
+ protected final Optional optional(String name) {
+ return Optional.ofNullable(attributes.get(name));
+ }
+
+ protected final String attribute(String name) {
+ return attributes.get(name);
+ }
+
+ @SuppressWarnings("unchecked")
+ protected final List elements(Class type) {
+ List result = new ArrayList<>();
+ for (XmlElement e : elements) {
+ if (type.isAssignableFrom(e.getClass())) {
+ result.add((T) e);
+ }
+ }
+ return result;
+ }
+
+ protected Result evaluate() {
+ if (producers.isEmpty()) {
+ throw new Error("No producer evaluate for " + getClass());
+ }
+ if (producers.size() != 1) {
+ throw new Error("Unsure how to evaluate multiple producers " + getClass());
+ }
+ return producers.get(0).evaluate();
+ }
+
+ protected void validateAttributes() throws ParseException {
+ for (String key : attributes()) {
+ if (!attributes.containsKey(key)) {
+ throw new ParseException("Missing mandatory attribute '" + key + "'", 0);
+ }
+ }
+ }
+
+ private void validateChildren() throws ParseException {
+ for (XmlElement child : elements) {
+ child.validate();
+ }
+ }
+
+ protected void validateChildConstraints() throws ParseException {
+ for (Constraint c : constraints()) {
+ validateConstraint(c);
+ }
+ }
+
+ private final void validateConstraint(Constraint c) throws ParseException {
+ int count = count(c.type());
+ if (count < c.min()) {
+ String elementName = Utilities.elementName(c.type());
+ throw new ParseException("Missing mandatory element <" + elementName + ">", 0);
+ }
+ if (count > c.max()) {
+ String elementName = Utilities.elementName(c.type());
+ throw new ParseException("Too many elements of type <" + elementName + ">", 0);
+ }
+ }
+
+ private void addProducer(XmlElement producer) {
+ producers.add(producer);
+ }
+
+ private int count(Class extends XmlElement> type) {
+ int count = 0;
+ for (XmlElement element : getChildren()) {
+ if (type.isAssignableFrom(element.getClass())) {
+ count++;
+ }
+ }
+ return count;
+ }
+}
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlEvent.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlEvent.java
new file mode 100644
index 00000000000..8a6b63147db
--- /dev/null
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlEvent.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jfr.internal.jfc.model;
+
+import java.util.List;
+
+// Corresponds to
+final class XmlEvent extends XmlElement {
+
+ public String getName() {
+ return attribute("name");
+ }
+
+ public List getSettings() {
+ return elements(XmlSetting.class);
+ }
+
+ XmlSetting getSetting(String settingName, boolean add) {
+ for (XmlSetting setting : getSettings()) {
+ if (settingName.equals(setting.getName())) {
+ return setting;
+ }
+ }
+ if (!add) {
+ String msg = "Could not find setting '" + settingName;
+ msg += "' for event '" + getName() + "'";
+ throw new IllegalArgumentException(msg);
+ }
+ XmlSetting setting = new XmlSetting();
+ setting.setAttribute("name", settingName);
+ addChild(setting);
+ return setting;
+ }
+
+ @Override
+ protected List attributes() {
+ return List.of("name");
+ }
+
+ @Override
+ protected List constraints() {
+ return List.of(Constraint.any(XmlSetting.class));
+ }
+}
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlExpression.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlExpression.java
new file mode 100644
index 00000000000..9162ed22e75
--- /dev/null
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlExpression.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jfr.internal.jfc.model;
+
+import java.text.ParseException;
+import java.util.List;
+
+// Base class for , , , and
+abstract class XmlExpression extends XmlElement {
+
+ public final List getExpressions() {
+ return elements(XmlExpression.class);
+ }
+
+ @Override
+ protected List constraints() {
+ return List.of(
+ Constraint.any(XmlOr.class),
+ Constraint.any(XmlAnd.class),
+ Constraint.any(XmlTest.class),
+ Constraint.any(XmlNot.class)
+ );
+ }
+
+ @Override
+ protected void validateChildConstraints() throws ParseException {
+ if (getExpressions().size() < 2) {
+ throw new ParseException("Expected + <" + getElementName() + "> to have at least two children", 0);
+ }
+ }
+}
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlFlag.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlFlag.java
new file mode 100644
index 00000000000..d494d3eb57d
--- /dev/null
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlFlag.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jfr.internal.jfc.model;
+
+// Corresponds to
+final class XmlFlag extends XmlInput {
+
+ @Override
+ public String getOptionSyntax() {
+ return getName() + "=";
+ }
+
+ @Override
+ public void configure(String value) {
+ Utilities.checkValid(value, "true", "false");
+ setContent(value);
+ notifyListeners();
+ }
+
+ @Override
+ public void configure(UserInterface ui) throws AbortException {
+ Result defaultValue = Result.of(getContent());
+ ui.println();
+ ui.println(getLabel() + " [Y/N]: " + (defaultValue.isTrue() ? "Yes" : "No") + " (default)");
+
+ while (!read(ui, defaultValue)) {
+ ;
+ }
+ }
+
+ @Override
+ protected Result evaluate() {
+ return Result.of(getContent());
+ }
+
+ private boolean read(UserInterface ui, Result defaultValue) throws AbortException {
+ String line = ui.readLine();
+ if (line.isBlank()) {
+ ui.println("Using default: " + (defaultValue.isTrue() ? "Yes" : "No"));
+ return true;
+ }
+ if (line.equalsIgnoreCase("Y") || line.equalsIgnoreCase("N")) {
+ boolean value = line.equalsIgnoreCase("Y");
+ ui.println("Using: " + (value ? "Yes" : "No"));
+ configure(String.valueOf(value));
+ return true;
+ }
+ ui.println("Not a valid choice.");
+ return false;
+ }
+}
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlInput.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlInput.java
new file mode 100644
index 00000000000..2754c7ca16c
--- /dev/null
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlInput.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jfr.internal.jfc.model;
+
+import java.util.List;
+import java.util.Optional;
+
+// Base class for elements that the user can interact with,
+// , and
+public abstract class XmlInput extends XmlElement implements ControlElement {
+
+ public abstract String getOptionSyntax();
+
+ abstract void configure(UserInterface ui) throws AbortException;
+
+ abstract void configure(String value);
+
+ public final Optional getContentType() {
+ return optional("contentType");
+ }
+
+ @Override
+ public final String getName() {
+ return attribute("name");
+ }
+
+ public final String getLabel() {
+ return attribute("label");
+ }
+
+ @Override
+ protected List attributes() {
+ return List.of("name", "label");
+ }
+}
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlNot.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlNot.java
new file mode 100644
index 00000000000..057382a6601
--- /dev/null
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlNot.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jfr.internal.jfc.model;
+
+import java.text.ParseException;
+import java.util.List;
+
+// Corresponds to
+final class XmlNot extends XmlExpression {
+
+ @Override
+ boolean isEntity() {
+ return false;
+ }
+
+ @Override
+ protected void validateChildConstraints() throws ParseException {
+ if (getExpressions().size() != 1) {
+ throw new ParseException("Expected to have a single child", 0);
+ }
+ }
+
+ @Override
+ protected Result evaluate() {
+ List producers = getProducers();
+ if (!producers.isEmpty()) {
+ Result r = producers.get(0).evaluate();
+ if (!r.isNull()) {
+ return r.isTrue() ? Result.FALSE : Result.TRUE;
+ }
+ }
+ return Result.NULL;
+ }
+}
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlOption.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlOption.java
new file mode 100644
index 00000000000..fca10115167
--- /dev/null
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/XmlOption.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jfr.internal.jfc.model;
+
+import java.util.List;
+
+// Corresponds to
- true
+ true
- false
+ false
@@ -207,7 +207,7 @@
true
- everyChunk
+ everyChunk
@@ -274,7 +274,7 @@
- false
+ falseeveryChunk
@@ -382,22 +382,22 @@
- false
+ false0 ms
- false
+ false0 ms
- true
+ true0 ms
- true
+ true0 ms
@@ -443,11 +443,11 @@
- false
+ false
- false
+ false
@@ -455,7 +455,7 @@
- false
+ falsetrue
@@ -464,27 +464,27 @@
- false
+ falseeveryChunk
- false
+ false
- false
+ falseeveryChunk
- false
+ false
- true
- false
- 0 ns
+ true
+ false
+ 0 ns
@@ -605,18 +605,18 @@
- false
+ falsetrue
- false
+ falsetrue
- true
- 150/s
+ true
+ 150/strue
@@ -638,31 +638,31 @@
truetrue
- 20 ms
+ 20 mstruetrue
- 20 ms
+ 20 mstruetrue
- 20 ms
+ 20 mstruetrue
- 20 ms
+ 20 mstruetrue
- 20 ms
+ 20 ms
@@ -819,67 +819,63 @@
-
-
+
-
+
-
-
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -887,96 +883,96 @@
-
+
-
-
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
-
+
+
+
-
+
-
+
-
-
-
-
+
+
+
+
-
+
-
+
@@ -984,44 +980,44 @@
-
-
+
+
-
+
-
+
-
-
-
+
+
+
-
-
+
+
-
+
-
-
+
+
-
-
+
+
- 20 ms
+ 20 ms
- 20 ms
+ 20 ms
- 20 ms
+ 20 ms
- false
+ false
diff --git a/src/jdk.jfr/share/conf/jfr/profile.jfc b/src/jdk.jfr/share/conf/jfr/profile.jfc
index 1ded1577adc..d8c7419264a 100644
--- a/src/jdk.jfr/share/conf/jfr/profile.jfc
+++ b/src/jdk.jfr/share/conf/jfr/profile.jfc
@@ -1,8 +1,8 @@
-
@@ -64,31 +64,31 @@
truetrue
- 10 ms
+ 10 mstruetrue
- 10 ms
+ 10 mstruetrue
- 10 ms
+ 10 mstruetrue
- 10 ms
+ 10 mstruetrue
- 10 ms
+ 10 ms
@@ -120,13 +120,13 @@
- false
+ falsetrue0 ms
- false
+ falsetrue
@@ -143,11 +143,11 @@
- true
+ true
- false
+ false
@@ -207,7 +207,7 @@
true
- 60 s
+ 60 s
@@ -274,7 +274,7 @@
- false
+ falseeveryChunk
@@ -382,22 +382,22 @@
- false
+ false0 ms
- false
+ false0 ms
- true
+ true0 ms
- true
+ true0 ms
@@ -443,11 +443,11 @@
- true
+ true
- true
+ true
@@ -455,7 +455,7 @@
- false
+ falsetrue
@@ -464,27 +464,27 @@
- false
+ falseeveryChunk
- false
+ false
- false
+ falseeveryChunk
- false
+ false
- true
- true
- 0 ns
+ true
+ true
+ 0 ns
@@ -605,18 +605,18 @@
- false
+ falsetrue
- false
+ falsetrue
- true
- 300/s
+ true
+ 300/strue
@@ -638,31 +638,31 @@
truetrue
- 10 ms
+ 10 mstruetrue
- 10 ms
+ 10 mstruetrue
- 10 ms
+ 10 mstruetrue
- 10 ms
+ 10 mstruetrue
- 10 ms
+ 10 ms
@@ -819,66 +819,63 @@
-
+
-
-
+
-
-
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -886,96 +883,96 @@
-
+
-
-
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
-
+
+
+
-
+
-
+
-
-
-
-
+
+
+
+
-
+
-
+
@@ -983,44 +980,44 @@
-
-
+
+
-
+
-
+
-
-
-
+
+
+
-
-
+
+
-
+
-
-
+
+
-
-
+
+
- 10 ms
+ 10 ms
- 10 ms
+ 10 ms
- 10 ms
+ 10 ms
- false
+ false
diff --git a/test/jdk/jdk/jfr/tool/TestConfigure.java b/test/jdk/jdk/jfr/tool/TestConfigure.java
new file mode 100644
index 00000000000..ae39b0935c5
--- /dev/null
+++ b/test/jdk/jdk/jfr/tool/TestConfigure.java
@@ -0,0 +1,362 @@
+/*
+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jfr.tool;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.text.ParseException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import jdk.jfr.Configuration;
+import jdk.test.lib.process.OutputAnalyzer;
+/**
+ * @test
+ * @summary Test jfr configure
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib /test/jdk
+ * @run main/othervm jdk.jfr.tool.TestConfigure
+ */
+public class TestConfigure {
+
+ private static final String DIR = System.getProperty("test.src", ".");
+
+ public static void main(String... args) throws Throwable {
+ testSelection();
+ testText();
+ testFlag();
+ testCondition();
+ testAnd();
+ testOr();
+ testNone();
+ testMissingControl();
+ testDefault();
+ testCopyPredefined();
+ testSuperflouos();
+ testModify();
+ testAdding();
+ testUnification();
+ testTimespan();
+ testVerbose();
+ }
+
+ private static void testVerbose() throws Throwable {
+ var input = newInputFile("flag.jfc");
+
+ var output = newOutputFile("verbose-1.jfc");
+ var result = jfrConfigure("--input", input, "--verbose", "mammal=true", "--output", output);
+ result.shouldContain("com.example.Lion#enabled=true");
+ result.shouldContain("com.example.Tiger#enabled=true");
+
+ output = newOutputFile("verbose-2.jfc");
+ result = jfrConfigure("--input", input, "--verbose", "+com.example.Albatross#enabled=true", "--output", output);
+ result.shouldContain("com.example.Albatross#enabled=true");
+ }
+
+ private static void testTimespan() throws Throwable {
+ var input = newInputFile("timespan.jfc");
+
+ var output = newOutputFile("quoted-timespan.jfc");
+ jfrConfigure("--input", input, "value=20 s","--output", output);
+ var outputSetting = readSettings(output);
+ var expected = readSettings(input);
+ expected.put("com.example.Tiger#threshold", "20 s");
+ expected.put("com.example.Lion#period", "20 s");
+
+ aseertEqual(outputSetting, expected);
+
+ output = newOutputFile("compact-timespan.jfc");
+ jfrConfigure("--input", input, "value=13s","--output", output);
+ outputSetting = readSettings(output);
+ expected = readSettings(input);
+ expected.put("com.example.Tiger#threshold", "13 s");
+ expected.put("com.example.Lion#period", "13 s");
+
+ aseertEqual(outputSetting, expected);
+
+ output = newOutputFile("threshold-period-timespan.jfc");
+ jfrConfigure("--input", input,
+ "com.example.Tiger#threshold=2s",
+ "com.example.Lion#period=3s",
+ "--output", output);
+
+ outputSetting = readSettings(output);
+ expected = readSettings(input);
+ expected.put("com.example.Tiger#threshold", "2 s");
+ expected.put("com.example.Lion#period", "3 s");
+
+ aseertEqual(outputSetting, expected);
+ }
+
+ private static void testUnification() throws Throwable {
+ var input1 = newInputFile("or.jfc");
+ var input2 = newInputFile("and.jfc");
+ var output = newOutputFile("combined.jfc");
+
+ jfrConfigure("--input", input1 + "," + input2, "--output", output);
+
+ var input1Setting = readSettings(input1);
+ var input2Setting = readSettings(input2);
+ var outputSetting = readSettings(output);
+
+ Map expected = new HashMap<>();
+ expected.putAll(input1Setting);
+ expected.putAll(input2Setting);
+
+ aseertEqual(outputSetting, expected);
+ }
+
+ private static void testAdding() throws Throwable {
+ var input = newInputFile("plain.jfc");
+
+ var output = newOutputFile("test-adding-succeed-1.jfc");
+ var result = jfrConfigure("--input", input, "+com.example.Tiger#legs=4", "--output", output);
+ result.shouldNotContain("Could not find");
+ var outputSetting = readSettings(output);
+ var expected = readSettings(input);
+ expected.put("com.example.Tiger#legs", "4");
+
+ aseertEqual(outputSetting, expected);
+
+ output = newOutputFile("test-adding-succeed-2.jfc");
+ result = jfrConfigure("--input", input, "+com.example.Foo#bar=baz", "--output", output);
+ result.shouldNotContain("Could not find");
+
+ outputSetting = readSettings(output);
+ expected = readSettings(input);
+ expected.put("com.example.Foo#bar", "baz");
+
+ aseertEqual(outputSetting, expected);
+ }
+
+ private static void testModify() throws Throwable {
+ var input = newInputFile("plain.jfc");
+
+ var output = newOutputFile("test-modify-fail-1.jfc");
+ var result = jfrConfigure("--input", input, "com.example.Zebra#stackTrace=true", "--output", output);
+ result.shouldContain("Could not find event 'com.example.Zebra'");
+
+ output = newOutputFile("test-modify-fail-2.jfc");
+ result = jfrConfigure("--input", input, "com.example.Tiger#foo=true", "--output", output);
+ result.shouldContain("Could not find setting 'foo' for event 'com.example.Tiger'");
+
+ output = newOutputFile("test-modify-succeed.jfc");
+ result = jfrConfigure("--input", input, "com.example.Tiger#enabled=true", "--output", output);
+ result.shouldNotContain("Could not find");
+
+ var outputSetting = readSettings(output);
+ var expected = readSettings(input);
+ expected.put("com.example.Tiger#enabled", "true");
+
+ aseertEqual(outputSetting, expected);
+ }
+
+ // JMC may add attributes or elements, make sure some random elements/attributes survive
+ private static void testSuperflouos() throws Throwable {
+ var output = newOutputFile("test-superfluous.jfc");
+ var input = newInputFile("superfluous.jfc");
+ jfrConfigure("--input", input, "--output", output);
+ String content = Files.readString(Path.of(output));
+ for (String t : List.of("legs=\"4\"", "radio", "red", "", "option")) {
+ if (!content.contains(t)) {
+ throw new Exception("Expected superfluous element '" + t + "' or attribute to survive");
+ }
+ }
+ }
+
+ private static void testMissingControl() throws Throwable {
+ var output = newOutputFile("missed.jfc");
+ var input = newInputFile("missing.jfc");
+ var result = jfrConfigure("--input", input, "--output", output);
+ result.shouldContain("Warning! Setting 'com.example.Tiger#enabled' refers to missing control 'tigre'");
+ }
+
+ private static void testDefault() throws Throwable {
+ var output = newOutputFile("fresh.jfc");
+ var result = jfrConfigure("--output", output);
+ result.shouldNotContain("Warning"); // checks dangling control reference in default.jfc
+ var outputSetting = readSettings(output);
+ aseertEqual(outputSetting, Configuration.getConfiguration("default").getSettings());
+ }
+
+ private static void testCopyPredefined() throws Throwable {
+ var output = newOutputFile("new.jfc");
+ var result = jfrConfigure("--input", "profile", "--output", output);
+ result.shouldNotContain("Warning"); // checks missing control reference in profile.jfc
+
+ var outputSetting = readSettings(output);
+ aseertEqual(outputSetting, Configuration.getConfiguration("profile").getSettings());
+ }
+
+ private static void testNone() throws Throwable {
+ var output = newOutputFile("new.jfc");
+ jfrConfigure("--input", "none", "--output", output);
+ var outputSetting = readSettings(output);
+ aseertEqual(outputSetting, Map.of());
+ }
+
+ private static void testOr() throws Throwable {
+ var output = newOutputFile("test-or-true.jfc");
+ var input = newInputFile("or.jfc");
+ jfrConfigure("--input", input, "month=May", "--output", output);
+ var outputSetting = readSettings(output);
+ var expected = readSettings(input);
+ expected.put("season.Spring#enabled", "true");
+
+ aseertEqual(outputSetting, expected);
+
+ output = newOutputFile("test-or-false.jfc");
+ jfrConfigure("--input", input, "month=September", "--output", output);
+ outputSetting = readSettings(output);
+ expected = readSettings(input);
+ expected.put("season.Spring#enabled", "false");
+
+ aseertEqual(outputSetting, expected);
+ }
+
+ private static void testAnd() throws Throwable {
+ var output = newOutputFile("test-and-true.jfc");
+ var input = newInputFile("and.jfc");
+ jfrConfigure("--input", input,
+ "closure=true",
+ "identity=true",
+ "associativity=true",
+ "inverse=true",
+ "--output", output);
+ var outputSetting = readSettings(output);
+ var expected = readSettings(input);
+ expected.put("algebra.Group#enabled", "true");
+
+ aseertEqual(outputSetting, expected);
+
+ output = newOutputFile("test-and-false.jfc");
+ jfrConfigure("--input", input,
+ "closure=true",
+ "identity=true",
+ "associativity=true",
+ "inverse=false",
+ "--output", output);
+ outputSetting = readSettings(output);
+ expected = readSettings(input);
+ expected.put("algebra.Group#enabled", "false");
+
+ aseertEqual(outputSetting, expected);
+ }
+
+
+ private static void testCondition() throws Throwable {
+ var output = newOutputFile("test-condition-1.jfc");
+ var input = newInputFile("condition.jfc");
+ jfrConfigure("--input", input, "variable=activate", "--output", output);
+ var outputSetting = readSettings(output);
+ var expected = readSettings(input);
+ expected.put("com.example.Tiger#period", "1 s");
+ expected.put("com.example.Lion#period", "3 s");
+
+ aseertEqual(outputSetting, expected);
+
+ output = newOutputFile("test-condition-2.jfc");
+ jfrConfigure("--input", input, "variable=whatever", "--output", output);
+ outputSetting = readSettings(output);
+ expected = readSettings(input);
+ expected.put("com.example.Lion#period", "5 s");
+ expected.put("com.example.Zebra#period", "7 s");
+
+ aseertEqual(outputSetting, expected);
+ }
+
+ private static void testFlag() throws Throwable {
+ var output = newOutputFile("test-flag.jfc");
+ var input = newInputFile("flag.jfc");
+ jfrConfigure("--input", input, "mammal=true", "--output", output);
+ var outputSetting = readSettings(output);
+ var expected = readSettings(input);
+ expected.put("com.example.Tiger#enabled", "true");
+ expected.put("com.example.Lion#enabled", "true");
+
+ aseertEqual(outputSetting, expected);
+ }
+
+ private static void testText() throws Throwable {
+ var output = newOutputFile("test-text.jfc");
+ var input = newInputFile("text.jfc");
+ jfrConfigure("--input", input, "animal-threshold=3s", "--output", output);
+ var outputSetting = readSettings(output);
+ var expected = readSettings(input);
+ expected.put("com.example.Tiger#threshold", "3 s");
+ expected.put("com.example.Lion#threshold", "3 s");
+
+ aseertEqual(outputSetting, expected);
+ }
+
+ private static void testSelection() throws Throwable {
+ var output = newOutputFile("test-selection.jfc");
+ var input = newInputFile("selection.jfc");
+ jfrConfigure("--input", input, "animal=medium", "--output", output);
+ var outputSetting = readSettings(output);
+ var expected = readSettings(input);
+ expected.put("com.example.Tiger#threshold", "10 s");
+ expected.put("com.example.Lion#threshold", "10 s");
+
+ aseertEqual(outputSetting, expected);
+ }
+
+ private static String newInputFile(String filename) {
+ return Path.of(DIR, "configure", filename).toAbsolutePath().toString();
+ }
+
+ private static Map readSettings(String text) throws IOException, ParseException {
+ return Configuration.create(Path.of(text)).getSettings();
+ }
+
+ private static OutputAnalyzer jfrConfigure(String... args) throws Throwable {
+ String[] all = new String[args.length + 1];
+ all[0] = "configure";
+ for (int i = 0; i < args.length; i++) {
+ all[i + 1] = args[i];
+ }
+ OutputAnalyzer o = ExecuteHelper.jfr(all);
+ System.out.println(o.getOutput());
+ return o;
+ }
+
+ private static String newOutputFile(String filename) {
+ return Path.of(DIR, System.currentTimeMillis() + filename).toAbsolutePath().toString();
+ }
+
+ private static void aseertEqual(Map output, Map expected) throws Exception {
+ if (!output.equals(expected)) {
+ System.out.println("Output:");
+ for (var e : output.entrySet()) {
+ System.out.println("\"" + e.getKey() + "=" + e.getValue() + "\"");
+ }
+ System.out.println("Expected:");
+ for (var e : expected.entrySet()) {
+ System.out.println("\"" + e.getKey() + "=" + e.getValue() + "\"");
+ }
+ throw new Exception("Mismatch between output and expected");
+ }
+ }
+}
diff --git a/test/jdk/jdk/jfr/tool/configure/and.jfc b/test/jdk/jdk/jfr/tool/configure/and.jfc
new file mode 100644
index 00000000000..ea591347d94
--- /dev/null
+++ b/test/jdk/jdk/jfr/tool/configure/and.jfc
@@ -0,0 +1,24 @@
+
+
+
+
+ unknown
+
+
+
+ false
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/jdk/jdk/jfr/tool/configure/condition.jfc b/test/jdk/jdk/jfr/tool/configure/condition.jfc
new file mode 100644
index 00000000000..579b4881ecd
--- /dev/null
+++ b/test/jdk/jdk/jfr/tool/configure/condition.jfc
@@ -0,0 +1,33 @@
+
+
+
+
+ 0 s
+
+
+
+ 0 s
+
+
+
+ 0 s
+
+
+
+ disabled
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/jdk/jdk/jfr/tool/configure/flag.jfc b/test/jdk/jdk/jfr/tool/configure/flag.jfc
new file mode 100644
index 00000000000..30f6f597c18
--- /dev/null
+++ b/test/jdk/jdk/jfr/tool/configure/flag.jfc
@@ -0,0 +1,21 @@
+
+
+
+
+ false
+ false
+
+
+
+ false
+
+
+
+ false
+
+
+
+ false
+
+
+
\ No newline at end of file
diff --git a/test/jdk/jdk/jfr/tool/configure/missing.jfc b/test/jdk/jdk/jfr/tool/configure/missing.jfc
new file mode 100644
index 00000000000..0c85a3b2a0b
--- /dev/null
+++ b/test/jdk/jdk/jfr/tool/configure/missing.jfc
@@ -0,0 +1,19 @@
+
+
+
+
+ false
+
+
+
+ 0 s
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/jdk/jdk/jfr/tool/configure/or.jfc b/test/jdk/jdk/jfr/tool/configure/or.jfc
new file mode 100644
index 00000000000..740f36d19dd
--- /dev/null
+++ b/test/jdk/jdk/jfr/tool/configure/or.jfc
@@ -0,0 +1,20 @@
+
+
+
+
+ unknown
+
+
+
+ January
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/jdk/jdk/jfr/tool/configure/plain.jfc b/test/jdk/jdk/jfr/tool/configure/plain.jfc
new file mode 100644
index 00000000000..aff2fd77568
--- /dev/null
+++ b/test/jdk/jdk/jfr/tool/configure/plain.jfc
@@ -0,0 +1,15 @@
+
+
+
+
+ false
+ false
+
+
+
+ false
+ true
+
+
+
+
\ No newline at end of file
diff --git a/test/jdk/jdk/jfr/tool/configure/selection.jfc b/test/jdk/jdk/jfr/tool/configure/selection.jfc
new file mode 100644
index 00000000000..e9939c93cce
--- /dev/null
+++ b/test/jdk/jdk/jfr/tool/configure/selection.jfc
@@ -0,0 +1,27 @@
+
+
+
+
+ false
+ 0 s
+
+
+
+ true
+ 0 s
+
+
+
+ 0 s
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/jdk/jdk/jfr/tool/configure/superfluous.jfc b/test/jdk/jdk/jfr/tool/configure/superfluous.jfc
new file mode 100644
index 00000000000..54646b47bda
--- /dev/null
+++ b/test/jdk/jdk/jfr/tool/configure/superfluous.jfc
@@ -0,0 +1,18 @@
+
+
+
+
+ false
+
+
+
+ true
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/jdk/jdk/jfr/tool/configure/text.jfc b/test/jdk/jdk/jfr/tool/configure/text.jfc
new file mode 100644
index 00000000000..879097511c6
--- /dev/null
+++ b/test/jdk/jdk/jfr/tool/configure/text.jfc
@@ -0,0 +1,22 @@
+
+
+
+
+ false
+ 0 s
+
+
+
+ true
+ 0 s
+
+
+
+ 0 s
+
+
+
+ 0 s
+
+
+
\ No newline at end of file
diff --git a/test/jdk/jdk/jfr/tool/configure/timespan.jfc b/test/jdk/jdk/jfr/tool/configure/timespan.jfc
new file mode 100644
index 00000000000..74c743eed13
--- /dev/null
+++ b/test/jdk/jdk/jfr/tool/configure/timespan.jfc
@@ -0,0 +1,18 @@
+
+
+
+
+ false
+ 0 s
+
+
+
+ true
+ 0 s
+
+
+
+ 0 s
+
+
+
\ No newline at end of file