4 * Copyright 2010 Codist Monk.
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 package net
.sourceforge
.aprog
.events
;
27 import static org
.junit
.Assert
.*;
29 import java
.lang
.reflect
.Array
;
30 import java
.lang
.reflect
.InvocationHandler
;
31 import java
.lang
.reflect
.Method
;
32 import java
.lang
.reflect
.Proxy
;
33 import java
.util
.ArrayList
;
34 import java
.util
.Collections
;
35 import java
.util
.List
;
36 import net
.sourceforge
.aprog
.events
.Observable
.Event
;
37 import net
.sourceforge
.aprog
.tools
.Tools
;
38 import org
.junit
.Test
;
42 * @author codistmonk (creation 2010-06-23)
44 public final class ObservableTest
{
47 public final <R
extends EventRecorder
& DummyObservable
.Listener
> void testFireEvent() {
48 final DummyObservable observable
= new DummyObservable();
49 @SuppressWarnings("unchecked")
50 final R recorder1
= (R
) newEventRecorder(DummyObservable
.Listener
.class);
51 @SuppressWarnings("unchecked")
52 final R recorder2
= (R
) newEventRecorder(DummyObservable
.Listener
.class);
54 observable
.addListener(recorder1
);
55 observable
.addListener(recorder2
);
56 observable
.fireNewEvent();
57 observable
.removeListener(recorder2
);
58 observable
.fireNewEvent();
60 assertTrue(recorder1
.getEvent(0) instanceof DummyObservable
.EventFiredEvent
);
61 assertTrue(recorder1
.getEvent(1) instanceof DummyObservable
.EventFiredEvent
);
62 assertSame(recorder1
.getEvent(0), recorder2
.getEvent(0));
63 assertEquals(2, recorder1
.getEvents().size());
64 assertEquals(1, recorder2
.getEvents().size());
69 * @param <R> the (multi)listener recorder proxy type
70 * @param listenerTypes
76 @SuppressWarnings("unchecked")
77 public static final <R
extends EventRecorder
> R
newEventRecorder(
78 final Class
<?
>... listenerTypes
) {
79 return (R
) Proxy
.newProxyInstance(
80 Tools
.getCallerClass().getClassLoader(),
81 add(listenerTypes
, EventRecorder
.class),
82 new RecorderInvocationHandler());
96 private static final <T
> T
[] add(final T
[] array
, final T
... moreElements
) {
97 @SuppressWarnings("unchecked")
98 final T
[] result
= (T
[]) Array
.newInstance(
99 array
.getClass().getComponentType(), array
.length
+ moreElements
.length
);
101 System
.arraycopy(array
, 0, result
, 0, array
.length
);
102 System
.arraycopy(moreElements
, 0, result
, array
.length
, moreElements
.length
);
109 * @author codistmonk (creation 2010-06-18)
111 public static interface EventRecorder
{
119 public abstract List
<Event
<?
>> getEvents();
123 * @param <E> the expected event type
125 * <br>Range: {@code [0 .. this.getEvents().size() - 1]}
129 * @throws IndexOutOfBoundsException if {@code index} is out of range
131 public abstract <E
extends Event
<?
>> E
getEvent(int index
);
137 * @author codistmonk (creation 2010-06-18)
139 private static class RecorderInvocationHandler
implements InvocationHandler
, EventRecorder
{
141 private final List
<Event
<?
>> events
;
143 RecorderInvocationHandler() {
144 this.events
= new ArrayList
<Event
<?
>>();
148 public final Object
invoke(final Object proxy
, final Method method
, final Object
[] arguments
)
150 if (method
.getDeclaringClass().isAssignableFrom(EventRecorder
.class)) {
151 return method
.invoke(this, arguments
);
154 if (arguments
.length
== 1 && arguments
[0] instanceof Event
<?
>) {
155 this.events
.add((Event
<?
>) arguments
[0]);
162 public final List
<Event
<?
>> getEvents() {
163 return Collections
.unmodifiableList(this.events
);
167 @SuppressWarnings("unchecked")
168 public final <E
extends Event
<?
>> E
getEvent(final int index
) {
169 return (E
) this.getEvents().get(index
);
173 public final boolean equals(final Object object
) {
174 return this == object
||
176 Proxy
.isProxyClass(object
.getClass()) &&
177 this == Proxy
.getInvocationHandler(object
);
181 public final int hashCode() {
182 return super.hashCode();
189 * @author codistmonk (creation 2010-06-23)
191 private static final class DummyObservable
extends AbstractObservable
<DummyObservable
.Listener
> {
193 public final void fireNewEvent() {
194 new EventFiredEvent().fire();
199 * @author codistmonk (creation 2010-06-23)
201 public static interface Listener
{
208 public abstract void eventFired(final EventFiredEvent event
);
214 * @author codistmonk (creation 2010-06-23)
216 public final class EventFiredEvent
extends AbstractEvent
<DummyObservable
, Listener
> {
219 protected final void notifyListener(final Listener listener
) {
220 listener
.eventFired(this);