More on GenericInstanceRegistry.

* Add a way to stay updated about the latest registration state for a
class.
* Add a class for the registry.
* Let the registry log all registrations.
* Make super interfaces for LogManager (simple logging of
String/Throwable).

Missing:
* More streams (REGISTRY, PLAYER/CHECK_STATUS/EVENTS or just CHECKS at
least). Make status rather the plugin status. Registry could have an
extra file.
* More efficient IGenericInstanceHandle use (wrap + reference counting).
This commit is contained in:
asofold 2016-06-16 22:23:54 +02:00
parent 0868e30994
commit 2bf3e14ab9
13 changed files with 502 additions and 75 deletions

View File

@ -14,7 +14,8 @@
*/
package fr.neatmonster.nocheatplus.logging;
import java.util.logging.Level;
import fr.neatmonster.nocheatplus.logging.details.ILogString;
import fr.neatmonster.nocheatplus.logging.details.ILogThrowable;
/**
* Central access point log manager with a bias towards String messages.
@ -22,7 +23,7 @@ import java.util.logging.Level;
* @author dev1mc
*
*/
public interface LogManager {
public interface LogManager extends ILogString, ILogThrowable {
/**
* A stream that skips all messages. It's not registered officially.
@ -99,24 +100,4 @@ public interface LogManager {
*/
public StreamID getStreamID(String name);
void debug(StreamID streamID, String message);
void info(StreamID streamID, String message);
void warning(StreamID streamID, String message);
void severe(StreamID streamID, String message);
void log(StreamID streamID, Level level, String message);
void debug(StreamID streamID, Throwable t);
void info(StreamID streamID, Throwable t);
void warning(StreamID streamID, Throwable t);
void severe(StreamID streamID, Throwable t);
void log(StreamID streamID, Level level, Throwable t);
}

View File

@ -0,0 +1,16 @@
package fr.neatmonster.nocheatplus.logging.details;
import fr.neatmonster.nocheatplus.logging.StreamID;
/**
* Allow to select/return a StgreamID instance.
*
* @author asofold
*
* @param <M>
*/
public interface IGetStreamId {
public StreamID getStreamId();
}

View File

@ -0,0 +1,25 @@
package fr.neatmonster.nocheatplus.logging.details;
import java.util.logging.Level;
import fr.neatmonster.nocheatplus.logging.StreamID;
/**
* Standard logging for String messages.
*
* @author asofold
*
*/
public interface ILogString {
void debug(StreamID streamID, String message);
void info(StreamID streamID, String message);
void warning(StreamID streamID, String message);
void severe(StreamID streamID, String message);
void log(StreamID streamID, Level level, String message);
}

View File

@ -0,0 +1,25 @@
package fr.neatmonster.nocheatplus.logging.details;
import java.util.logging.Level;
import fr.neatmonster.nocheatplus.logging.StreamID;
/**
* Standard logging for Throwable throwables.
*
* @author asofold
*
*/
public interface ILogThrowable {
void debug(StreamID streamID, Throwable t);
void info(StreamID streamID, Throwable t);
void warning(StreamID streamID, Throwable t);
void severe(StreamID streamID, Throwable t);
void log(StreamID streamID, Level level, Throwable t);
}

View File

@ -14,44 +14,63 @@
*/
package fr.neatmonster.nocheatplus.components.registry;
import fr.neatmonster.nocheatplus.components.registry.event.IGenericInstanceHandle;
/**
* A registry for unique instances of any class type.<br>
* Currently there is no specification for what happens with registering for an
* already registered class, neither if exceptions are thrown, nor if
* dependencies will use those then.
*
* @author dev1mc
* @author asofold
*
*/
public interface GenericInstanceRegistry {
/**
* Register the instance by its own class.
* @param instance
*/
public <T> T registerGenericInstance(T instance);
/**
* Register an instance under for a super-class.
* @todo The registry implementation might specify if overriding is allowed.
* @param registerAs
* @param instance
* @return The previously registered instance. If none was registered, null is returned.
*/
public <T, TI extends T> T registerGenericInstance(Class<T> registerFor, TI instance);
/**
* Retrieve the instance registered for the given class.
* @param registeredBy
* @return The instance, or null, if none is registered.
*/
public <T> T getGenericInstance(Class<T> registeredFor);
/**
* Remove a registration. The registry implementation might specify id removing is allowed.
* @param registeredFor
* @return The previously registered instance. If none was registered, null is returned.
*/
public <T> T unregisterGenericInstance(Class<T> registeredFor);
/**
* Register the instance by its own class. This demands type parameters to
* be aligned to the actual class.
*
* @param instance
*/
public <T> T registerGenericInstance(T instance);
/**
* Register an instance under for a super-class.
*
* @todo The registry implementation might specify if overriding is allowed.
* @param registerAs
* @param instance
* @return The previously registered instance. If none was registered, null
* is returned.
*/
public <T, TI extends T> T registerGenericInstance(Class<T> registerFor, TI instance);
/**
* Retrieve the instance registered for the given class.
*
* @param registeredBy
* @return The instance, or null, if none is registered.
*/
public <T> T getGenericInstance(Class<T> registeredFor);
/**
* Remove a registration. The registry implementation might specify id
* removing is allowed.
*
* @param registeredFor
* @return The previously registered instance. If none was registered, null
* is returned.
*/
public <T> T unregisterGenericInstance(Class<T> registeredFor);
/**
* Get a self-updating handle for conveniently getting the currently
* registered instance.
*
* @param registeredFor
* @return
*/
public <T> IGenericInstanceHandle<T> getGenericInstanceHandle(Class<T> registeredFor);
}

View File

@ -0,0 +1,39 @@
package fr.neatmonster.nocheatplus.components.registry.event;
/**
* Convenience to retrieve the currently registered instance. Note that
* registrations by other plugins might be problematic, thus removing
* registrations and stored IGenericInstanceHandle instances is within the
* responsibility of the hooking plugin.
*
* @author asofold
*
* @param <T>
* The type instances are registered for.
*/
public interface IGenericInstanceHandle<T> {
// TODO: <? extends T> ?
/**
* Get the currently registered instance.
*
* @return
* @throws RuntimeException,
* if disableHandle has been called.
*/
public T getHandle();
/**
* Unlink from the registry. Subsequent calls to getHandle will yield a
* RuntimeException, while disableHandle can still be called without effect.
* This may not be necessary, if the registration lasts during an entire
* runtime, however if an object that holds IGenericInstanceHandle instances
* gets overridden on reloading the configuration of the plugin, keeping
* handles may leak a little bit of memory and increase CPU load with each
* time such happens. Often changing registration is not a typical use-case.
* This can not be undone.
*/
public void disableHandle();
}

View File

@ -0,0 +1,48 @@
package fr.neatmonster.nocheatplus.components.registry.event;
/**
* Receive Notifications about generic instance registry events. Listeners may
* be possible to register even before an actual instance has been registered
* (and even before it's known to the registry that anything is intended to be
* registered) - subject to change.
*
* @author asofold
*
*/
public interface IGenericInstanceRegistryListener<T> {
// TODO: <? extends T> ?
/**
* Registration, without an entry being present.
*
* @param registerFor
* @param instance
* Might be null, if the registry allows that.
*/
public void onGenericInstanceRegister(Class<T> registerFor, T instance);
/**
* An already registered entry gets overridden.
*
* @param registerFor
* @param newInstance
* The instance that just got registered. Might be null, if the
* registry allows that.
* @param oldInstance
* The instance that had been registered before. Might be null,
* if the registry allows that.
*/
public void onGenericInstanceOverride(Class<T> registerFor, T newInstance, T oldInstance);
/**
* A registration is removed explicitly.
*
* @param registerFor
* @param oldInstance
* The instance that had been registered before. Might be null,
* if the registry allows that.
*/
public void onGenericInstanceRemove(Class<T> registerFor, T oldInstance);
}

View File

@ -0,0 +1,13 @@
package fr.neatmonster.nocheatplus.components.registry.event;
/**
* Rather an internal interface.
*
* @author asofold
*
*/
public interface IUnregisterGenericInstanceListener {
public <T> void unregisterGenericInstanceListener(Class<T> registeredFor, IGenericInstanceHandle<T> listener);
}

View File

@ -82,6 +82,8 @@ import fr.neatmonster.nocheatplus.compat.versions.GenericVersion;
import fr.neatmonster.nocheatplus.compat.versions.ServerVersion;
import fr.neatmonster.nocheatplus.components.NoCheatPlusAPI;
import fr.neatmonster.nocheatplus.components.registry.ComponentRegistry;
import fr.neatmonster.nocheatplus.components.registry.DefaultGenericInstanceRegistry;
import fr.neatmonster.nocheatplus.components.registry.event.IGenericInstanceHandle;
import fr.neatmonster.nocheatplus.components.registry.feature.ComponentWithName;
import fr.neatmonster.nocheatplus.components.registry.feature.ConsistencyChecker;
import fr.neatmonster.nocheatplus.components.registry.feature.DisableListener;
@ -110,7 +112,9 @@ import fr.neatmonster.nocheatplus.hooks.allviolations.AllViolationsHook;
import fr.neatmonster.nocheatplus.logging.BukkitLogManager;
import fr.neatmonster.nocheatplus.logging.LogManager;
import fr.neatmonster.nocheatplus.logging.StaticLog;
import fr.neatmonster.nocheatplus.logging.StreamID;
import fr.neatmonster.nocheatplus.logging.Streams;
import fr.neatmonster.nocheatplus.logging.details.IGetStreamId;
import fr.neatmonster.nocheatplus.permissions.PermissionUtil;
import fr.neatmonster.nocheatplus.permissions.PermissionUtil.CommandProtectionEntry;
import fr.neatmonster.nocheatplus.permissions.Permissions;
@ -184,9 +188,6 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
/** The event listeners. */
private final List<Listener> listeners = new ArrayList<Listener>();
/** Storage for generic instances registration. */
private final Map<Class<?>, Object> genericInstances = new HashMap<Class<?>, Object>();
/** Components that need notification on reloading.
* (Kept here, for if during runtime some might get added.)*/
private final List<INotifyReload> notifyReload = new LinkedList<INotifyReload>();
@ -230,6 +231,9 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
/** Listener for the BlockChangeTracker (register once, lazy). */
private BlockChangeListener blockChangeListener = null;
private final DefaultGenericInstanceRegistry genericInstanceRegistry = new DefaultGenericInstanceRegistry();
/** Tick listener that is only needed sometimes (component registration). */
protected final OnDemandTickListener onDemandTickListener = new OnDemandTickListener() {
@Override
@ -262,6 +266,12 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
private boolean clearExemptionsOnJoin = true;
private boolean clearExemptionsOnLeave = true;
private StreamID getRegistryStreamId() {
// TODO: Select by config, or add Streams.REGISTRY for a new default.
// For now prefer log file, unless extended status is set.
return ConfigManager.getConfigFile().getBoolean(ConfPaths.LOGGING_EXTENDED_STATUS) ? Streams.STATUS : Streams.DEFAULT_FILE;
}
/**
* Remove expired entries.
*/
@ -737,7 +747,7 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
// Just in case: clear the subComponentHolders.
subComponentholders.clear();
// Generic instances registry.
genericInstances.clear();
genericInstanceRegistry.clear();
// Feature tags.
featureTags.clear();
// BlockChangeTracker.
@ -850,6 +860,14 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
StaticLog.setUseLogManager(true);
logManager.info(Streams.INIT, "Logging system initialized.");
logManager.info(Streams.INIT, "Detected Minecraft version: " + ServerVersion.getMinecraftVersion());
genericInstanceRegistry.setLogger(
logManager, new IGetStreamId() {
@Override
public StreamID getStreamId() {
// TODO Auto-generated method stub
return NoCheatPlus.this.getRegistryStreamId();
}
}, "[GenericInstanceRegistry] ");
}
}
@ -1468,33 +1486,29 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
}
}
@Override
public <T> T getGenericInstance(Class<T> registeredFor) {
return genericInstanceRegistry.getGenericInstance(registeredFor);
}
@Override
public <T> T registerGenericInstance(T instance) {
@SuppressWarnings("unchecked")
Class<T> clazz = (Class<T>) instance.getClass();
T registered = getGenericInstance(clazz);
genericInstances.put(clazz, instance);
return registered;
return genericInstanceRegistry.registerGenericInstance(instance);
}
@Override
public <T, TI extends T> T registerGenericInstance(Class<T> registerFor, TI instance) {
T registered = getGenericInstance(registerFor);
genericInstances.put(registerFor, instance);
return registered;
}
@SuppressWarnings("unchecked")
@Override
public <T> T getGenericInstance(Class<T> registeredFor) {
return (T) genericInstances.get(registeredFor);
return genericInstanceRegistry.registerGenericInstance(registerFor, instance);
}
@Override
public <T> T unregisterGenericInstance(Class<T> registeredFor) {
T registered = getGenericInstance(registeredFor); // Convenience.
genericInstances.remove(registeredFor);
return registered;
return genericInstanceRegistry.unregisterGenericInstance(registeredFor);
}
@Override
public <T> IGenericInstanceHandle<T> getGenericInstanceHandle(Class<T> registeredFor) {
return genericInstanceRegistry.getGenericInstanceHandle(registeredFor);
}
@Override

