diff --git a/eventbus/pom.xml b/eventbus/pom.xml
index fae03f1..583f434 100644
--- a/eventbus/pom.xml
+++ b/eventbus/pom.xml
@@ -26,6 +26,11 @@
feathercore-eventbus
+
+ ru.progrm-jarvis
+ reflector
+
+
org.projectlombok
lombok
diff --git a/eventbus/src/main/java/org/feathercore/eventbus/SimpleEventManager.java b/eventbus/src/main/java/org/feathercore/eventbus/SimpleEventManager.java
index f1d12e7..73396dc 100644
--- a/eventbus/src/main/java/org/feathercore/eventbus/SimpleEventManager.java
+++ b/eventbus/src/main/java/org/feathercore/eventbus/SimpleEventManager.java
@@ -21,11 +21,9 @@
import lombok.experimental.FieldDefaults;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import ru.progrm_jarvis.reflector.invoke.InvokeUtil;
-import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandles.Lookup;
-import java.lang.invoke.MethodType;
-import java.lang.reflect.Method;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Queue;
@@ -83,7 +81,14 @@ public void register(@NonNull final Object listener) {
}
EventHandler annotation = method.getAnnotation(EventHandler.class);
- register((Class extends E>) parameterType, constructConsumer(listener, method), annotation.priority());
+ register(
+ (Class) parameterType,
+ InvokeUtil., Object>invokeFactory()
+ .boundTo(listener)
+ .implementing(Consumer.class)
+ .via(method)
+ .createUnsafely(),
+ annotation.priority());
}
}
@@ -106,23 +111,6 @@ public void call(@NotNull final T event) {
}
}
- @SuppressWarnings("unchecked")
- protected static Consumer constructConsumer(Object listener, Method method) {
- try {
- Lookup lookup = constructLookup(listener.getClass());
- return (Consumer) LambdaMetafactory.metafactory(
- lookup,
- "accept",
- MethodType.methodType(Consumer.class, listener.getClass()),
- MethodType.methodType(void.class, Object.class),
- lookup.unreflect(method),
- MethodType.methodType(void.class, method.getParameterTypes()[0])
- ).getTarget().invoke(listener);
- } catch (Throwable t) {
- throw new RuntimeException("Could not create event listener", t);
- }
- }
-
/**
* We are in need of our own Lookup creation with an argument of given owner class, because we want to be able
* to execute private and any other non-public methods.
diff --git a/module-api/pom.xml b/module-api/pom.xml
index 426da93..5bf647c 100644
--- a/module-api/pom.xml
+++ b/module-api/pom.xml
@@ -31,6 +31,16 @@
feathercore-shared
+
+ ru.progrm-jarvis
+ java-commons
+
+
+
+ com.google.guava
+ guava
+
+
org.projectlombok
lombok
@@ -45,6 +55,10 @@
org.junit.jupiter
junit-jupiter-api
+
+ org.junit.jupiter
+ junit-jupiter-params
+
org.mockito
mockito-core
diff --git a/module-api/src/main/java/ru/feathercore/moduleapi/AbstractModuleLoader.java b/module-api/src/main/java/ru/feathercore/moduleapi/AbstractModuleLoader.java
index e73ef94..f32bdd3 100644
--- a/module-api/src/main/java/ru/feathercore/moduleapi/AbstractModuleLoader.java
+++ b/module-api/src/main/java/ru/feathercore/moduleapi/AbstractModuleLoader.java
@@ -20,58 +20,116 @@
import lombok.experimental.FieldDefaults;
import org.jetbrains.annotations.NotNull;
-import java.util.Collection;
-import java.util.Collections;
+import java.util.*;
/**
* An abstract implementation of {@link ModuleLoader} sufficient for all operations it requires.
*
- * @param super-type of all modules managed
+ * @param super-type of modules which this module-loader can manage
+ *
+ * // TODO: 15.11.2019 Docs
*/
@ToString
@EqualsAndHashCode
@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true)
-public abstract class AbstractModuleLoader implements ModuleLoader {
+public class AbstractModuleLoader implements ModuleLoader {
+
+ @NonNull Map, M> modules;
+ /**
+ * Storage of unique instanced of loaded modules.
+ */
+ @NonNull Set loadedModules, loadedModulesView;
+
+ /**
+ * Creates new instance of module loader using the given {@link Collection collections} for storage of modules.
+ *
+ * @param modulesMap set which will be used for storage of associations of module types with their implementations
+ * @param loadedModulesSet set which will be used for storage of loaded modules
+ * @throws IllegalArgumentException if the given set is non-empty
+ */
+ public AbstractModuleLoader(@NonNull final Map, M> modulesMap,
+ @NonNull final Set loadedModulesSet) {
+ if (!modulesMap.isEmpty()) throw new IllegalArgumentException("modules map should be empty");
+ if (!loadedModulesSet.isEmpty()) throw new IllegalArgumentException("loaded modules set should be empty");
+
+ modules = modulesMap;
+ loadedModules = loadedModulesSet;
+ loadedModulesView = Collections.unmodifiableSet(loadedModules);
+ }
- @NonNull Collection modules, modulesView;
+ /* ************************************************* Callbacks ************************************************* */
- public AbstractModuleLoader(@NonNull final Collection modules) {
- this.modules = modules;
- modulesView = Collections.unmodifiableCollection(modules);
+ /**
+ * Callback used by method-loading methods to notify that the module has been loaded.
+ *
+ * @param module module which has just been loaded
+ */
+ protected void onModuleLoad(@NotNull final M module) {}
+
+ /**
+ * Callback used by {@link #unloadModule(Class)} to notify that the module has been unloaded.
+ *
+ * @param module module which has just been unloaded
+ */
+ protected void onModuleUnload(@NotNull final M module) {}
+
+ /* *************************************************** Logic *************************************************** */
+
+ @Override
+ public Collection getModules() {
+ return loadedModulesView;
}
@Override
- public Collection extends M> getModules() {
- return modulesView;
+ @SuppressWarnings("unchecked")
+ public Optional extends T> getModule(@NonNull final Class type) {
+ return Optional.ofNullable((T) modules.get(type));
}
@Override
- public T loadModule(@NonNull final ModuleInitializer initializer, final C configuration) {
+ public boolean isModuleLoaded(@NonNull final Class extends M> type) {
+ return modules.containsKey(type);
+ }
+
+ @Override
+ public boolean isModuleLoaded(@NonNull final T module) {
+ return modules.containsValue(module);
+ }
+
+ @Override
+ public T loadModule(@NonNull final ModuleInitializer initializer, final C configuration,
+ final Iterable> moduleTypes) {
val module = initializer.loadModule(configuration);
- if (modules.add(module)) onModuleLoad(module);
+ for (val moduleType : moduleTypes) modules.put(moduleType, module);
+ loadedModules.add(module);
+
+ onModuleLoad(module);
return module;
}
@Override
- public boolean unloadModule(@NonNull final T module) {
- val unloaded = modules.remove(module);
- if (unloaded) onModuleUnload(module);
+ public Optional unloadModule(@NonNull final Class extends T> moduleType) {
+ @SuppressWarnings("unchecked") val module = (T) modules.remove(moduleType);
+ if (module != null) {
+ if (!modules.containsValue(module)) loadedModules.remove(module);
+ onModuleUnload(module);
+
+ return Optional.of(module);
+ }
- return unloaded;
+ return Optional.empty();
}
- /**
- * Callback used by {@link #loadModule(ModuleInitializer, Object)} to notify that the module has been loaded.
- *
- * @param module module which has just been loaded
- */
- protected void onModuleLoad(@NotNull final M module) {}
+ @Override
+ public Collection extends M> unloadAllModules() {
+ val modulesUnloaded = new ArrayList<>(loadedModules);
- /**
- * Callback used by {@link #unloadModule(Module)} to notify that the module has been unloaded.
- *
- * @param module module which has just been loaded
- */
- protected void onModuleUnload(@NotNull final M module) {}
+ for (val module : modulesUnloaded) onModuleUnload(module);
+
+ modules.clear();
+ loadedModules.clear();
+
+ return modulesUnloaded;
+ }
}
diff --git a/module-api/src/main/java/ru/feathercore/moduleapi/ModuleInitializer.java b/module-api/src/main/java/ru/feathercore/moduleapi/ModuleInitializer.java
index 4883ccf..9007e56 100644
--- a/module-api/src/main/java/ru/feathercore/moduleapi/ModuleInitializer.java
+++ b/module-api/src/main/java/ru/feathercore/moduleapi/ModuleInitializer.java
@@ -16,8 +16,8 @@
package ru.feathercore.moduleapi;
-import org.feathercore.shared.object.ValueContainer;
import org.jetbrains.annotations.NotNull;
+import ru.progrm_jarvis.javacommons.object.ValueContainer;
/**
* Initializer of a module which is normally used by {@link ModuleLoader} to load a specific module.
diff --git a/module-api/src/main/java/ru/feathercore/moduleapi/ModuleLoader.java b/module-api/src/main/java/ru/feathercore/moduleapi/ModuleLoader.java
index 168ea3b..097085c 100644
--- a/module-api/src/main/java/ru/feathercore/moduleapi/ModuleLoader.java
+++ b/module-api/src/main/java/ru/feathercore/moduleapi/ModuleLoader.java
@@ -17,17 +17,15 @@
package ru.feathercore.moduleapi;
import lombok.NonNull;
-import lombok.val;
-import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.Optional;
-import java.util.stream.Collectors;
/**
* Loader of {@link Module modules} which uses {@link ModuleInitializer initializers} for this purpose.
*
- * @param super-type of all modules managed
+ * @param super-type of modules which this module-loader can manage
*/
public interface ModuleLoader {
@@ -40,22 +38,7 @@ public interface ModuleLoader {
* however it is recommended to perform copying of it if loading/unloading modules while iterating
* so that {@link java.util.ConcurrentModificationException} does not happen
*/
- Collection extends M> getModules();
-
- /**
- * Gets all modules of given type loaded by this module loader.
- *
- * @param type type of modules for which to look
- * @param exact type of modules searched
- * @return all loaded modules of given type
- */
- @SuppressWarnings("unchecked")
- default Collection extends T> getModules(@NonNull final Class type) {
- return (Collection extends T>) getModules()
- .stream()
- .filter(module -> type.isAssignableFrom(module.getClass()))
- .collect(Collectors.toSet());
- }
+ Collection getModules();
/**
* Finds a module of given type loaded by this module loader.
@@ -64,13 +47,7 @@ default Collection extends T> getModules(@NonNull final Class
* @param exact type of module searched
* @return {@link Optional} containing a module of given type if it was found or an empty {@link Optional} otherwise
*/
- @SuppressWarnings("unchecked")
- default Optional extends T> getModule(@NonNull final Class type) {
- return (Optional extends T>) getModules()
- .stream()
- .filter(module -> type.isAssignableFrom(module.getClass()))
- .findAny();
- }
+ Optional extends T> getModule(@NonNull final Class type);
/**
* Checks whether there is a module of given type currently loaded by this module loader.
@@ -80,9 +57,7 @@ default Optional extends T> getModule(@NonNull final Class ty
*
* @apiNote multiple modules of given type may be loaded at once
*/
- default boolean isModuleLoaded(@NonNull final Class extends M> type) {
- return getModules().stream().anyMatch(module -> type.isAssignableFrom(module.getClass()));
- }
+ boolean isModuleLoaded(@NonNull final Class extends M> type);
/**
* Checks whether the given module is currently loaded by this module loader.
@@ -90,69 +65,63 @@ default boolean isModuleLoaded(@NonNull final Class extends M> type) {
* @param module module to check
* @return {@link true} if the module is loaded and {@link false} otherwise
*/
- default boolean isModuleLoaded(@NonNull final T module) {
- return getModules().contains(module);
- }
+ boolean isModuleLoaded(@NonNull final T module);
/**
* Loads a module provided by the specified initializer using the given configuration.
*
+ * @param exact type of a module loaded
+ * @param configuration type of a module ({@link Void} if none is expected)
* @param initializer initializer to use for module's initialization
* @param configuration configuration required by the module
* (might be {@link null} depending on the initializer implementation)
- * @param exact type of a module loaded
- * @param configuration type of a module ({@link Void} if none is expected)
+ * @param moduleTypes
* @return initialized module
*/
- T loadModule(@NonNull ModuleInitializer initializer, C configuration);
+ T loadModule(@NonNull ModuleInitializer initializer, C configuration,
+ Iterable> moduleTypes);
/**
* Loads a module provided by the specified initializer using its default configuration.
*
- * @param initializer initializer to use for module's initialization
* @param exact type of a module loaded
* @param configuration type of a module ({@link Void} if none is expected)
+ * @param initializer initializer to use for module's initialization
+ * @param moduleTypes types by which the modules will be known
* @return initialized module
* @throws ModuleConfigurationException if this initializer doesn't allow default configuration
*/
- default T loadModule(@NonNull ModuleInitializer initializer) {
+ default T loadModule(@NonNull final ModuleInitializer initializer,
+ final Iterable> moduleTypes) {
return loadModule(initializer, initializer.getDefaultConfiguration().orElseThrow(
() -> new ModuleConfigurationException(initializer + " does not allow usage of default configuration")
- ));
+ ), moduleTypes);
+ }
+
+ default T loadModule(@NonNull final ModuleInitializer initializer, C configuration,
+ @NonNull final Class moduleType) {
+ return loadModule(initializer, configuration, Collections.singletonList(moduleType));
}
- /**
- * Unloads all modules of the specified type.
- *
- * @param moduleTypes type of modules to unload
- * @param exact type of modules unloaded
- * @return collection (possibly empty) of unloaded modules of given type
- */
- default Collection extends T> unloadModules(@NonNull final Class moduleTypes) {
- val modules = getModules(moduleTypes);
- for (val loadedModule : modules) unloadModule(loadedModule);
- return modules;
+ default T loadModule(@NonNull final ModuleInitializer initializer,
+ @NonNull final Class moduleType) {
+ return loadModule(initializer, Collections.singletonList(moduleType));
}
/**
* Unloads the specified module.
*
- * @param module module which should unloaded
* @param exact type of a module unloaded
+ * @param moduleType module which should unloaded
* @return {@code true} if the specified module was loaded (and so got unloaded) and {@link false} otherwise
*/
- boolean unloadModule(@NonNull T module);
+ Optional unloadModule(@NonNull Class extends T> moduleType);
/**
* Unloads all modules loaded at current time.
*
* @return all modules unloaded
*/
- default Collection extends M> unloadAllModules() {
- val modules = new ArrayList<>(getModules());
- for (val loadedModule : modules) unloadModule(loadedModule);
-
- return modules;
- }
+ Collection extends M> unloadAllModules();
}
diff --git a/module-api/src/main/java/ru/feathercore/moduleapi/SimpleModuleLoader.java b/module-api/src/main/java/ru/feathercore/moduleapi/SimpleModuleLoader.java
new file mode 100644
index 0000000..8b2f9c3
--- /dev/null
+++ b/module-api/src/main/java/ru/feathercore/moduleapi/SimpleModuleLoader.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2019 Feather Core
+ *
+ * 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 ru.feathercore.moduleapi;
+
+import lombok.NonNull;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Simple module loader based on {@link AbstractModuleLoader}.
+ * It provides shorthand constructors for most commons use-cases.
+ *
+ * @param super-type of modules which this module-loader can manage
+ */
+public class SimpleModuleLoader extends AbstractModuleLoader {
+
+ /**
+ * Creates new instance of module loader using the given collections for storage of modules.
+ *
+ * @param modulesMap set which will be used for storage of associations of module types with their implementations
+ * @param loadedModulesSet set which will be used for storage of loaded modules
+ *
+ * @throws IllegalArgumentException if the given set is non-empty
+ */
+ protected SimpleModuleLoader(@NonNull final Map, M> modulesMap,
+ @NonNull final Set loadedModulesSet) {
+ super(modulesMap, loadedModulesSet);
+ }
+
+ public static ModuleLoader create() {
+ return new SimpleModuleLoader(new HashMap<>(), new HashSet<>());
+ }
+
+ public static ModuleLoader createConcurrent() {
+ return new SimpleModuleLoader(new ConcurrentHashMap<>(), ConcurrentHashMap.newKeySet());
+ }
+}
diff --git a/module-api/src/test/java/ru/feathercore/moduleapi/AbstractModuleLoaderTest.java b/module-api/src/test/java/ru/feathercore/moduleapi/AbstractModuleLoaderTest.java
index 486a5b9..14e9aad 100644
--- a/module-api/src/test/java/ru/feathercore/moduleapi/AbstractModuleLoaderTest.java
+++ b/module-api/src/test/java/ru/feathercore/moduleapi/AbstractModuleLoaderTest.java
@@ -16,20 +16,21 @@
package ru.feathercore.moduleapi;
-import org.feathercore.shared.object.ValueContainer;
-import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.Answers;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
+import ru.progrm_jarvis.javacommons.object.ValueContainer;
-import java.util.HashSet;
+import java.util.stream.Stream;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.*;
-import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@@ -39,134 +40,138 @@ class AbstractModuleLoaderTest {
@Mock FooModule fooModule;
@Spy ModuleInitializer fooModuleInitializer;
@Mock BarModule barModule;
- @Spy ModuleInitializer barModuleInitializer;
- // < exact type is required for use of protected methods >
- @SuppressWarnings("unchecked") private AbstractModuleLoader moduleLoader = mock(AbstractModuleLoader.class,
- withSettings().defaultAnswer(Answers.CALLS_REAL_METHODS).useConstructor(new HashSet<>())
- );
+ @Spy ModuleInitializer barModuleInitializer;
private Exception
- nullConfigException = new RuntimeException("null passed as config"){},
- config0Exception = new RuntimeException("0 passed as config"){},
- config1Exception = new RuntimeException("1 passed as config"){};
+ nullConfigException = new RuntimeException("null passed as config") {},
+ config0Exception = new RuntimeException("0 passed as config") {},
+ config1Exception = new RuntimeException("1 passed as config") {};
+
+
+ interface SomeModule extends Module {}
// Stubs to have different parent classes for modules
- private class FooModule implements Module {}
- private class BarModule implements Module {}
- private class BazModule implements Module {}
+ private static class FooModule implements SomeModule {}
+
+ private static class BarModule implements SomeModule {}
+
+ private static class BazModule implements SomeModule {}
- @Test
- void testSimpleLoadUnload() {
+ protected static Stream provideTestedModuleLoader() {
+ throw new IllegalStateException("provideTestedModuleLoader() should be overwritten in extending classes");
+ }
+
+ @ParameterizedTest
+ @MethodSource("provideTestedModuleLoader")
+ void testSimpleLoadUnload(final ModuleLoader moduleLoader) {
doReturn(fooModule).when(fooModuleInitializer).loadModule(any());
// check initial state
assertThat(moduleLoader.getModules(), empty());
// load foo ...
- assertThat(moduleLoader.loadModule(fooModuleInitializer), is(fooModule));
- verify(moduleLoader).onModuleLoad(fooModule);
+ assertSame(moduleLoader.loadModule(fooModuleInitializer, FooModule.class), fooModule);
// ... and check if it was loaded normally
assertThat(moduleLoader.getModules(), contains(fooModule));
- assertThat(moduleLoader.getModules(fooModule.getClass()), contains(fooModule));
- assertThat(moduleLoader.isModuleLoaded(fooModule), is(true));
- assertThat(moduleLoader.isModuleLoaded(fooModule.getClass()), is(true));
+ assertSame(moduleLoader.getModule(FooModule.class).orElseThrow(AssertionError::new), fooModule);
+ assertTrue(moduleLoader.isModuleLoaded(fooModule));
+ assertTrue(moduleLoader.isModuleLoaded(FooModule.class));
// unload foo
- assertThat(moduleLoader.unloadModule(fooModule), is(true));
- verify(moduleLoader).onModuleUnload(fooModule);
+ assertThat(moduleLoader.unloadModule(FooModule.class), notNullValue());
// and check if it was unloaded
assertThat(moduleLoader.getModules(), empty());
}
- @Test
- void checkContainment() {
+ @ParameterizedTest
+ @MethodSource("provideTestedModuleLoader")
+ void checkContainment(final ModuleLoader moduleLoader) {
doReturn(fooModule).when(fooModuleInitializer).loadModule(any());
- assertThat(moduleLoader.loadModule(fooModuleInitializer), is(fooModule));
+ assertSame(moduleLoader.loadModule(fooModuleInitializer, FooModule.class), fooModule);
assertThat(moduleLoader.getModules(), contains(fooModule));
- assertThat(moduleLoader.getModules(fooModule.getClass()), contains(fooModule));
- assertThat(moduleLoader.isModuleLoaded(fooModule), is(true));
- assertThat(moduleLoader.isModuleLoaded(fooModule.getClass()), is(true));
+ assertSame(moduleLoader.getModule(FooModule.class).orElseThrow(AssertionError::new), fooModule);
+ assertTrue(moduleLoader.isModuleLoaded(fooModule));
+ assertTrue(moduleLoader.isModuleLoaded(FooModule.class));
assertThat(moduleLoader.getModules(), not(contains(barModule)));
- assertThat(moduleLoader.getModules(barModule.getClass()), empty());
- assertThat(moduleLoader.getModule(barModule.getClass()).isPresent(), is(false));
- assertThat(moduleLoader.isModuleLoaded(barModule), is(false));
- assertThat(moduleLoader.isModuleLoaded(barModule.getClass()), is(false));
+ assertSame(moduleLoader.getModule(BarModule.class).isPresent(), false);
+ assertSame(moduleLoader.isModuleLoaded(barModule), false);
+ assertSame(moduleLoader.isModuleLoaded(BarModule.class), false);
}
- @Test
- void testDuplicateLoading() {
+ @ParameterizedTest
+ @MethodSource("provideTestedModuleLoader")
+ void testDuplicateLoading(final ModuleLoader moduleLoader) {
doReturn(fooModule).when(fooModuleInitializer).loadModule(any());
// check initial state
assertThat(moduleLoader.getModules(), empty());
// load foo once ...
- assertThat(moduleLoader.loadModule(fooModuleInitializer), is(fooModule));
- verify(moduleLoader).onModuleLoad(fooModule);
+ assertSame(moduleLoader.loadModule(fooModuleInitializer, FooModule.class), fooModule);
// ... and check if it was loaded normally
assertThat(moduleLoader.getModules(), contains(fooModule));
- assertThat(moduleLoader.getModules(fooModule.getClass()), contains(fooModule));
- assertThat(moduleLoader.isModuleLoaded(fooModule), is(true));
- assertThat(moduleLoader.isModuleLoaded(fooModule.getClass()), is(true));
+ assertSame(moduleLoader.getModule(FooModule.class).orElseThrow(AssertionError::new), fooModule);
+ assertTrue(moduleLoader.isModuleLoaded(fooModule));
+ assertTrue(moduleLoader.isModuleLoaded(FooModule.class));
// load foo again ...
- assertThat(moduleLoader.loadModule(fooModuleInitializer), is(fooModule));
- verify(moduleLoader).onModuleLoad(fooModule); // 1 time again which means that it was not called again
+ assertSame(moduleLoader.loadModule(fooModuleInitializer, FooModule.class), fooModule);
// ... and check if module loader did not load anything new
assertThat(moduleLoader.getModules(), contains(fooModule));
- assertThat(moduleLoader.getModules(fooModule.getClass()), contains(fooModule));
- assertThat(moduleLoader.isModuleLoaded(fooModule), is(true));
- assertThat(moduleLoader.isModuleLoaded(fooModule.getClass()), is(true));
+ assertSame(moduleLoader.getModule(FooModule.class).orElseThrow(AssertionError::new), fooModule);
+ assertTrue(moduleLoader.isModuleLoaded(fooModule));
+ assertTrue(moduleLoader.isModuleLoaded(FooModule.class));
}
- @Test
- void testMultipleLoading() {
+ @ParameterizedTest
+ @MethodSource("provideTestedModuleLoader")
+ void testMultipleLoading(final ModuleLoader moduleLoader) {
doReturn(fooModule).when(fooModuleInitializer).loadModule(any());
doReturn(barModule).when(barModuleInitializer).loadModule(any());
// load foo ...
- assertThat(moduleLoader.loadModule(fooModuleInitializer), is(fooModule));
- verify(moduleLoader).onModuleLoad(fooModule);
+ assertSame(moduleLoader.loadModule(fooModuleInitializer, FooModule.class), fooModule);
// ... and check if it was loaded normally
assertThat(moduleLoader.getModules(), contains(fooModule));
- assertThat(moduleLoader.getModules(fooModule.getClass()), contains(fooModule));
- assertThat(moduleLoader.isModuleLoaded(fooModule), is(true));
- assertThat(moduleLoader.isModuleLoaded(fooModule.getClass()), is(true));
+ assertSame(moduleLoader.getModule(FooModule.class).orElseThrow(AssertionError::new), fooModule);
+ assertTrue(moduleLoader.isModuleLoaded(fooModule));
+ assertTrue(moduleLoader.isModuleLoaded(FooModule.class));
// load bar ...
- assertThat(moduleLoader.loadModule(barModuleInitializer), is(barModule));
- verify(moduleLoader).onModuleLoad(barModule);
+ assertSame(moduleLoader.loadModule(barModuleInitializer, BarModule.class), barModule);
// ... and check if it was loaded normally
assertThat(moduleLoader.getModules(), containsInAnyOrder(fooModule, barModule));
- assertThat(moduleLoader.getModules(fooModule.getClass()), contains(fooModule));
- assertThat(moduleLoader.getModules(barModule.getClass()), contains(barModule));
- assertThat(moduleLoader.isModuleLoaded(fooModule), is(true));
- assertThat(moduleLoader.isModuleLoaded(fooModule.getClass()), is(true));
- assertThat(moduleLoader.isModuleLoaded(barModule), is(true));
- assertThat(moduleLoader.isModuleLoaded(barModule.getClass()), is(true));
+ assertSame(moduleLoader.getModule(FooModule.class).orElseThrow(AssertionError::new), fooModule);
+ assertSame(moduleLoader.getModule(BarModule.class).orElseThrow(AssertionError::new), barModule);
+ assertTrue(moduleLoader.isModuleLoaded(fooModule));
+ assertTrue(moduleLoader.isModuleLoaded(FooModule.class));
+ assertTrue(moduleLoader.isModuleLoaded(barModule));
+ assertTrue(moduleLoader.isModuleLoaded(BarModule.class));
// unload each
- assertThat(moduleLoader.unloadModule(fooModule), is(true));
+ assertSame(moduleLoader.unloadModule(FooModule.class).orElseThrow(AssertionError::new), fooModule);
}
- @Test
- void testUnloadAll() {
+ @ParameterizedTest
+ @MethodSource("provideTestedModuleLoader")
+ void testUnloadAll(final ModuleLoader moduleLoader) {
doReturn(fooModule).when(fooModuleInitializer).loadModule(any());
doReturn(barModule).when(barModuleInitializer).loadModule(any());
// load foo ...
- assertThat(moduleLoader.loadModule(fooModuleInitializer), is(fooModule));
- assertThat(moduleLoader.loadModule(barModuleInitializer), is(barModule));
+ assertSame(moduleLoader.loadModule(fooModuleInitializer, FooModule.class), fooModule);
+ assertSame(moduleLoader.loadModule(barModuleInitializer, BarModule.class), barModule);
assertThat(moduleLoader.unloadAllModules(), containsInAnyOrder(fooModule, barModule));
assertThat(moduleLoader.getModules(), empty());
}
- @Test
- void testIntensiveLoadingWithParameters() {
+ @ParameterizedTest
+ @MethodSource("provideTestedModuleLoader")
+ void testIntensiveLoadingWithParameters(final ModuleLoader moduleLoader) {
// foo module
doReturn(fooModule).when(fooModuleInitializer).loadModule(any());
// bar module
@@ -181,60 +186,49 @@ void testIntensiveLoadingWithParameters() {
///////////////////////////////////////////////////////////////////////////
// make sure that by default there are no loaded modules
- assertThat(moduleLoader.isModuleLoaded(fooModule), is(false));
- assertThat(moduleLoader.isModuleLoaded(FooModule.class), is(false));
- assertThat(moduleLoader.isModuleLoaded(barModule), is(false));
- assertThat(moduleLoader.isModuleLoaded(BarModule.class), is(false));
- assertThat(moduleLoader.isModuleLoaded(Module.class), is(false));
+ assertSame(moduleLoader.isModuleLoaded(fooModule), false);
+ assertSame(moduleLoader.isModuleLoaded(FooModule.class), false);
+ assertSame(moduleLoader.isModuleLoaded(barModule), false);
+ assertSame(moduleLoader.isModuleLoaded(BarModule.class), false);
assertThat(moduleLoader.getModules(), empty());
// try to load foo module...
- assertThat(moduleLoader.loadModule(fooModuleInitializer), is(fooModule));
- verify(moduleLoader).onModuleLoad(fooModule);
+ assertSame(moduleLoader.loadModule(fooModuleInitializer, FooModule.class), fooModule);
// ... and check if it is now loaded
- assertThat(moduleLoader.isModuleLoaded(fooModule), is(true));
- assertThat(moduleLoader.isModuleLoaded(FooModule.class), is(true));
- assertThat(moduleLoader.isModuleLoaded(Module.class), is(true));
+ assertTrue(moduleLoader.isModuleLoaded(fooModule));
+ assertTrue(moduleLoader.isModuleLoaded(FooModule.class));
assertThat(moduleLoader.getModules(), contains(fooModule));
// try to load bar module...
// * With default (0) configuration
- assertThrows(config0Exception.getClass(), () -> moduleLoader.loadModule(barModuleInitializer));
+ assertThrows(config0Exception.getClass(), () -> moduleLoader.loadModule(barModuleInitializer, BarModule.class));
verify(barModuleInitializer).getDefaultConfiguration();
- verify(moduleLoader, never()).onModuleLoad(barModule);
// ~ checking if nothing has broken
- assertThat(moduleLoader.isModuleLoaded(Module.class), is(true));
- assertThat(moduleLoader.isModuleLoaded(fooModule), is(true));
- assertThat(moduleLoader.isModuleLoaded(FooModule.class), is(true));
- assertThat(moduleLoader.isModuleLoaded(barModule), is(false));
- assertThat(moduleLoader.isModuleLoaded(BarModule.class), is(false));
+ assertTrue(moduleLoader.isModuleLoaded(fooModule));
+ assertTrue(moduleLoader.isModuleLoaded(FooModule.class));
+ assertSame(moduleLoader.isModuleLoaded(barModule), false);
+ assertSame(moduleLoader.isModuleLoaded(BarModule.class), false);
// * With null configuration
- assertThrows(nullConfigException.getClass(), () -> moduleLoader.loadModule(barModuleInitializer, null));
- verify(moduleLoader, never()).onModuleLoad(barModule);
+ assertThrows(nullConfigException.getClass(), () -> moduleLoader.loadModule(barModuleInitializer, null, BarModule.class));
// ~ checking if nothing has broken
- assertThat(moduleLoader.isModuleLoaded(Module.class), is(true));
- assertThat(moduleLoader.isModuleLoaded(fooModule), is(true));
- assertThat(moduleLoader.isModuleLoaded(FooModule.class), is(true));
- assertThat(moduleLoader.isModuleLoaded(barModule), is(false));
- assertThat(moduleLoader.isModuleLoaded(BarModule.class), is(false));
+ assertTrue(moduleLoader.isModuleLoaded(fooModule));
+ assertTrue(moduleLoader.isModuleLoaded(FooModule.class));
+ assertSame(moduleLoader.isModuleLoaded(barModule), false);
+ assertSame(moduleLoader.isModuleLoaded(BarModule.class), false);
// * With 1 as configuration
- assertThrows(config1Exception.getClass(), () -> moduleLoader.loadModule(barModuleInitializer, 1));
- verify(moduleLoader, never()).onModuleLoad(barModule);
+ assertThrows(config1Exception.getClass(), () -> moduleLoader.loadModule(barModuleInitializer, 1, BarModule.class));
// ~ checking if nothing has broken
- assertThat(moduleLoader.isModuleLoaded(Module.class), is(true));
- assertThat(moduleLoader.isModuleLoaded(fooModule), is(true));
- assertThat(moduleLoader.isModuleLoaded(FooModule.class), is(true));
- assertThat(moduleLoader.isModuleLoaded(barModule), is(false));
- assertThat(moduleLoader.isModuleLoaded(BarModule.class), is(false));
+ assertTrue(moduleLoader.isModuleLoaded(fooModule));
+ assertTrue(moduleLoader.isModuleLoaded(FooModule.class));
+ assertSame(moduleLoader.isModuleLoaded(barModule), false);
+ assertSame(moduleLoader.isModuleLoaded(BarModule.class), false);
// * Normally
- assertThat(moduleLoader.loadModule(barModuleInitializer, 2), is(barModule));
- verify(moduleLoader).onModuleLoad(barModule);
+ assertSame(moduleLoader.loadModule(barModuleInitializer, 2, BarModule.class), barModule);
// ... and check if it is now loaded
- assertThat(moduleLoader.isModuleLoaded(Module.class), is(true));
- assertThat(moduleLoader.isModuleLoaded(fooModule), is(true));
- assertThat(moduleLoader.isModuleLoaded(FooModule.class), is(true));
- assertThat(moduleLoader.isModuleLoaded(barModule), is(true));
- assertThat(moduleLoader.isModuleLoaded(BarModule.class), is(true));
+ assertTrue(moduleLoader.isModuleLoaded(fooModule));
+ assertTrue(moduleLoader.isModuleLoaded(FooModule.class));
+ assertTrue(moduleLoader.isModuleLoaded(barModule));
+ assertTrue(moduleLoader.isModuleLoaded(BarModule.class));
assertThat(moduleLoader.getModules(), containsInAnyOrder(fooModule, barModule));
///////////////////////////////////////////////////////////////////////////
@@ -242,40 +236,32 @@ void testIntensiveLoadingWithParameters() {
///////////////////////////////////////////////////////////////////////////
// make sure that unloading dummy modules does nothing
- assertThat(moduleLoader.unloadModule(new Module(){}), is(false));
- assertThat(moduleLoader.isModuleLoaded(Module.class), is(true));
- assertThat(moduleLoader.isModuleLoaded(fooModule), is(true));
- assertThat(moduleLoader.isModuleLoaded(FooModule.class), is(true));
- assertThat(moduleLoader.isModuleLoaded(barModule), is(true));
- assertThat(moduleLoader.isModuleLoaded(BarModule.class), is(true));
-
- assertThat(moduleLoader.unloadModules(new Module(){}.getClass()), empty());
- assertThat(moduleLoader.isModuleLoaded(Module.class), is(true));
- assertThat(moduleLoader.isModuleLoaded(fooModule), is(true));
- assertThat(moduleLoader.isModuleLoaded(FooModule.class), is(true));
- assertThat(moduleLoader.isModuleLoaded(barModule), is(true));
- assertThat(moduleLoader.isModuleLoaded(BarModule.class), is(true));
+ assertTrue(moduleLoader.isModuleLoaded(fooModule));
+ assertTrue(moduleLoader.isModuleLoaded(FooModule.class));
+ assertTrue(moduleLoader.isModuleLoaded(barModule));
+ assertTrue(moduleLoader.isModuleLoaded(BarModule.class));
+
+ assertTrue(moduleLoader.isModuleLoaded(fooModule));
+ assertTrue(moduleLoader.isModuleLoaded(FooModule.class));
+ assertTrue(moduleLoader.isModuleLoaded(barModule));
+ assertTrue(moduleLoader.isModuleLoaded(BarModule.class));
// unload foo module ...
- assertThat(moduleLoader.unloadModule(fooModule), is(true));
- verify(moduleLoader).onModuleUnload(fooModule);
+ assertSame(moduleLoader.unloadModule(FooModule.class).orElseThrow(AssertionError::new), fooModule);
// ... and check loaded modules
- assertThat(moduleLoader.isModuleLoaded(Module.class), is(true));
- assertThat(moduleLoader.isModuleLoaded(fooModule), is(false));
- assertThat(moduleLoader.isModuleLoaded(FooModule.class), is(false));
- assertThat(moduleLoader.isModuleLoaded(barModule), is(true));
- assertThat(moduleLoader.isModuleLoaded(BarModule.class), is(true));
+ assertSame(moduleLoader.isModuleLoaded(fooModule), false);
+ assertSame(moduleLoader.isModuleLoaded(FooModule.class), false);
+ assertTrue(moduleLoader.isModuleLoaded(barModule));
+ assertTrue(moduleLoader.isModuleLoaded(BarModule.class));
assertThat(moduleLoader.getModules(), contains(barModule));
// unload bar module...
- assertThat(moduleLoader.unloadModule(barModule), is(true));
- verify(moduleLoader).onModuleUnload(barModule);
+ assertSame(moduleLoader.unloadModule(BarModule.class).orElseThrow(AssertionError::new), barModule);
// ... and check loaded modules
- assertThat(moduleLoader.isModuleLoaded(Module.class), is(false));
- assertThat(moduleLoader.isModuleLoaded(fooModule), is(false));
- assertThat(moduleLoader.isModuleLoaded(FooModule.class), is(false));
- assertThat(moduleLoader.isModuleLoaded(barModule), is(false));
- assertThat(moduleLoader.isModuleLoaded(BarModule.class), is(false));
+ assertSame(moduleLoader.isModuleLoaded(fooModule), false);
+ assertSame(moduleLoader.isModuleLoaded(FooModule.class), false);
+ assertSame(moduleLoader.isModuleLoaded(barModule), false);
+ assertSame(moduleLoader.isModuleLoaded(BarModule.class), false);
assertThat(moduleLoader.getModules(), empty());
///////////////////////////////////////////////////////////////////////////
@@ -283,12 +269,10 @@ void testIntensiveLoadingWithParameters() {
///////////////////////////////////////////////////////////////////////////
// try to load foo module...
- assertThat(moduleLoader.loadModule(fooModuleInitializer), is(fooModule));
- verify(moduleLoader, times(2)).onModuleLoad(fooModule);
+ assertSame(moduleLoader.loadModule(fooModuleInitializer, FooModule.class), fooModule);
// ... and check if it is now loaded
- assertThat(moduleLoader.isModuleLoaded(fooModule), is(true));
- assertThat(moduleLoader.isModuleLoaded(FooModule.class), is(true));
- assertThat(moduleLoader.isModuleLoaded(Module.class), is(true));
+ assertTrue(moduleLoader.isModuleLoaded(fooModule));
+ assertTrue(moduleLoader.isModuleLoaded(FooModule.class));
assertThat(moduleLoader.getModules(), contains(fooModule));
///////////////////////////////////////////////////////////////////////////
@@ -296,14 +280,12 @@ void testIntensiveLoadingWithParameters() {
///////////////////////////////////////////////////////////////////////////
// unload bar module...
- assertThat(moduleLoader.unloadModule(fooModule), is(true));
- verify(moduleLoader, times(2)).onModuleUnload(fooModule);
+ assertSame(moduleLoader.unloadModule(FooModule.class).orElseThrow(AssertionError::new), fooModule);
// ... and check loaded modules
- assertThat(moduleLoader.isModuleLoaded(Module.class), is(false));
- assertThat(moduleLoader.isModuleLoaded(fooModule), is(false));
- assertThat(moduleLoader.isModuleLoaded(FooModule.class), is(false));
- assertThat(moduleLoader.isModuleLoaded(barModule), is(false));
- assertThat(moduleLoader.isModuleLoaded(BarModule.class), is(false));
+ assertSame(moduleLoader.isModuleLoaded(fooModule), false);
+ assertSame(moduleLoader.isModuleLoaded(FooModule.class), false);
+ assertSame(moduleLoader.isModuleLoaded(barModule), false);
+ assertSame(moduleLoader.isModuleLoaded(BarModule.class), false);
assertThat(moduleLoader.getModules(), empty());
///////////////////////////////////////////////////////////////////////////
@@ -311,12 +293,10 @@ void testIntensiveLoadingWithParameters() {
///////////////////////////////////////////////////////////////////////////
// try to load foo module...
- assertThat(moduleLoader.loadModule(fooModuleInitializer), is(fooModule));
- verify(moduleLoader, times(3)).onModuleLoad(fooModule);
+ assertSame(moduleLoader.loadModule(fooModuleInitializer, FooModule.class), fooModule);
// ... and check if it is now loaded
- assertThat(moduleLoader.isModuleLoaded(fooModule), is(true));
- assertThat(moduleLoader.isModuleLoaded(FooModule.class), is(true));
- assertThat(moduleLoader.isModuleLoaded(Module.class), is(true));
+ assertTrue(moduleLoader.isModuleLoaded(fooModule));
+ assertTrue(moduleLoader.isModuleLoaded(FooModule.class));
assertThat(moduleLoader.getModules(), contains(fooModule));
///////////////////////////////////////////////////////////////////////////
@@ -324,26 +304,24 @@ void testIntensiveLoadingWithParameters() {
///////////////////////////////////////////////////////////////////////////
// unload foo module...
- assertThat(moduleLoader.unloadModule(fooModule), is(true));
+ assertSame(moduleLoader.unloadModule(FooModule.class).orElseThrow(AssertionError::new), fooModule);
// ... and check loaded modules
- assertThat(moduleLoader.isModuleLoaded(Module.class), is(false));
- assertThat(moduleLoader.isModuleLoaded(fooModule), is(false));
- assertThat(moduleLoader.isModuleLoaded(FooModule.class), is(false));
- assertThat(moduleLoader.isModuleLoaded(barModule), is(false));
- assertThat(moduleLoader.isModuleLoaded(BarModule.class), is(false));
+ assertSame(moduleLoader.isModuleLoaded(fooModule), false);
+ assertSame(moduleLoader.isModuleLoaded(FooModule.class), false);
+ assertSame(moduleLoader.isModuleLoaded(barModule), false);
+ assertSame(moduleLoader.isModuleLoaded(BarModule.class), false);
assertThat(moduleLoader.getModules(), empty());
///////////////////////////////////////////////////////////////////////////
// Try to load multiple modules now
///////////////////////////////////////////////////////////////////////////
- assertThat(moduleLoader.loadModule(fooModuleInitializer), is(fooModule));
- assertThat(moduleLoader.loadModule(barModuleInitializer, 2), is(barModule));
- assertThat(moduleLoader.isModuleLoaded(Module.class), is(true));
- assertThat(moduleLoader.isModuleLoaded(fooModule), is(true));
- assertThat(moduleLoader.isModuleLoaded(FooModule.class), is(true));
- assertThat(moduleLoader.isModuleLoaded(barModule), is(true));
- assertThat(moduleLoader.isModuleLoaded(BarModule.class), is(true));
+ assertSame(moduleLoader.loadModule(fooModuleInitializer, FooModule.class), fooModule);
+ assertSame(moduleLoader.loadModule(barModuleInitializer, 2, BarModule.class), barModule);
+ assertTrue(moduleLoader.isModuleLoaded(fooModule));
+ assertTrue(moduleLoader.isModuleLoaded(FooModule.class));
+ assertTrue(moduleLoader.isModuleLoaded(barModule));
+ assertTrue(moduleLoader.isModuleLoaded(BarModule.class));
assertThat(moduleLoader.getModules(), containsInAnyOrder(fooModule, barModule));
///////////////////////////////////////////////////////////////////////////
@@ -357,26 +335,25 @@ void testIntensiveLoadingWithParameters() {
// Try to load multiple modules now again for other unload type
///////////////////////////////////////////////////////////////////////////
- assertThat(moduleLoader.loadModule(fooModuleInitializer), is(fooModule));
- assertThat(moduleLoader.loadModule(barModuleInitializer, 2), is(barModule));
- assertThat(moduleLoader.isModuleLoaded(Module.class), is(true));
- assertThat(moduleLoader.isModuleLoaded(fooModule), is(true));
- assertThat(moduleLoader.isModuleLoaded(FooModule.class), is(true));
- assertThat(moduleLoader.isModuleLoaded(barModule), is(true));
- assertThat(moduleLoader.isModuleLoaded(BarModule.class), is(true));
+ assertSame(moduleLoader.loadModule(fooModuleInitializer, FooModule.class), fooModule);
+ assertSame(moduleLoader.loadModule(barModuleInitializer, 2, BarModule.class), barModule);
+ assertTrue(moduleLoader.isModuleLoaded(fooModule));
+ assertTrue(moduleLoader.isModuleLoaded(FooModule.class));
+ assertTrue(moduleLoader.isModuleLoaded(barModule));
+ assertTrue(moduleLoader.isModuleLoaded(BarModule.class));
assertThat(moduleLoader.getModules(), containsInAnyOrder(fooModule, barModule));
///////////////////////////////////////////////////////////////////////////
// Unload by type now
///////////////////////////////////////////////////////////////////////////
- assertThat(moduleLoader.unloadModules(fooModule.getClass()), contains(fooModule));
+ assertSame(moduleLoader.unloadModule(FooModule.class).orElseThrow(AssertionError::new), fooModule);
assertThat(moduleLoader.getModules(), contains(barModule));
- assertThat(moduleLoader.unloadModules(BazModule.class), empty());
+ assertFalse(moduleLoader.unloadModule(BazModule.class).isPresent());
assertThat(moduleLoader.getModules(), contains(barModule));
- assertThat(moduleLoader.unloadModules(barModule.getClass()), contains(barModule));
+ assertSame(moduleLoader.unloadModule(BarModule.class).orElseThrow(AssertionError::new), barModule);
assertThat(moduleLoader.getModules(), empty());
}
}
\ No newline at end of file
diff --git a/module-api/src/test/java/ru/feathercore/moduleapi/SimpleModuleLoaderTest.java b/module-api/src/test/java/ru/feathercore/moduleapi/SimpleModuleLoaderTest.java
new file mode 100644
index 0000000..7f0a81c
--- /dev/null
+++ b/module-api/src/test/java/ru/feathercore/moduleapi/SimpleModuleLoaderTest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2019 Feather Core
+ *
+ * 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 ru.feathercore.moduleapi;
+
+import org.junit.jupiter.params.provider.Arguments;
+
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.params.provider.Arguments.arguments;
+
+public class SimpleModuleLoaderTest extends AbstractModuleLoaderTest {
+
+ public static Stream provideTestedModuleLoader() {
+ return Stream.of(
+ arguments(SimpleModuleLoader.create()),
+ arguments(SimpleModuleLoader.createConcurrent())
+ );
+ }
+}
diff --git a/pom.xml b/pom.xml
index b65e4df..585f038 100644
--- a/pom.xml
+++ b/pom.xml
@@ -27,6 +27,7 @@
1.8
1.8
4.1.43.Final
+ 1.0-SNAPSHOT
5.5.2
1.5.2
@@ -53,6 +54,7 @@
eventbus
module-api
protocol
+ server
@@ -61,6 +63,10 @@
Minecraft Libraries
https://libraries.minecraft.net
+
+ sonatype-snapshot-repo
+ https://oss.sonatype.org/content/repositories/snapshots/
+
@@ -107,6 +113,11 @@
feathercore-protocol
${project.version}
+
+ ${project.groupId}
+ feathercore-server
+ ${project.version}
+
org.hamcrest
@@ -115,6 +126,16 @@
test
+
+ ru.progrm-jarvis
+ java-commons
+ ${version.padla}
+
+
+ ru.progrm-jarvis
+ reflector
+ ${version.padla}
+
io.netty
netty-all
@@ -123,7 +144,7 @@
net.md-5
bungeecord-chat
- 1.13-SNAPSHOT
+ 1.14-SNAPSHOT
com.google.guava
@@ -159,7 +180,7 @@
org.projectlombok
lombok
- 1.18.8
+ 1.18.10
provided
true
diff --git a/protocol/pom.xml b/protocol/pom.xml
index a3272e3..417546d 100644
--- a/protocol/pom.xml
+++ b/protocol/pom.xml
@@ -64,5 +64,22 @@
io.netty
netty-all
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+
+
+ org.mockito
+ mockito-core
+
+
+ org.mockito
+ mockito-junit-jupiter
+
+
+ org.hamcrest
+ hamcrest-all
+
\ No newline at end of file
diff --git a/protocol/src/main/java/org/feathercore/protocol/annotation/PacketFactory.java b/protocol/src/main/java/org/feathercore/protocol/annotation/PacketFactory.java
new file mode 100644
index 0000000..3c6d95d
--- /dev/null
+++ b/protocol/src/main/java/org/feathercore/protocol/annotation/PacketFactory.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2019 Feather Core
+ *
+ * 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.feathercore.protocol.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * Annotation used for creation of {@link org.feathercore.protocol.packet.PacketType}
+ * from the class containing the annotation.
+ *
+ * - When present on the class, it should normally be of type {@link org.feathercore.protocol.packet.Packet}
+ * - When present on the method or constructor, it should be the default factory for the packet
+ * and its containing class should normally be of type {@link org.feathercore.protocol.packet.Packet}
+ *
.
+ */
+@Documented
+@Target({ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface PacketFactory {
+
+ /**
+ * Retrieves the ID which should be used for creation of {@link org.feathercore.protocol.packet.PacketType}.
+ *
+ * @return ID which should be used for creation of {@link org.feathercore.protocol.packet.PacketType}
+ int value();
+ */
+}
diff --git a/protocol/src/main/java/org/feathercore/protocol/annotation/PacketId.java b/protocol/src/main/java/org/feathercore/protocol/annotation/PacketId.java
new file mode 100644
index 0000000..0f71334
--- /dev/null
+++ b/protocol/src/main/java/org/feathercore/protocol/annotation/PacketId.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2019 Feather Core
+ *
+ * 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.feathercore.protocol.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * Annotation used for creation of {@link org.feathercore.protocol.packet.PacketType}
+ * from the class annotated with it.
+ *
+ * @apiNote This is an alternative to {@link PacketFactory} - they cannot be used at the same time.
+ */
+@Documented
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface PacketId {
+ /**
+ * ID of a packet which this class represents.
+ *
+ * @return ID of the packet which this class represents
+ */
+ int value();
+}
diff --git a/protocol/src/main/java/org/feathercore/protocol/exception/PacketHandleException.java b/protocol/src/main/java/org/feathercore/protocol/exception/PacketHandleException.java
index 9b415fc..5d2c615 100644
--- a/protocol/src/main/java/org/feathercore/protocol/exception/PacketHandleException.java
+++ b/protocol/src/main/java/org/feathercore/protocol/exception/PacketHandleException.java
@@ -16,13 +16,20 @@
package org.feathercore.protocol.exception;
-/**
- * Created by k.shandurenko on 12/04/2019
- */
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor
public class PacketHandleException extends IllegalStateException {
- public PacketHandleException(String message, Throwable cause) {
+ public PacketHandleException(final String s) {
+ super(s);
+ }
+
+ public PacketHandleException(final String message, final Throwable cause) {
super(message, cause);
}
+ public PacketHandleException(final Throwable cause) {
+ super(cause);
+ }
}
diff --git a/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/handshake/client/HandshakePacketClientHandshake.java b/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/handshake/client/HandshakePacketClientHandshake.java
index 68ad70f..643f57e 100644
--- a/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/handshake/client/HandshakePacketClientHandshake.java
+++ b/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/handshake/client/HandshakePacketClientHandshake.java
@@ -17,6 +17,7 @@
package org.feathercore.protocol.minecraft.packet.handshake.client;
import org.feathercore.protocol.Buffer;
+import org.feathercore.protocol.annotation.PacketId;
import org.feathercore.protocol.minecraft.packet.MinecraftPacket;
import org.feathercore.protocol.packet.ConnectionState;
import org.jetbrains.annotations.NotNull;
@@ -24,6 +25,7 @@
/**
* Created by k.shandurenko on 09/04/2019
*/
+@PacketId(HandshakePacketClientHandshake.ID)
public class HandshakePacketClientHandshake implements MinecraftPacket {
public static final int ID = 0x00;
diff --git a/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/login/client/LoginPacketClientEncryptionResponse.java b/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/login/client/LoginPacketClientEncryptionResponse.java
index e6459c3..2579f17 100644
--- a/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/login/client/LoginPacketClientEncryptionResponse.java
+++ b/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/login/client/LoginPacketClientEncryptionResponse.java
@@ -22,6 +22,7 @@
import lombok.NoArgsConstructor;
import lombok.experimental.FieldDefaults;
import org.feathercore.protocol.Buffer;
+import org.feathercore.protocol.annotation.PacketId;
import org.feathercore.protocol.minecraft.packet.MinecraftPacket;
import org.jetbrains.annotations.NotNull;
@@ -32,6 +33,7 @@
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PROTECTED)
+@PacketId(LoginPacketClientEncryptionResponse.ID)
public class LoginPacketClientEncryptionResponse implements MinecraftPacket {
public static final int ID = 0x01;
diff --git a/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/login/client/LoginPacketClientLoginStart.java b/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/login/client/LoginPacketClientLoginStart.java
index f165903..d58ae1b 100644
--- a/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/login/client/LoginPacketClientLoginStart.java
+++ b/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/login/client/LoginPacketClientLoginStart.java
@@ -23,6 +23,7 @@
import lombok.NoArgsConstructor;
import lombok.experimental.FieldDefaults;
import org.feathercore.protocol.Buffer;
+import org.feathercore.protocol.annotation.PacketId;
import org.feathercore.protocol.minecraft.packet.MinecraftPacket;
import org.jetbrains.annotations.NotNull;
@@ -33,6 +34,7 @@
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PROTECTED)
+@PacketId(LoginPacketClientLoginStart.ID)
public class LoginPacketClientLoginStart implements MinecraftPacket {
public static final int ID = 0x00;
diff --git a/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/login/server/LoginPacketServerDisconnect.java b/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/login/server/LoginPacketServerDisconnect.java
index 24ab75d..8341f4f 100644
--- a/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/login/server/LoginPacketServerDisconnect.java
+++ b/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/login/server/LoginPacketServerDisconnect.java
@@ -23,6 +23,7 @@
import lombok.experimental.FieldDefaults;
import net.md_5.bungee.api.chat.BaseComponent;
import org.feathercore.protocol.Buffer;
+import org.feathercore.protocol.annotation.PacketId;
import org.feathercore.protocol.minecraft.packet.MinecraftPacket;
import org.jetbrains.annotations.NotNull;
@@ -33,6 +34,7 @@
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PROTECTED)
+@PacketId(LoginPacketServerDisconnect.ID)
public class LoginPacketServerDisconnect implements MinecraftPacket {
public static final int ID = 0x00;
diff --git a/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/login/server/LoginPacketServerEnableCompression.java b/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/login/server/LoginPacketServerEnableCompression.java
index 7c67388..cfe0f00 100644
--- a/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/login/server/LoginPacketServerEnableCompression.java
+++ b/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/login/server/LoginPacketServerEnableCompression.java
@@ -22,6 +22,7 @@
import lombok.NoArgsConstructor;
import lombok.experimental.FieldDefaults;
import org.feathercore.protocol.Buffer;
+import org.feathercore.protocol.annotation.PacketId;
import org.feathercore.protocol.minecraft.packet.MinecraftPacket;
import org.jetbrains.annotations.NotNull;
@@ -32,6 +33,7 @@
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PROTECTED)
+@PacketId(LoginPacketServerEnableCompression.ID)
public class LoginPacketServerEnableCompression implements MinecraftPacket {
public static final int ID = 0x03;
diff --git a/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/login/server/LoginPacketServerEncryptionRequest.java b/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/login/server/LoginPacketServerEncryptionRequest.java
index 82e6ccd..efce816 100644
--- a/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/login/server/LoginPacketServerEncryptionRequest.java
+++ b/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/login/server/LoginPacketServerEncryptionRequest.java
@@ -22,6 +22,7 @@
import lombok.NoArgsConstructor;
import lombok.experimental.FieldDefaults;
import org.feathercore.protocol.Buffer;
+import org.feathercore.protocol.annotation.PacketId;
import org.feathercore.protocol.minecraft.packet.MinecraftPacket;
import org.feathercore.protocol.encrypt.CryptManager;
import org.jetbrains.annotations.NotNull;
@@ -35,6 +36,7 @@
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PROTECTED)
+@PacketId(LoginPacketServerEncryptionRequest.ID)
public class LoginPacketServerEncryptionRequest implements MinecraftPacket {
public static final int ID = 0x01;
diff --git a/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/login/server/LoginPacketServerLoginSuccess.java b/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/login/server/LoginPacketServerLoginSuccess.java
index 7cafc54..4641e4e 100644
--- a/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/login/server/LoginPacketServerLoginSuccess.java
+++ b/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/login/server/LoginPacketServerLoginSuccess.java
@@ -23,6 +23,7 @@
import lombok.NoArgsConstructor;
import lombok.experimental.FieldDefaults;
import org.feathercore.protocol.Buffer;
+import org.feathercore.protocol.annotation.PacketId;
import org.feathercore.protocol.minecraft.packet.MinecraftPacket;
import org.jetbrains.annotations.NotNull;
@@ -35,6 +36,7 @@
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PROTECTED)
+@PacketId(LoginPacketServerLoginSuccess.ID)
public class LoginPacketServerLoginSuccess implements MinecraftPacket {
public static final int ID = 0x02;
diff --git a/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/status/client/StatusPacketClientPing.java b/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/status/client/StatusPacketClientPing.java
index c3732aa..e5b6943 100644
--- a/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/status/client/StatusPacketClientPing.java
+++ b/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/status/client/StatusPacketClientPing.java
@@ -22,6 +22,7 @@
import lombok.NoArgsConstructor;
import lombok.experimental.FieldDefaults;
import org.feathercore.protocol.Buffer;
+import org.feathercore.protocol.annotation.PacketId;
import org.feathercore.protocol.minecraft.packet.MinecraftPacket;
import org.jetbrains.annotations.NotNull;
@@ -32,6 +33,7 @@
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PROTECTED)
+@PacketId(StatusPacketClientPing.ID)
public class StatusPacketClientPing implements MinecraftPacket {
public static final int ID = 0x01;
diff --git a/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/status/client/StatusPacketClientServerQuery.java b/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/status/client/StatusPacketClientServerQuery.java
index 943a21c..b9322e7 100644
--- a/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/status/client/StatusPacketClientServerQuery.java
+++ b/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/status/client/StatusPacketClientServerQuery.java
@@ -19,6 +19,7 @@
import lombok.Data;
import lombok.NoArgsConstructor;
import org.feathercore.protocol.Buffer;
+import org.feathercore.protocol.annotation.PacketId;
import org.feathercore.protocol.minecraft.packet.MinecraftPacket;
import org.jetbrains.annotations.NotNull;
@@ -27,6 +28,7 @@
*/
@Data
@NoArgsConstructor
+@PacketId(StatusPacketClientServerQuery.ID)
public class StatusPacketClientServerQuery implements MinecraftPacket {
public static final int ID = 0x00;
diff --git a/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/status/server/StatusPacketServerPong.java b/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/status/server/StatusPacketServerPong.java
index e23724c..e45d0a5 100644
--- a/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/status/server/StatusPacketServerPong.java
+++ b/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/status/server/StatusPacketServerPong.java
@@ -22,6 +22,7 @@
import lombok.NoArgsConstructor;
import lombok.experimental.FieldDefaults;
import org.feathercore.protocol.Buffer;
+import org.feathercore.protocol.annotation.PacketId;
import org.feathercore.protocol.minecraft.packet.MinecraftPacket;
import org.jetbrains.annotations.NotNull;
@@ -32,6 +33,7 @@
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PROTECTED)
+@PacketId(StatusPacketServerPong.ID)
public class StatusPacketServerPong implements MinecraftPacket {
public static final int ID = 0x01;
diff --git a/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/status/server/StatusPacketServerServerInfo.java b/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/status/server/StatusPacketServerServerInfo.java
index 09b1989..6a1a5e7 100644
--- a/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/status/server/StatusPacketServerServerInfo.java
+++ b/protocol/src/main/java/org/feathercore/protocol/minecraft/packet/status/server/StatusPacketServerServerInfo.java
@@ -22,6 +22,7 @@
import lombok.experimental.FieldDefaults;
import net.md_5.bungee.api.chat.BaseComponent;
import org.feathercore.protocol.Buffer;
+import org.feathercore.protocol.annotation.PacketId;
import org.feathercore.protocol.minecraft.packet.MinecraftPacket;
import org.feathercore.shared.util.json.JsonUtil;
import org.jetbrains.annotations.NotNull;
@@ -36,6 +37,7 @@
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PROTECTED)
+@PacketId(StatusPacketServerPong.ID)
public class StatusPacketServerServerInfo implements MinecraftPacket {
public static final int ID = 0x00;
diff --git a/protocol/src/main/java/org/feathercore/protocol/netty/HandlerBoss.java b/protocol/src/main/java/org/feathercore/protocol/netty/HandlerBoss.java
index a16ca51..0b85501 100644
--- a/protocol/src/main/java/org/feathercore/protocol/netty/HandlerBoss.java
+++ b/protocol/src/main/java/org/feathercore/protocol/netty/HandlerBoss.java
@@ -31,7 +31,7 @@
import org.feathercore.protocol.netty.util.NettyAttributes;
import org.feathercore.protocol.packet.Packet;
import org.feathercore.protocol.registry.PacketRegistry;
-import org.feathercore.protocol.server.BaseServer;
+import org.feathercore.protocol.server.Server;
import java.io.IOException;
import java.lang.ref.SoftReference;
@@ -44,12 +44,12 @@
public class HandlerBoss extends ChannelInboundHandlerAdapter {
@Setter @Getter private PacketRegistry> packetRegistry;
- private SoftReference serverSoftReference;
+ private SoftReference serverSoftReference;
private final Logger logger;
- public HandlerBoss(SoftReference serverSoftReference, Logger logger) {
+ public HandlerBoss(SoftReference serverSoftReference, Logger logger) {
this.serverSoftReference = serverSoftReference;
- this.packetRegistry = serverSoftReference.get().getInitialPacketRegistry();
+ this.packetRegistry = serverSoftReference.get().getPacketRegistry();
this.logger = logger;
}
@@ -68,7 +68,7 @@ public void channelActive(ChannelHandlerContext ctx) {
NettyAttributes.setAttribute(ctx, NettyAttributes.CONNECTION_ATTRIBUTE_KEY, connection);
NettyAttributes.setAttribute(ctx, NettyAttributes.HANDLER_BOSS_ATTRIBUTE_KEY, this);
- this.serverSoftReference.get().onConnected(connection);
+ this.serverSoftReference.get().handleConnect(connection);
}
@Override
@@ -78,7 +78,7 @@ public void channelInactive(ChannelHandlerContext ctx) {
return;
}
new PreDisconnectionEvent(connection).callGlobally();
- this.serverSoftReference.get().onDisconnected(connection);
+ this.serverSoftReference.get().handleDisconnect(connection);
}
@Override
diff --git a/protocol/src/main/java/org/feathercore/protocol/netty/NettyServer.java b/protocol/src/main/java/org/feathercore/protocol/netty/NettyServer.java
index cba7ead..553f379 100644
--- a/protocol/src/main/java/org/feathercore/protocol/netty/NettyServer.java
+++ b/protocol/src/main/java/org/feathercore/protocol/netty/NettyServer.java
@@ -22,46 +22,50 @@
import io.netty.channel.ChannelFutureListener;
import lombok.NonNull;
import lombok.extern.log4j.Log4j2;
-import org.feathercore.protocol.server.BaseServer;
import org.feathercore.protocol.netty.channel.ChannelInitializer;
import org.feathercore.protocol.netty.util.SharedNettyResources;
+import org.feathercore.protocol.packet.Packet;
+import org.feathercore.protocol.server.AbstractServer;
import java.lang.ref.SoftReference;
+import java.net.SocketAddress;
/**
- * Created by k.shandurenko on 12/04/2019
+ * Netty-based {@link org.feathercore.protocol.server.Server} implementation.
+ *
+ * @param super-type of packets used by this server's protocol
*/
@Log4j2
-public abstract class NettyServer extends BaseServer {
+public abstract class NettyServer
extends AbstractServer
{
@NonNull private final SharedNettyResources sharedNettyResources;
protected Channel channel;
- public NettyServer(@NonNull final String host, final int port) {
- super(host, port);
- this.sharedNettyResources = SharedNettyResources.builder().build();
+ protected NettyServer(@NonNull final SocketAddress socketAddress,
+ @NonNull final SharedNettyResources sharedNettyResources) {
+ super(socketAddress);
+ this.sharedNettyResources = sharedNettyResources;
}
@Override
- public ChannelFuture start() {
+ protected ChannelFuture performStart() {
ServerBootstrap bootstrap = new ServerBootstrap()
.group(sharedNettyResources.getBossLoopGroup(), sharedNettyResources.getWorkerLoopGroup())
- .channel(sharedNettyResources.getServerChannelClass())
+ .channel(sharedNettyResources.getTransportType().getServerChannelClass())
.childHandler(new ChannelInitializer(new SoftReference<>(this), log));
- return bootstrap.bind(host, port).addListener((ChannelFutureListener) future -> {
+ return bootstrap.bind(address).addListener((ChannelFutureListener) future -> {
if (future.isSuccess()) {
channel = future.channel();
- log.info("BaseServer has been started on " + host + ":" + port);
+ log.info("Server has been started on " + address);
} else {
- log.warn("BaseServer could not bind to " + host + ":" + port, future.cause());
+ log.warn("Server could not bind to " + address, future.cause());
}
});
}
@Override
- public ChannelFuture stop() {
+ protected ChannelFuture performStop() {
return channel.close().syncUninterruptibly();
}
-
}
diff --git a/protocol/src/main/java/org/feathercore/protocol/netty/channel/ChannelInitializer.java b/protocol/src/main/java/org/feathercore/protocol/netty/channel/ChannelInitializer.java
index d00e1d1..6ecc6c1 100644
--- a/protocol/src/main/java/org/feathercore/protocol/netty/channel/ChannelInitializer.java
+++ b/protocol/src/main/java/org/feathercore/protocol/netty/channel/ChannelInitializer.java
@@ -21,12 +21,12 @@
import io.netty.handler.timeout.ReadTimeoutHandler;
import lombok.RequiredArgsConstructor;
import org.apache.logging.log4j.Logger;
-import org.feathercore.protocol.server.BaseServer;
import org.feathercore.protocol.netty.HandlerBoss;
import org.feathercore.protocol.netty.codec.InboundDecoder;
import org.feathercore.protocol.netty.codec.InboundPacketDecoder;
import org.feathercore.protocol.netty.codec.OutboundEncoder;
import org.feathercore.protocol.netty.codec.ServerPingAdapter;
+import org.feathercore.protocol.server.Server;
import java.lang.ref.SoftReference;
@@ -36,7 +36,7 @@
@RequiredArgsConstructor
public class ChannelInitializer extends io.netty.channel.ChannelInitializer {
- private final SoftReference serverSoftReference;
+ private final SoftReference serverSoftReference;
private final Logger logger;
@Override
diff --git a/protocol/src/main/java/org/feathercore/protocol/netty/util/AbstractSharedNettyResources.java b/protocol/src/main/java/org/feathercore/protocol/netty/util/AbstractSharedNettyResources.java
new file mode 100644
index 0000000..64ac261
--- /dev/null
+++ b/protocol/src/main/java/org/feathercore/protocol/netty/util/AbstractSharedNettyResources.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2019 Feather Core
+ *
+ * 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.feathercore.protocol.netty.util;
+
+import io.netty.channel.EventLoopGroup;
+import io.netty.util.concurrent.Future;
+import lombok.*;
+import lombok.experimental.FieldDefaults;
+import lombok.experimental.NonFinal;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true)
+public abstract class AbstractSharedNettyResources implements SharedNettyResources {
+
+ @NonNull @Getter TransportType transportType;
+ boolean bossIsWorker;
+
+ @NonNull final Lock readLock, writeLock;
+ @NonNull final AtomicBoolean shutDown;
+
+ @NonFinal @Nullable volatile EventLoopGroup bossLoopGroup;
+ @NonFinal @Nullable volatile EventLoopGroup workerLoopGroup;
+
+ public AbstractSharedNettyResources(@NonNull final TransportType transportType, final boolean bossIsWorker) {
+ this.transportType = transportType;
+ this.bossIsWorker = bossIsWorker;
+
+ shutDown = new AtomicBoolean();
+ {
+ val lock = new ReentrantReadWriteLock();
+ readLock = lock.readLock();
+ writeLock = lock.writeLock();
+ }
+ }
+
+ /* *********************************************** Stateful data *********************************************** */
+
+ protected abstract EventLoopGroup newBossLoopGroup();
+
+ protected abstract EventLoopGroup newWorkerLoopGroup();
+
+ /**
+ * Asserts that this instance is not shut down.
+ *
+ * @throws IllegalStateException if this instance is already shut down
+ */
+ protected void assertNotShutDown() {
+ if (isShutDown()) {
+ throw new IllegalStateException("LoopGroupGracefully is already shut down.");
+ }
+ }
+
+ @Override
+ public boolean isInitialized() {
+ assertNotShutDown();
+
+ readLock.lock();
+ try {
+ return bossLoopGroup != null && workerLoopGroup != null;
+ } finally {
+ readLock.unlock();
+ }
+ }
+
+ @Override
+ public boolean isShutDown() {
+ return shutDown.get();
+ }
+
+ @SneakyThrows(InterruptedException.class)
+ protected void performLoopGroupsShutdownGracefully(final boolean await) {
+ readLock.lock();
+ try {
+ if (bossLoopGroup != null || workerLoopGroup != null) {
+ writeLock.lock();
+ try {
+ // used not to duplicate GETFIELD invocations and to omit potential null-checks
+ var loopGroup = bossLoopGroup;
+ @Nullable final Future> bossShutDown;
+ /* Shutdown boss */
+ if (loopGroup != null) {
+ bossShutDown = loopGroup.shutdownGracefully();
+ bossLoopGroup = null;
+ } else {
+ bossShutDown = null;
+ }
+
+ loopGroup = workerLoopGroup;
+ @Nullable final Future> workerShutDown;
+ if (loopGroup != null) {
+ /* Shutdown worker */
+ workerShutDown = loopGroup.shutdownGracefully().await();
+ workerLoopGroup = null;
+ } else {
+ workerShutDown = null;
+ }
+
+ if (await) {
+ if (bossShutDown != null) {
+ bossShutDown.await();
+ }
+ if (workerShutDown != null) {
+ workerShutDown.await();
+ }
+ }
+ } finally {
+ writeLock.unlock();
+ }
+ }
+ } finally {
+ readLock.unlock();
+ }
+ }
+
+ @Override
+ public void shutdownLoopGroupsGracefully(final boolean await) {
+ if (shutDown.compareAndSet(false, true)) {
+ performLoopGroupsShutdownGracefully(await);
+ } else {
+ throw new IllegalStateException("LoopGroupGracefully is already shut down.");
+ }
+ }
+
+ @Override
+ public boolean tryShutdownLoopGroupsGracefully(final boolean await) {
+ if (shutDown.compareAndSet(false, true)) {
+ performLoopGroupsShutdownGracefully(await);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ @NotNull public EventLoopGroup getBossLoopGroup() {
+ assertNotShutDown();
+
+ readLock.lock();
+ try {
+ val loopGroup = bossLoopGroup;
+ if (loopGroup == null) {
+ writeLock.lock();
+ try {
+ return bossLoopGroup = newWorkerLoopGroup();
+ } finally {
+ writeLock.unlock();
+ }
+ }
+
+ return loopGroup;
+ } finally {
+ readLock.unlock();
+ }
+ }
+
+ @Override
+ @NotNull public EventLoopGroup getWorkerLoopGroup() {
+ assertNotShutDown();
+
+ readLock.lock();
+ try {
+ val loopGroup = workerLoopGroup;
+ if (loopGroup == null) {
+ writeLock.lock();
+ try {
+ return workerLoopGroup = newWorkerLoopGroup();
+ } finally {
+ writeLock.unlock();
+ }
+ }
+
+ return loopGroup;
+ } finally {
+ readLock.unlock();
+ }
+ }
+}
diff --git a/protocol/src/main/java/org/feathercore/protocol/netty/util/DefaultTransportType.java b/protocol/src/main/java/org/feathercore/protocol/netty/util/DefaultTransportType.java
new file mode 100644
index 0000000..8f58bd3
--- /dev/null
+++ b/protocol/src/main/java/org/feathercore/protocol/netty/util/DefaultTransportType.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2019 Feather Core
+ *
+ * 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.feathercore.protocol.netty.util;
+
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.ServerChannel;
+import io.netty.channel.epoll.Epoll;
+import io.netty.channel.epoll.EpollEventLoopGroup;
+import io.netty.channel.epoll.EpollServerSocketChannel;
+import io.netty.channel.epoll.EpollSocketChannel;
+import io.netty.channel.kqueue.KQueue;
+import io.netty.channel.kqueue.KQueueEventLoopGroup;
+import io.netty.channel.kqueue.KQueueServerSocketChannel;
+import io.netty.channel.kqueue.KQueueSocketChannel;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import lombok.*;
+import lombok.experimental.FieldDefaults;
+
+import java.util.concurrent.ThreadFactory;
+
+@Getter
+@RequiredArgsConstructor
+@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
+public enum DefaultTransportType implements TransportType {
+ EPOLL(EpollSocketChannel.class, EpollServerSocketChannel.class) {
+ @Override
+ public boolean isAvailable() {
+ return Epoll.isAvailable();
+ }
+
+ @Override
+ public EventLoopGroup newEventLoopGroup(final int threads, @NonNull final ThreadFactory threadFactory) {
+ return new EpollEventLoopGroup(threads, threadFactory);
+ }
+ },
+ KQUEUE(KQueueSocketChannel.class, KQueueServerSocketChannel.class) {
+ @Override
+ public boolean isAvailable() {
+ return KQueue.isAvailable();
+ }
+
+ @Override
+ public EventLoopGroup newEventLoopGroup(final int threads, @NonNull final ThreadFactory threadFactory) {
+ return new KQueueEventLoopGroup(threads, threadFactory);
+ }
+ },
+ NIO(NioSocketChannel.class, NioServerSocketChannel.class) {
+ @Override
+ public boolean isAvailable() {
+ return true;
+ }
+
+ @Override
+ public EventLoopGroup newEventLoopGroup(final int threads, @NonNull final ThreadFactory threadFactory) {
+ return new NioEventLoopGroup(threads, threadFactory);
+ }
+ };
+
+ Class extends SocketChannel> socketChannelClass;
+ Class extends ServerChannel> serverChannelClass;
+
+ public static TransportType getNative() {
+ for (val transportType : values()) {
+ if (transportType.isAvailable()) {
+ return transportType;
+ }
+ }
+
+ return getDefault();
+ }
+
+ public static TransportType getDefault() {
+ return NIO;
+ }
+}
diff --git a/protocol/src/main/java/org/feathercore/protocol/netty/util/SharedNettyResources.java b/protocol/src/main/java/org/feathercore/protocol/netty/util/SharedNettyResources.java
index 8093096..6620345 100644
--- a/protocol/src/main/java/org/feathercore/protocol/netty/util/SharedNettyResources.java
+++ b/protocol/src/main/java/org/feathercore/protocol/netty/util/SharedNettyResources.java
@@ -17,164 +17,32 @@
package org.feathercore.protocol.netty.util;
import io.netty.channel.EventLoopGroup;
-import io.netty.channel.ServerChannel;
-import io.netty.channel.socket.SocketChannel;
-import lombok.*;
-import lombok.experimental.FieldDefaults;
-import lombok.experimental.NonFinal;
-import org.feathercore.shared.util.NamedThreadFactory;
-import org.jetbrains.annotations.Nullable;
-
-import javax.annotation.Nonnegative;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.function.Supplier;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static java.lang.Math.max;
+import org.jetbrains.annotations.NotNull;
/**
- * Created by k.shandurenko on 12/04/2019
+ * Holder of shared data used by Netty's basic elements.
*/
-@Value
-@Builder
-@FieldDefaults(level = AccessLevel.PROTECTED)
-@NonFinal
-public class SharedNettyResources {
-
- @NonNull protected final ReadWriteLock lock;
-
- boolean bossIsWorker;
- @Builder.Default boolean useNativeTransport = true;
- @Builder.Default @Nonnegative int bossThreads = 2, workerThreads = 0;
-
- @NonFinal @Nullable volatile EventLoopGroup bossLoopGroup, workerLoopGroup;
-
- @NonNull Supplier
- bossThreadFactorySupplier = () -> new NamedThreadFactory("Netty Boss Thread #", false),
- workerThreadFactorySupplier = () -> new NamedThreadFactory("Netty Worker Thread #", false);
+public interface SharedNettyResources {
- public boolean isInitialized() {
- lock.readLock().lock();
- try {
- return bossLoopGroup != null && workerLoopGroup != null;
- } finally {
- lock.readLock().unlock();
- }
- }
-
- @SneakyThrows(InterruptedException.class)
- public void shutdownLoopGroupsGracefully(final boolean await) {
- lock.readLock().lock();
- try {
- if (bossLoopGroup != null) {
- lock.writeLock().lock();
- try {
- if (await) {
- bossLoopGroup.shutdownGracefully().await();
- } else {
- bossLoopGroup.shutdownGracefully();
- }
-
- bossLoopGroup = null;
- } finally {
- lock.writeLock().unlock();
- }
- }
- if (workerLoopGroup != null) {
- lock.writeLock().lock();
- try {
- if (await) {
- workerLoopGroup.shutdownGracefully().await();
- }
- workerLoopGroup.shutdownGracefully();
+ boolean isInitialized();
- workerLoopGroup = null;
- } finally {
- lock.writeLock().unlock();
- }
- }
- } finally {
- lock.readLock().unlock();
- }
- }
+ boolean isShutDown();
- public void shutdownLoopGroupsGracefully() {
- shutdownLoopGroupsGracefully(false);
- }
+ @NotNull TransportType getTransportType();
- public EventLoopGroup getBossLoopGroup() {
- lock.readLock().lock();
- try {
- if (bossLoopGroup == null) {
- lock.writeLock().lock();
- try {
- bossLoopGroup = (useNativeTransport ? TransportType.getNative() : TransportType.getDefault())
- .newEventLoopGroup(bossThreads, bossThreadFactorySupplier.get());
- } finally {
- lock.writeLock().unlock();
- }
- }
+ @NotNull EventLoopGroup getBossLoopGroup();
- return bossLoopGroup;
- } finally {
- lock.readLock().unlock();
- }
- }
+ @NotNull EventLoopGroup getWorkerLoopGroup();
- public EventLoopGroup getWorkerLoopGroup() {
- lock.readLock().lock();
- try {
- if (workerLoopGroup == null) {
- lock.writeLock().lock();
- try {
- if (bossIsWorker) {
- workerLoopGroup = getWorkerLoopGroup();
- } else {
- workerLoopGroup = (useNativeTransport ? TransportType.getNative() : TransportType.getDefault())
- .newEventLoopGroup(max(1, workerThreads), workerThreadFactorySupplier.get());
- }
- } finally {
- lock.writeLock().unlock();
- }
- }
+ void shutdownLoopGroupsGracefully(final boolean await);
- return workerLoopGroup;
- } finally {
- lock.readLock().unlock();
- }
+ default void shutdownLoopGroupsGracefully() {
+ shutdownLoopGroupsGracefully(true);
}
- public Class extends SocketChannel> getSocketChannelClass() {
- return (useNativeTransport ? TransportType.getNative() : TransportType.getDefault()).getSocketChannelClass();
- }
-
- public Class extends ServerChannel> getServerChannelClass() {
- return (useNativeTransport ? TransportType.getNative() : TransportType.getDefault()).getServerChannelClass();
- }
-
- public static class SharedNettyResourcesBuilder {
-
- public SharedNettyResourcesBuilder bossThreads(@Nonnegative final int bossThreads) {
- checkArgument(bossThreads > 0, "There should be at least 1 boss thread");
-
- this.bossThreads = bossThreads;
-
- return this;
- }
-
- public SharedNettyResourcesBuilder workerThreads(@Nonnegative final int workerThreads) {
- checkArgument(workerThreads <= 0, "Number of worker threads cannot be negative");
-
- this.workerThreads = workerThreads;
-
- return this;
- }
-
- protected SharedNettyResourcesBuilder lock(@NonNull final ReadWriteLock lock) {
- this.lock = lock;
+ boolean tryShutdownLoopGroupsGracefully(final boolean await);
- return this;
- }
+ default boolean tryShutdownLoopGroupsGracefully() {
+ return tryShutdownLoopGroupsGracefully(true);
}
-}
\ No newline at end of file
+}
diff --git a/protocol/src/main/java/org/feathercore/protocol/netty/util/SimpleSharedNettyResources.java b/protocol/src/main/java/org/feathercore/protocol/netty/util/SimpleSharedNettyResources.java
new file mode 100644
index 0000000..1a9c01a
--- /dev/null
+++ b/protocol/src/main/java/org/feathercore/protocol/netty/util/SimpleSharedNettyResources.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2019 Feather Core
+ *
+ * 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.feathercore.protocol.netty.util;
+
+import io.netty.channel.EventLoopGroup;
+import lombok.AccessLevel;
+import lombok.Builder;
+import lombok.NonNull;
+import lombok.experimental.FieldDefaults;
+import lombok.experimental.NonFinal;
+import org.feathercore.shared.util.thread.ThreadFactories;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true)
+@NonFinal public class SimpleSharedNettyResources extends AbstractSharedNettyResources {
+
+ protected int bossThreads, workerThreads;
+
+ @Builder
+ public SimpleSharedNettyResources(@NonNull final TransportType transportType, final boolean bossIsWorker,
+ final int bossThreads, final int workerThreads) {
+ super(transportType, bossIsWorker);
+
+ checkArgument(bossThreads >= 0, "There should be at least 1 boss thread (or 0 for default)");
+ checkArgument(workerThreads >= 0, "There should be at least 1 worker thread (or 0 for default)");
+
+ this.bossThreads = bossThreads;
+ this.workerThreads = workerThreads;
+ }
+
+ public static SimpleSharedNettyResources createDefault() {
+ return SimpleSharedNettyResources.builder()
+ .transportType(DefaultTransportType.getNative())
+ .bossThreads(2)
+ .workerThreads(0)
+ .build();
+ }
+
+ @Override
+ protected EventLoopGroup newBossLoopGroup() {
+ return transportType.newEventLoopGroup(
+ bossThreads, ThreadFactories.createPaginated("Netty Worker Thread #", false)
+ );
+ }
+
+ @Override
+ protected EventLoopGroup newWorkerLoopGroup() {
+ return transportType.newEventLoopGroup(
+ workerThreads, ThreadFactories.createPaginated("Netty Worker Thread #", false)
+ );
+ }
+}
\ No newline at end of file
diff --git a/protocol/src/main/java/org/feathercore/protocol/netty/util/TransportType.java b/protocol/src/main/java/org/feathercore/protocol/netty/util/TransportType.java
index b2231ea..8a13336 100644
--- a/protocol/src/main/java/org/feathercore/protocol/netty/util/TransportType.java
+++ b/protocol/src/main/java/org/feathercore/protocol/netty/util/TransportType.java
@@ -18,81 +18,19 @@
import io.netty.channel.EventLoopGroup;
import io.netty.channel.ServerChannel;
-import io.netty.channel.epoll.Epoll;
-import io.netty.channel.epoll.EpollEventLoopGroup;
-import io.netty.channel.epoll.EpollServerSocketChannel;
-import io.netty.channel.epoll.EpollSocketChannel;
-import io.netty.channel.kqueue.KQueue;
-import io.netty.channel.kqueue.KQueueEventLoopGroup;
-import io.netty.channel.kqueue.KQueueServerSocketChannel;
-import io.netty.channel.kqueue.KQueueSocketChannel;
-import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
-import io.netty.channel.socket.nio.NioServerSocketChannel;
-import io.netty.channel.socket.nio.NioSocketChannel;
-import lombok.*;
-import lombok.experimental.FieldDefaults;
+import lombok.NonNull;
+import javax.annotation.Nonnegative;
import java.util.concurrent.ThreadFactory;
-@Getter
-@RequiredArgsConstructor
-@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
-public enum TransportType {
- EPOLL(EpollSocketChannel.class, EpollServerSocketChannel.class) {
- @Override
- public boolean isAvailable() {
- return Epoll.isAvailable();
- }
+public interface TransportType {
- @Override
- public EventLoopGroup newEventLoopGroup(final int threads, @NonNull final ThreadFactory threadFactory) {
- return new EpollEventLoopGroup(threads, threadFactory);
- }
- },
- KQUEUE(KQueueSocketChannel.class, KQueueServerSocketChannel.class) {
- @Override
- public boolean isAvailable() {
- return KQueue.isAvailable();
- }
+ boolean isAvailable();
- @Override
- public EventLoopGroup newEventLoopGroup(final int threads, @NonNull final ThreadFactory threadFactory) {
- return new KQueueEventLoopGroup(threads, threadFactory);
- }
- },
- NIO(NioSocketChannel.class, NioServerSocketChannel.class) {
- @Override
- public boolean isAvailable() {
- return true;
- }
+ EventLoopGroup newEventLoopGroup(@Nonnegative int threads, @NonNull ThreadFactory threadFactory);
- @Override
- public EventLoopGroup newEventLoopGroup(final int threads, @NonNull final ThreadFactory threadFactory) {
- return new NioEventLoopGroup(threads, threadFactory);
- }
- };
+ Class extends SocketChannel> getSocketChannelClass();
- private static final TransportType[] TRANSPORT_TYPES = values();
-
- Class extends SocketChannel> socketChannelClass;
- Class extends ServerChannel> serverChannelClass;
-
- public abstract boolean isAvailable();
-
- public abstract EventLoopGroup newEventLoopGroup(int threads, @NonNull ThreadFactory threadFactory);
-
- public static TransportType getNative() {
- for (val transportType : TRANSPORT_TYPES) {
- if (transportType.isAvailable()) {
- return transportType;
- }
- }
-
- return getDefault();
- }
-
- public static TransportType getDefault() {
- return NIO;
- }
+ Class extends ServerChannel> getServerChannelClass();
}
diff --git a/protocol/src/main/java/org/feathercore/protocol/packet/PacketType.java b/protocol/src/main/java/org/feathercore/protocol/packet/PacketType.java
index b7d0325..b765111 100644
--- a/protocol/src/main/java/org/feathercore/protocol/packet/PacketType.java
+++ b/protocol/src/main/java/org/feathercore/protocol/packet/PacketType.java
@@ -21,40 +21,121 @@
import lombok.Value;
import lombok.experimental.FieldDefaults;
import lombok.experimental.NonFinal;
+import lombok.val;
+import org.feathercore.protocol.annotation.PacketFactory;
+import org.feathercore.protocol.annotation.PacketId;
+import ru.progrm_jarvis.reflector.invoke.InvokeUtil;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Optional;
import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static com.google.common.base.Preconditions.checkArgument;
/**
* A value-class containing all data identifying packet type.
*
* @param type of packet
*/
-@Value
@NonFinal
+@Value(staticConstructor = "create")
@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true)
public class PacketType
{
+ /**
+ * ID of the packet
+ */
int id;
- @NonNull Class
type;
- @NonNull Supplier
supplier;
-
- private PacketType(Class
type, Supplier
supplier) {
- try {
- this.id = type.getField("ID").getInt(null);
- } catch (IllegalAccessException | NoSuchFieldException e) {
- throw new RuntimeException(
- "Could not instantiate packet type, because there is not ID field in given packet class or it is "
- + "not accessible",
- e
+ /**
+ * Factory used for instantiation of packets
+ */
+ @NonNull Supplier
factory;
+
+ /**
+ * Creates a packet type from the given class using its meta-structure.
+ *
+ * This will look for constructors and methods annotated with {@link PacketFactory} which will be used as factories
+ * for the packets of given IDs.
+ *
+ * @param type class which should be analyzed to create packet type from it
+ * @param
type of the packet
+ * @return created packet type
+ *
+ * @throws IllegalArgumentException if the given class contains multiple methods annotated with {@link PacketFactory}
+ */
+ public static
PacketType
create(@NonNull final Class
type) {
+ {
+ @SuppressWarnings("unchecked") val optionalConstructor
+ = lookupPacketFactory(Arrays.stream((Constructor
[]) type.getDeclaredConstructors()));
+ if (optionalConstructor.isPresent()) {
+ val constructor = optionalConstructor.get();
+
+ return create(type, InvokeUtil.toSupplier(constructor));
+ }
+ }
+ {
+ val optionalMethod = lookupPacketFactory(
+ Arrays.stream(type.getDeclaredMethods())
+ .filter(method -> Modifier.isStatic(method.getModifiers()))
);
+ if (optionalMethod.isPresent()) {
+ val method = optionalMethod.get();
+
+ return create(type, InvokeUtil.toStaticSupplier(method));
+ }
}
- this.type = type;
- this.supplier = supplier;
+
+ throw new IllegalArgumentException(
+ "There should be a no-args method or constructor annotated with @PacketFactory"
+ );
}
+ /**
+ * Creates a packet-type for the given class or its sub-type using the given factory.
+ *
+ * This will try to use {@link PacketFactory} annotation on this class
+ * and {@link PacketId} on its static field or method to get the ID of the packet.
+ *
+ * @param type type of the packet whose type is being created
+ * @param factory factory to be used for instantiation of packets
+ * @param
exact type of packet
+ * @return created packet-type
+ */
public static
PacketType
create(@NonNull final Class
type,
- @NonNull final Supplier
supplier) {
- return new PacketType<>(type, supplier);
+ @NonNull final Supplier
factory) {
+ val packetIdAnnotation = type.getAnnotation(PacketId.class);
+ checkArgument(packetIdAnnotation != null, "Type should be annotated with @PacketId");
+
+ return create(packetIdAnnotation.value(), factory);
}
+ /**
+ * Finds an executable which may be used as a packet-type factory.
+ *
+ * @param possibleFactories array of elements some of which which may be packet factories
+ * @param exact type of passed executable
+ * @return {@link Optional} containing found factory (if there was only one found) or
+ * an empty one if there were no possible packet-factories in the given array
+ *
+ * @throws IllegalArgumentException if there is more than one possible packet-factory in the given array
+ */
+ private static Optional lookupPacketFactory(@NonNull final Stream possibleFactories) {
+ val factories = possibleFactories
+ .filter(possibleFactory -> possibleFactory.getParameterCount() == 0)
+ .filter(possibleFactory -> possibleFactory.isAnnotationPresent(PacketFactory.class))
+ .collect(Collectors.toList());
+
+ if (factories.isEmpty()) return Optional.empty();
+ checkArgument(
+ factories.size() == 1,
+ "There should only be one no-args method or constructor annotated with @PacketFactory"
+ );
+
+ return Optional.of(factories.get(0));
+ }
}
diff --git a/protocol/src/main/java/org/feathercore/protocol/registry/PacketRegistry.java b/protocol/src/main/java/org/feathercore/protocol/registry/PacketRegistry.java
index 45d6510..744b93f 100644
--- a/protocol/src/main/java/org/feathercore/protocol/registry/PacketRegistry.java
+++ b/protocol/src/main/java/org/feathercore/protocol/registry/PacketRegistry.java
@@ -54,7 +54,7 @@ public interface PacketRegistry {
default P createById(final int id) {
val type = getTypeById(id);
- return type == null ? null : type.getSupplier().get();
+ return type == null ? null : type.getFactory().get();
}
void handlePacket(@NotNull Connection connection, @NotNull Packet packet);
diff --git a/protocol/src/main/java/org/feathercore/protocol/registry/common/CommonHandshakePacketRegistry.java b/protocol/src/main/java/org/feathercore/protocol/registry/common/CommonHandshakePacketRegistry.java
index 4a95a96..f011417 100644
--- a/protocol/src/main/java/org/feathercore/protocol/registry/common/CommonHandshakePacketRegistry.java
+++ b/protocol/src/main/java/org/feathercore/protocol/registry/common/CommonHandshakePacketRegistry.java
@@ -32,6 +32,7 @@ public class CommonHandshakePacketRegistry {
@SuppressWarnings("unchecked")
public static PacketRegistry createNew() {
+ //noinspection RedundantCast this is a javac bug: cast is required
return ArrayBasedPacketRegistry.Builder.create()
.exceptionHandler((BiConsumer) (connection, throwable) ->
connection.disconnect())
diff --git a/protocol/src/main/java/org/feathercore/protocol/server/AbstractServer.java b/protocol/src/main/java/org/feathercore/protocol/server/AbstractServer.java
index f2c8d26..bc9944a 100644
--- a/protocol/src/main/java/org/feathercore/protocol/server/AbstractServer.java
+++ b/protocol/src/main/java/org/feathercore/protocol/server/AbstractServer.java
@@ -16,28 +16,56 @@
package org.feathercore.protocol.server;
+import lombok.AccessLevel;
+import lombok.Getter;
import lombok.NonNull;
-import org.feathercore.protocol.Connection;
-import org.feathercore.protocol.netty.NettyServer;
-import org.jetbrains.annotations.NotNull;
+import lombok.RequiredArgsConstructor;
+import lombok.experimental.FieldDefaults;
+import lombok.experimental.NonFinal;
+import org.feathercore.protocol.packet.Packet;
+
+import java.net.SocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
- * Created by k.shandurenko on 16/04/2019
+ * Abstract base for a {@link Server implementation}.
+ *
+ * @param super-type of packets used by this server's protocol
*/
-public abstract class AbstractServer extends NettyServer {
+@RequiredArgsConstructor
+@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true)
+public abstract class AbstractServer
implements Server
{
- public AbstractServer(final @NonNull String host, final int port) {
- super(host, port);
- }
+ @NonNull @Getter SocketAddress address;
+
+ @NonNull Object startStopMutex = new Object[0];
+ @NonNull @NonFinal volatile boolean running = false;
+
+ protected abstract Future performStart();
+
+ protected abstract Future performStop();
@Override
- public void onConnected(@NotNull final Connection connection) {
+ public Future start() {
+ synchronized (startStopMutex) {
+ if (running) throw new IllegalStateException("This server is already started");
+
+ running = true;
+ return performStart();
+ }
}
@Override
- public void onDisconnected(@NotNull final Connection connection) {
+ public Future stop() {
+ synchronized (startStopMutex) {
+ if (!running) throw new IllegalStateException("This server is not running");
- }
+ running = false;
+ return performStop();
+ }
+ }
}
diff --git a/protocol/src/main/java/org/feathercore/protocol/server/BaseServer.java b/protocol/src/main/java/org/feathercore/protocol/server/Server.java
similarity index 59%
rename from protocol/src/main/java/org/feathercore/protocol/server/BaseServer.java
rename to protocol/src/main/java/org/feathercore/protocol/server/Server.java
index 503f62b..009a76c 100644
--- a/protocol/src/main/java/org/feathercore/protocol/server/BaseServer.java
+++ b/protocol/src/main/java/org/feathercore/protocol/server/Server.java
@@ -16,35 +16,32 @@
package org.feathercore.protocol.server;
-import lombok.AccessLevel;
-import lombok.Getter;
import lombok.NonNull;
-import lombok.RequiredArgsConstructor;
import org.feathercore.protocol.Connection;
import org.feathercore.protocol.packet.Packet;
import org.feathercore.protocol.registry.PacketRegistry;
import org.jetbrains.annotations.NotNull;
+import java.net.SocketAddress;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
/**
- * Created by k.shandurenko on 16/04/2019
+ * Base for IO-server based on {@link PacketRegistry}.
+ *
+ * @param super-type of packets used by this server's protocol
*/
-@Getter
-@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
-public abstract class BaseServer {
-
- @NonNull protected final String host;
- protected final int port;
+public interface Server
{
- public abstract Future start();
+ SocketAddress getAddress();
- public abstract Future stop();
+ Future start();
- public abstract void onConnected(@NotNull Connection connection);
+ Future stop();
- public abstract void onDisconnected(@NotNull Connection connection);
+ @NonNull PacketRegistry getPacketRegistry();
- public abstract @NonNull PacketRegistry extends Packet> getInitialPacketRegistry();
+ default void handleConnect(@NotNull final Connection connection) {}
+ default void handleDisconnect(@NotNull final Connection connection) {}
}
diff --git a/protocol/src/main/java/org/feathercore/protocol/server/CommonServer.java b/protocol/src/main/java/org/feathercore/protocol/server/SimpleMinecraftServer.java
similarity index 61%
rename from protocol/src/main/java/org/feathercore/protocol/server/CommonServer.java
rename to protocol/src/main/java/org/feathercore/protocol/server/SimpleMinecraftServer.java
index f0eb7e5..20cebaa 100644
--- a/protocol/src/main/java/org/feathercore/protocol/server/CommonServer.java
+++ b/protocol/src/main/java/org/feathercore/protocol/server/SimpleMinecraftServer.java
@@ -16,26 +16,26 @@
package org.feathercore.protocol.server;
+import lombok.Getter;
import lombok.NonNull;
import org.feathercore.protocol.minecraft.packet.MinecraftPacket;
-import org.feathercore.protocol.packet.Packet;
+import org.feathercore.protocol.netty.NettyServer;
+import org.feathercore.protocol.netty.util.SharedNettyResources;
import org.feathercore.protocol.registry.PacketRegistry;
import org.feathercore.protocol.registry.common.CommonHandshakePacketRegistry;
+import java.net.SocketAddress;
+
/**
* Created by k.shandurenko on 16/04/2019
*/
-public class CommonServer extends AbstractServer {
-
- private final PacketRegistry initialPacketRegistry = CommonHandshakePacketRegistry.createNew();
+@Deprecated
+public class SimpleMinecraftServer extends NettyServer {
- public CommonServer(final @NonNull String host, final int port) {
- super(host, port);
- }
+ @Getter private final PacketRegistry packetRegistry = CommonHandshakePacketRegistry.createNew();
- @Override
- public @NonNull PacketRegistry extends Packet> getInitialPacketRegistry() {
- return this.initialPacketRegistry;
+ public SimpleMinecraftServer(@NonNull final SocketAddress address,
+ @NonNull final SharedNettyResources sharedNettyResources) {
+ super(address, sharedNettyResources);
}
-
}
diff --git a/protocol/src/test/java/org/feathercore/protocol/packet/PacketTypeTest.java b/protocol/src/test/java/org/feathercore/protocol/packet/PacketTypeTest.java
new file mode 100644
index 0000000..078eb3d
--- /dev/null
+++ b/protocol/src/test/java/org/feathercore/protocol/packet/PacketTypeTest.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2019 Feather Core
+ *
+ * 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.feathercore.protocol.packet;
+
+import lombok.AccessLevel;
+import lombok.RequiredArgsConstructor;
+import lombok.val;
+import lombok.var;
+import org.apache.commons.lang3.RandomUtils;
+import org.feathercore.protocol.annotation.PacketFactory;
+import org.feathercore.protocol.annotation.PacketId;
+import org.junit.jupiter.api.Test;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class PacketTypeTest {
+
+ @Test
+ public void testCreateValidations() {
+ // test that there should be @PacketFactory
+ assertThrows(
+ IllegalArgumentException.class, () -> PacketType.create(PacketWithNoFactory.class)
+ );
+ // test that there should be only one @PacketFactory on static method
+ assertThrows(
+ IllegalArgumentException.class, () -> PacketType.create(PacketWithMultipleStaticMethodFactories.class)
+ );
+
+ }
+
+ @Test
+ public void testConstructorFactoryCreate() {
+ val packetType = PacketType.create(PacketWithConstructorFactory.class);
+
+ assertThat(packetType.getId(), is(PacketWithConstructorFactory.ID));
+
+ val attempts = RandomUtils.nextInt(8, 16 + 1);
+ for (var i = 0; i < attempts; i++) {
+ val instanceId = PacketWithConstructorFactory.nextInstanceTestId();
+ val packet = packetType.getFactory().get();
+ assertThat(packet, notNullValue());
+
+ assertThat(instanceId, is(packet.instanceId));
+ }
+ }
+
+ @Test
+ public void testStaticMethodFactoryCreate() {
+ val packetType = PacketType.create(PacketWithStaticMethodFactory.class);
+
+ assertThat(packetType.getId(), is(PacketWithStaticMethodFactory.ID));
+
+ val attempts = RandomUtils.nextInt(8, 16 + 1);
+ for (var i = 0; i < attempts; i++) {
+ val instanceId = PacketWithStaticMethodFactory.nextInstanceTestId();
+ val packet = packetType.getFactory().get();
+ assertThat(packet, notNullValue());
+
+ assertThat(instanceId, is(packet.instanceId));
+ }
+ }
+
+ @Test
+ public void testSupplierFactoryCreate() {
+ val instanceIdCounter = new AtomicInteger();
+ val packetType = PacketType.create(
+ PacketWithNoFactory.class, () -> new PacketWithNoFactory(instanceIdCounter.incrementAndGet())
+ );
+
+ assertThat(packetType.getId(), is(PacketWithNoFactory.ID));
+
+ val attempts = RandomUtils.nextInt(8, 16 + 1);
+ for (var i = 0; i < attempts; i++) {
+ val packet = packetType.getFactory().get();
+
+ assertThat(packet, notNullValue());
+ assertThat(packet.instanceId, is(instanceIdCounter.intValue()));
+ }
+ }
+
+ @RequiredArgsConstructor
+ @PacketId(PacketWithNoFactory.ID)
+ private static final class PacketWithNoFactory implements Packet {
+
+ private static final int ID = 0x10;
+
+ private final int instanceId;
+
+ @Override
+ public int getId() {
+ return ID;
+ }
+ }
+
+ @PacketId(PacketWithMultipleStaticMethodFactories.ID)
+ private static final class PacketWithMultipleStaticMethodFactories implements Packet {
+
+ private static final int ID = 0x20;
+
+ @Override
+ public int getId() {
+ return ID;
+ }
+
+ @PacketFactory
+ public static PacketWithMultipleStaticMethodFactories create1() {
+ return new PacketWithMultipleStaticMethodFactories();
+ }
+
+ @PacketFactory
+ public static PacketWithMultipleStaticMethodFactories create2() {
+ return new PacketWithMultipleStaticMethodFactories();
+ }
+ }
+
+ @PacketId(PacketWithConstructorFactory.ID)
+ @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+ public static final class PacketWithConstructorFactory implements Packet {
+
+ private static final ThreadLocal PACKET_TEST_ID_COUNTER = new ThreadLocal<>();
+ private static final int ID = 0x50;
+
+ private final int instanceId;
+
+ private static AtomicInteger instanceIdCounter() {
+ var counter = PACKET_TEST_ID_COUNTER.get();
+ if (counter == null) {
+ counter = new AtomicInteger();
+ PACKET_TEST_ID_COUNTER.set(counter);
+ }
+
+ return counter;
+ }
+
+ public static int nextInstanceTestId() {
+ return instanceIdCounter().intValue();
+ }
+
+ @PacketFactory
+ public PacketWithConstructorFactory() {
+ instanceId = instanceIdCounter().getAndIncrement();
+ }
+
+ @Override
+ public int getId() {
+ return ID;
+ }
+ }
+
+ @PacketId(PacketWithStaticMethodFactory.ID)
+ @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+ public static final class PacketWithStaticMethodFactory implements Packet {
+
+ private static final ThreadLocal PACKET_TEST_ID_COUNTER = new ThreadLocal<>();
+ private static final int ID = 0x60;
+
+ private final int instanceId;
+
+ private static AtomicInteger testIdCounter() {
+ var counter = PACKET_TEST_ID_COUNTER.get();
+ if (counter == null) {
+ counter = new AtomicInteger();
+ PACKET_TEST_ID_COUNTER.set(counter);
+ }
+
+ return counter;
+ }
+
+ public static int nextInstanceTestId() {
+ return testIdCounter().intValue();
+ }
+
+ @PacketFactory
+ public static PacketWithStaticMethodFactory create() {
+ return new PacketWithStaticMethodFactory(testIdCounter().getAndIncrement());
+ }
+
+ @Override
+ public int getId() {
+ return ID;
+ }
+ }
+}
\ No newline at end of file
diff --git a/server/pom.xml b/server/pom.xml
new file mode 100644
index 0000000..726661b
--- /dev/null
+++ b/server/pom.xml
@@ -0,0 +1,47 @@
+
+
+
+ 4.0.0
+
+ feather-core
+ org.feathercore
+ 1.0.0-SNAPSHOT
+
+
+ feathercore-server
+
+
+
+ ${project.parent.groupId}
+ feathercore-protocol
+
+
+ ${project.parent.groupId}
+ feathercore-module-api
+
+
+
+ org.projectlombok
+ lombok
+
+
+ org.jetbrains
+ annotations
+
+
+
\ No newline at end of file
diff --git a/server/src/main/java/AbstractFeatherServer.java b/server/src/main/java/AbstractFeatherServer.java
new file mode 100644
index 0000000..bafa873
--- /dev/null
+++ b/server/src/main/java/AbstractFeatherServer.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2019 Feather Core
+ *
+ * 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 lombok.AccessLevel;
+import lombok.Getter;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+import lombok.experimental.FieldDefaults;
+import lombok.extern.log4j.Log4j2;
+import org.feathercore.protocol.packet.Packet;
+import org.feathercore.protocol.server.Server;
+import ru.feathercore.moduleapi.Module;
+import ru.feathercore.moduleapi.ModuleLoader;
+
+import javax.annotation.OverridingMethodsMustInvokeSuper;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Base for a regular feather-server implementation.
+ *
+ * @param super-type of packets used by the server's protocol
+ * @param super-type of modules which this server is able to load
+ */
+@Log4j2
+@RequiredArgsConstructor
+@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true)
+public abstract class AbstractFeatherServer implements FeatherServer
{
+
+ /**
+ * IO-server used by this feather-server
+ */
+ @NonNull @Getter Server
ioServer;
+ /**
+ * Module-loader used by this server
+ */
+ @NonNull @Getter ModuleLoader moduleLoader;
+
+ @Override
+ @OverridingMethodsMustInvokeSuper
+ public void start() {
+ try {
+ ioServer.start().get();
+ } catch (final InterruptedException | ExecutionException e) {
+ log.error("An exception occurred while starting the IO-server", e);
+ }
+ }
+
+ @Override
+ @OverridingMethodsMustInvokeSuper
+ public void close() {
+ try {
+ moduleLoader.unloadAllModules();
+ } catch (final Throwable e) {
+ log.error("An exception occurred while unloading modules", e);
+ }
+ try {
+ ioServer.stop().get();
+ } catch (final Throwable e) {
+ log.error("An exception occurred while stopping the IO-server", e);
+ }
+ }
+}
diff --git a/server/src/main/java/FeatherServer.java b/server/src/main/java/FeatherServer.java
new file mode 100644
index 0000000..c5439cf
--- /dev/null
+++ b/server/src/main/java/FeatherServer.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2019 Feather Core
+ *
+ * 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 org.feathercore.protocol.packet.Packet;
+import org.feathercore.protocol.server.Server;
+import ru.feathercore.moduleapi.Module;
+import ru.feathercore.moduleapi.ModuleLoader;
+
+/**
+ * feather-core server, core component of the whole project.
+ *
+ * @param super-type of packets used by the server's protocol
+ * @param super-type of modules which this server is able to load
+ */
+public interface FeatherServer extends AutoCloseable {
+
+ /**
+ * Gets the IO-server used by this feather-server.
+ *
+ * @return IO-server used by this feather-server
+ */
+ Server
getIoServer();
+
+ /**
+ * Gets the module-loader of this feather-server.
+ *
+ * @return module-loader of this feather-server
+ */
+ ModuleLoader getModuleLoader();
+
+ /**
+ * Starts this feather-server.
+ */
+ void start();
+
+ /**
+ * Shuts down this feather-server.
+ */
+ @Override
+ void close();
+}
diff --git a/server/src/main/java/SimpleMinecraftFeatherServer.java b/server/src/main/java/SimpleMinecraftFeatherServer.java
new file mode 100644
index 0000000..209787a
--- /dev/null
+++ b/server/src/main/java/SimpleMinecraftFeatherServer.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2019 Feather Core
+ *
+ * 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 lombok.NonNull;
+import org.feathercore.protocol.minecraft.packet.MinecraftPacket;
+import org.feathercore.protocol.netty.util.SimpleSharedNettyResources;
+import org.feathercore.protocol.server.SimpleMinecraftServer;
+import ru.feathercore.moduleapi.Module;
+import ru.feathercore.moduleapi.SimpleModuleLoader;
+
+import java.net.SocketAddress;
+
+/**
+ * Simple feather-server for standard minecraft protocol.
+ *
+ * @param super-type of modules which this server is able to load
+ */
+public class SimpleMinecraftFeatherServer extends AbstractFeatherServer {
+
+ /**
+ * Creates new feather-server for standard minecraft protocol
+ *
+ * @param address address of this server used by IO-server
+ */
+ public SimpleMinecraftFeatherServer(@NonNull final SocketAddress address) {
+ super(
+ new SimpleMinecraftServer(address, SimpleSharedNettyResources.createDefault()),
+ SimpleModuleLoader.createConcurrent()
+ );
+ }
+}
diff --git a/shared/pom.xml b/shared/pom.xml
index 155b851..7993e47 100644
--- a/shared/pom.xml
+++ b/shared/pom.xml
@@ -26,6 +26,16 @@
feathercore-shared
+
+ ru.progrm-jarvis
+ java-commons
+
+
+
+ com.google.guava
+ guava
+
+
com.google.code.gson
gson
@@ -40,5 +50,14 @@
org.jetbrains
annotations
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+
+
+ org.hamcrest
+ hamcrest-all
+
\ No newline at end of file
diff --git a/shared/src/main/java/org/feathercore/shared/object/ValueContainer.java b/shared/src/main/java/org/feathercore/shared/object/ValueContainer.java
deleted file mode 100644
index 454a99e..0000000
--- a/shared/src/main/java/org/feathercore/shared/object/ValueContainer.java
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- * Copyright 2019 Feather Core
- *
- * 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.feathercore.shared.object;
-
-import lombok.*;
-import lombok.experimental.Delegate;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.function.Supplier;
-
-/**
- * A value which may be present (which includes {@link null}) or not-present.
- *
- * This differs from {@link java.util.Optional} as presence of a {@link null} value
- * does not make this value container empty.
- */
-public interface ValueContainer extends Supplier {
-
- /**
- * Gets a value container being empty.
- *
- * @param type of value container
- * @return empty value container
- */
- @SuppressWarnings("unchecked")
- static ValueContainer empty() {
- return (ValueContainer) Empty.INSTANCE;
- }
-
- /**
- * Gets a non-empty value container containing {@link null}.
- *
- * @param type of value container
- * @return value container containing {@link null}
- */
- @SuppressWarnings("unchecked")
- static ValueContainer ofNull() {
- return (ValueContainer) OfNull.INSTANCE;
- }
-
- /**
- * Gets a non-empty value container containing the specified value.
- *
- * @param type of value container
- * @return non-empty value container containing the specified value
- */
- @SuppressWarnings("unchecked")
- static ValueContainer of(@Nullable final T value) {
- return value == null ? (ValueContainer) OfNull.INSTANCE : new Containing<>(value);
- }
-
- /**
- * Gets a value container containing the specified value or an empty one if the values is {@link null}.
- *
- * @param type of value container
- * @return non-empty value container containing the specified value
- * if it is not {@link null} and an empty one otherwise
- */
- @SuppressWarnings("unchecked")
- static ValueContainer nonnullOrEmpty(@Nullable final T value) {
- return value == null ? (ValueContainer) Empty.INSTANCE : new Containing<>(value);
- }
-
- /**
- * {@inheritDoc}
- *
- * @return {@inheritDoc}
- * @throws EmptyValueException if this value container is empty
- */
- @Override
- T get() throws EmptyValueException;
-
- /**
- * Checks whether the object is present ot not.
- *
- * @return {@link true} if the value is present (might be {@link null}) and {@link false} otherwise
- */
- boolean isPresent();
-
- /**
- * Gets the value if it is present otherwise using the specified one.
- *
- * @param value value to use if this value container is empty
- * @return this value container's value if it is not empty or the specified value otherwise
- */
- @Nullable default T orElse(@Nullable final T value) {
- return isPresent() ? get() : value;
- }
-
- /**
- * Gets the value if it is present otherwise using the one got from the specified supplier.
- *
- * @param valueSupplier supplier to be used for getting the value if this value container is empty
- * @return this value container's value if it is not empty or the one got from the supplier otherwise
- */
- @Nullable default T orElseGet(@NonNull final Supplier valueSupplier) {
- return isPresent() ? get() : valueSupplier.get();
- }
-
- /**
- * Gets the value if it is present otherwise throwing an exception.
- *
- * @param exceptionSupplier supplier to be used for getting the exception if this value container is empty
- * @param type of an exception thrown if this value container is empty
- * @return this value container's value if it is not empty
- * @throws X if this value container is empty
- */
- @Nullable default T orElseThrow(@NonNull final Supplier<@NotNull X> exceptionSupplier)
- throws X {
- if (isPresent()) {
- return get();
- }
-
- throw exceptionSupplier.get();
- }
-
- // use default equals and hashCode
- @NoArgsConstructor(access = AccessLevel.PRIVATE)
- final class Empty implements ValueContainer {
-
- private static final Empty> INSTANCE = new Empty<>();
-
- @Override
- public T get() throws EmptyValueException {
- throw new EmptyValueException("There is no value associated with this value container");
- }
-
- @Override
- public boolean isPresent() {
- return false;
- }
-
- @Override
- public String toString() {
- return "Empty ValueContainer";
- }
- }
-
- // use default equals and hashCode
- @NoArgsConstructor(access = AccessLevel.PRIVATE)
- final class OfNull implements ValueContainer {
-
- private static final OfNull> INSTANCE = new OfNull<>();
-
- @Override
- public T get() throws EmptyValueException {
- return null;
- }
-
- @Override
- public boolean isPresent() {
- return true;
- }
-
- @Override
- public String toString() {
- return "ValueContainer{null}";
- }
- }
-
- @Value
- @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
- class Containing implements ValueContainer {
-
- @NonNull private final T value;
-
- @Override
- public T get() throws EmptyValueException {
- return value;
- }
-
- @Override
- public boolean isPresent() {
- return true;
- }
- }
-
- @Value
- @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
- class Supplying implements ValueContainer {
-
- @NonNull @Delegate private final Supplier valueSupplier;
-
- @Override
- public boolean isPresent() {
- return true;
- }
- }
-
- /**
- * An exception thrown whenever {@link #get()} is called on an empty value container.
- */
- @NoArgsConstructor
- class EmptyValueException extends RuntimeException {
- //
- public EmptyValueException(final String message) {
- super(message);
- }
-
- public EmptyValueException(final String message, final Throwable cause) {
- super(message, cause);
- }
-
- public EmptyValueException(final Throwable cause) {
- super(cause);
- }
-
- public EmptyValueException(final String message, final Throwable cause, final boolean enableSuppression,
- final boolean writableStackTrace) {
- super(message, cause, enableSuppression, writableStackTrace);
- }
- //
- }
-}
diff --git a/shared/src/main/java/org/feathercore/shared/util/NamedThreadFactory.java b/shared/src/main/java/org/feathercore/shared/util/NamedThreadFactory.java
deleted file mode 100644
index acb3be4..0000000
--- a/shared/src/main/java/org/feathercore/shared/util/NamedThreadFactory.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2019 Feather Core
- *
- * 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.feathercore.shared.util;
-
-import lombok.*;
-import lombok.experimental.FieldDefaults;
-
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * Created by k.shandurenko on 12/04/2019
- */
-@ToString
-@EqualsAndHashCode
-@RequiredArgsConstructor
-@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true)
-public class NamedThreadFactory implements ThreadFactory {
-
- @NonNull String name;
- boolean daemon;
- @NonNull AtomicInteger counter = new AtomicInteger(1);
-
- @Override
- public Thread newThread(@NonNull final Runnable runnable) {
- val thread = new Thread(runnable);
- thread.setDaemon(daemon);
- thread.setName(name + counter.getAndIncrement());
-
- return thread;
- }
-}
\ No newline at end of file
diff --git a/shared/src/main/java/org/feathercore/shared/util/thread/ThreadFactories.java b/shared/src/main/java/org/feathercore/shared/util/thread/ThreadFactories.java
new file mode 100644
index 0000000..3fa7f9f
--- /dev/null
+++ b/shared/src/main/java/org/feathercore/shared/util/thread/ThreadFactories.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2019 Feather Core
+ *
+ * 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.feathercore.shared.util.thread;
+
+import lombok.NonNull;
+import lombok.experimental.UtilityClass;
+import lombok.val;
+
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Utility-methods used for creation of typical {@link ThreadFactory thread factories}.
+ *
+ * @author Dimasik
+ * @author PROgrammer_JARvis
+ */
+@UtilityClass
+public class ThreadFactories {
+
+ /**
+ * Creates a {@link ThreadFactory thread factory} which picks names of created threads using a counter.
+ *
+ * @param prefix prefix of the thread name
+ * @param suffix suffix of the thread name
+ * @param daemon daemon-flag of the thread
+ * @return created thread factory
+ *
+ * @see #createPaginated(String, boolean) simpler alternative
+ */
+ public ThreadFactory createPaginated(@NonNull final String prefix,
+ @NonNull final String suffix,
+ @NonNull final boolean daemon) {
+ // intialized here so that it gets captured by lambda
+ val counter = new AtomicInteger();
+
+ return (runnable) -> {
+ val thread = new Thread(runnable);
+ thread.setDaemon(daemon);
+ thread.setName(prefix + counter.getAndIncrement() + suffix);
+
+ return thread;
+ };
+ }
+
+ /**
+ * Creates a {@link ThreadFactory thread factory} which picks names of created threads using a counter.
+ *
+ * @param prefix prefix of the thread name
+ * @param daemon daemon-flag of the thread
+ * @return created thread factory
+ *
+ * @see #createPaginated(String, String, boolean) alternative method with richer naming control
+ */
+ public ThreadFactory createPaginated(@NonNull final String prefix,
+ @NonNull final boolean daemon) {
+ return createPaginated(prefix, "", daemon);
+ }
+}