Extracted DataContainer into an interface

This commit is contained in:
Rsl1122 2019-02-06 16:06:24 +02:00
parent 9520d20c3e
commit b1a579cd88
12 changed files with 245 additions and 105 deletions

View File

@ -16,7 +16,7 @@
*/
package com.djrapitops.plan.data.container;
import com.djrapitops.plan.data.store.containers.DataContainer;
import com.djrapitops.plan.data.store.containers.SupplierDataContainer;
import com.djrapitops.plan.data.store.keys.SessionKeys;
import com.djrapitops.plan.data.store.objects.DateHolder;
import com.djrapitops.plan.data.time.WorldTimes;
@ -29,7 +29,7 @@ import java.util.*;
* @author Rsl1122
* @see SessionKeys for Key objects.
*/
public class Session extends DataContainer implements DateHolder {
public class Session extends SupplierDataContainer implements DateHolder {
private long sessionStart;
private WorldTimes worldTimes;

View File

@ -59,7 +59,7 @@ import java.util.stream.Collectors;
* @see com.djrapitops.plan.data.store.keys.AnalysisKeys for Key objects
* @see com.djrapitops.plan.data.store.PlaceholderKey for placeholder information
*/
public class AnalysisContainer extends DataContainer {
public class AnalysisContainer extends SupplierDataContainer {
private final ServerContainer serverContainer;

View File

@ -16,67 +16,56 @@
*/
package com.djrapitops.plan.data.store.containers;
import com.djrapitops.plan.data.store.CachingSupplier;
import com.djrapitops.plan.data.store.Key;
import com.djrapitops.plan.utilities.formatting.Formatter;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
* Abstract representation of an object that holds the Values for different Keys.
* Interface for an object that can store arbitrary data referenced via {@link Key} objects.
* <p>
* The methods in this object are used for placing and fetching the data from the container.
* Methods to use depend on your use case.
* Implementations should mainly be concerned on how the data given to it is stored.
* Retrieval has some details that should be followed.
*
* @author Rsl1122
*/
public class DataContainer {
private final Map<Key, Supplier> map;
private long timeToLive;
public DataContainer() {
this(TimeUnit.SECONDS.toMillis(30L));
}
public DataContainer(long timeToLive) {
this.timeToLive = timeToLive;
map = new HashMap<>();
}
public interface DataContainer {
/**
* Place your data inside the container.
* <p>
* What the container does with the object depends on the implementation.
*
* @param key Key of type T that identifies the data and will be used later when the data needs to be fetched.
* @param obj object to store
* @param <T> Type of the object
*/
public <T> void putRawData(Key<T> key, T obj) {
putSupplier(key, () -> obj);
}
<T> void putRawData(Key<T> key, T obj);
public <T> void putSupplier(Key<T> key, Supplier<T> supplier) {
if (supplier == null) {
return;
}
map.put(key, supplier);
}
/**
* Place a data supplier inside the container.
* <p>
* What the container does with the supplier depends on the implementation.
*
* @param key Key of type T that identifies the data and will be used later when the data needs to be fetched.
* @param supplier Supplier to store
* @param <T> Type of the object
*/
<T> void putSupplier(Key<T> key, Supplier<T> supplier);
public <T> void putCachingSupplier(Key<T> key, Supplier<T> supplier) {
if (supplier == null) {
return;
}
map.put(key, new CachingSupplier<>(supplier, timeToLive));
}
private <T> Supplier<T> getSupplier(Key<T> key) {
return (Supplier<T>) map.get(key);
}
/**
* Place a caching data supplier inside the container.
* <p>
* If the supplier is called the value is cached according to the implementation of the container.
* What the container does with the supplier depends on the implementation.
*
* @param key Key of type T that identifies the data and will be used later when the data needs to be fetched.
* @param supplier Supplier to store
* @param <T> Type of the object
*/
<T> void putCachingSupplier(Key<T> key, Supplier<T> supplier);
/**
* Check if a Value with the given Key has been placed into the container.
@ -85,65 +74,79 @@ public class DataContainer {
* @param <T> Type of the object returned by the Value if it is present.
* @return true if found, false if not.
*/
public <T> boolean supports(Key<T> key) {
return map.containsKey(key);
}
<T> boolean supports(Key<T> key);
/**
* Get an Optional of the data identified by the Key.
* Get an Optional of the Value identified by the Key.
* <p>
* Since Value is a functional interface, its method may call blocking methods via Value implementations,
* It is therefore recommended to not call this method on the server thread.
* <p>
* It is recommended to check if the Optional is present as null values returned by plugins will be empty.
* It is recommended to check if the Optional is present as null values will be empty.
*
* @param key Key that identifies the Value
* @param <T> Type of the object returned by Value
* @return Optional of the object if the key is registered and key matches the type of the object. Otherwise empty.
*/
public <T> Optional<T> getValue(Key<T> key) {
Supplier<T> supplier = getSupplier(key);
if (supplier == null) {
return Optional.empty();
}
try {
return Optional.ofNullable(supplier.get());
} catch (ClassCastException e) {
return Optional.empty();
}
}
<T> Optional<T> getValue(Key<T> key);
public <T> T getUnsafe(Key<T> key) {
Supplier supplier = map.get(key);
if (supplier == null) {
throw new IllegalArgumentException("Unsupported Key: " + key.getKeyName());
}
return (T) supplier.get();
}
/**
* Get data identified by the Key, or throw an exception.
* <p>
* It is recommended to use {@link DataContainer#supports(Key)} before using this method.
*
* @param key Key that identifies the Value
* @param <T> Type of the object returned by Value
* @return the value
* @throws IllegalArgumentException If the key is not supported.
*/
<T> T getUnsafe(Key<T> key);
public <T> String getFormatted(Key<T> key, Formatter<Optional<T>> formatter) {
/**
* Get formatted Value identified by the Key.
* <p>
*
* @param key Key that identifies the Value
* @param formatter Formatter for the Optional returned by {@link DataContainer#getValue(Key)}
* @param <T> Type of the object returned by Value
* @return Optional of the object if the key is registered and key matches the type of the object. Otherwise empty.
*/
default <T> String getFormatted(Key<T> key, Formatter<Optional<T>> formatter) {
Optional<T> value = getValue(key);
return formatter.apply(value);
}
public <T> String getFormattedUnsafe(Key<T> key, Formatter<T> formatter) {
/**
* Get formatted Value identified by the Key, or throw an exception.
* <p>
* It is recommended to use {@link DataContainer#supports(Key)} before using this method.
*
* @param key Key that identifies the Value
* @param formatter Formatter for the value
* @param <T> Type of the object returned by Value
* @return the value
* @throws IllegalArgumentException If the key is not supported.
*/
default <T> String getFormattedUnsafe(Key<T> key, Formatter<T> formatter) {
T value = getUnsafe(key);
return formatter.apply(value);
}
private void putAll(Map<Key, Supplier> toPut) {
map.putAll(toPut);
}
/**
* Place all values from given DataContainer into this container.
*
* @param dataContainer Container with values.
*/
void putAll(DataContainer dataContainer);
public void putAll(DataContainer dataContainer) {
putAll(dataContainer.map);
}
/**
* Clear the container of all data.
*/
void clear();
public void clear() {
map.clear();
}
public Map<Key, Object> getMap() {
return map.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().get()));
}
}
/**
* Return a Key - Value Map of the data in the container.
* <p>
* This method may call blocking methods if underlying implementation uses the given Suppliers.
*
* @return Map: Key - Object
*/
Map<Key, Object> getMap();
}

View File

@ -56,7 +56,7 @@ import java.util.concurrent.TimeUnit;
* @see com.djrapitops.plan.data.store.keys.NetworkKeys for Key objects
* @see com.djrapitops.plan.data.store.PlaceholderKey for placeholder information
*/
public class NetworkContainer extends DataContainer {
public class NetworkContainer extends SupplierDataContainer {
private final ServerContainer bungeeContainer;

View File

@ -30,7 +30,7 @@ import java.util.Map;
* @author Rsl1122
* @see com.djrapitops.plan.data.store.keys.PlayerKeys For Key objects.
*/
public class PlayerContainer extends DataContainer {
public class PlayerContainer extends SupplierDataContainer {
private Map<Long, ActivityIndex> activityIndexCache;

View File

@ -22,5 +22,5 @@ package com.djrapitops.plan.data.store.containers;
* @author Rsl1122
* @see com.djrapitops.plan.data.store.keys.ServerKeys For Key objects.
*/
public class ServerContainer extends DataContainer {
public class ServerContainer extends SupplierDataContainer {
}

View File

@ -0,0 +1,133 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.data.store.containers;
import com.djrapitops.plan.data.store.CachingSupplier;
import com.djrapitops.plan.data.store.Key;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
* Abstract representation of an object that holds the Values for different Keys.
* <p>
* The methods in this object are used for placing and fetching the data from the container.
* Methods to use depend on your use case.
*
* @author Rsl1122
*/
public class SupplierDataContainer implements DataContainer {
private final Map<Key, Supplier> map;
private long timeToLive;
/**
* Create a SupplierDataContainer with a default TTL of 30 seconds.
*/
public SupplierDataContainer() {
this(TimeUnit.SECONDS.toMillis(30L));
}
/**
* Create a SupplierDataContainer with a custom TTL.
* <p>
* The old value is not removed from memory until the supplier is called again.
*
* @param timeToLive TTL that determines how long a CachingSupplier value is deemed valid.
*/
public SupplierDataContainer(long timeToLive) {
this.timeToLive = timeToLive;
map = new HashMap<>();
}
@Override
public <T> void putRawData(Key<T> key, T obj) {
putSupplier(key, () -> obj);
}
@Override
public <T> void putSupplier(Key<T> key, Supplier<T> supplier) {
if (supplier == null) {
return;
}
map.put(key, supplier);
}
@Override
public <T> void putCachingSupplier(Key<T> key, Supplier<T> supplier) {
if (supplier == null) {
return;
}
map.put(key, new CachingSupplier<>(supplier, timeToLive));
}
private <T> Supplier<T> getSupplier(Key<T> key) {
return (Supplier<T>) map.get(key);
}
@Override
public <T> boolean supports(Key<T> key) {
return map.containsKey(key);
}
@Override
public <T> Optional<T> getValue(Key<T> key) {
Supplier<T> supplier = getSupplier(key);
if (supplier == null) {
return Optional.empty();
}
try {
return Optional.ofNullable(supplier.get());
} catch (ClassCastException e) {
return Optional.empty();
}
}
@Override
public <T> T getUnsafe(Key<T> key) {
Supplier supplier = map.get(key);
if (supplier == null) {
throw new IllegalArgumentException("Unsupported Key: " + key.getKeyName());
}
return (T) supplier.get();
}
private void putAll(Map<Key, Supplier> toPut) {
map.putAll(toPut);
}
@Override
public void putAll(DataContainer dataContainer) {
if (dataContainer instanceof SupplierDataContainer) {
putAll(((SupplierDataContainer) dataContainer).map);
}
}
@Override
public void clear() {
map.clear();
}
@Override
public Map<Key, Object> getMap() {
return map.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().get()));
}
}

View File

@ -19,6 +19,7 @@ package com.djrapitops.plan.data.store.mutators.health;
import com.djrapitops.plan.data.store.Key;
import com.djrapitops.plan.data.store.containers.DataContainer;
import com.djrapitops.plan.data.store.containers.NetworkContainer;
import com.djrapitops.plan.data.store.containers.SupplierDataContainer;
import com.djrapitops.plan.data.store.keys.AnalysisKeys;
import com.djrapitops.plan.data.store.keys.NetworkKeys;
import com.djrapitops.plan.data.store.mutators.PlayersMutator;
@ -146,7 +147,7 @@ public class NetworkHealthInformation extends AbstractHealthInfo {
for (Server server : servers) {
UUID serverUUID = server.getUuid();
DataContainer serverContainer = new DataContainer();
DataContainer serverContainer = new SupplierDataContainer();
serverContainer.putRawData(serverKey, server);
PlayersMutator serverPlayers = playersMutator.filterPlayedOnServer(serverUUID);

View File

@ -20,6 +20,7 @@ import com.djrapitops.plan.data.container.*;
import com.djrapitops.plan.data.store.containers.DataContainer;
import com.djrapitops.plan.data.store.containers.PerServerContainer;
import com.djrapitops.plan.data.store.containers.PlayerContainer;
import com.djrapitops.plan.data.store.containers.SupplierDataContainer;
import com.djrapitops.plan.data.store.keys.PerServerKeys;
import com.djrapitops.plan.data.store.keys.PlayerKeys;
import com.djrapitops.plan.data.store.mutators.PerServerMutator;
@ -61,7 +62,7 @@ public class AllPlayerContainersQuery implements Query<List<PlayerContainer>> {
continue;
}
PerServerContainer perServerContainer = perServerContainers.getOrDefault(uuid, new PerServerContainer());
DataContainer container = perServerContainer.getOrDefault(serverUUID, new DataContainer());
DataContainer container = perServerContainer.getOrDefault(serverUUID, new SupplierDataContainer());
container.putRawData(PlayerKeys.REGISTERED, userInfo.getRegistered());
container.putRawData(PlayerKeys.BANNED, userInfo.isBanned());
container.putRawData(PlayerKeys.OPERATOR, userInfo.isOperator());
@ -77,7 +78,7 @@ public class AllPlayerContainersQuery implements Query<List<PlayerContainer>> {
for (Map.Entry<UUID, List<Session>> sessionEntry : serverUserSessions.entrySet()) {
UUID uuid = sessionEntry.getKey();
PerServerContainer perServerContainer = perServerContainers.getOrDefault(uuid, new PerServerContainer());
DataContainer container = perServerContainer.getOrDefault(serverUUID, new DataContainer());
DataContainer container = perServerContainer.getOrDefault(serverUUID, new SupplierDataContainer());
List<Session> serverSessions = sessionEntry.getValue();
container.putRawData(PerServerKeys.SESSIONS, serverSessions);
@ -103,7 +104,7 @@ public class AllPlayerContainersQuery implements Query<List<PlayerContainer>> {
for (Ping ping : entry.getValue()) {
UUID serverUUID = ping.getServerUUID();
PerServerContainer perServerContainer = perServerContainers.getOrDefault(uuid, new PerServerContainer());
DataContainer container = perServerContainer.getOrDefault(serverUUID, new DataContainer());
DataContainer container = perServerContainer.getOrDefault(serverUUID, new SupplierDataContainer());
if (!container.supports(PerServerKeys.PING)) {
container.putRawData(PerServerKeys.PING, new ArrayList<>());

View File

@ -21,6 +21,7 @@ import com.djrapitops.plan.data.container.UserInfo;
import com.djrapitops.plan.data.store.Key;
import com.djrapitops.plan.data.store.containers.DataContainer;
import com.djrapitops.plan.data.store.containers.PerServerContainer;
import com.djrapitops.plan.data.store.containers.SupplierDataContainer;
import com.djrapitops.plan.data.store.keys.PerServerKeys;
import com.djrapitops.plan.data.store.keys.PlayerKeys;
import com.djrapitops.plan.data.store.mutators.SessionsMutator;
@ -70,7 +71,7 @@ public class PerServerContainerQuery implements Query<PerServerContainer> {
UUID serverUUID = entry.getKey();
List<Session> serverSessions = entry.getValue();
DataContainer serverContainer = perServerContainer.getOrDefault(serverUUID, new DataContainer());
DataContainer serverContainer = perServerContainer.getOrDefault(serverUUID, new SupplierDataContainer());
serverContainer.putRawData(PerServerKeys.SESSIONS, serverSessions);
serverContainer.putSupplier(PerServerKeys.PLAYER_KILLS, () -> SessionsMutator.forContainer(serverContainer).toPlayerKillList());
@ -123,7 +124,7 @@ public class PerServerContainerQuery implements Query<PerServerContainer> {
}
private <T> void placeToPerServerContainer(UUID serverUUID, Key<T> key, T value, PerServerContainer perServerContainer) {
DataContainer container = perServerContainer.getOrDefault(serverUUID, new DataContainer());
DataContainer container = perServerContainer.getOrDefault(serverUUID, new SupplierDataContainer());
container.putRawData(key, value);
perServerContainer.put(serverUUID, container);
}

View File

@ -18,6 +18,7 @@ package com.djrapitops.plan.db.sql.tables;
import com.djrapitops.plan.data.store.Key;
import com.djrapitops.plan.data.store.containers.DataContainer;
import com.djrapitops.plan.data.store.containers.SupplierDataContainer;
import com.djrapitops.plan.data.store.keys.PlayerKeys;
import com.djrapitops.plan.db.DBType;
import com.djrapitops.plan.db.SQLDB;
@ -297,7 +298,7 @@ public class UsersTable extends Table {
public DataContainer getUserInformation(UUID uuid) {
Key<DataContainer> user_data = new Key<>(DataContainer.class, "plan_users_data");
DataContainer returnValue = new DataContainer();
DataContainer returnValue = new SupplierDataContainer();
returnValue.putSupplier(user_data, () -> getUserInformationDataContainer(uuid));
returnValue.putRawData(PlayerKeys.UUID, uuid);
@ -318,7 +319,7 @@ public class UsersTable extends Table {
@Override
public DataContainer processResults(ResultSet set) throws SQLException {
DataContainer container = new DataContainer();
DataContainer container = new SupplierDataContainer();
if (set.next()) {
long registered = set.getLong(REGISTERED);

View File

@ -22,18 +22,18 @@ import org.junit.Test;
import static org.junit.Assert.*;
/**
* Test for {@link DataContainer} programming errors.
* Test for {@link SupplierDataContainer} programming errors.
*
* @author Rsl1122
*/
public class DataContainerTest {
public class SupplierDataContainerTest {
private static final Key<String> TEST_KEY = new Key<>(String.class, "TEST_KEY");
private static final Key<String> TEST_KEY_COPY = new Key<>(String.class, "TEST_KEY");
@Test
public void safeUnsafeKeySupplierSameObject() {
DataContainer container = new DataContainer();
DataContainer container = new SupplierDataContainer();
container.putSupplier(TEST_KEY, () -> "Success");
assertEquals("Success", container.getUnsafe(TEST_KEY));
@ -41,7 +41,7 @@ public class DataContainerTest {
@Test
public void safeUnsafeKeySupplierDifferentObject() {
DataContainer container = new DataContainer();
DataContainer container = new SupplierDataContainer();
container.putSupplier(TEST_KEY, () -> "Success");
assertEquals("Success", container.getUnsafe(TEST_KEY_COPY));
@ -49,7 +49,7 @@ public class DataContainerTest {
@Test
public void safeUnsafeKeyRawSameObject() {
DataContainer container = new DataContainer();
DataContainer container = new SupplierDataContainer();
container.putRawData(TEST_KEY, "Success");
assertEquals("Success", container.getUnsafe(TEST_KEY));
@ -57,7 +57,7 @@ public class DataContainerTest {
@Test
public void safeUnsafeKeyRawDifferentObject() {
DataContainer container = new DataContainer();
DataContainer container = new SupplierDataContainer();
container.putRawData(TEST_KEY, "Success");
assertEquals("Success", container.getUnsafe(TEST_KEY_COPY));
@ -65,7 +65,7 @@ public class DataContainerTest {
@Test
public void safeUnsafeKeyRawNull() {
DataContainer container = new DataContainer();
DataContainer container = new SupplierDataContainer();
container.putRawData(TEST_KEY, null);
assertTrue(container.supports(TEST_KEY));
@ -74,7 +74,7 @@ public class DataContainerTest {
@Test
public void safeUnsafeKeyNullSupplier() {
DataContainer container = new DataContainer();
DataContainer container = new SupplierDataContainer();
container.putSupplier(TEST_KEY, null);
assertFalse(container.supports(TEST_KEY));
@ -82,7 +82,7 @@ public class DataContainerTest {
@Test
public void safeUnsafeKeySupplierNull() {
DataContainer container = new DataContainer();
DataContainer container = new SupplierDataContainer();
container.putSupplier(TEST_KEY, () -> null);
assertTrue(container.supports(TEST_KEY));
@ -91,7 +91,7 @@ public class DataContainerTest {
@Test
public void cachingSupplier() {
DataContainer container = new DataContainer();
DataContainer container = new SupplierDataContainer();
String firstObj = "First";
String secondObj = "Second";