View File

@ -141,7 +141,6 @@ public class RegistryHelper {
public static <T, ET extends T> T registerGenericInstance(Class<T> registerFor, ET result) {
if (result != null) {
NCPAPIProvider.getNoCheatPlusAPI().registerGenericInstance(registerFor, result);
NCPAPIProvider.getNoCheatPlusAPI().getLogManager().info(Streams.STATUS, "Registered for " + registerFor.getName() + ": " + result.getClass().getName());
}
else {
NCPAPIProvider.getNoCheatPlusAPI().getLogManager().info(Streams.STATUS, "Could not register an instance for: " + registerFor.getName());

View File

@ -0,0 +1,153 @@
package fr.neatmonster.nocheatplus.components.registry;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import fr.neatmonster.nocheatplus.components.registry.event.GenericInstanceHandle;
import fr.neatmonster.nocheatplus.components.registry.event.IGenericInstanceHandle;
import fr.neatmonster.nocheatplus.components.registry.event.IGenericInstanceRegistryListener;
import fr.neatmonster.nocheatplus.components.registry.event.IUnregisterGenericInstanceListener;
import fr.neatmonster.nocheatplus.logging.details.IGetStreamId;
import fr.neatmonster.nocheatplus.logging.details.ILogString;
public class DefaultGenericInstanceRegistry implements GenericInstanceRegistry, IUnregisterGenericInstanceListener {
/** Storage for generic instances registration. */
private final Map<Class<?>, Object> instances = new HashMap<Class<?>, Object>();
/** Listeners for registry events. */
private final Map<Class<?>, Collection<IGenericInstanceRegistryListener<?>>> listeners = new HashMap<Class<?>, Collection<IGenericInstanceRegistryListener<?>>>();
/** Handles created within this class, that have to be detached. */
private final Set<IGenericInstanceHandle<?>> ownedHandles = new LinkedHashSet<IGenericInstanceHandle<?>>();
private ILogString logger = null;
private IGetStreamId selectStream;
private String logPrefix;
public void setLogger(ILogString logger, IGetStreamId selectStream, String logPrefix) {
this.logger = logger;
this.selectStream = selectStream;
this.logPrefix = logPrefix;
}
@Override
public <T> void unregisterGenericInstanceListener(Class<T> registeredFor, IGenericInstanceHandle<T> listener) {
Collection<IGenericInstanceRegistryListener<?>> registered = listeners.get(registeredFor);
if (registered != null) {
registered.remove(listener);
if (registered.isEmpty()) {
listeners.remove(registeredFor);
}
}
if ((listener instanceof IGenericInstanceHandle<?>) && ownedHandles.contains(listener)) {
ownedHandles.remove(listener);
((IGenericInstanceHandle<?>) listener).disableHandle();
}
}
@SuppressWarnings("unchecked")
@Override
public <T> T registerGenericInstance(T instance) {
return registerGenericInstance((Class<T>) instance.getClass(), instance);
}
@SuppressWarnings("unchecked")
@Override
public <T, TI extends T> T registerGenericInstance(Class<T> registerFor, TI instance) {
T registered = getGenericInstance(registerFor);
final boolean had = instances.containsKey(registerFor);
instances.put(registerFor, instance);
Collection<IGenericInstanceRegistryListener<?>> registeredListeners = listeners.get(registerFor);
if (registeredListeners != null) {
for (IGenericInstanceRegistryListener<?> rawListener : registeredListeners) {
if (had) {
((IGenericInstanceRegistryListener<T>) rawListener).onGenericInstanceOverride(registerFor, instance, registered);
}
else {
((IGenericInstanceRegistryListener<T>) rawListener).onGenericInstanceRegister(registerFor, instance);
}
}
}
if (had) {
logRegistryEvent("Registered (override) for " + registerFor.getName() + ": " + instance.getClass().getName());
}
else {
logRegistryEvent("Registered for " + registerFor.getName() + ": " + instance.getClass().getName());
}
return registered;
}
@SuppressWarnings("unchecked")
@Override
public <T> T getGenericInstance(Class<T> registeredFor) {
return (T) instances.get(registeredFor);
}
@SuppressWarnings("unchecked")
@Override
public <T> T unregisterGenericInstance(Class<T> registeredFor) {
T registered = getGenericInstance(registeredFor); // Convenience.
final boolean had = instances.containsKey(registeredFor);
instances.remove(registeredFor);
Collection<IGenericInstanceRegistryListener<?>> registeredListeners = listeners.get(registeredFor);
if (registeredListeners != null) {
for (IGenericInstanceRegistryListener<?> rawListener : registeredListeners) {
((IGenericInstanceRegistryListener<T>) rawListener).onGenericInstanceRemove(registeredFor, registered);
}
}
if (had) {
logRegistryEvent("Unregister, remove mapping for: " + registeredFor.getName());
}
else {
logRegistryEvent("Unregister, no mapping present for: " + registeredFor.getName());
}
return registered;
}
@Override
public <T> IGenericInstanceHandle<T> getGenericInstanceHandle(Class<T> registeredFor) {
/*
* More efficient should be to return a wrapper for a unique instance,
* for which disableHandle runs once, so only one listener per
* registered class is necessary, which then uses reference counting for
* actual removal. That's double-wrapped then (returned instance disable
* once -> reference counting instance -> actual instance).
*/
final IGenericInstanceHandle<T> handle = new GenericInstanceHandle<T>(registeredFor, this, this);
ownedHandles.add(handle);
Collection<IGenericInstanceRegistryListener<?>> registered = listeners.get(registeredFor);
if (registered == null) {
registered = new HashSet<IGenericInstanceRegistryListener<?>>();
listeners.put(registeredFor, registered);
}
registered.add((IGenericInstanceRegistryListener<?>) handle);
return handle;
}
public void clear() {
instances.clear();
listeners.clear();
// TODO: consider fire unregister or add a removal method ?
// Force detach all handles.
for (IGenericInstanceHandle<?> handle : new ArrayList<IGenericInstanceHandle<?>>(ownedHandles)) {
handle.disableHandle();
}
ownedHandles.clear();
logRegistryEvent("Registry cleared.");
}
protected void logRegistryEvent(String message) {
if (logger != null) {
logger.info(selectStream.getStreamId(), logPrefix == null ? message : logPrefix + message);
}
}
}

View File

@ -0,0 +1,89 @@
package fr.neatmonster.nocheatplus.components.registry.event;
import fr.neatmonster.nocheatplus.components.registry.GenericInstanceRegistry;
/**
* Default implementation for retrieving a IGenericInstanceHandle from a
* registry.
*
* @author asofold
*
* @param <T>
*/
public class GenericInstanceHandle<T> implements IGenericInstanceRegistryListener<T>, IGenericInstanceHandle<T> {
// TODO: <? extends T> ?
// TODO: Might move to NCPPlugin, or later split (mostly) interface based api from default implementations.
private GenericInstanceRegistry registry;
private IUnregisterGenericInstanceListener unregister;
private Class<T> registeredFor;
private T handle = null;
private boolean initialized = false;
private boolean disabled = false;
// TODO: Remove method?
/**
* Note that this doesn't register with the registry, as the registry may
* return unique handles on request rather.
*
* @param registeredFor
* @param registry
* @param unregister
*/
public GenericInstanceHandle(Class<T> registeredFor, GenericInstanceRegistry registry, IUnregisterGenericInstanceListener unregister) {
this.registry = registry;
this.unregister = unregister;
this.registeredFor = registeredFor;
}
private T fetchHandle() {
return registry.getGenericInstance(registeredFor);
}
@Override
public void onGenericInstanceRegister(Class<T> registerFor, T instance) {
this.handle = instance;
initialized = true;
}
@Override
public void onGenericInstanceOverride(Class<T> registerFor, T newInstance, T oldInstance) {
this.handle = newInstance;
initialized = true;
}
@Override
public void onGenericInstanceRemove(Class<T> registerFor, T oldInstance) {
this.handle = null;
initialized = true;
}
@Override
public T getHandle() {
if (initialized) {
return handle;
}
else if (disabled) {
throw new RuntimeException("Already disabled.");
}
else {
return fetchHandle();
}
}
@Override
public void disableHandle() {
if (unregister != null) {
disabled = true;
initialized = false;
handle = null;
registeredFor = null;
registry = null;
unregister.unregisterGenericInstanceListener(registeredFor, this);
unregister = null;
}
}
}

View File

@ -23,6 +23,7 @@ import fr.neatmonster.nocheatplus.compat.blocks.BlockChangeTracker;
import fr.neatmonster.nocheatplus.compat.bukkit.MCAccessBukkit;
import fr.neatmonster.nocheatplus.components.NoCheatPlusAPI;
import fr.neatmonster.nocheatplus.components.registry.ComponentRegistry;
import fr.neatmonster.nocheatplus.components.registry.event.IGenericInstanceHandle;
import fr.neatmonster.nocheatplus.logging.LogManager;
import fr.neatmonster.nocheatplus.logging.StaticLog;
@ -82,6 +83,11 @@ public class PluginTests {
throw new UnsupportedOperationException();
}
@Override
public <T> IGenericInstanceHandle<T> getGenericInstanceHandle(Class<T> registeredFor) {
throw new UnsupportedOperationException();
}
@Override
public boolean addComponent(Object obj, boolean allowComponentFactory) {
throw new UnsupportedOperationException();