From c39394983760a87df0572eff3ff1ca81fc606813 Mon Sep 17 00:00:00 2001 From: Eugene Date: Sun, 15 Apr 2018 23:31:25 +0200 Subject: [PATCH 1/2] Added utilities to measure execution time and provide hashCode/equals/toString helpers (cut versions from Guava) String representation added to FeatureRepository and Queue Execution time measurement and logging with debug level added to Queue --- .../eu/chargetime/ocpp/FeatureRepository.java | 8 + .../main/java/eu/chargetime/ocpp/Queue.java | 16 +- .../ocpp/utilities/MoreObjects.java | 656 ++++++++++++++++++ .../chargetime/ocpp/utilities/Stopwatch.java | 189 +++++ 4 files changed, 867 insertions(+), 2 deletions(-) create mode 100644 ocpp-common/src/main/java/eu/chargetime/ocpp/utilities/MoreObjects.java create mode 100644 ocpp-common/src/main/java/eu/chargetime/ocpp/utilities/Stopwatch.java diff --git a/ocpp-common/src/main/java/eu/chargetime/ocpp/FeatureRepository.java b/ocpp-common/src/main/java/eu/chargetime/ocpp/FeatureRepository.java index b328614ea..0bcb2a95a 100644 --- a/ocpp-common/src/main/java/eu/chargetime/ocpp/FeatureRepository.java +++ b/ocpp-common/src/main/java/eu/chargetime/ocpp/FeatureRepository.java @@ -29,6 +29,7 @@ of this software and associated documentation files (the "Software"), to deal import eu.chargetime.ocpp.feature.profile.Profile; import eu.chargetime.ocpp.model.Confirmation; import eu.chargetime.ocpp.model.Request; +import eu.chargetime.ocpp.utilities.MoreObjects; import java.util.ArrayList; import java.util.Collections; @@ -94,4 +95,11 @@ private boolean featureContains(Feature feature, Object object) { contains |= object instanceof Confirmation && feature.getConfirmationType() == object.getClass(); return contains; } + + @Override + public String toString() { + return MoreObjects.toStringHelper("FeatureRepository") + .add("featureList", featureList) + .toString(); + } } diff --git a/ocpp-common/src/main/java/eu/chargetime/ocpp/Queue.java b/ocpp-common/src/main/java/eu/chargetime/ocpp/Queue.java index 5b7112707..b251f332c 100644 --- a/ocpp-common/src/main/java/eu/chargetime/ocpp/Queue.java +++ b/ocpp-common/src/main/java/eu/chargetime/ocpp/Queue.java @@ -1,6 +1,8 @@ package eu.chargetime.ocpp; import eu.chargetime.ocpp.model.Request; +import eu.chargetime.ocpp.utilities.MoreObjects; +import eu.chargetime.ocpp.utilities.Stopwatch; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -55,10 +57,12 @@ public Queue () { * @return a unique identifier used to fetch the request. */ public String store(Request request) { + Stopwatch stopwatch = Stopwatch.createStarted(); + String ticket = UUID.randomUUID().toString(); requestQueue.put(ticket, request); - logger.debug("Queue size: {}", requestQueue.size()); + logger.debug("Queue size: {}, store time: {}", requestQueue.size(), stopwatch.stop()); return ticket; } @@ -72,12 +76,13 @@ public String store(Request request) { * @return the optional with stored {@link Request} */ public Optional restoreRequest(String ticket) { + Stopwatch stopwatch = Stopwatch.createStarted(); try { Request request = requestQueue.get(ticket); requestQueue.remove(ticket); - logger.debug("Queue size: {}", requestQueue.size()); + logger.debug("Queue size: {}, store time: {}", requestQueue.size(), stopwatch.stop()); return Optional.ofNullable(request); } catch (Exception ex) { @@ -85,4 +90,11 @@ public Optional restoreRequest(String ticket) { } return Optional.empty(); } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("requestQueue", requestQueue) + .toString(); + } } diff --git a/ocpp-common/src/main/java/eu/chargetime/ocpp/utilities/MoreObjects.java b/ocpp-common/src/main/java/eu/chargetime/ocpp/utilities/MoreObjects.java new file mode 100644 index 000000000..50c764411 --- /dev/null +++ b/ocpp-common/src/main/java/eu/chargetime/ocpp/utilities/MoreObjects.java @@ -0,0 +1,656 @@ +package eu.chargetime.ocpp.utilities; + +/* + * Copyright (C) 2014 The Guava Authors + * Modified by Evgeny Pakhomov + * + * Licensed 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. + */ + +import java.util.*; + +/** + * This class consists of {@code static} utility methods for operating + * on objects. These utilities include {@code null}-safe or {@code + * null}-tolerant methods for computing the hash code of an object, + * returning a string for an object, comparing two objects, etc. + * + * Cut version from Guava (needed to minimize dependency tree for client). + * + * @author Eugene Pakhomov + */ +public final class MoreObjects { + + /** + * Utility classes should have no instances. + */ + private MoreObjects() { + throw new AssertionError("No Objects instances should exist"); + } + + /** + * Returns {@code true} if the arguments are equal to each other and {@code false} otherwise. Consequently, if both + * arguments are {@code null}, {@code true} is returned and if exactly one argument is {@code null}, {@code false} + * is returned. Otherwise, equality is determined by using the {@link Object#equals equals} method of the first + * argument. + * + * @param a an object + * @param b an object to be compared with {@code a} for equality + * @return {@code true} if the arguments are equal to each other and {@code false} otherwise + * @see Object#equals(Object) + */ + public static boolean equals(Object a, Object b) { + return (a == b) || (a != null && a.equals(b)); + } + + + /** + * Returns {@code true} if the arguments are deeply equal to each other + * and {@code false} otherwise. + * + * Two {@code null} values are deeply equal. If both arguments are + * arrays, the algorithm in {@link Arrays#deepEquals(Object[], + * Object[]) Arrays.deepEquals} is used to determine equality. + * Otherwise, equality is determined by using the {@link + * Object#equals equals} method of the first argument. + * + * @param a an object + * @param b an object to be compared with {@code a} for deep equality + * @return {@code true} if the arguments are deeply equal to each other + * and {@code false} otherwise + * @see Arrays#deepEquals(Object[], Object[]) + * @see MoreObjects#equals(Object, Object) + */ + public static boolean deepEquals(Object a, Object b) { + if (a == b) { + return true; + } else if (a == null || b == null) { + return false; + } else { + return Arrays.deepEquals(new Object[]{a}, new Object[]{b}); + } + } + + /** + * Returns the hash code of a non-{@code null} argument and 0 for + * a {@code null} argument. + * + * @param o an object + * @return the hash code of a non-{@code null} argument and 0 for + * a {@code null} argument + * @see Object#hashCode + */ + public static int hashCode(Object o) { + return o != null ? o.hashCode() : 0; + } + + /** + * Generates a hash code for a sequence of input values. The hash + * code is generated as if all the input values were placed into an + * array, and that array were hashed by calling {@link + * Arrays#hashCode(Object[])}. + * + *

This method is useful for implementing {@link + * Object#hashCode()} on objects containing multiple fields. For + * example, if an object that has three fields, {@code x}, {@code + * y}, and {@code z}, one could write: + * + *

+     * @Override public int hashCode() {
+     *     return Objects.hash(x, y, z);
+     * }
+     * 
+ * + * Warning: When a single object reference is supplied, the returned + * value does not equal the hash code of that object reference. This + * value can be computed by calling {@link #hashCode(Object)}. + * + * @param values the values to be hashed + * @return a hash value of the sequence of input values + * @see Arrays#hashCode(Object[]) + */ + public static int hash(Object... values) { + return Arrays.hashCode(values); + } + + /** + * Creates shallow copy of input array. Returns null in case when null is passed as argument. + * + * @param array the array to be copied + * @param type of passed array elements + * @return copy of input array or null in case when null is passed as argument + */ + public static T[] clone(T[] array) { return array == null ? null : Arrays.copyOf(array, array.length); } + + /** + * Creates shallow copy of input array. Returns null in case when null is passed as argument. + * + * @param array the array to be copied + * @return copy of input array or null in case when null is passed as argument + */ + public static byte[] clone(byte[] array) { return array == null ? null : Arrays.copyOf(array, array.length); } + + /** + * Returns helper to generate string representation of given input object. + * + * @param self the object to be represented as string + * @return helper to generate string representation of given input object + */ + public static MoreObjects.ToStringHelper toStringHelper(Object self) { + return new MoreObjects.ToStringHelper(self); + } + + /** + * Returns helper to generate string representation of given input object. + * + * @param self the object to be represented as string + * @param outputFullDetails the flag to be set to output all elements of container (list, set, queue, map) or array of objects + * @return helper to generate string representation of given input object + */ + public static MoreObjects.ToStringHelper toStringHelper(Object self, boolean outputFullDetails) { + return new MoreObjects.ToStringHelper(self, outputFullDetails); + } + + /** + * Returns helper to generate string representation of given input class. + * + * @param clazz the class to be represented as string + * @return helper to generate string representation of given input class + */ + public static MoreObjects.ToStringHelper toStringHelper(Class clazz) { + return new MoreObjects.ToStringHelper(clazz); + } + + /** + * Returns helper to generate string representation of given input class. + * + * @param clazz the class to be represented as string + * @param outputFullDetails the flag to be set to output all elements of container (list, set, queue, map) or array of objects + * @return helper to generate string representation of given input class + */ + public static MoreObjects.ToStringHelper toStringHelper(Class clazz, boolean outputFullDetails) { + return new MoreObjects.ToStringHelper(clazz, outputFullDetails); + } + + /** + * Returns helper to generate string representation of class with given className. + * + * @param className the name of class to be represented as string + * @return helper to generate string representation of class with given className + */ + public static MoreObjects.ToStringHelper toStringHelper(String className) { + return new MoreObjects.ToStringHelper(className); + } + + /** + * Returns helper to generate string representation of class with given className. + * + * @param className the name of class to be represented as string + * @param outputFullDetails the flag to be set to output all elements of container (list, set, queue, map) or array of objects + * @return helper to generate string representation of class with given className + */ + public static MoreObjects.ToStringHelper toStringHelper(String className, boolean outputFullDetails) { + return new MoreObjects.ToStringHelper(className, outputFullDetails); + } + + /** + * Simple decorator to encapsulate actual toString helper implementation. + * If array of primitives passed as input parameter to {@link MoreObjects.ToStringHelper#add} function + * when if array length more than {@link MoreObjects.ToStringHelper#MAXIMUM_ARRAY_SIZE_TO_OUTPUT_DETAILS} + * then only length of that array will be written in output. If any container (list, set, queue, map) + * or array of objects passed as input parameter to {@link MoreObjects.ToStringHelper#add} function then only + * size of that container (array of objects) will be written in output. + */ + public static final class ToStringHelper { + + public static final int MAXIMUM_ARRAY_SIZE_TO_OUTPUT_DETAILS = 32; + public static final String FIELD_NAME_LENGTH_POSTFIX = ".length"; + public static final String FIELD_NAME_SIZE_POSTFIX = ".size"; + public static final String SECURE_FIELD_VALUE_REPLACEMENT = "********"; + + private final boolean outputFullDetails; + private final MoreObjects.ToStringHelper helperImplementation; + + private ToStringHelper(MoreObjects.ToStringHelper helperImplementation, boolean outputFullDetails) { + this.helperImplementation = helperImplementation; + this.outputFullDetails = outputFullDetails; + } + + private ToStringHelper(Object self) { + this(MoreObjects.toStringHelper(self), false); + } + + private ToStringHelper(Class clazz) { + this(MoreObjects.toStringHelper(clazz), false); + } + + private ToStringHelper(String className) { + this(MoreObjects.toStringHelper(className), false); + } + + private ToStringHelper(Object self, boolean outputFullDetails) { + this(MoreObjects.toStringHelper(self), outputFullDetails); + } + + private ToStringHelper(Class clazz, boolean outputFullDetails) { + this(MoreObjects.toStringHelper(clazz), outputFullDetails); + } + + private ToStringHelper(String className, boolean outputFullDetails) { + this(MoreObjects.toStringHelper(className), outputFullDetails ); + } + + /** + * Exclude from output fields with null value. + * + * @return ToStringHelper instance + */ + public MoreObjects.ToStringHelper omitNullValues() { + helperImplementation.omitNullValues(); + return this; + } + + /** + * Add field name and value to output. + * It's safe to pass null as value. + * + * @param name field name + * @param value field value + * @return ToStringHelper instance + */ + public MoreObjects.ToStringHelper add(String name, Object value) { + helperImplementation.add(name, value); + return this; + } + + /** + * Add field name and value to output. + * It's safe to pass null as value. + * + * @param name field name + * @param value field value + * @return ToStringHelper instance + */ + public MoreObjects.ToStringHelper add(String name, boolean value) { + helperImplementation.add(name, String.valueOf(value)); + return this; + } + + /** + * Add field name and value to output. + * It's safe to pass null as value. + * + * @param name field name + * @param value field value + * @return ToStringHelper instance + */ + public MoreObjects.ToStringHelper add(String name, char value) { + helperImplementation.add(name, String.valueOf(value)); + return this; + } + + /** + * Add field name and value to output. + * It's safe to pass null as value. + * + * @param name field name + * @param value field value + * @return ToStringHelper instance + */ + public MoreObjects.ToStringHelper add(String name, double value) { + helperImplementation.add(name, String.valueOf(value)); + return this; + } + + /** + * Add field name and value to output. + * It's safe to pass null as value. + * + * @param name field name + * @param value field value + * @return ToStringHelper instance + */ + public MoreObjects.ToStringHelper add(String name, float value) { + helperImplementation.add(name, String.valueOf(value)); + return this; + } + + /** + * Add field name and value to output. + * It's safe to pass null as value. + * + * @param name field name + * @param value field value + * @return ToStringHelper instance + */ + public MoreObjects.ToStringHelper add(String name, int value) { + helperImplementation.add(name, String.valueOf(value)); + return this; + } + + /** + * Add field name and value to output. + * It's safe to pass null as value. + * + * @param name field name + * @param value field value + * @return ToStringHelper instance + */ + public MoreObjects.ToStringHelper add(String name, long value) { + helperImplementation.add(name, String.valueOf(value)); + return this; + } + + /** + * Add field name and value to output. + * It's safe to pass null as value. + * + * @param name field name + * @param value field value + * @return ToStringHelper instance + */ + public MoreObjects.ToStringHelper add(String name, List value) { + return addCollection(name, value); + } + + /** + * Add field name and value to output. + * It's safe to pass null as value. + * + * @param name field name + * @param value field value + * @return ToStringHelper instance + */ + public MoreObjects.ToStringHelper add(String name, Set value) { + return addCollection(name, value); + } + + /** + * Add field name and value to output. + * It's safe to pass null as value. + * + * @param name field name + * @param value field value + * @return ToStringHelper instance + */ + public MoreObjects.ToStringHelper add(String name, Map value) { + return addMap(name, value); + } + + /** + * Add field name and value to output. + * It's safe to pass null as value. + * + * @param name field name + * @param value field value + * @return ToStringHelper instance + */ + public MoreObjects.ToStringHelper add(String name, Queue value) { + return addCollection(name, value); + } + + private MoreObjects.ToStringHelper addCollection(String name, Collection value) { + if(value != null && !outputFullDetails) { + helperImplementation.add(name + FIELD_NAME_SIZE_POSTFIX, value.size()); + } else { + helperImplementation.add(name, value); + } + return this; + } + + private MoreObjects.ToStringHelper addMap(String name, Map value) { + if(value != null && !outputFullDetails) { + helperImplementation.add(name + FIELD_NAME_SIZE_POSTFIX, value.size()); + } else { + helperImplementation.add(name, value); + } + return this; + } + + + /** + * Add field name and value to output. + * It's safe to pass null as value. + * + * @param name field name + * @param value field value + * @param type of passed array elements + * @return ToStringHelper instance + */ + public MoreObjects.ToStringHelper add(String name, T[] value) { + if(value != null && !outputFullDetails) { + helperImplementation.add(name + FIELD_NAME_LENGTH_POSTFIX, value.length); + } else { + helperImplementation.add(name, Arrays.toString(value)); + } + return this; + } + + /** + * Add field name and value to output. + * It's safe to pass null as value. + * + * @param name field name + * @param value field value + * @return ToStringHelper instance + */ + public MoreObjects.ToStringHelper add(String name, byte[] value) { + if(value != null && value.length > MAXIMUM_ARRAY_SIZE_TO_OUTPUT_DETAILS && !outputFullDetails) { + helperImplementation.add(name + FIELD_NAME_LENGTH_POSTFIX, value.length); + } else { + helperImplementation.add(name, Arrays.toString(value)); + } + return this; + } + + /** + * Add field name and value to output. + * It's safe to pass null as value. + * + * @param name field name + * @param value field value + * @return ToStringHelper instance + */ + public MoreObjects.ToStringHelper add(String name, boolean[] value) { + if(value != null && value.length > MAXIMUM_ARRAY_SIZE_TO_OUTPUT_DETAILS && !outputFullDetails) { + helperImplementation.add(name + FIELD_NAME_LENGTH_POSTFIX, value.length); + } else { + helperImplementation.add(name, Arrays.toString(value)); + } + return this; + } + + /** + * Add field name and value to output. + * It's safe to pass null as value. + * + * @param name field name + * @param value field value + * @return ToStringHelper instance + */ + public MoreObjects.ToStringHelper add(String name, char[] value) { + if(value != null && value.length > MAXIMUM_ARRAY_SIZE_TO_OUTPUT_DETAILS && !outputFullDetails) { + helperImplementation.add(name + FIELD_NAME_LENGTH_POSTFIX, value.length); + } else { + helperImplementation.add(name, Arrays.toString(value)); + } + return this; + } + + /** + * Add field name and value to output. + * It's safe to pass null as value. + * + * @param name field name + * @param value field value + * @return ToStringHelper instance + */ + public MoreObjects.ToStringHelper add(String name, double[] value) { + if(value != null && value.length > MAXIMUM_ARRAY_SIZE_TO_OUTPUT_DETAILS && !outputFullDetails) { + helperImplementation.add(name + FIELD_NAME_LENGTH_POSTFIX, value.length); + } else { + helperImplementation.add(name, Arrays.toString(value)); + } + return this; + } + + /** + * Add field name and value to output. + * It's safe to pass null as value. + * + * @param name field name + * @param value field value + * @return ToStringHelper instance + */ + public MoreObjects.ToStringHelper add(String name, float[] value) { + if(value != null && value.length > MAXIMUM_ARRAY_SIZE_TO_OUTPUT_DETAILS && !outputFullDetails) { + helperImplementation.add(name + FIELD_NAME_LENGTH_POSTFIX, value.length); + } else { + helperImplementation.add(name, Arrays.toString(value)); + } + return this; + } + + /** + * Add field name and value to output. + * It's safe to pass null as value. + * + * @param name field name + * @param value field value + * @return ToStringHelper instance + */ + public MoreObjects.ToStringHelper add(String name, int[] value ) { + if(value != null && value.length > MAXIMUM_ARRAY_SIZE_TO_OUTPUT_DETAILS && !outputFullDetails) { + helperImplementation.add(name + FIELD_NAME_LENGTH_POSTFIX, value.length); + } else { + helperImplementation.add(name, Arrays.toString(value)); + } + return this; + } + + /** + * Add field name and value to output. + * It's safe to pass null as value. + * + * @param name field name + * @param value field value + * @return ToStringHelper instance + */ + public MoreObjects.ToStringHelper add(String name, long[] value) { + if(value != null && value.length > MAXIMUM_ARRAY_SIZE_TO_OUTPUT_DETAILS && !outputFullDetails) { + helperImplementation.add(name + FIELD_NAME_LENGTH_POSTFIX, value.length); + } else { + helperImplementation.add(name, Arrays.toString(value)); + } + return this; + } + + /** + * Add field name and mask instead of real value to output. + * It's safe to pass null as value. + * + * @param name field name + * @param value field value + * @return ToStringHelper instance + */ + public MoreObjects.ToStringHelper addSecure(String name, String value) { + value = SECURE_FIELD_VALUE_REPLACEMENT; + helperImplementation.add(name, value); + return this; + } + + /** + * Add value to output. + * + * @param value to add to output + * @return ToStringHelper instance + */ + public MoreObjects.ToStringHelper addValue(Object value) { + helperImplementation.addValue(value); + return this; + } + + /** + * Add value to output. + * + * @param value to add to output + * @return ToStringHelper instance + */ + public MoreObjects.ToStringHelper addValue(boolean value) { + helperImplementation.addValue(String.valueOf(value)); + return this; + } + + /** + * Add value to output. + * + * @param value to add to output + * @return ToStringHelper instance + */ + public MoreObjects.ToStringHelper addValue(char value) { + helperImplementation.addValue(String.valueOf(value)); + return this; + } + + /** + * Add value to output. + * + * @param value to add to output + * @return ToStringHelper instance + */ + public MoreObjects.ToStringHelper addValue(double value) { + helperImplementation.addValue(String.valueOf(value)); + return this; + } + + /** + * Add value to output. + * + * @param value to add to output + * @return ToStringHelper instance + */ + public MoreObjects.ToStringHelper addValue(float value) { + helperImplementation.addValue(String.valueOf(value)); + return this; + } + + /** + * Add value to output. + * + * @param value to add to output + * @return ToStringHelper instance + */ + public MoreObjects.ToStringHelper addValue(int value) { + helperImplementation.addValue(String.valueOf(value)); + return this; + } + + /** + * Add value to output. + * + * @param value to add to output + * @return ToStringHelper instance + */ + public MoreObjects.ToStringHelper addValue(long value) { + helperImplementation.addValue(String.valueOf(value)); + return this; + } + + /** + * Returns resulting output string. + * + * @return resulting output string + */ + public String toString() { + return helperImplementation.toString(); + } + } +} diff --git a/ocpp-common/src/main/java/eu/chargetime/ocpp/utilities/Stopwatch.java b/ocpp-common/src/main/java/eu/chargetime/ocpp/utilities/Stopwatch.java new file mode 100644 index 000000000..d003b0a8d --- /dev/null +++ b/ocpp-common/src/main/java/eu/chargetime/ocpp/utilities/Stopwatch.java @@ -0,0 +1,189 @@ +package eu.chargetime.ocpp.utilities; + +/* + * Copyright (C) 2014 The Guava Authors + * Modified by Evgeny Pakhomov + * + * Licensed 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. + */ + +import java.time.Duration; +import java.util.Locale; +import java.util.concurrent.TimeUnit; + +import static java.util.concurrent.TimeUnit.*; + +/** + * Stopwatch implementation. + * Cut version from Guava (needed to minimize dependency tree for client). + */ +public final class Stopwatch { + private boolean isRunning; + private long elapsedNanos; + private long startTick; + + /** + * Creates (but does not start) a new stopwatch using {@link System#nanoTime} as its time source. + */ + public static Stopwatch createUnstarted() { + return new Stopwatch(); + } + + /** + * Creates (and starts) a new stopwatch using {@link System#nanoTime} as its time source. + */ + public static Stopwatch createStarted() { + return new Stopwatch().start(); + } + + /** + * Returns {@code true} if {@link #start()} has been called on this stopwatch, and {@link #stop()} + * has not been called since the last call to {@code start()}. + */ + public boolean isRunning() { + return isRunning; + } + + /** + * Starts the stopwatch. + * + * @return this {@code Stopwatch} instance + * @throws IllegalStateException if the stopwatch is already running. + */ + + public Stopwatch start() { + checkState(false); + + isRunning = true; + startTick = System.nanoTime(); + return this; + } + + /** + * Stops the stopwatch. Future reads will return the fixed duration that had elapsed up to this + * point. + * + * @return this {@code Stopwatch} instance + * @throws IllegalStateException if the stopwatch is already stopped. + */ + + public Stopwatch stop() { + checkState(true); + + long tick = System.nanoTime(); + isRunning = false; + elapsedNanos += tick - startTick; + return this; + } + + /** + * Sets the elapsed time for this stopwatch to zero, and places it in a stopped state. + * + * @return this {@code Stopwatch} instance + */ + public Stopwatch reset() { + elapsedNanos = 0; + isRunning = false; + return this; + } + + private long elapsedNanos() { + return isRunning ? System.nanoTime() - startTick + elapsedNanos : elapsedNanos; + } + + /** + * Returns the current elapsed time shown on this stopwatch, expressed in the desired time unit, + * with any fraction rounded down. + * + *

Note: the overhead of measurement can be more than a microsecond, so it is generally + * not useful to specify {@link TimeUnit#NANOSECONDS} precision here. + * + *

It is generally not a good idea to use an ambiguous, unitless {@code long} to represent + * elapsed time. Therefore, we recommend using {@link #elapsed()} instead, which returns a + * strongly-typed {@link Duration} instance. + */ + public long elapsed(TimeUnit desiredUnit) { + return desiredUnit.convert(elapsedNanos(), NANOSECONDS); + } + + /** + * Returns the current elapsed time shown on this stopwatch as a {@link Duration}. Unlike {@link + * #elapsed(TimeUnit)}, this method does not lose any precision due to rounding. + */ + public Duration elapsed() { + return Duration.ofNanos(elapsedNanos()); + } + + /** Returns a string representation of the current elapsed time. */ + @Override + public String toString() { + long nanos = elapsedNanos(); + + TimeUnit unit = chooseUnit(nanos); + double value = (double) nanos / NANOSECONDS.convert(1, unit); + + return formatCompact4Digits(value) + " " + abbreviate(unit); + } + + private void checkState(boolean stateExpectation) { + if(isRunning != stateExpectation) { + throw new IllegalStateException("This stopwatch is already " + + (isRunning ? "running" : "stopped")); + }; + } + + private static TimeUnit chooseUnit(long nanos) { + if (DAYS.convert(nanos, NANOSECONDS) > 0) { + return DAYS; + } + if (HOURS.convert(nanos, NANOSECONDS) > 0) { + return HOURS; + } + if (MINUTES.convert(nanos, NANOSECONDS) > 0) { + return MINUTES; + } + if (SECONDS.convert(nanos, NANOSECONDS) > 0) { + return SECONDS; + } + if (MILLISECONDS.convert(nanos, NANOSECONDS) > 0) { + return MILLISECONDS; + } + if (MICROSECONDS.convert(nanos, NANOSECONDS) > 0) { + return MICROSECONDS; + } + return NANOSECONDS; + } + + private static String abbreviate(TimeUnit unit) { + switch (unit) { + case NANOSECONDS: + return "ns"; + case MICROSECONDS: + return "\u03bcs"; // μs + case MILLISECONDS: + return "ms"; + case SECONDS: + return "s"; + case MINUTES: + return "min"; + case HOURS: + return "h"; + case DAYS: + return "d"; + default: + throw new AssertionError(); + } + } + + private static String formatCompact4Digits(double value) { + return String.format(Locale.ROOT, "%.4g", value); + } +} \ No newline at end of file From 1153ada63cb902ae88897c7ce18266df86b4043a Mon Sep 17 00:00:00 2001 From: Eugene Date: Sun, 15 Apr 2018 23:52:12 +0200 Subject: [PATCH 2/2] Flag `closed` added to be able to check from calling code if connection still alive --- .../java/eu/chargetime/ocpp/Listener.java | 2 +- .../main/java/eu/chargetime/ocpp/Radio.java | 11 +++++++- .../java/eu/chargetime/ocpp/IClientAPI.java | 2 ++ .../java/eu/chargetime/ocpp/IServerAPI.java | 1 + .../java/eu/chargetime/ocpp/JSONClient.java | 5 ++++ .../java/eu/chargetime/ocpp/JSONServer.java | 5 ++++ .../java/eu/chargetime/ocpp/SOAPClient.java | 10 +++++++ .../java/eu/chargetime/ocpp/SOAPServer.java | 19 +++++++++++--- .../chargetime/ocpp/WebServiceListener.java | 11 ++++++++ .../chargetime/ocpp/WebServiceReceiver.java | 5 ++++ .../ocpp/WebServiceTransmitter.java | 5 ++++ .../eu/chargetime/ocpp/WebSocketListener.java | 26 ++++++++++++++++--- .../eu/chargetime/ocpp/WebSocketReceiver.java | 5 ++++ .../ocpp/WebSocketReceiverEvents.java | 10 +++++++ .../chargetime/ocpp/WebSocketTransmitter.java | 8 ++++++ 15 files changed, 116 insertions(+), 9 deletions(-) diff --git a/ocpp-common/src/main/java/eu/chargetime/ocpp/Listener.java b/ocpp-common/src/main/java/eu/chargetime/ocpp/Listener.java index a0cb545ac..664cf2b77 100644 --- a/ocpp-common/src/main/java/eu/chargetime/ocpp/Listener.java +++ b/ocpp-common/src/main/java/eu/chargetime/ocpp/Listener.java @@ -28,6 +28,6 @@ of this software and associated documentation files (the "Software"), to deal public interface Listener { void open(String hostname, int port, ListenerEvents listenerEvents); void close(); - + boolean isClosed(); void setAsyncRequestHandler(boolean async); } diff --git a/ocpp-common/src/main/java/eu/chargetime/ocpp/Radio.java b/ocpp-common/src/main/java/eu/chargetime/ocpp/Radio.java index ab52b0248..130976f1b 100644 --- a/ocpp-common/src/main/java/eu/chargetime/ocpp/Radio.java +++ b/ocpp-common/src/main/java/eu/chargetime/ocpp/Radio.java @@ -1,4 +1,5 @@ -package eu.chargetime.ocpp;/* +package eu.chargetime.ocpp; +/* ChargeTime.eu - Java-OCA-OCPP MIT License @@ -41,4 +42,12 @@ public interface Radio { * @exception NotConnectedException Message couldn't be sent due to the lack of connection. */ void send(Object message) throws NotConnectedException; + + + /** + * If connection is closed. + * + * @return true if connection is closed or was not opened + */ + boolean isClosed(); } diff --git a/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/IClientAPI.java b/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/IClientAPI.java index 1a74ee956..5f19dccc2 100644 --- a/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/IClientAPI.java +++ b/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/IClientAPI.java @@ -39,4 +39,6 @@ public interface IClientAPI { CompletionStage send(Request request) throws OccurenceConstraintException, UnsupportedFeatureException; void disconnect(); + + boolean isClosed(); } diff --git a/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/IServerAPI.java b/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/IServerAPI.java index bb9d17f72..436c96a90 100644 --- a/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/IServerAPI.java +++ b/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/IServerAPI.java @@ -41,5 +41,6 @@ public interface IServerAPI { void close(); + boolean isClosed(); CompletionStage send(UUID session, Request request) throws OccurenceConstraintException, UnsupportedFeatureException; } \ No newline at end of file diff --git a/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/JSONClient.java b/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/JSONClient.java index dc6e9ed2e..760228d10 100644 --- a/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/JSONClient.java +++ b/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/JSONClient.java @@ -91,4 +91,9 @@ public CompletionStage send(Request request) throws OccurenceConst public void disconnect() { client.disconnect(); } + + @Override + public boolean isClosed() { + return transmitter.isClosed(); + } } diff --git a/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/JSONServer.java b/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/JSONServer.java index e579c0b47..621ffa5ff 100644 --- a/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/JSONServer.java +++ b/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/JSONServer.java @@ -77,6 +77,11 @@ public void close() { server.close(); } + @Override + public boolean isClosed() { + return listener.isClosed(); + } + @Override public CompletionStage send(UUID session, Request request) throws OccurenceConstraintException, UnsupportedFeatureException { return server.send(session, request); diff --git a/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/SOAPClient.java b/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/SOAPClient.java index aedf38a64..b66c12410 100644 --- a/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/SOAPClient.java +++ b/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/SOAPClient.java @@ -112,6 +112,16 @@ public void disconnect() { } } + /** + * Flag if connection is closed. + * + * @return true if connection was closed or not opened + */ + @Override + public boolean isClosed() { + return transmitter.isClosed(); + } + private int getPort() { return callback.getPort() == -1 ? 8000 : callback.getPort(); } diff --git a/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/SOAPServer.java b/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/SOAPServer.java index 588736b19..8171a10b5 100644 --- a/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/SOAPServer.java +++ b/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/SOAPServer.java @@ -35,15 +35,16 @@ of this software and associated documentation files (the "Software"), to deal public class SOAPServer implements IServerAPI { - private FeatureRepository featureRepository; - private Server server; + private final FeatureRepository featureRepository; + private final Server server; + private final WebServiceListener listener; public SOAPServer(ServerCoreProfile coreProfile) { featureRepository = new FeatureRepository(); ServerSessionFactory sessionFactory = new ServerSessionFactory(featureRepository); - Listener listener = new WebServiceListener(sessionFactory); - server = new Server(listener, featureRepository, new PromiseRepository()); + this.listener = new WebServiceListener(sessionFactory); + server = new Server(this.listener, featureRepository, new PromiseRepository()); featureRepository.addFeatureProfile(coreProfile); } @@ -67,6 +68,16 @@ public void close() { server.close(); } + /** + * Flag if connection is closed. + * + * @return true if connection was closed or not opened + */ + @Override + public boolean isClosed() { + return listener.isClosed(); + } + @Override public CompletionStage send(UUID session, Request request) throws OccurenceConstraintException, UnsupportedFeatureException { return server.send(session, request); diff --git a/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/WebServiceListener.java b/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/WebServiceListener.java index 990c6a81f..94ca0fd65 100644 --- a/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/WebServiceListener.java +++ b/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/WebServiceListener.java @@ -47,6 +47,7 @@ public class WebServiceListener implements Listener { private String fromUrl = null; private HttpServer server; private boolean handleRequestAsync; + private volatile boolean closed = true; public WebServiceListener(IServerSessionFactory sessionFactory) { this.sessionFactory = sessionFactory; @@ -62,6 +63,8 @@ public void open(String hostname, int port, ListenerEvents listenerEvents) { server.setExecutor(java.util.concurrent.Executors.newCachedThreadPool()); server.start(); + + closed = false; } catch (IOException e) { logger.warn("open() failed", e); } @@ -71,6 +74,12 @@ public void open(String hostname, int port, ListenerEvents listenerEvents) { public void close() { if (server != null) server.stop(1); + closed = true; + } + + @Override + public boolean isClosed() { + return closed; } @Override @@ -108,6 +117,8 @@ public SOAPMessage incomingRequest(SOAPMessageInfo messageInfo) { session.close(); chargeBoxes.remove(identity); }); + + // TODO: Decorator created but not used ISession sessionDecorator = new TimeoutSessionDecorator(timeoutTimer, session); SessionInformation information = new SessionInformation.Builder().Identifier(identity).InternetAddress(messageInfo.getAddress()).SOAPtoURL(toUrl).build(); diff --git a/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/WebServiceReceiver.java b/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/WebServiceReceiver.java index a7db48f33..318e2e630 100644 --- a/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/WebServiceReceiver.java +++ b/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/WebServiceReceiver.java @@ -63,6 +63,11 @@ public void disconnect() { receiverEvents.disconnect(); } + @Override + public boolean isClosed() { + return !connected; + } + @Override public void accept(RadioEvents events) { this.events = events; diff --git a/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/WebServiceTransmitter.java b/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/WebServiceTransmitter.java index 06fa83008..dc6a6948c 100644 --- a/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/WebServiceTransmitter.java +++ b/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/WebServiceTransmitter.java @@ -60,6 +60,11 @@ public void disconnect() { events.disconnected(); } + @Override + public boolean isClosed() { + return !connected; + } + @Override public void connect(String uri, RadioEvents events) { url = uri; diff --git a/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/WebSocketListener.java b/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/WebSocketListener.java index 02bbca725..3b980a660 100644 --- a/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/WebSocketListener.java +++ b/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/WebSocketListener.java @@ -50,6 +50,7 @@ public class WebSocketListener implements Listener { private WebSocketServer server; private HashMap sockets; + private volatile boolean closed = true; private boolean handleRequestAsync; public WebSocketListener(IServerSessionFactory sessionFactory) { @@ -62,8 +63,21 @@ public void open(String hostname, int port, ListenerEvents handler) { Draft_6455 draft = new Draft_6455(Collections.emptyList(), Collections.singletonList(new Protocol("ocpp1.6"))); server = new WebSocketServer(new InetSocketAddress(hostname, port), Collections.singletonList(draft)) { @Override - public void onOpen(WebSocket webSocket, ClientHandshake clientHandshake) { - WebSocketReceiver receiver = new WebSocketReceiver(message -> webSocket.send(message)); + public void onOpen(WebSocket webSocket, ClientHandshake clientHandshake) { + + WebSocketReceiver receiver = new WebSocketReceiver( + new WebSocketReceiverEvents() { + @Override + public boolean isClosed() { + return closed; + } + + @Override + public void relay(String message) { + webSocket.send(message); + } + } + ); sockets.put(webSocket, receiver); SessionInformation information = new SessionInformation.Builder() .Identifier(clientHandshake.getResourceDescriptor()) @@ -94,6 +108,7 @@ public void onStart() { } }; server.start(); + closed = false; } public void enableWSS(SSLContext sslContext) { @@ -103,7 +118,7 @@ public void enableWSS(SSLContext sslContext) { @Override public void close() { try { - + closed = true; for (WebSocket ws : sockets.keySet()) ws.close(); @@ -116,6 +131,11 @@ public void close() { } } + @Override + public boolean isClosed() { + return closed; + } + @Override public void setAsyncRequestHandler(boolean async) { this.handleRequestAsync = async; diff --git a/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/WebSocketReceiver.java b/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/WebSocketReceiver.java index c481e570a..e6e61f4df 100644 --- a/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/WebSocketReceiver.java +++ b/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/WebSocketReceiver.java @@ -48,6 +48,11 @@ public void send(Object message) { receiverEvents.relay(message.toString()); } + @Override + public boolean isClosed() { + return receiverEvents.isClosed(); + } + @Override public void accept(RadioEvents events) { handler = events; diff --git a/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/WebSocketReceiverEvents.java b/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/WebSocketReceiverEvents.java index 80434a130..8c8503681 100644 --- a/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/WebSocketReceiverEvents.java +++ b/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/WebSocketReceiverEvents.java @@ -25,5 +25,15 @@ of this software and associated documentation files (the "Software"), to deal */ public interface WebSocketReceiverEvents { + /** + * @return true if connection is closed (either not connected or was disconnected) + */ + boolean isClosed(); + + /** + * Send a message. + * + * @param message message to send + */ void relay(String message); } diff --git a/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/WebSocketTransmitter.java b/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/WebSocketTransmitter.java index b8823ff5a..98777cd4b 100644 --- a/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/WebSocketTransmitter.java +++ b/ocpp-v1_6/src/main/java/eu/chargetime/ocpp/WebSocketTransmitter.java @@ -54,6 +54,7 @@ public class WebSocketTransmitter implements Transmitter { private static final Logger logger = LoggerFactory.getLogger(WebSocketTransmitter.class); + private volatile boolean closed = true; private WebSocketClient client; public WebSocketTransmitter() { @@ -94,6 +95,7 @@ public void onError(Exception ex) }; try { client.connectBlocking(); + closed = false; } catch (Exception ex) { logger.warn("client.connectBlocking() failed", ex); } @@ -111,6 +113,8 @@ public void disconnect() client.closeBlocking(); } catch (Exception ex) { logger.info("client.closeBlocking() failed", ex); + } finally { + closed = true; } } @@ -123,4 +127,8 @@ public void send(Object request) throws NotConnectedException { throw new NotConnectedException(); } } + + public boolean isClosed() { + return closed; + } }