| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to you 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.apache.pivot.tutorials.explorer.tools; |
| |
| import java.lang.reflect.InvocationHandler; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.lang.reflect.ParameterizedType; |
| import java.lang.reflect.Proxy; |
| import java.lang.reflect.Type; |
| import java.util.Iterator; |
| |
| import org.apache.pivot.collections.Group; |
| import org.apache.pivot.collections.HashMap; |
| import org.apache.pivot.collections.HashSet; |
| import org.apache.pivot.util.ImmutableIterator; |
| import org.apache.pivot.util.ListenerList; |
| import org.apache.pivot.util.Vote; |
| import org.apache.pivot.wtk.Component; |
| import org.apache.pivot.wtk.Container; |
| |
| /** |
| * A component that monitors a source component for events. |
| */ |
| public class EventLogger extends Container { |
| /** |
| * Event logger skin interface. Event logger skins must implement this. |
| */ |
| public interface Skin { |
| /** |
| * Clears the event log. |
| */ |
| public void clearLog(); |
| |
| /** |
| * Select/Deselect all Events to log. |
| * |
| * @param select if true, all events will be selected for the log, |
| * otherwise all events will be deselected |
| */ |
| public void selectAllEvents(boolean select); |
| } |
| |
| /** |
| * A read-only group of events that an event logger is capable of firing. To |
| * make an event logger actually fire declared events, callers add them to |
| * the event logger's include event group. |
| */ |
| public final class DeclaredEventGroup implements Group<Method>, Iterable<Method> { |
| private DeclaredEventGroup() { |
| } |
| |
| @Override |
| public boolean add(Method event) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public boolean remove(Method event) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public boolean contains(Method event) { |
| return declaredEvents.contains(event); |
| } |
| |
| @Override |
| public Iterator<Method> iterator() { |
| return new ImmutableIterator<>(declaredEvents.iterator()); |
| } |
| } |
| |
| /** |
| * A read/write group of events that an event logger will actually fire. |
| * This group is guaranteed to be a subset of the declared event group. |
| */ |
| public final class IncludeEventGroup implements Group<Method>, Iterable<Method> { |
| private IncludeEventGroup() { |
| } |
| |
| @Override |
| public boolean add(Method event) { |
| boolean added = false; |
| |
| if (!declaredEvents.contains(event)) { |
| throw new IllegalArgumentException("Event has not been declared."); |
| } |
| |
| if (!includeEvents.contains(event)) { |
| includeEvents.add(event); |
| eventLoggerListeners.eventIncluded(EventLogger.this, event); |
| added = true; |
| } |
| |
| return added; |
| } |
| |
| @Override |
| public boolean remove(Method event) { |
| boolean removed = false; |
| |
| if (includeEvents.contains(event)) { |
| includeEvents.remove(event); |
| eventLoggerListeners.eventExcluded(EventLogger.this, event); |
| removed = true; |
| } |
| |
| return removed; |
| } |
| |
| @Override |
| public boolean contains(Method event) { |
| return includeEvents.contains(event); |
| } |
| |
| @Override |
| public Iterator<Method> iterator() { |
| return new ImmutableIterator<>(includeEvents.iterator()); |
| } |
| } |
| |
| /** |
| * Event logger invocation handler. |
| */ |
| private class LoggerInvocationHandler implements InvocationHandler { |
| @Override |
| public Object invoke(Object proxy, Method event, Object[] arguments) throws Throwable { |
| if (includeEvents.contains(event)) { |
| eventLoggerListeners.eventFired(EventLogger.this, event, arguments); |
| } |
| |
| Object result = null; |
| Class<?> returnType = event.getReturnType(); |
| if (returnType == Vote.class) { |
| result = Vote.APPROVE; |
| } else if (returnType == Boolean.TYPE) { |
| result = false; |
| } |
| |
| return result; |
| } |
| } |
| |
| private Component source = null; |
| |
| private HashMap<Class<?>, Object> eventListenerProxies = new HashMap<>(); |
| private LoggerInvocationHandler loggerInvocationHandler = new LoggerInvocationHandler(); |
| |
| private HashSet<Method> declaredEvents = new HashSet<>(); |
| private DeclaredEventGroup declaredEventGroup = new DeclaredEventGroup(); |
| |
| private HashSet<Method> includeEvents = new HashSet<>(); |
| private IncludeEventGroup includeEventGroup = new IncludeEventGroup(); |
| |
| private EventLoggerListener.Listeners eventLoggerListeners = new EventLoggerListener.Listeners(); |
| |
| /** |
| * Creates a new event logger that is not tied to any source component. |
| */ |
| public EventLogger() { |
| this(null); |
| } |
| |
| /** |
| * Creates a new event logger that will log events on the specified source. |
| */ |
| public EventLogger(Component source) { |
| setSource(source); |
| setSkin(new EventLoggerSkin()); |
| } |
| |
| /** |
| * Gets this event logger's source component. |
| * |
| * @return The source component, or <tt>null</tt> if no source has been set. |
| */ |
| public Component getSource() { |
| return source; |
| } |
| |
| /** |
| * Sets this event logger's source component. |
| * |
| * @param source The source component, or <tt>null</tt> to clear the source. |
| */ |
| public void setSource(Component source) { |
| Component previousSource = this.source; |
| |
| if (source != previousSource) { |
| if (previousSource != null) { |
| unregisterEventListeners(); |
| } |
| |
| this.source = source; |
| |
| declaredEvents.clear(); |
| includeEvents.clear(); |
| |
| if (source != null) { |
| registerEventListeners(); |
| } |
| |
| eventLoggerListeners.sourceChanged(this, previousSource); |
| } |
| } |
| |
| /** |
| * Gets the declared event group, a read-only group that includes the |
| * complete list of events that this event logger's source declares. |
| * |
| * @return the declared events group. |
| */ |
| public DeclaredEventGroup getDeclaredEvents() { |
| return declaredEventGroup; |
| } |
| |
| /** |
| * Gets the include events group, which callers can use to include or |
| * exclude declared events from those that get fired by this logger. This |
| * group is guaranteed to be a subset of the declared event group (attempts |
| * to add events to this group that are not included in the declared event |
| * group will fail). |
| * |
| * @return The include events group. |
| */ |
| public IncludeEventGroup getIncludeEvents() { |
| return includeEventGroup; |
| } |
| |
| /** |
| * Clears the event log. |
| */ |
| public void clearLog() { |
| EventLogger.Skin eventLoggerSkin = (EventLogger.Skin) getSkin(); |
| eventLoggerSkin.clearLog(); |
| } |
| |
| /** |
| * Select/Deselect all Events to log. |
| * |
| * @param select if true, all events will be selected for the log, otherwise |
| * all events will be deselected |
| */ |
| public void selectAllEvents(boolean select) { |
| // Include or exclude each possible method from the group of monitored |
| // events |
| IncludeEventGroup includeEventsLocal = getIncludeEvents(); |
| for (Method event : declaredEvents) { |
| if (select) { |
| includeEventsLocal.add(event); |
| } else { |
| includeEventsLocal.remove(event); |
| } |
| } |
| // Update the skin (Checkboxes) |
| EventLogger.Skin eventLoggerSkin = (EventLogger.Skin) getSkin(); |
| eventLoggerSkin.selectAllEvents(select); |
| } |
| |
| /** |
| * Registers event listeners on this event logger's source. |
| */ |
| private void registerEventListeners() { |
| Method[] methods = source.getClass().getMethods(); |
| |
| for (int i = 0; i < methods.length; i++) { |
| Method method = methods[i]; |
| |
| if (ListenerList.class.isAssignableFrom(method.getReturnType()) |
| && (method.getModifiers() & Modifier.STATIC) == 0) { |
| ParameterizedType genericType = (ParameterizedType) method.getGenericReturnType(); |
| Type[] typeArguments = genericType.getActualTypeArguments(); |
| |
| if (typeArguments.length == 1) { |
| Type type = typeArguments[0]; |
| Class<?> listenerInterface; |
| if (type instanceof ParameterizedType) { |
| ParameterizedType paramType = (ParameterizedType) type; |
| listenerInterface = (Class<?>) paramType.getRawType(); |
| } else { |
| listenerInterface = (Class<?>) type; |
| } |
| |
| if (!listenerInterface.isInterface()) { |
| throw new RuntimeException(listenerInterface.getName() |
| + " is not an interface."); |
| } |
| |
| Method[] interfaceMethods = listenerInterface.getMethods(); |
| for (int j = 0; j < interfaceMethods.length; j++) { |
| Method interfaceMethod = interfaceMethods[j]; |
| declaredEvents.add(interfaceMethod); |
| } |
| |
| // Get the listener list |
| Object listenerList; |
| try { |
| listenerList = method.invoke(source); |
| } catch (InvocationTargetException exception) { |
| throw new RuntimeException(exception); |
| } catch (IllegalAccessException exception) { |
| throw new RuntimeException(exception); |
| } |
| |
| // Get the listener for this interface |
| Object listener = eventListenerProxies.get(listenerInterface); |
| if (listener == null) { |
| listener = Proxy.newProxyInstance( |
| Thread.currentThread().getContextClassLoader(), |
| new Class<?>[] {listenerInterface}, loggerInvocationHandler); |
| eventListenerProxies.put(listenerInterface, listener); |
| } |
| |
| // Add the listener |
| Class<?> listenerListClass = listenerList.getClass(); |
| Method addMethod; |
| try { |
| addMethod = listenerListClass.getMethod("add", Object.class); |
| } catch (NoSuchMethodException exception) { |
| throw new RuntimeException(exception); |
| } |
| |
| try { |
| addMethod.invoke(listenerList, listener); |
| } catch (IllegalAccessException exception) { |
| throw new RuntimeException(exception); |
| } catch (InvocationTargetException exception) { |
| throw new RuntimeException(exception); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Unregisters event listeners on this event logger's source. |
| */ |
| private void unregisterEventListeners() { |
| Method[] methods = source.getClass().getMethods(); |
| |
| for (int i = 0; i < methods.length; i++) { |
| Method method = methods[i]; |
| |
| if (ListenerList.class.isAssignableFrom(method.getReturnType()) |
| && (method.getModifiers() & Modifier.STATIC) == 0) { |
| ParameterizedType genericType = (ParameterizedType) method.getGenericReturnType(); |
| Type[] typeArguments = genericType.getActualTypeArguments(); |
| |
| if (typeArguments.length == 1) { |
| Type type = typeArguments[0]; |
| Class<?> listenerInterface; |
| if (type instanceof ParameterizedType) { |
| ParameterizedType paramType = (ParameterizedType) type; |
| listenerInterface = (Class<?>) paramType.getRawType(); |
| } else { |
| listenerInterface = (Class<?>) type; |
| } |
| |
| // Get the listener list |
| Object listenerList; |
| try { |
| listenerList = method.invoke(source); |
| } catch (InvocationTargetException exception) { |
| throw new RuntimeException(exception); |
| } catch (IllegalAccessException exception) { |
| throw new RuntimeException(exception); |
| } |
| |
| // Get the listener for this interface |
| Object listener = eventListenerProxies.get(listenerInterface); |
| |
| // Remove the listener |
| Class<?> listenerListClass = listenerList.getClass(); |
| Method removeMethod; |
| try { |
| removeMethod = listenerListClass.getMethod("remove", Object.class); |
| } catch (NoSuchMethodException exception) { |
| throw new RuntimeException(exception); |
| } |
| |
| try { |
| removeMethod.invoke(listenerList, listener); |
| } catch (IllegalAccessException exception) { |
| throw new RuntimeException(exception); |
| } catch (InvocationTargetException exception) { |
| throw new RuntimeException(exception); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Gets the event logger listener list. |
| */ |
| public ListenerList<EventLoggerListener> getEventLoggerListeners() { |
| return eventLoggerListeners; |
| } |
| } |