[BLEEDING] Flesh out aspects of ComponentRegistry.

* Fixes data removal ignoring chat.logins and chat.text for a part.
* Move some components interfaces and ReflectionUtil to NCPCommons.
* Unregister components in reverse order.
* Add ComponentRegistryProvider for generic sub-registries (DataManager
for instance).
* Add IHoldSUbComponents for delayed sub-component registration
(convenient for iteration over parent-components with later registration
of sub components not missing out any registered parent components for
those). [Partly implemented: Using this during runtime does not yet
work, only used in onEnable.]
* Let CheckListener implement IHoldSubComponents and use this with
addCheck to register the queued checks after all the listeners.
* Register the core system components in a bunch before the
CheckListenerS, to allow sub-registries to work directly and to allow
getAPI().addComponent on the plugin class during construction of
CheckListeners.
This commit is contained in:
asofold 2013-04-15 16:11:08 +02:00
parent 652342c6c9
commit de5f152df9
13 changed files with 310 additions and 83 deletions

View File

@ -1,27 +1,26 @@
package fr.neatmonster.nocheatplus.components;
/**
* A ComponentRegistry allows registering components, that then are delegated to where they belong.<br>
* A ComponentRegistry allows registering components, that then should be delegated to where they belong or just be ignored.<br>
* Notes:
* <li>Implementations should somehow specify what components can be registered.</li>
* <li>Implementations should somehow specify if/when they are unregistered automatically.</li>
* @author mc_dev
*
*/
public interface ComponentRegistry {
public interface ComponentRegistry<T>{
/**
* Convenience method to add components according to implemented interfaces,
* like Listener, INotifyReload, INeedConfig.<br>
* For the NoCheatPlus instance this must be done after the configuration has been initialized.
* @param obj
* Register a component.
* @param component
* @return If (newly) added. Adding an already present component should do nothing.
*/
public boolean addComponent(final Object obj);
public boolean addComponent(final T component);
/**
* Remove a registered component. <br>
* Does not unregister listeners currently.
* @param obj
* @param component
*/
public void removeComponent(final Object obj);
public void removeComponent(final T component);
}

View File

@ -0,0 +1,20 @@
package fr.neatmonster.nocheatplus.components;
import java.util.Collection;
/**
* An object allowing to get ComponentRegistry implementations of a specific type.
* This class is not specialized to maintain flexibility.
* @author mc_dev
*
*/
public interface ComponentRegistryProvider{
/**
* Get all available specialized ComponentFactory instances matching the given signature. This is not meant as a factory method but for more efficient registration for the case of the regestry being present.
* @param clazz
* @return Some collection, empty collection in case no matches are found.
*/
public <T> Collection<ComponentRegistry<T>> getComponentRegistries(Class<ComponentRegistry<T>> clazz);
}

View File

@ -0,0 +1,78 @@
package fr.neatmonster.nocheatplus.utilities;
import java.lang.reflect.Method;
public class ReflectionUtil {
/**
* Convenience method to check if members exist and fail if not.
* @param prefix
* @param specs
* @throws RuntimeException
*/
public static void checkMembers(String prefix, String[]... specs){
try {
for (String[] spec : specs){
Class<?> clazz = Class.forName(prefix + spec[0]);
for (int i = 1; i < spec.length; i++){
if (clazz.getField(spec[i]) == null) throw new NoSuchFieldException(prefix + spec[0] + " : " + spec[i]);
}
}
} catch (SecurityException e) {
// Let this one pass.
//throw new RuntimeException(e);
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
/**
* Dirty method. Does try.catch and return null for method invokation.
* @param obj
* @param methodName
* @param arg
* @return
*/
public static Object invokeGenericMethodOneArg(final Object obj, final String methodName, final Object arg){
// TODO: Isn't there a one-line-call for this ??
final Class<?> objClass = obj.getClass();
final Class<?> argClass = arg.getClass();
// Collect methods that might work.
Method methodFound = null;
boolean denyObject = false;
for (final Method method : objClass.getDeclaredMethods()){
if (method.getName().equals(methodName)){
final Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length == 1 ){
// Prevent using Object as argument if there exists a method with a specialized argument.
if (parameterTypes[0] != Object.class && !parameterTypes[0].isAssignableFrom(argClass)){
denyObject = true;
}
// Override the found method if none found yet and assignment is possible, or if it has a specialized argument of an already found one.
if ((methodFound == null && parameterTypes[0].isAssignableFrom(argClass) || methodFound != null && methodFound.getParameterTypes()[0].isAssignableFrom(parameterTypes[0]))){
methodFound = method;
}
}
}
}
if (denyObject && methodFound.getParameterTypes()[0] == Object.class){
// TODO: Throw something !?
return null;
}
else if (methodFound != null && methodFound.getParameterTypes()[0].isAssignableFrom(argClass)){
try{
final Object res = methodFound.invoke(obj, arg);
return res;
}
catch (Throwable t){
// TODO: Throw something !?
return null;
}
}
else{
// TODO: Throw something !?
return null;
}
}
}

View File

@ -1,27 +0,0 @@
package fr.neatmonster.nocheatplus.utilities;
public class ReflectionUtil {
/**
* Convenience method to check if members exist and fail if not.
* @param prefix
* @param specs
* @throws RuntimeException
*/
public static void checkMembers(String prefix, String[]... specs){
try {
for (String[] spec : specs){
Class<?> clazz = Class.forName(prefix + spec[0]);
for (int i = 1; i < spec.length; i++){
if (clazz.getField(spec[i]) == null) throw new NoSuchFieldException(prefix + spec[0] + " : " + spec[i]);
}
}
} catch (SecurityException e) {
// Let this one pass.
//throw new RuntimeException(e);
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
}

View File

@ -3,6 +3,7 @@ package fr.neatmonster.nocheatplus;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@ -46,8 +47,10 @@ import fr.neatmonster.nocheatplus.command.CommandHandler;
import fr.neatmonster.nocheatplus.command.INotifyReload;
import fr.neatmonster.nocheatplus.compat.MCAccess;
import fr.neatmonster.nocheatplus.compat.MCAccessFactory;
import fr.neatmonster.nocheatplus.components.ComponentRegistry;
import fr.neatmonster.nocheatplus.components.ComponentWithName;
import fr.neatmonster.nocheatplus.components.ConsistencyChecker;
import fr.neatmonster.nocheatplus.components.IHoldSubComponents;
import fr.neatmonster.nocheatplus.components.INeedConfig;
import fr.neatmonster.nocheatplus.components.JoinLeaveListener;
import fr.neatmonster.nocheatplus.components.MCAccessHolder;
@ -74,6 +77,7 @@ import fr.neatmonster.nocheatplus.permissions.PermissionUtil.CommandProtectionEn
import fr.neatmonster.nocheatplus.permissions.Permissions;
import fr.neatmonster.nocheatplus.players.DataManager;
import fr.neatmonster.nocheatplus.utilities.BlockProperties;
import fr.neatmonster.nocheatplus.utilities.ReflectionUtil;
import fr.neatmonster.nocheatplus.utilities.TickTask;
import fr.neatmonster.nocheatplus.utilities.Updates;
@ -326,16 +330,59 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
/** Listeners for players joining and leaving (monitor level) */
protected final List<JoinLeaveListener> joinLeaveListeners = new ArrayList<JoinLeaveListener>();
/** Sub component registries. */
protected final List<ComponentRegistry<?>> subRegistries = new ArrayList<ComponentRegistry<?>>();
/** Queued sub component holders, emptied on the next tick usually. */
protected final List<IHoldSubComponents> subComponentholders = new ArrayList<IHoldSubComponents>(20);
/** All registered components. */
protected Set<Object> allComponents = new LinkedHashSet<Object>(50);
@SuppressWarnings("unchecked")
@Override
public <T> Collection<ComponentRegistry<T>> getComponentRegistries(final Class<ComponentRegistry<T>> clazz) {
final List<ComponentRegistry<T>> result = new LinkedList<ComponentRegistry<T>>();
for (final ComponentRegistry<?> registry : subRegistries){
if (clazz.isAssignableFrom(registry.getClass())){
try{
result.add((ComponentRegistry<T>) registry);
}
catch(Throwable t){
// Ignore.
}
}
}
return result;
}
/**
* Convenience method to add components according to implemented interfaces,
* like Listener, INotifyReload, INeedConfig.<br>
* For the NoCheatPlus instance this must be done after the configuration has been initialized.
* This will also register ComponentRegistry instances if given.
*/
@Override
public boolean addComponent(final Object obj) {
return addComponent(obj, true);
}
/**
* Convenience method to add components according to implemented interfaces,
* like Listener, INotifyReload, INeedConfig.<br>
* For the NoCheatPlus instance this must be done after the configuration has been initialized.
* @param allowComponentRegistry Only registers ComponentRegistry instances if this is set to true.
*/
@Override
public boolean addComponent(final Object obj, final boolean allowComponentRegistry) {
// TODO: Allow to add ComponentFactory + contract (renew with reload etc.)?
if (obj == this) throw new IllegalArgumentException("Can not register NoCheatPlus with itself.");
if (allComponents.contains(obj)) return false;
if (allComponents.contains(obj)){
// All added components are in here.
return false;
}
boolean added = false;
if (obj instanceof Listener) {
addListener((Listener) obj);
@ -370,9 +417,26 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
joinLeaveListeners.add((JoinLeaveListener) obj);
added = true;
}
// Also add to DataManager, which will pick what it needs.
// TODO: This is fishy in principle, something more concise?
if (dataMan.addComponent(obj)) added = true;
// Add to sub registries.
for (final ComponentRegistry<?> registry : subRegistries){
final Object res = ReflectionUtil.invokeGenericMethodOneArg(registry, "addComponent", obj);
if (res != null && (res instanceof Boolean) && ((Boolean) res).booleanValue()){
added = true;
}
}
// Add ComponentRegistry instances after adding to sub registries to prevent adding it to itself.
if (allowComponentRegistry && (obj instanceof ComponentRegistry<?>)){
subRegistries.add((ComponentRegistry<?>) obj);
added = true;
}
if (obj instanceof IHoldSubComponents){
subComponentholders.add((IHoldSubComponents) obj);
added = true; // Convention.
}
// Add to allComponents if in fact added.
if (added) allComponents.add(obj);
return added;
@ -382,7 +446,8 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
* Interfaces checked for managed listeners: IHaveMethodOrder (method), ComponentWithName (tag)<br>
* @param listener
*/
protected void addListener(final Listener listener) {
private void addListener(final Listener listener) {
// private: Use addComponent.
if (manageListeners){
String tag = "NoCheatPlus";
if (listener instanceof ComponentWithName){
@ -430,7 +495,16 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
if (obj instanceof JoinLeaveListener){
joinLeaveListeners.remove((JoinLeaveListener) obj);
}
dataMan.removeComponent(obj);
// Remove sub registries.
if (obj instanceof ComponentRegistry<?>){
subRegistries.remove(obj);
}
// Remove from present registries, order prevents to remove from itself.
for (final ComponentRegistry<?> registry : subRegistries){
ReflectionUtil.invokeGenericMethodOneArg(registry, "removeComponent", obj);
}
allComponents.remove(obj);
}
@ -503,8 +577,9 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
// Unregister all added components explicitly.
if (verbose) LogUtil.logInfo("[NoCheatPlus] Unregister all registered components...");
for (Object obj : new ArrayList<Object>(allComponents)){
removeComponent(obj);
final ArrayList<Object> allComponents = new ArrayList<Object>(this.allComponents);
for (int i = allComponents.size() - 1; i >= 0; i--){
removeComponent(allComponents.get(i));
}
// Cleanup BlockProperties.
@ -518,6 +593,10 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
notifyReload.clear();
// World specific permissions.
permStateReceivers.clear();
// Sub registries.
subRegistries.clear();
// Just in case: clear the subComponentHolders.
subComponentholders.clear();
// Clear command changes list (compatibility issues with NPCs, leads to recalculation of perms).
if (changedCommands != null){
@ -610,25 +689,35 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
listenerManager.setRegisterDirectly(false);
listenerManager.clear();
}
addComponent(nameSetPerms);
addListener(getCoreListener());
// Add the "low level" system components first.
for (final Object obj : new Object[]{
nameSetPerms,
getCoreListener(),
// Put ReloadListener first, because Checks could also listen to it.
new INotifyReload() {
// Only for reloading, not INeedConfig.
@Override
public void onReload() {
processReload();
}
},
NCPExemptionManager.getListener(),
new ConsistencyChecker() {
@Override
public void checkConsistency(final Player[] onlinePlayers) {
NCPExemptionManager.checkConsistency(onlinePlayers);
}
},
dataMan,
}){
addComponent(obj);
}
// Register sub-components (!).
processQueuedSubComponentHolders();
// Register "highe level" components (check listeners).
for (final Object obj : new Object[]{
// Put ReloadListener first, because Checks could also listen to it.
new INotifyReload() {
// Only for reloading, not INeedConfig.
@Override
public void onReload() {
processReload();
}
},
NCPExemptionManager.getListener(),
new ConsistencyChecker() {
@Override
public void checkConsistency(final Player[] onlinePlayers) {
NCPExemptionManager.checkConsistency(onlinePlayers);
}
},
dataMan,
new BlockInteractListener(),
new BlockBreakListener(),
new BlockPlaceListener(),
@ -642,6 +731,9 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
addComponent(obj);
}
// Register sub-components (!).
processQueuedSubComponentHolders();
// Register the commands handler.
PluginCommand command = getCommand("nocheatplus");
CommandHandler commandHandler = new CommandHandler(this, notifyReload);
@ -736,6 +828,20 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI {
LogUtil.logInfo("[NoCheatPlus] Version " + getDescription().getVersion() + " is enabled.");
}
/**
* Empties and registers the subComponentHolders list.
*/
protected void processQueuedSubComponentHolders(){
if (subComponentholders.isEmpty()) return;
final List<IHoldSubComponents> copied = new ArrayList<IHoldSubComponents>(subComponentholders);
subComponentholders.clear();
for (final IHoldSubComponents holder : copied){
for (final Object component : holder.getSubComponents()){
addComponent(component);
}
}
}
/**
* All action done on reload.
*/

View File

@ -1,7 +1,13 @@
package fr.neatmonster.nocheatplus.checks;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import fr.neatmonster.nocheatplus.NoCheatPlus;
import fr.neatmonster.nocheatplus.compat.MCAccess;
import fr.neatmonster.nocheatplus.components.IHoldSubComponents;
import fr.neatmonster.nocheatplus.components.MCAccessHolder;
import fr.neatmonster.nocheatplus.components.NCPListener;
@ -11,12 +17,15 @@ import fr.neatmonster.nocheatplus.components.NCPListener;
* @author mc_dev
*
*/
public class CheckListener extends NCPListener implements MCAccessHolder{
public class CheckListener extends NCPListener implements MCAccessHolder, IHoldSubComponents{
/** Check group / type which this listener is for. */
protected final CheckType checkType;
protected MCAccess mcAccess;
/** */
protected final List<Object> queuedComponents = new LinkedList<Object>();
public CheckListener(){
this(null);
}
@ -43,13 +52,22 @@ public class CheckListener extends NCPListener implements MCAccessHolder{
}
/**
* Convenience method to add checks as components to NCP.
* Convenience method to add checks as components to NCP with a delay (IHoldSubComponent).
* This should not be used after having added the check to the ComponentRegistry (NCP-API).
* @param check
* @return The given Check instance, for chaining.
*/
protected <C extends Check> C addCheck(C check){
// Could also set up a map from check type to check, etc.
NoCheatPlus.getAPI().addComponent(check);
queuedComponents.add(check);
return check;
}
@Override
public Collection<Object> getSubComponents() {
final List<Object> res = new ArrayList<Object>(this.queuedComponents);
this.queuedComponents.clear();
return res;
}
}

View File

@ -31,7 +31,7 @@ import fr.neatmonster.nocheatplus.utilities.StringUtil;
*/
public class Text extends AsyncCheck implements INotifyReload{
private LetterEngine engine;
private LetterEngine engine = null;
/** Not really cancelled but above threshold for actions. */
private String lastCancelledMessage = "";

View File

@ -205,7 +205,7 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
private final NoCheatPlus plugin = (NoCheatPlus) Bukkit.getPluginManager().getPlugin("NoCheatPlus");
/** The no fall check. **/
public final NoFall noFall = new NoFall();
public final NoFall noFall = addCheck(new NoFall());
/** The creative fly check. */
private final CreativeFly creativeFly = addCheck(new CreativeFly());

View File

@ -0,0 +1,11 @@
package fr.neatmonster.nocheatplus.compat;
/**
* Default factory for add-in components which might only be available under certain circumstances.
* This will be called from within the plugin from within onEnable to register components in a flexible way.
* @author mc_dev
*
*/
public class DefaultComponentFactory {
}

View File

@ -0,0 +1,19 @@
package fr.neatmonster.nocheatplus.components;
import java.util.Collection;
/**
* This component queues other components to automatically register "later", i.e. with an unspecified but finite delay, depending on specification coming with the implementation.
* The convention is to add this as a component "officially", even if only the sub-components are used.
* @author mc_dev
*
*/
public interface IHoldSubComponents {
/**
* This is to be called after the specified delay after registering the implementation in a registry.
* It is recommended to not return the same elements again on a second call, for the case of delegating to further registries supporting this interface.
* @return Always a collection, may be empty, should be empty on the second call.
*/
public Collection<Object> getSubComponents();
}

View File

@ -4,12 +4,22 @@ package fr.neatmonster.nocheatplus.components;
/**
* ComponentRegistry:
* <li>Supported components: Listener, TickListener, PermStateReceiver, INotifyReload, INeedConfig, IRemoveData, MCAccessHolder, ConsistencyChecker, JoinLeaveListener</li>
* <li>ComponentRegistry instances will be registered as sub registries unless you use the addComponent(Object, boolean) method appropriately. </li>
* <li>IHoldSubComponents instances will be registered in the next tick (scheduled task), those added within onEnable will get registered after looping in onEnable.</li>
* <li>Registering components should be done during onEnable or any time while the plugin is enabled, all components will be unregistered in onDisable.</li>
* <li>References to all components will be held until onDisable is finished.</li>
* <li>Interfaces checked for managed listeners: IHaveMethodOrder (method), ComponentWithName (tag)</li>
* @author mc_dev
*
*/
public interface NoCheatPlusAPI extends ComponentRegistry{
public interface NoCheatPlusAPI extends ComponentRegistry<Object>, ComponentRegistryProvider{
/**
* By default addComponent(Object) will register ComponentFactories as well.
* @param obj
* @param allowComponentRegistry If to allow registering ComponentFactories.
* @return
*/
public boolean addComponent(Object obj, boolean allowComponentRegistry);
}

View File

@ -58,7 +58,7 @@ import fr.neatmonster.nocheatplus.utilities.StringUtil;
* @author mc_dev
*
*/
public class DataManager implements Listener, INotifyReload, INeedConfig, ComponentRegistry, ComponentWithName, ConsistencyChecker{
public class DataManager implements Listener, INotifyReload, INeedConfig, ComponentRegistry<IRemoveData>, ComponentWithName, ConsistencyChecker{
protected static DataManager instance = null;
@ -351,26 +351,19 @@ public class DataManager implements Listener, INotifyReload, INeedConfig, Compon
}
@Override
public boolean addComponent(Object obj) {
if (obj instanceof IRemoveData) {
if (iRemoveData.contains(obj)){
return false;
}
else{
iRemoveData.add((IRemoveData) obj);
return true;
}
public boolean addComponent(IRemoveData obj) {
if (iRemoveData.contains(obj)){
return false;
}
else{
iRemoveData.add((IRemoveData) obj);
return true;
}
}
@Override
public void removeComponent(Object obj) {
if (obj instanceof IRemoveData) {
iRemoveData.remove((IRemoveData) obj);
}
public void removeComponent(IRemoveData obj) {
iRemoveData.remove((IRemoveData) obj);
}
/**