Refactor common integration test logic into the o.s.d.g.tests.integration.IntegrationTestsSupport class.
Add utility method to close a o.a.g.cache.GemFireCache instance and wait on the cache closed event.
This commit is contained in:
@@ -27,7 +27,6 @@ import java.net.Socket;
|
|||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import org.apache.geode.cache.server.CacheServer;
|
import org.apache.geode.cache.server.CacheServer;
|
||||||
@@ -49,49 +48,37 @@ import org.springframework.data.gemfire.util.CollectionUtils;
|
|||||||
* @see java.net.Socket
|
* @see java.net.Socket
|
||||||
* @see java.time.LocalDateTime
|
* @see java.time.LocalDateTime
|
||||||
* @see org.apache.geode.cache.server.CacheServer
|
* @see org.apache.geode.cache.server.CacheServer
|
||||||
|
* @see org.springframework.context.ApplicationContext
|
||||||
* @see org.springframework.context.annotation.AnnotationConfigApplicationContext
|
* @see org.springframework.context.annotation.AnnotationConfigApplicationContext
|
||||||
|
* @see org.springframework.data.gemfire.tests.integration.IntegrationTestsSupport
|
||||||
* @see org.springframework.data.gemfire.tests.process.ProcessExecutor
|
* @see org.springframework.data.gemfire.tests.process.ProcessExecutor
|
||||||
* @see org.springframework.data.gemfire.tests.process.ProcessWrapper
|
* @see org.springframework.data.gemfire.tests.process.ProcessWrapper
|
||||||
* @see SocketUtils
|
|
||||||
* @see ThreadUtils
|
|
||||||
* @since 0.0.1
|
* @since 0.0.1
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class ClientServerIntegrationTestsSupport {
|
public abstract class ClientServerIntegrationTestsSupport extends IntegrationTestsSupport {
|
||||||
|
|
||||||
protected static final long DEFAULT_WAIT_DURATION = TimeUnit.SECONDS.toMillis(30);
|
|
||||||
protected static final long DEFAULT_WAIT_INTERVAL = 500L; // milliseconds
|
|
||||||
|
|
||||||
protected static final String DEBUG_ENDPOINT = "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005";
|
protected static final String DEBUG_ENDPOINT = "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005";
|
||||||
protected static final String DEBUGGING_ENABLED_PROPERTY = "spring.data.gemfire.debugging.enabled";
|
protected static final String DEBUGGING_ENABLED_PROPERTY = "spring.data.gemfire.debugging.enabled";
|
||||||
protected static final String DEFAULT_HOSTNAME = "localhost";
|
protected static final String DEFAULT_HOSTNAME = "localhost";
|
||||||
protected static final String DIRECTORY_DELETE_ON_EXIT_PROPERTY = "spring.data.gemfire.directory.delete-on-exit";
|
protected static final String DIRECTORY_DELETE_ON_EXIT_PROPERTY = "spring.data.gemfire.directory.delete-on-exit";
|
||||||
protected static final String GEMFIRE_CACHE_SERVER_PORT_PROPERTY = "spring.data.gemfire.cache.server.port";
|
protected static final String GEMFIRE_CACHE_SERVER_PORT_PROPERTY = "spring.data.gemfire.cache.server.port";
|
||||||
protected static final String GEMFIRE_LOG_FILE = "gemfire-server.log";
|
|
||||||
protected static final String GEMFIRE_LOG_FILE_PROPERTY = "spring.data.gemfire.log.file";
|
|
||||||
protected static final String GEMFIRE_LOG_LEVEL = "warning";
|
|
||||||
protected static final String GEMFIRE_LOG_LEVEL_PROPERTY = "spring.data.gemfire.log.level";
|
|
||||||
protected static final String PROCESS_RUN_MANUAL_PROPERTY = "spring.data.gemfire.process.run-manual";
|
protected static final String PROCESS_RUN_MANUAL_PROPERTY = "spring.data.gemfire.process.run-manual";
|
||||||
protected static final String SYSTEM_PROPERTIES_LOG_FILE = "system-properties.log";
|
protected static final String SYSTEM_PROPERTIES_LOG_FILE = "system-properties.log";
|
||||||
protected static final String TEST_GEMFIRE_LOG_LEVEL = "warning";
|
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static String asApplicationName(Class<?> type) {
|
protected static String asApplicationName(Class<?> type) {
|
||||||
return type.getSimpleName();
|
return type.getSimpleName();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static String asDirectoryName(Class<?> type) {
|
protected static String asDirectoryName(Class<?> type) {
|
||||||
return String.format("%1$s-%2$s", asApplicationName(type),
|
return String.format("%1$s-%2$s", asApplicationName(type),
|
||||||
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd-hh-mm-ss")));
|
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd-hh-mm-ss")));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static File createDirectory(String pathname) {
|
protected static File createDirectory(String pathname) {
|
||||||
return createDirectory(new File(pathname));
|
return createDirectory(new File(pathname));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static File createDirectory(File directory) {
|
protected static File createDirectory(File directory) {
|
||||||
|
|
||||||
assertThat(directory.isDirectory() || directory.mkdirs())
|
assertThat(directory.isDirectory() || directory.mkdirs())
|
||||||
@@ -104,7 +91,6 @@ public class ClientServerIntegrationTestsSupport {
|
|||||||
return directory;
|
return directory;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static int findAvailablePort() throws IOException {
|
protected static int findAvailablePort() throws IOException {
|
||||||
|
|
||||||
ServerSocket serverSocket = null;
|
ServerSocket serverSocket = null;
|
||||||
@@ -118,110 +104,89 @@ public class ClientServerIntegrationTestsSupport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static String getClassNameAsPath(Class type) {
|
protected static String getClassNameAsPath(Class type) {
|
||||||
return type.getName().replaceAll("\\.", "/");
|
return type.getName().replaceAll("\\.", "/");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static String getClassNameAsPath(Object obj) {
|
protected static String getClassNameAsPath(Object obj) {
|
||||||
return getClassNameAsPath(obj.getClass());
|
return getClassNameAsPath(obj.getClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static String getPackageNameAsPath(Class type) {
|
protected static String getPackageNameAsPath(Class type) {
|
||||||
return type.getPackage().getName().replaceAll("\\.", "/");
|
return type.getPackage().getName().replaceAll("\\.", "/");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static String getPackageNameAsPath(Object obj) {
|
protected static String getPackageNameAsPath(Object obj) {
|
||||||
return getPackageNameAsPath(obj.getClass());
|
return getPackageNameAsPath(obj.getClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static String getContextXmlFileLocation(Class type) {
|
protected static String getContextXmlFileLocation(Class type) {
|
||||||
return getClassNameAsPath(type).concat("-context.xml");
|
return getClassNameAsPath(type).concat("-context.xml");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static String getServerContextXmlFileLocation(Class type) {
|
protected static String getServerContextXmlFileLocation(Class type) {
|
||||||
return getClassNameAsPath(type).concat("-server-context.xml");
|
return getClassNameAsPath(type).concat("-server-context.xml");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static boolean isDeleteDirectoryOnExit() {
|
protected static boolean isDeleteDirectoryOnExit() {
|
||||||
return Boolean.valueOf(System.getProperty(DIRECTORY_DELETE_ON_EXIT_PROPERTY, Boolean.TRUE.toString()));
|
return Boolean.valueOf(System.getProperty(DIRECTORY_DELETE_ON_EXIT_PROPERTY, Boolean.TRUE.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static int intValue(Number number) {
|
protected static int intValue(Number number) {
|
||||||
return (number != null ? number.intValue() : 0);
|
return number != null ? number.intValue() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static String logFile() {
|
protected static String logFile() {
|
||||||
return logFile(GEMFIRE_LOG_FILE);
|
return logFile(GEMFIRE_LOG_FILE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static String logFile(String defaultLogFilePathname) {
|
protected static String logFile(String defaultLogFilePathname) {
|
||||||
return System.getProperty(GEMFIRE_LOG_FILE_PROPERTY, defaultLogFilePathname);
|
return System.getProperty(GEMFIRE_LOG_FILE_PROPERTY, defaultLogFilePathname);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static String logLevel() {
|
protected static String logLevel() {
|
||||||
return logLevel(GEMFIRE_LOG_LEVEL);
|
return logLevel(GEMFIRE_LOG_LEVEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static String logLevel(String defaultLogLevel) {
|
protected static String logLevel(String defaultLogLevel) {
|
||||||
return System.getProperty(GEMFIRE_LOG_LEVEL_PROPERTY, defaultLogLevel);
|
return System.getProperty(GEMFIRE_LOG_LEVEL_PROPERTY, defaultLogLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static void logSystemProperties() throws IOException {
|
protected static void logSystemProperties() throws IOException {
|
||||||
FileUtils.write(new File(SYSTEM_PROPERTIES_LOG_FILE),
|
FileUtils.write(new File(SYSTEM_PROPERTIES_LOG_FILE),
|
||||||
String.format("%s", CollectionUtils.toString(System.getProperties())));
|
String.format("%s", CollectionUtils.toString(System.getProperties())));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static ProcessWrapper run(Class<?> type, String... arguments) throws IOException {
|
protected static ProcessWrapper run(Class<?> type, String... arguments) throws IOException {
|
||||||
return run(createDirectory(asDirectoryName(type)), type, arguments);
|
return run(createDirectory(asDirectoryName(type)), type, arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static ProcessWrapper run(File workingDirectory, Class<?> type, String... arguments) throws IOException {
|
protected static ProcessWrapper run(File workingDirectory, Class<?> type, String... arguments) throws IOException {
|
||||||
return (isProcessRunAuto() ? launch(createDirectory(workingDirectory), type, arguments) : null);
|
return isProcessRunAuto() ? launch(createDirectory(workingDirectory), type, arguments) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static ProcessWrapper run(String classpath, Class<?> type, String... arguments) throws IOException {
|
protected static ProcessWrapper run(String classpath, Class<?> type, String... arguments) throws IOException {
|
||||||
return run(createDirectory(asDirectoryName(type)), classpath, type, arguments);
|
return run(createDirectory(asDirectoryName(type)), classpath, type, arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static ProcessWrapper run(File workingDirectory, String classpath, Class<?> type, String... arguments)
|
protected static ProcessWrapper run(File workingDirectory, String classpath, Class<?> type, String... arguments)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
|
||||||
return (isProcessRunAuto() ? launch(createDirectory(workingDirectory), classpath, type, arguments) : null);
|
return isProcessRunAuto() ? launch(createDirectory(workingDirectory), classpath, type, arguments) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static boolean isProcessRunAuto() {
|
protected static boolean isProcessRunAuto() {
|
||||||
return !isProcessRunManual();
|
return !isProcessRunManual();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static boolean isProcessRunManual() {
|
protected static boolean isProcessRunManual() {
|
||||||
return Boolean.getBoolean(PROCESS_RUN_MANUAL_PROPERTY);
|
return Boolean.getBoolean(PROCESS_RUN_MANUAL_PROPERTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static AnnotationConfigApplicationContext runSpringApplication(Class<?> annotatedClass, String... args) {
|
protected static AnnotationConfigApplicationContext runSpringApplication(Class<?> annotatedClass, String... args) {
|
||||||
return runSpringApplication(asArray(annotatedClass), args);
|
return runSpringApplication(asArray(annotatedClass), args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static AnnotationConfigApplicationContext runSpringApplication(Class<?>[] annotatedClasses,
|
protected static AnnotationConfigApplicationContext runSpringApplication(Class<?>[] annotatedClasses,
|
||||||
String... args) {
|
String... args) {
|
||||||
|
|
||||||
@@ -233,12 +198,10 @@ public class ClientServerIntegrationTestsSupport {
|
|||||||
return applicationContext;
|
return applicationContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static boolean stop(ProcessWrapper process) {
|
protected static boolean stop(ProcessWrapper process) {
|
||||||
return stop(process, DEFAULT_WAIT_DURATION);
|
return stop(process, DEFAULT_WAIT_DURATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static boolean stop(ProcessWrapper process, long duration) {
|
protected static boolean stop(ProcessWrapper process, long duration) {
|
||||||
|
|
||||||
return Optional.ofNullable(process)
|
return Optional.ofNullable(process)
|
||||||
@@ -255,22 +218,18 @@ public class ClientServerIntegrationTestsSupport {
|
|||||||
.orElse(true);
|
.orElse(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static boolean waitForCacheServerToStart(CacheServer cacheServer) {
|
protected static boolean waitForCacheServerToStart(CacheServer cacheServer) {
|
||||||
return waitForServerToStart(cacheServer.getBindAddress(), cacheServer.getPort(), DEFAULT_WAIT_DURATION);
|
return waitForServerToStart(cacheServer.getBindAddress(), cacheServer.getPort(), DEFAULT_WAIT_DURATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static boolean waitForCacheServerToStart(CacheServer cacheServer, long duration) {
|
protected static boolean waitForCacheServerToStart(CacheServer cacheServer, long duration) {
|
||||||
return waitForServerToStart(cacheServer.getBindAddress(), cacheServer.getPort(), duration);
|
return waitForServerToStart(cacheServer.getBindAddress(), cacheServer.getPort(), duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static boolean waitForServerToStart(String host, int port) {
|
protected static boolean waitForServerToStart(String host, int port) {
|
||||||
return waitForServerToStart(host, port, DEFAULT_WAIT_DURATION);
|
return waitForServerToStart(host, port, DEFAULT_WAIT_DURATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc) */
|
|
||||||
protected static boolean waitForServerToStart(final String host, final int port, long duration) {
|
protected static boolean waitForServerToStart(final String host, final int port, long duration) {
|
||||||
|
|
||||||
return ThreadUtils.timedWait(duration, DEFAULT_WAIT_INTERVAL, new ThreadUtils.WaitCondition() {
|
return ThreadUtils.timedWait(duration, DEFAULT_WAIT_INTERVAL, new ThreadUtils.WaitCondition() {
|
||||||
@@ -297,31 +256,4 @@ public class ClientServerIntegrationTestsSupport {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static boolean waitOn(Condition condition) {
|
|
||||||
return waitOn(condition, DEFAULT_WAIT_DURATION);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("all")
|
|
||||||
protected static boolean waitOn(Condition condition, long duration) {
|
|
||||||
|
|
||||||
long timeout = (System.currentTimeMillis() + duration);
|
|
||||||
|
|
||||||
try {
|
|
||||||
while (!condition.evaluate() && System.currentTimeMillis() < timeout) {
|
|
||||||
synchronized (condition) {
|
|
||||||
TimeUnit.MILLISECONDS.timedWait(condition, DEFAULT_WAIT_INTERVAL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
}
|
|
||||||
|
|
||||||
return condition.evaluate();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected interface Condition {
|
|
||||||
boolean evaluate();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 the original author or authors.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.data.gemfire.tests.integration;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
import org.apache.geode.cache.CacheClosedException;
|
||||||
|
import org.apache.geode.cache.GemFireCache;
|
||||||
|
import org.springframework.data.gemfire.GemfireUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The IntegrationTestsSupport class...
|
||||||
|
*
|
||||||
|
* @author John Blum
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public abstract class IntegrationTestsSupport {
|
||||||
|
|
||||||
|
protected static final long DEFAULT_WAIT_DURATION = TimeUnit.SECONDS.toMillis(30);
|
||||||
|
protected static final long DEFAULT_WAIT_INTERVAL = 500L; // milliseconds
|
||||||
|
|
||||||
|
protected static final String GEMFIRE_LOG_FILE = "gemfire-server.log";
|
||||||
|
protected static final String GEMFIRE_LOG_FILE_PROPERTY = "spring.data.gemfire.log.file";
|
||||||
|
protected static final String GEMFIRE_LOG_LEVEL = "warning";
|
||||||
|
protected static final String GEMFIRE_LOG_LEVEL_PROPERTY = "spring.data.gemfire.log.level";
|
||||||
|
protected static final String TEST_GEMFIRE_LOG_LEVEL = "warning";
|
||||||
|
|
||||||
|
public static void closeGemFireCacheWaitOnCloseEvent() {
|
||||||
|
closeGemFireCacheWaitOnCloseEvent(DEFAULT_WAIT_DURATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void closeGemFireCacheWaitOnCloseEvent(long duration) {
|
||||||
|
|
||||||
|
AtomicBoolean closed = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
waitOn(() -> {
|
||||||
|
try {
|
||||||
|
return Optional.ofNullable(GemfireUtils.resolveGemFireCache())
|
||||||
|
.filter(cache -> !closed.get())
|
||||||
|
.filter(cache -> !cache.isClosed())
|
||||||
|
.map(IntegrationTestsSupport::close)
|
||||||
|
.map(GemFireCache::isClosed)
|
||||||
|
.orElse(true);
|
||||||
|
}
|
||||||
|
catch (CacheClosedException ignore) {
|
||||||
|
closed.set(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}, duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GemFireCache close(GemFireCache cache) {
|
||||||
|
cache.close();
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static boolean waitOn(Condition condition) {
|
||||||
|
return waitOn(condition, DEFAULT_WAIT_DURATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
protected static boolean waitOn(Condition condition, long duration) {
|
||||||
|
|
||||||
|
long timeout = System.currentTimeMillis() + duration;
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (!condition.evaluate() && System.currentTimeMillis() < timeout) {
|
||||||
|
synchronized (condition) {
|
||||||
|
TimeUnit.MILLISECONDS.timedWait(condition, DEFAULT_WAIT_INTERVAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (InterruptedException cause) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
return condition.evaluate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
protected interface Condition {
|
||||||
|
boolean evaluate();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user