| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.cassandra.tools; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.io.OutputStreamWriter; |
| import java.io.PrintStream; |
| import java.io.UnsupportedEncodingException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Comparator; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.TreeMap; |
| import java.util.TreeSet; |
| import java.util.concurrent.Callable; |
| import java.util.function.BiConsumer; |
| import java.util.regex.Pattern; |
| import java.util.stream.Collectors; |
| import java.util.stream.Stream; |
| import javax.inject.Inject; |
| import javax.management.InstanceNotFoundException; |
| import javax.management.IntrospectionException; |
| import javax.management.MBeanAttributeInfo; |
| import javax.management.MBeanFeatureInfo; |
| import javax.management.MBeanInfo; |
| import javax.management.MBeanOperationInfo; |
| import javax.management.MBeanParameterInfo; |
| import javax.management.MBeanServerConnection; |
| import javax.management.MalformedObjectNameException; |
| import javax.management.ObjectName; |
| import javax.management.ReflectionException; |
| import javax.management.remote.JMXConnector; |
| import javax.management.remote.JMXConnectorFactory; |
| import javax.management.remote.JMXServiceURL; |
| |
| import com.google.common.base.Preconditions; |
| import com.google.common.base.Predicate; |
| import com.google.common.collect.Sets; |
| import com.google.common.collect.Sets.SetView; |
| |
| import com.fasterxml.jackson.core.type.TypeReference; |
| import com.fasterxml.jackson.databind.ObjectMapper; |
| import io.airlift.airline.Arguments; |
| import io.airlift.airline.Cli; |
| import io.airlift.airline.Command; |
| import io.airlift.airline.Help; |
| import io.airlift.airline.HelpOption; |
| import io.airlift.airline.Option; |
| import org.yaml.snakeyaml.TypeDescription; |
| import org.yaml.snakeyaml.Yaml; |
| import org.yaml.snakeyaml.constructor.Constructor; |
| import org.yaml.snakeyaml.nodes.MappingNode; |
| import org.yaml.snakeyaml.nodes.Node; |
| import org.yaml.snakeyaml.nodes.Tag; |
| import org.yaml.snakeyaml.representer.Representer; |
| |
| public class JMXTool |
| { |
| private static final List<String> METRIC_PACKAGES = Arrays.asList("org.apache.cassandra.metrics", |
| "org.apache.cassandra.db", |
| "org.apache.cassandra.hints", |
| "org.apache.cassandra.internal", |
| "org.apache.cassandra.net", |
| "org.apache.cassandra.request", |
| "org.apache.cassandra.service"); |
| |
| private static final Comparator<MBeanOperationInfo> OPERATOR_COMPARATOR = (a, b) -> { |
| int rc = a.getName().compareTo(b.getName()); |
| if (rc != 0) |
| return rc; |
| String[] aSig = Stream.of(a.getSignature()).map(MBeanParameterInfo::getName).toArray(String[]::new); |
| String[] bSig = Stream.of(b.getSignature()).map(MBeanParameterInfo::getName).toArray(String[]::new); |
| rc = Integer.compare(aSig.length, bSig.length); |
| if (rc != 0) |
| return rc; |
| for (int i = 0; i < aSig.length; i++) |
| { |
| rc = aSig[i].compareTo(bSig[i]); |
| if (rc != 0) |
| return rc; |
| } |
| return rc; |
| }; |
| |
| @Command(name = "dump", description = "Dump the Apache Cassandra JMX objects and metadata.") |
| public static final class Dump implements Callable<Void> |
| { |
| @Inject |
| private HelpOption helpOption; |
| |
| @Option(title = "url", name = { "-u", "--url" }, description = "JMX url to target") |
| private String targetUrl = "service:jmx:rmi:///jndi/rmi://localhost:7199/jmxrmi"; |
| |
| @Option(title = "format", name = { "-f", "--format" }, description = "What format to dump content as; supported values are console (default), json, and yaml") |
| private Format format = Format.console; |
| |
| public Void call() throws Exception |
| { |
| Map<String, Info> map = load(new JMXServiceURL(targetUrl)); |
| format.dump(System.out, map); |
| return null; |
| } |
| |
| public enum Format |
| { |
| console |
| { |
| void dump(OutputStream output, Map<String, Info> map) |
| { |
| @SuppressWarnings("resource") |
| // output should be released by caller |
| PrintStream out = toPrintStream(output); |
| for (Map.Entry<String, Info> e : map.entrySet()) |
| { |
| String name = e.getKey(); |
| Info info = e.getValue(); |
| |
| out.println(name); |
| out.println("\tAttributes"); |
| Stream.of(info.attributes).forEach(a -> printRow(out, a.name, a.type, a.access)); |
| out.println("\tOperations"); |
| Stream.of(info.operations).forEach(o -> { |
| String args = Stream.of(o.parameters) |
| .map(i -> i.name + ": " + i.type) |
| .collect(Collectors.joining(",", "(", ")")); |
| printRow(out, o.name, o.returnType, args); |
| }); |
| } |
| } |
| }, |
| json |
| { |
| void dump(OutputStream output, Map<String, Info> map) throws IOException |
| { |
| ObjectMapper mapper = new ObjectMapper(); |
| mapper.writeValue(output, map); |
| } |
| }, |
| yaml |
| { |
| void dump(OutputStream output, Map<String, Info> map) throws IOException |
| { |
| Representer representer = new Representer(); |
| representer.addClassTag(Info.class, Tag.MAP); // avoid the auto added tag |
| Yaml yaml = new Yaml(representer); |
| yaml.dump(map, new OutputStreamWriter(output)); |
| } |
| }; |
| |
| private static PrintStream toPrintStream(OutputStream output) |
| { |
| try |
| { |
| return output instanceof PrintStream ? (PrintStream) output : new PrintStream(output, true, "UTF-8"); |
| } |
| catch (UnsupportedEncodingException e) |
| { |
| throw new AssertionError(e); // utf-8 is a required charset for the JVM |
| } |
| } |
| |
| abstract void dump(OutputStream output, Map<String, Info> map) throws IOException; |
| } |
| } |
| |
| @Command(name = "diff", description = "Diff two jmx dump files and report their differences") |
| public static final class Diff implements Callable<Void> |
| { |
| @Inject |
| private HelpOption helpOption; |
| |
| @Arguments(title = "files", usage = "<left> <right>", description = "Files to diff") |
| private List<File> files; |
| |
| @Option(title = "format", name = { "-f", "--format" }, description = "What format the files are in; only support json and yaml as format") |
| private Format format = Format.yaml; |
| |
| @Option(title = "ignore left", name = { "--ignore-missing-on-left" }, description = "Ignore results missing on the left") |
| private boolean ignoreMissingLeft; |
| |
| @Option(title = "ignore right", name = { "--ignore-missing-on-right" }, description = "Ignore results missing on the right") |
| private boolean ignoreMissingRight; |
| |
| @Option(title = "exclude objects", name = "--exclude-object", description |
| = "Ignores processing specific objects. " + |
| "Each usage should take a single object, " + |
| "but can use this flag multiple times.") |
| private List<CliPattern> excludeObjects = new ArrayList<>(); |
| |
| @Option(title = "exclude attributes", name = "--exclude-attribute", description |
| = "Ignores processing specific attributes. " + |
| "Each usage should take a single attribute, " + |
| "but can use this flag multiple times.") |
| private List<CliPattern> excludeAttributes = new ArrayList<>(); |
| |
| @Option(title = "exclude operations", name = "--exclude-operation", description |
| = "Ignores processing specific operations. " + |
| "Each usage should take a single operation, " + |
| "but can use this flag multiple times.") |
| private List<CliPattern> excludeOperations = new ArrayList<>(); |
| |
| public Void call() throws Exception |
| { |
| Preconditions.checkArgument(files.size() == 2, "files requires 2 arguments but given %s", files); |
| Map<String, Info> left; |
| Map<String, Info> right; |
| try (FileInputStream leftStream = new FileInputStream(files.get(0)); |
| FileInputStream rightStream = new FileInputStream(files.get(1))) |
| { |
| left = format.load(leftStream); |
| right = format.load(rightStream); |
| } |
| |
| diff(left, right); |
| return null; |
| } |
| |
| private void diff(Map<String, Info> left, Map<String, Info> right) |
| { |
| DiffResult<String> objectNames = diff(left.keySet(), right.keySet(), name -> { |
| for (CliPattern p : excludeObjects) |
| { |
| if (p.pattern.matcher(name).matches()) |
| return false; |
| } |
| return true; |
| }); |
| |
| if (!ignoreMissingRight && !objectNames.notInRight.isEmpty()) |
| { |
| System.out.println("Objects not in right:"); |
| printSet(0, objectNames.notInRight); |
| } |
| if (!ignoreMissingLeft && !objectNames.notInLeft.isEmpty()) |
| { |
| System.out.println("Objects not in left: "); |
| printSet(0, objectNames.notInLeft); |
| } |
| Runnable printHeader = new Runnable() |
| { |
| boolean printedHeader = false; |
| |
| public void run() |
| { |
| if (!printedHeader) |
| { |
| System.out.println("Difference found in attribute or operation"); |
| printedHeader = true; |
| } |
| } |
| }; |
| |
| for (String key : objectNames.shared) |
| { |
| Info leftInfo = left.get(key); |
| Info rightInfo = right.get(key); |
| DiffResult<Attribute> attributes = diff(leftInfo.attributeSet(), rightInfo.attributeSet(), attribute -> { |
| for (CliPattern p : excludeAttributes) |
| { |
| if (p.pattern.matcher(attribute.name).matches()) |
| return false; |
| } |
| return true; |
| }); |
| if (!ignoreMissingRight && !attributes.notInRight.isEmpty()) |
| { |
| printHeader.run(); |
| System.out.println(key + "\tattribute not in right:"); |
| printSet(1, attributes.notInRight); |
| } |
| if (!ignoreMissingLeft && !attributes.notInLeft.isEmpty()) |
| { |
| printHeader.run(); |
| System.out.println(key + "\tattribute not in left:"); |
| printSet(1, attributes.notInLeft); |
| } |
| |
| DiffResult<Operation> operations = diff(leftInfo.operationSet(), rightInfo.operationSet(), operation -> { |
| for (CliPattern p : excludeOperations) |
| { |
| if (p.pattern.matcher(operation.name).matches()) |
| return false; |
| } |
| return true; |
| }); |
| if (!ignoreMissingRight && !operations.notInRight.isEmpty()) |
| { |
| printHeader.run(); |
| System.out.println(key + "\toperation not in right:"); |
| printSet(1, operations.notInRight, (sb, o) -> |
| rightInfo.getOperation(o.name).ifPresent(match -> |
| sb.append("\t").append("similar in right: ").append(match))); |
| } |
| if (!ignoreMissingLeft && !operations.notInLeft.isEmpty()) |
| { |
| printHeader.run(); |
| System.out.println(key + "\toperation not in left:"); |
| printSet(1, operations.notInLeft, (sb, o) -> |
| leftInfo.getOperation(o.name).ifPresent(match -> |
| sb.append("\t").append("similar in left: ").append(match))); |
| } |
| } |
| } |
| |
| private static <T extends Comparable<T>> void printSet(int indent, Set<T> set) |
| { |
| printSet(indent, set, (i1, i2) -> {}); |
| } |
| |
| private static <T extends Comparable<T>> void printSet(int indent, Set<T> set, BiConsumer<StringBuilder, T> fn) |
| { |
| StringBuilder sb = new StringBuilder(); |
| for (T t : new TreeSet<>(set)) |
| { |
| sb.setLength(0); |
| for (int i = 0; i < indent; i++) |
| sb.append('\t'); |
| sb.append(t); |
| fn.accept(sb, t); |
| System.out.println(sb); |
| } |
| } |
| |
| private static <T> DiffResult<T> diff(Set<T> left, Set<T> right, Predicate<T> fn) |
| { |
| left = Sets.filter(left, fn); |
| right = Sets.filter(right, fn); |
| return new DiffResult<>(Sets.difference(left, right), Sets.difference(right, left), Sets.intersection(left, right)); |
| } |
| |
| private static final class DiffResult<T> |
| { |
| private final SetView<T> notInRight; |
| private final SetView<T> notInLeft; |
| private final SetView<T> shared; |
| |
| private DiffResult(SetView<T> notInRight, SetView<T> notInLeft, SetView<T> shared) |
| { |
| this.notInRight = notInRight; |
| this.notInLeft = notInLeft; |
| this.shared = shared; |
| } |
| } |
| |
| public enum Format |
| { |
| json |
| { |
| Map<String, Info> load(InputStream input) throws IOException |
| { |
| ObjectMapper mapper = new ObjectMapper(); |
| return mapper.readValue(input, new TypeReference<Map<String, Info>>() {}); |
| } |
| }, |
| yaml |
| { |
| Map<String, Info> load(InputStream input) throws IOException |
| { |
| Yaml yaml = new Yaml(new CustomConstructor()); |
| return (Map<String, Info>) yaml.load(input); |
| } |
| }; |
| |
| abstract Map<String, Info> load(InputStream input) throws IOException; |
| } |
| |
| private static final class CustomConstructor extends Constructor |
| { |
| private static final String ROOT = "__root__"; |
| private static final TypeDescription INFO_TYPE = new TypeDescription(Info.class); |
| |
| public CustomConstructor() |
| { |
| this.rootTag = new Tag(ROOT); |
| this.addTypeDescription(INFO_TYPE); |
| } |
| |
| protected Object constructObject(Node node) |
| { |
| if (ROOT.equals(node.getTag().getValue()) && node instanceof MappingNode) |
| { |
| MappingNode mn = (MappingNode) node; |
| return mn.getValue().stream() |
| .collect(Collectors.toMap(t -> super.constructObject(t.getKeyNode()), |
| t -> { |
| Node child = t.getValueNode(); |
| child.setType(INFO_TYPE.getType()); |
| return super.constructObject(child); |
| })); |
| } |
| else |
| { |
| return super.constructObject(node); |
| } |
| } |
| } |
| } |
| |
| private static Map<String, Info> load(JMXServiceURL url) throws IOException, MalformedObjectNameException, IntrospectionException, InstanceNotFoundException, ReflectionException |
| { |
| try (JMXConnector jmxc = JMXConnectorFactory.connect(url, null)) |
| { |
| MBeanServerConnection mbsc = jmxc.getMBeanServerConnection(); |
| |
| Map<String, Info> map = new TreeMap<>(); |
| for (String pkg : new TreeSet<>(METRIC_PACKAGES)) |
| { |
| Set<ObjectName> metricNames = new TreeSet<>(mbsc.queryNames(new ObjectName(pkg + ":*"), null)); |
| for (ObjectName name : metricNames) |
| { |
| if (mbsc.isRegistered(name)) |
| { |
| MBeanInfo info = mbsc.getMBeanInfo(name); |
| map.put(name.toString(), Info.from(info)); |
| } |
| } |
| } |
| return map; |
| } |
| } |
| |
| private static String getAccess(MBeanAttributeInfo a) |
| { |
| String access; |
| if (a.isReadable()) |
| { |
| if (a.isWritable()) |
| access = "read/write"; |
| else |
| access = "read-only"; |
| } |
| else if (a.isWritable()) |
| access = "write-only"; |
| else |
| access = "no-access"; |
| return access; |
| } |
| |
| private static String normalizeType(String type) |
| { |
| switch (type) |
| { |
| case "[Z": |
| return "boolean[]"; |
| case "[B": |
| return "byte[]"; |
| case "[S": |
| return "short[]"; |
| case "[I": |
| return "int[]"; |
| case "[J": |
| return "long[]"; |
| case "[F": |
| return "float[]"; |
| case "[D": |
| return "double[]"; |
| case "[C": |
| return "char[]"; |
| } |
| if (type.startsWith("[L")) |
| return type.substring(2, type.length() - 1) + "[]"; // -1 will remove the ; at the end |
| return type; |
| } |
| |
| private static final StringBuilder ROW_BUFFER = new StringBuilder(); |
| |
| private static void printRow(PrintStream out, String... args) |
| { |
| ROW_BUFFER.setLength(0); |
| ROW_BUFFER.append("\t\t"); |
| for (String a : args) |
| ROW_BUFFER.append(a).append("\t"); |
| out.println(ROW_BUFFER); |
| } |
| |
| public static final class Info |
| { |
| private Attribute[] attributes; |
| private Operation[] operations; |
| |
| public Info() |
| { |
| } |
| |
| public Info(Attribute[] attributes, Operation[] operations) |
| { |
| this.attributes = attributes; |
| this.operations = operations; |
| } |
| |
| private static Info from(MBeanInfo info) |
| { |
| Attribute[] attributes = Stream.of(info.getAttributes()) |
| .sorted(Comparator.comparing(MBeanFeatureInfo::getName)) |
| .map(Attribute::from) |
| .toArray(Attribute[]::new); |
| |
| Operation[] operations = Stream.of(info.getOperations()) |
| .sorted(OPERATOR_COMPARATOR) |
| .map(Operation::from) |
| .toArray(Operation[]::new); |
| return new Info(attributes, operations); |
| } |
| |
| public Attribute[] getAttributes() |
| { |
| return attributes; |
| } |
| |
| public void setAttributes(Attribute[] attributes) |
| { |
| this.attributes = attributes; |
| } |
| |
| public Set<String> attributeNames() |
| { |
| return Stream.of(attributes).map(a -> a.name).collect(Collectors.toSet()); |
| } |
| |
| public Set<Attribute> attributeSet() |
| { |
| return new HashSet<>(Arrays.asList(attributes)); |
| } |
| |
| public Operation[] getOperations() |
| { |
| return operations; |
| } |
| |
| public void setOperations(Operation[] operations) |
| { |
| this.operations = operations; |
| } |
| |
| public Set<String> operationNames() |
| { |
| return Stream.of(operations).map(o -> o.name).collect(Collectors.toSet()); |
| } |
| |
| public Set<Operation> operationSet() |
| { |
| return new HashSet<>(Arrays.asList(operations)); |
| } |
| |
| public Optional<Attribute> getAttribute(String name) |
| { |
| return Stream.of(attributes).filter(a -> a.name.equals(name)).findFirst(); |
| } |
| |
| public Attribute getAttributePresent(String name) |
| { |
| return getAttribute(name).orElseThrow(AssertionError::new); |
| } |
| |
| public Optional<Operation> getOperation(String name) |
| { |
| return Stream.of(operations).filter(o -> o.name.equals(name)).findFirst(); |
| } |
| |
| public Operation getOperationPresent(String name) |
| { |
| return getOperation(name).orElseThrow(AssertionError::new); |
| } |
| |
| @Override |
| public boolean equals(Object o) |
| { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| Info info = (Info) o; |
| return Arrays.equals(attributes, info.attributes) && |
| Arrays.equals(operations, info.operations); |
| } |
| |
| @Override |
| public int hashCode() |
| { |
| int result = Arrays.hashCode(attributes); |
| result = 31 * result + Arrays.hashCode(operations); |
| return result; |
| } |
| } |
| |
| public static final class Attribute implements Comparable<Attribute> |
| { |
| private String name; |
| private String type; |
| private String access; |
| |
| public Attribute() |
| { |
| } |
| |
| public Attribute(String name, String type, String access) |
| { |
| this.name = name; |
| this.type = type; |
| this.access = access; |
| } |
| |
| private static Attribute from(MBeanAttributeInfo info) |
| { |
| return new Attribute(info.getName(), normalizeType(info.getType()), JMXTool.getAccess(info)); |
| } |
| |
| public String getName() |
| { |
| return name; |
| } |
| |
| public void setName(String name) |
| { |
| this.name = name; |
| } |
| |
| public String getType() |
| { |
| return type; |
| } |
| |
| public void setType(String type) |
| { |
| this.type = type; |
| } |
| |
| public String getAccess() |
| { |
| return access; |
| } |
| |
| public void setAccess(String access) |
| { |
| this.access = access; |
| } |
| |
| public boolean equals(Object o) |
| { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| Attribute attribute = (Attribute) o; |
| return Objects.equals(name, attribute.name) && |
| Objects.equals(type, attribute.type); |
| } |
| |
| public int hashCode() |
| { |
| return Objects.hash(name, type); |
| } |
| |
| public String toString() |
| { |
| return name + ": " + type; |
| } |
| |
| public int compareTo(Attribute o) |
| { |
| int rc = name.compareTo(o.name); |
| if (rc != 0) |
| return rc; |
| return type.compareTo(o.type); |
| } |
| } |
| |
| public static final class Operation implements Comparable<Operation> |
| { |
| private String name; |
| private Parameter[] parameters; |
| private String returnType; |
| |
| public Operation() |
| { |
| } |
| |
| public Operation(String name, Parameter[] parameters, String returnType) |
| { |
| this.name = name; |
| this.parameters = parameters; |
| this.returnType = returnType; |
| } |
| |
| private static Operation from(MBeanOperationInfo info) |
| { |
| Parameter[] params = Stream.of(info.getSignature()).map(Parameter::from).toArray(Parameter[]::new); |
| return new Operation(info.getName(), params, normalizeType(info.getReturnType())); |
| } |
| |
| public String getName() |
| { |
| return name; |
| } |
| |
| public void setName(String name) |
| { |
| this.name = name; |
| } |
| |
| public Parameter[] getParameters() |
| { |
| return parameters; |
| } |
| |
| public void setParameters(Parameter[] parameters) |
| { |
| this.parameters = parameters; |
| } |
| |
| public List<String> parameterTypes() |
| { |
| return Stream.of(parameters).map(p -> p.type).collect(Collectors.toList()); |
| } |
| |
| public String getReturnType() |
| { |
| return returnType; |
| } |
| |
| public void setReturnType(String returnType) |
| { |
| this.returnType = returnType; |
| } |
| |
| public boolean equals(Object o) |
| { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| Operation operation = (Operation) o; |
| return Objects.equals(name, operation.name) && |
| Arrays.equals(parameters, operation.parameters) && |
| Objects.equals(returnType, operation.returnType); |
| } |
| |
| public int hashCode() |
| { |
| int result = Objects.hash(name, returnType); |
| result = 31 * result + Arrays.hashCode(parameters); |
| return result; |
| } |
| |
| public String toString() |
| { |
| return name + Stream.of(parameters).map(Parameter::toString).collect(Collectors.joining(", ", "(", ")")) + ": " + returnType; |
| } |
| |
| public int compareTo(Operation o) |
| { |
| int rc = name.compareTo(o.name); |
| if (rc != 0) |
| return rc; |
| rc = Integer.compare(parameters.length, o.parameters.length); |
| if (rc != 0) |
| return rc; |
| for (int i = 0; i < parameters.length; i++) |
| { |
| rc = parameters[i].type.compareTo(o.parameters[i].type); |
| if (rc != 0) |
| return rc; |
| } |
| return returnType.compareTo(o.returnType); |
| } |
| } |
| |
| public static final class Parameter |
| { |
| private String name; |
| private String type; |
| |
| public Parameter() |
| { |
| } |
| |
| public Parameter(String name, String type) |
| { |
| this.name = name; |
| this.type = type; |
| } |
| |
| private static Parameter from(MBeanParameterInfo info) |
| { |
| return new Parameter(info.getName(), normalizeType(info.getType())); |
| } |
| |
| public String getName() |
| { |
| return name; |
| } |
| |
| public void setName(String name) |
| { |
| this.name = name; |
| } |
| |
| public String getType() |
| { |
| return type; |
| } |
| |
| public void setType(String type) |
| { |
| this.type = type; |
| } |
| |
| public boolean equals(Object o) |
| { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| Parameter parameter = (Parameter) o; |
| return Objects.equals(type, parameter.type); |
| } |
| |
| public int hashCode() |
| { |
| return Objects.hash(type); |
| } |
| |
| public String toString() |
| { |
| return name + ": " + type; |
| } |
| } |
| |
| public static final class CliPattern |
| { |
| private final Pattern pattern; |
| |
| public CliPattern(String pattern) |
| { |
| this.pattern = Pattern.compile(pattern); |
| } |
| } |
| |
| public static void main(String[] args) throws Exception |
| { |
| Cli.CliBuilder<Callable<Void>> builder = Cli.builder("jmxtool"); |
| builder.withDefaultCommand(Help.class); |
| builder.withCommands(Help.class, Dump.class, Diff.class); |
| |
| Cli<Callable<Void>> parser = builder.build(); |
| Callable<Void> command = parser.parse(args); |
| command.call(); |
| } |
| } |