/* * Copyright (c) 2006-2013 DMDirc Developers * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.dmdirc.parser.common; import com.dmdirc.parser.interfaces.Parser; import com.dmdirc.parser.interfaces.SpecificCallback; import com.dmdirc.parser.interfaces.callbacks.*; //NOPMD import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * Parser Callback Manager. * Manages adding/removing/calling callbacks. */ public class CallbackManager { private static final Class[] CLASSES = { AwayStateListener.class, OtherAwayStateListener.class, ChannelOtherAwayStateListener.class, ChannelActionListener.class, ChannelCtcpListener.class, ChannelCtcpReplyListener.class, ChannelListModeListener.class, ChannelNamesListener.class, ChannelJoinListener.class, ChannelKickListener.class, ChannelMessageListener.class, ChannelModeChangeListener.class, ChannelNickChangeListener.class, ChannelNonUserModeChangeListener.class, ChannelModeMessageListener.class, ChannelModeNoticeListener.class, ChannelNoticeListener.class, ChannelPartListener.class, ChannelQuitListener.class, ChannelSelfJoinListener.class, ChannelSingleModeChangeListener.class, ChannelTopicListener.class, ChannelUserModeChangeListener.class, CompositionStateChangeListener.class, ConnectErrorListener.class, DataInListener.class, DataOutListener.class, DebugInfoListener.class, ErrorInfoListener.class, GroupListStartListener.class, GroupListEntryListener.class, GroupListEndListener.class, NetworkDetectedListener.class, InviteListener.class, MotdEndListener.class, MotdLineListener.class, MotdStartListener.class, NickChangeListener.class, NickInUseListener.class, AuthNoticeListener.class, NumericListener.class, PasswordRequiredListener.class, PingFailureListener.class, PingSuccessListener.class, PingSentListener.class, PrivateActionListener.class, PrivateCtcpListener.class, PrivateCtcpReplyListener.class, PrivateMessageListener.class, PrivateNoticeListener.class, QuitListener.class, ServerErrorListener.class, ServerReadyListener.class, SocketCloseListener.class, UnknownActionListener.class, UnknownCtcpListener.class, UnknownCtcpReplyListener.class, UnknownMessageListener.class, UnknownNoticeListener.class, UserModeChangeListener.class, UserModeDiscoveryListener.class, WallDesyncListener.class, WallopListener.class, WalluserListener.class, ServerNoticeListener.class, UnknownServerNoticeListener.class, }; /** Hashtable used to store the different types of callback known. */ private final Map, CallbackObject> callbackHash = new HashMap, CallbackObject>(); /** A map of implementations to use for parser interfaces. */ private final Map, Class> implementationMap; /** * Constructor to create a CallbackManager. * * @param parser Parser that owns this callback manager. * @param implementationMap A map of implementations to use */ public CallbackManager(final Parser parser, final Map, Class> implementationMap) { this.implementationMap = implementationMap; initialise(parser); } /** * Initialises this callback manager. * * @param parser The parser associated with this CallbackManager */ protected void initialise(final Parser parser) { for (Class type : CLASSES) { if (type.isAnnotationPresent(SpecificCallback.class)) { addCallbackType(getSpecificCallbackObject(parser, type)); } else { addCallbackType(getCallbackObject(parser, type)); } } } /** * Retrieves a relevant {@link CallbackObject} for the specified type. * * @param parser The parser that this manager belongs to * @param type The type of callback to create an object for * @return The relevant CallbackObject */ protected CallbackObject getCallbackObject(final Parser parser, final Class type) { return new CallbackObject(parser, this, type.asSubclass(CallbackInterface.class), implementationMap); } /** * Retrieves a relevant {@link CallbackObjectSpecific} for the specified type. * * @param parser The parser that this manager belongs to * @param type The type of callback to create an object for * @return The relevant CallbackObject */ protected CallbackObjectSpecific getSpecificCallbackObject(final Parser parser, final Class type) { return new CallbackObjectSpecific(parser, this, type.asSubclass(CallbackInterface.class), implementationMap); } /** * Add new callback type. * * @param callback CallbackObject subclass for the callback. * @return if adding succeeded or not. */ public boolean addCallbackType(final CallbackObject callback) { if (!callbackHash.containsKey(callback.getType())) { callbackHash.put(callback.getType(), callback); return true; } return false; } /** * Remove a callback type. * * @param callback CallbackObject subclass to remove. * @return if removal succeeded or not. */ public boolean delCallbackType(final CallbackObject callback) { if (callbackHash.containsKey(callback.getType())) { callbackHash.remove(callback.getType()); return true; } return false; } /** * Get reference to callback object. * * @param callback Name of type of callback object. * @return CallbackObject returns the callback object for this type */ public CallbackObject getCallbackType(final Class callback) { if (!callbackHash.containsKey(callback)) { throw new CallbackNotFoundException("Callback not found: " + callback.getName() + "\n\nMy class: " + getClass().getName() + "\nContents: " + callbackHash.keySet() + "\nThread: " + Thread.currentThread().getName()); } return callbackHash.get(callback); } /** * Remove all callbacks associated with a specific object. * * @param o instance of ICallbackInterface to remove. */ public void delAllCallback(final CallbackInterface o) { for (CallbackObject cb : callbackHash.values()) { if (cb != null && cb.getType().isInstance(o)) { cb.del(o); } } } /** * Add all callbacks that this object implements. * * @param o instance of ICallbackInterface to add. */ public void addAllCallback(final CallbackInterface o) { for (CallbackObject cb : callbackHash.values()) { if (cb != null && cb.getType().isInstance(o)) { cb.add(o); } } } /** * Add a callback. * This method will throw a CallbackNotFoundException if the callback does not exist. * * @param The type of callback * @param callback Type of callback object * @param o instance of ICallbackInterface to add. * @throws CallbackNotFoundException If callback is not found. * @throws NullPointerException If 'o' is null */ public void addCallback( final Class callback, final S o) throws CallbackNotFoundException { if (o == null) { throw new NullPointerException("CallbackInterface is null"); } final CallbackObject cb = getCallbackType(callback); if (cb != null) { cb.add(o); } } /** * Add a callback with a specific target. * This method will throw a CallbackNotFoundException if the callback does not exist. * * @param The type of callback * @param callback Type of callback object. * @param o instance of ICallbackInterface to add. * @param target Parameter to specify that a callback should only fire for specific things * @throws CallbackNotFoundException If callback is not found. * @throws NullPointerException If 'o' is null */ public void addCallback( final Class callback, final S o, final String target) throws CallbackNotFoundException { if (o == null) { throw new NullPointerException("CallbackInterface is null"); } ((CallbackObjectSpecific) getCallbackType(callback)).add(o, target); } /** * Add a callback without an exception. * This should be used if a callback is not essential for execution (ie the DebugOut callback) * * @param The type of callback object * @param callback Type of callback object. * @param o instance of ICallbackInterface to add. * @return true/false if the callback was added or not. */ public boolean addNonCriticalCallback( final Class callback, final S o) { try { addCallback(callback, o); return true; } catch (CallbackNotFoundException e) { return false; } } /** * Add a callback with a specific target. * This should be used if a callback is not essential for execution * * @param The type of callback * @param callback Type of callback object. * @param o instance of ICallbackInterface to add. * @param target Parameter to specify that a callback should only fire for specific things * @return true/false if the callback was added or not. */ public boolean addNonCriticalCallback( final Class callback, final S o, final String target) { try { addCallback(callback, o, target); return true; } catch (CallbackNotFoundException e) { return false; } } /** * Remove a callback. * * @param callback Type of callback object. * @param o instance of ICallbackInterface to remove. */ public void delCallback(final Class callback, final CallbackInterface o) { getCallbackType(callback).del(o); } /** * Gets a proxy object which can be used to despatch callbacks of the * specified type. Callers may pass null for the first two * arguments of any callback, and these will automatically be replaced * by the relevant Parser instance and the current date. * * @param The type of the callback to retrieve * @param callback The callback to retrieve * @return A proxy object which can be used to call the specified callback */ @SuppressWarnings("unchecked") public T getCallback(final Class callback) { return (T) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{ callback }, new CallbackHandler(callback)); } /** * A Proxy invocation handler for a specified parser callback. */ private class CallbackHandler implements InvocationHandler { /** The callback that should be called. */ private final Class callback; /** * Creates a new callback handler for the specified callback. * * @param callback The callback to handle */ public CallbackHandler(final Class callback) { this.callback = callback; } /** {@inheritDoc} */ @Override public Object invoke(final Object proxy, final Method method, final Object[] args) { final Object[] modifiedArgs = new Object[args.length - 2]; System.arraycopy(args, 2, modifiedArgs, 0, args.length - 2); if (args[1] == null) { getCallbackType(callback).call(modifiedArgs); } else { getCallbackType(callback).call((Date) args[1], modifiedArgs); } return null; } } }