🐛 fix race-condition

This commit is contained in:
lia
2025-08-08 21:19:44 +02:00
parent b9cf30150f
commit 4381f704fb
8 changed files with 183 additions and 175 deletions

View File

@@ -7,11 +7,11 @@ plugins {
id("java-library")
id("maven-publish")
id("me.champeau.jmh") version "0.7.2"
id("me.champeau.jmh") version "0.7.3"
}
group = "cc.lunary"
version = "1.1.1-release"
version = "1.2.0-release"
repositories {
mavenLocal()
@@ -62,12 +62,12 @@ dependencies {
java {
withSourcesJar()
withJavadocJar()
toolchain.languageVersion = JavaLanguageVersion.of(JavaVersion.VERSION_17.toString())
toolchain.languageVersion = JavaLanguageVersion.of(JavaVersion.VERSION_21.toString())
}
tasks.withType<JavaCompile> {
sourceCompatibility = JavaVersion.VERSION_17.toString()
targetCompatibility = JavaVersion.VERSION_17.toString()
sourceCompatibility = JavaVersion.VERSION_21.toString()
targetCompatibility = JavaVersion.VERSION_21.toString()
options.encoding = StandardCharsets.UTF_8.toString()
}
@@ -94,7 +94,7 @@ publishing {
pom {
name.set("Lilith")
description.set("A blazingly fast, easy-to-use Java-17 event system.")
description.set("A blazingly fast, easy-to-use Java-21 event system.")
url.set("https://github.com/lunarydess/Library-Lilith-JVM")
packaging = "jar"
@@ -110,11 +110,11 @@ publishing {
name.set("Lucielle R. H.")
}
}
issueManagement { url = "https://github.com/lunarydess/Library-TinyEvents/issues" }
issueManagement { url = "https://git.celesteflare.cc/i0uring/lib_tinyevents/issues" }
scm {
connection = "scm:git:git://github.com/lunarydess/Library-TinyEvents.git"
developerConnection = "scm:git:ssh://github.com/lunarydess/Library-TinyEvents.git"
url = "github.com/lunarydess/Library-TinyEvents"
connection = "scm:git:git://git.celesteflare.cc/i0uring/lib_tinyevents.git"
developerConnection = "scm:git:ssh://git.celesteflare.cc/i0uring/lib_tinyevents.git"
url = "git.celesteflare.cc/i0uring/lib_tinyevents"
}
}
}

View File

@@ -1,3 +1,4 @@
org.gradle.java.home=/usr/lib/jvm/openjdk21
# gradle tweaks
org.gradle.daemon = true
org.gradle.configureondemand = true

View File

@@ -1,6 +1,6 @@
/**
* This file is part of <a href="https://github.com/lunarydess/Library-TinyEvents">TinyEvents</a>
* Copyright (C) 2024 lunarydess (inbox@luzey.zip)
* This file is part of <a href="https://git.celesteflare.cc/i0uring/lib_tinyevents">TinyEvents</a>
* Copyright (C) 2024 lucielle (inbox@celesteflare.cc)
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cc.lunary.tinyevents;
package io.lucielle.tinyevents;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
@@ -47,7 +47,7 @@ public class BenchmarkCaller implements BenchmarkListener {
@Fork(value = 1, warmups = 1)
public void callBenchmarkListener(Blackhole blackhole) {
for (int i = 0; i < ITERATIONS; i++) {
EVENTS.call(new BenchmarkListener.BenchmarkEvent(blackhole));
EVENTS.call0(new BenchmarkListener.BenchmarkEvent(blackhole));
}
}

View File

@@ -1,6 +1,6 @@
/**
* This file is part of <a href="https://github.com/lunarydess/Library-TinyEvents">TinyEvents</a>
* Copyright (C) 2024 lunarydess (inbox@luzey.zip)
* This file is part of <a href="https://git.celesteflare.cc/i0uring/lib_tinyevents">TinyEvents</a>
* Copyright (C) 2024 lucielle (inbox@celesteflare.cc)
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,10 +14,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cc.lunary.tinyevents;
package io.lucielle.tinyevents;
import cc.lunary.tinyevents.BenchmarkListener.BenchmarkEvent;
import cc.lunary.tinyevents.EventHandlers.IHandler;
import io.lucielle.tinyevents.BenchmarkListener.BenchmarkEvent;
import io.lucielle.tinyevents.EventHandlers.IHandler;
import org.openjdk.jmh.infra.Blackhole;
import java.util.Objects;
@@ -63,8 +63,8 @@ public interface BenchmarkListener extends IHandler<BenchmarkEvent> {
public @Override String toString() {
return "BenchmarkEvent{" +
"blackhole=" + blackhole +
'}';
"blackhole=" + blackhole +
'}';
}
}
}

View File

@@ -1,6 +1,6 @@
/**
* This file is part of <a href="https://github.com/lunarydess/Library-TinyEvents">TinyEvents</a>
* Copyright (C) 2024 lunarydess (inbox@luzey.zip)
* This file is part of <a href="https://git.celesteflare.cc/i0uring/lib_tinyevents">TinyEvents</a>
* Copyright (C) 2024 lucielle (inbox@celesteflare.cc)
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cc.lunary.tinyevents;
package io.lucielle.tinyevents;
/**
* The abstraction layer for all events.

View File

@@ -1,6 +1,6 @@
/**
* This file is part of <a href="https://github.com/lunarydess/Library-TinyEvents">TinyEvents</a>
* Copyright (C) 2024 lunarydess (inbox@luzey.zip)
* This file is part of <a href="https://git.celesteflare.cc/i0uring/lib_tinyevents">TinyEvents</a>
* Copyright (C) 2024 lucielle (inbox@celesteflare.cc)
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cc.lunary.tinyevents;
package io.lucielle.tinyevents;
import org.jetbrains.annotations.NotNull;
@@ -29,14 +29,16 @@ import java.util.function.Consumer;
@SuppressWarnings("unused")
public final class EventHandlers {
/**
* This is an abstraction layer for a consumer-handlers to implement our own handle-logic for events.
* This is an abstraction layer for a consumer-handlers to implement our own
* handle-logic for events.
*
* @param <E> The event-type of our handler.
*/
@FunctionalInterface
public interface IHandler<E extends AbstractEvent> extends Consumer<E>, Comparable<IHandler<E>> {
/**
* Handles our incoming events when {@link TinyEvents#call(AbstractEvent)} gets called.
* Handles our incoming events when {@link TinyEvents#call(AbstractEvent)} gets
* called.
*
* @param event The event we want to implement the logic for.
*/
@@ -63,13 +65,15 @@ public final class EventHandlers {
}
/**
* This is an abstraction layer for class-handlers to implement our own handle-logic for events.
* This is an abstraction layer for class-handlers to implement our own
* handle-logic for events.
*
* @param <E> The event-type of our handler.
*/
public abstract static class AbstractHandler<E extends AbstractEvent> implements IHandler<E> {
/**
* Handles our incoming events when {@link TinyEvents#call(AbstractEvent)} gets called.
* Handles our incoming events when {@link TinyEvents#call(AbstractEvent)} gets
* called.
*
* @param event The event we want to implement the logic for.
*/

View File

@@ -1,6 +1,6 @@
/**
* This file is part of <a href="https://github.com/lunarydess/Library-TinyEvents">TinyEvents</a>
* Copyright (C) 2024 lunarydess (inbox@luzey.zip)
* This file is part of <a href="https://git.celesteflare.cc/i0uring/lib_tinyevents">TinyEvents</a>
* Copyright (C) 2024 lucielle (inbox@celesteflare.cc)
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cc.lunary.tinyevents;
package io.lucielle.tinyevents;
import cc.lunary.tinyevents.EventHandlers.IHandler;
import io.lucielle.tinyevents.EventHandlers.IHandler;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -39,37 +40,37 @@ public final class TinyEvents {
private final Map<Class<? extends AbstractEvent>, IHandler<? extends AbstractEvent>[]> handlers;
private final Object2IntMap<IHandler<? extends AbstractEvent>> handlersIndices = new Object2IntMap<>();
final transient Object lock = new Object();
/**
* Creates a new event-manager with a default {@link IdentityHashMap map} and {@link TinyEvents#DEFAULT_ON_ERROR error-handler}.
* Creates a new event-manager with a default {@link IdentityHashMap map} and
* {@link TinyEvents#DEFAULT_ON_ERROR error-handler}.
*/
public TinyEvents() {
this(IdentityHashMap::new, DEFAULT_ON_ERROR);
}
/**
* Creates a new event-manager with a custom {@link Supplier<Map> map} and default {@link TinyEvents#DEFAULT_ON_ERROR error-handler}.
* Creates a new event-manager with a custom {@link Supplier<Map> map} and
* default {@link TinyEvents#DEFAULT_ON_ERROR error-handler}.
*
* @param factory The custom map we want to provide.
*/
public TinyEvents(
final Supplier<Map<Class<? extends AbstractEvent>,
IHandler<? extends AbstractEvent>[]
>> factory
) {
final Supplier<Map<Class<? extends AbstractEvent>, IHandler<? extends AbstractEvent>[]>> factory) {
this(factory, DEFAULT_ON_ERROR);
}
/**
* Creates a new event-manager with a custom {@link Supplier<Map> map} and {@link Consumer<Throwable> error-handler}.
* Creates a new event-manager with a custom {@link Supplier<Map> map} and
* {@link Consumer<Throwable> error-handler}.
*
* @param factory The custom map we want to provide.
* @param onError The custom error-handler we want to provide.
*/
public TinyEvents(
final Supplier<Map<Class<? extends AbstractEvent>, IHandler<? extends AbstractEvent>[]>> factory,
final Consumer<Throwable> onError
) {
final Supplier<Map<Class<? extends AbstractEvent>, IHandler<? extends AbstractEvent>[]>> factory,
final Consumer<Throwable> onError) {
this.handlers = factory.get();
this.onError = onError;
}
@@ -81,20 +82,19 @@ public final class TinyEvents {
* @param <E> The type of the event for our handler.
*/
public <H extends EventHandlers.IHandler<E>, E extends AbstractEvent> void register(
final Class<E> clazz,
final H handler
) {
try {
final Class<E> clazz,
final H handler) {
synchronized (this.lock) {
final IHandler<? extends AbstractEvent>[] current = this.handlers.getOrDefault(clazz, new IHandler<?>[0]);
final IHandler<? extends AbstractEvent>[] updated = Arrays.copyOf(current, current.length + 1);
updated[updated.length - 1] = handler;
final Comparator<IHandler<? extends AbstractEvent>> sort = Comparator.comparingInt(wrapper1 -> handler.priority());
final Comparator<IHandler<? extends AbstractEvent>> sort = Comparator
.comparingInt(wrapper1 -> handler.priority());
Arrays.sort(updated, sort);
this.handlers.put(clazz, updated);
this.handlersIndices.put(handler, Arrays.binarySearch(updated, handler, sort));
} catch (final Throwable throwable) {
onError.accept(throwable);
}
}
@@ -105,32 +105,30 @@ public final class TinyEvents {
* @param <E> The type of the event for our handler.
*/
public <H extends EventHandlers.IHandler<E>, E extends AbstractEvent> void unregister(
final Class<E> clazz,
final H handler
) {
final IHandler<? extends AbstractEvent>[] current = this.handlers.getOrDefault(clazz, new IHandler<?>[0]);
final Class<E> clazz,
final H handler) {
synchronized (this.lock) {
final IHandler<? extends AbstractEvent>[] current = this.handlers.getOrDefault(clazz, new IHandler<?>[0]);
if (current.length == 0) {
this.handlers.remove(clazz);
return;
if (current.length == 0) {
this.handlers.remove(clazz);
return;
}
int index = this.handlersIndices.get(handler);
if (index < 0 || index > current.length - 1) {
return;
}
final IHandler<? extends AbstractEvent>[] updated = new IHandler<?>[current.length - 1];
if (updated.length > 0) {
System.arraycopy(current, 0, updated, 0, index);
System.arraycopy(current, index + 1, updated, index, current.length - index - 1);
this.handlers.put(clazz, updated);
} else
this.handlers.remove(clazz);
this.handlersIndices.remove(handler);
}
int index = this.handlersIndices.get(handler);
if (index < 0 || index > current.length - 1) {
this.onError.accept(new NoSuchFieldError(String.format(
"The handler %s doesn't exist.",
handler.toString()
)));
return;
}
final IHandler<? extends AbstractEvent>[] updated = new IHandler<?>[current.length - 1];
if (updated.length > 0) {
System.arraycopy(current, 0, updated, 0, index);
System.arraycopy(current, index + 1, updated, index, current.length - index - 1);
this.handlers.put(clazz, updated);
} else this.handlers.remove(clazz);
this.handlersIndices.remove(handler);
}
/**
@@ -140,7 +138,16 @@ public final class TinyEvents {
@SuppressWarnings("unchecked")
public <E extends AbstractEvent> void call(final E event) {
final IHandler<E>[] handlers = (IHandler<E>[]) this.handlers.get(event.getClass());
if (handlers == null) return;
if (handlers == null)
return;
if (this.onError == null) {
for (final IHandler<E> handler : handlers)
handler.accept(event);
return;
}
for (final IHandler<E> handler : handlers) {
try {
handler.accept(event);
@@ -150,6 +157,21 @@ public final class TinyEvents {
}
}
/**
* @param event The event we want to call.
* @param <E> The type of our event.
*/
@SuppressWarnings("unchecked")
public <E extends AbstractEvent> void call0(final E event) {
final IHandler<E>[] handlers = (IHandler<E>[]) this.handlers.get(event.getClass());
if (handlers == null)
return;
for (final IHandler<E> handler : handlers)
handler.accept(event);
}
/**
* @return the internal error-handler
* @see TinyEvents#call(AbstractEvent)
@@ -186,7 +208,6 @@ public final class TinyEvents {
return this.handlersIndices;
}
/**
* @param <K> The object-type we want to use.
*/
@@ -199,7 +220,8 @@ public final class TinyEvents {
private int size;
/**
* Creates a {@link Object2IntMap<K> map} with a {@link Object2IntMap#INITIAL_CAPACITY initial capacity}.
* Creates a {@link Object2IntMap<K> map} with a
* {@link Object2IntMap#INITIAL_CAPACITY initial capacity}.
*/
public Object2IntMap() {
this(INITIAL_CAPACITY);
@@ -220,14 +242,14 @@ public final class TinyEvents {
* @param value The int-value we want to add.
*/
public void put(
final K key,
final int value
) {
final K key,
final int value) {
if (size >= table.length * LOAD_FACTOR) {
int newCapacity = table.length * 2;
LinkedList<Entry<K>>[] newTable = new LinkedList[newCapacity];
for (LinkedList<Entry<K>> entries : table) {
if (entries == null) continue;
if (entries == null)
continue;
for (Entry<K> entry : entries) {
int index = hash(entry.key) & (newCapacity - 1);
(newTable[index] == null ? (newTable[index] = new LinkedList<>()) : newTable[index]).add(entry);
@@ -259,10 +281,12 @@ public final class TinyEvents {
*/
public int get(K key) {
int index = hash(key) & (table.length - 1);
if (table[index] == null) return -1;
if (table[index] == null)
return -1;
for (Entry<K> entry : table[index]) {
if (!Objects.equals(entry.key, key)) continue;
if (!Objects.equals(entry.key, key))
continue;
return entry.value;
}
@@ -306,9 +330,8 @@ public final class TinyEvents {
* @param value The value of our entry.
*/
Entry(
final K key,
final int value
) {
final K key,
final int value) {
this.key = key;
this.value = value;
}

View File

@@ -1,6 +1,6 @@
/**
* This file is part of <a href="https://github.com/lunarydess/Library-TinyEvents">TinyEvents</a>
* Copyright (C) 2024 lunarydess (inbox@luzey.zip)
* This file is part of <a href="https://git.celesteflare.cc/i0uring/lib_tinyevents">TinyEvents</a>
* Copyright (C) 2024 lucielle (inbox@celesteflare.cc)
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,13 +14,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cc.lunary.tinyevents;
package io.lucielle.tinyevents;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import cc.lunary.tinyevents.AbstractEvent.Cancellable;
import cc.lunary.tinyevents.EventHandlers.IHandler;
import io.lucielle.tinyevents.AbstractEvent.Cancellable;
import io.lucielle.tinyevents.EventHandlers.IHandler;
import java.util.IdentityHashMap;
import java.util.Objects;
@@ -63,7 +63,8 @@ class TestTinyEvents {
assertEquals(2, events.getHandlers().size());
IHandler<DummyEvent3> handler3 = event -> {
if (event.cancelled()) return;
if (event.cancelled())
return;
String string1 = event.getString1(), string2 = event.getString2();
event.setString1(string2);
@@ -74,7 +75,8 @@ class TestTinyEvents {
assertEquals(3, events.getHandlers().size());
IHandler<DummyEvent4> handler4 = event -> {
if (event.cancelled()) return;
if (event.cancelled())
return;
int num1 = event.getNum1(), num2 = event.getNum2();
event.setNum1(num2);
@@ -85,26 +87,21 @@ class TestTinyEvents {
assertEquals(4, events.getHandlers().size());
DummyEvent1 event1 = new DummyEvent1(
"waow",
"hellow there"
);
"waow",
"hellow there");
assertEquals(
"waow",
event1.getString1()
);
"waow",
event1.getString1());
assertEquals(
"hellow there",
event1.getString2()
);
"hellow there",
event1.getString2());
handler1.accept(event1);
assertEquals(
"how are y'all doing",
event1.getString1()
);
"how are y'all doing",
event1.getString1());
assertEquals(
"hope y'all keep going :)",
event1.getString2()
);
"hope y'all keep going :)",
event1.getString2());
DummyEvent2 event2 = new DummyEvent2(1337, 9090);
assertEquals(1337, event2.getNum1());
@@ -114,37 +111,30 @@ class TestTinyEvents {
assertEquals(1337, event2.getNum2());
DummyEvent3 event3 = new DummyEvent3(
"i'm doing fine curr",
"just wishing it stays like this ;w;"
);
"i'm doing fine curr",
"just wishing it stays like this ;w;");
assertEquals(
"i'm doing fine curr",
event3.getString1()
);
"i'm doing fine curr",
event3.getString1());
assertEquals(
"just wishing it stays like this ;w;",
event3.getString2()
);
"just wishing it stays like this ;w;",
event3.getString2());
assertFalse(event3.cancelled());
handler3.accept(event3);
assertEquals(
"just wishing it stays like this ;w;",
event3.getString1()
);
"just wishing it stays like this ;w;",
event3.getString1());
assertEquals(
"i'm doing fine curr",
event3.getString2()
);
"i'm doing fine curr",
event3.getString2());
assertTrue(event3.cancelled());
handler3.accept(event3);
assertEquals(
"just wishing it stays like this ;w;",
event3.getString1()
);
"just wishing it stays like this ;w;",
event3.getString1());
assertEquals(
"i'm doing fine curr",
event3.getString2()
);
"i'm doing fine curr",
event3.getString2());
DummyEvent4 event4 = new DummyEvent4(9090, 1337);
assertEquals(9090, event4.getNum1());
@@ -201,9 +191,8 @@ class TestTinyEvents {
public @Override int hashCode() {
return Objects.hash(
this.string1,
this.string2
);
this.string1,
this.string2);
}
public @Override boolean equals(Object object) {
@@ -211,17 +200,16 @@ class TestTinyEvents {
}
public <E extends DummyEvent1> boolean equals(E event) {
return Objects.equals(this.hashCode(), event.hashCode()) || (
(Objects.equals(this.getString1(), event.getString1())) &&
(Objects.equals(this.getString2(), event.getString2()))
);
return Objects.equals(this.hashCode(), event.hashCode())
|| ((Objects.equals(this.getString1(), event.getString1())) &&
(Objects.equals(this.getString2(), event.getString2())));
}
public @Override String toString() {
return new StringJoiner(", ", DummyEvent1.class.getSimpleName() + "[", "]")
.add("string1='" + this.string1 + "'")
.add("string2='" + this.string2 + "'")
.toString();
.add("string1='" + this.string1 + "'")
.add("string2='" + this.string2 + "'")
.toString();
}
}
@@ -251,9 +239,8 @@ class TestTinyEvents {
public @Override int hashCode() {
return Objects.hash(
this.num1,
this.num2
);
this.num1,
this.num2);
}
public @Override boolean equals(Object object) {
@@ -261,17 +248,15 @@ class TestTinyEvents {
}
public <E extends DummyEvent2> boolean equals(E event) {
return Objects.equals(this.hashCode(), event.hashCode()) || (
(Objects.equals(this.getNum1(), event.getNum1())) &&
(Objects.equals(this.getNum2(), event.getNum2()))
);
return Objects.equals(this.hashCode(), event.hashCode()) || ((Objects.equals(this.getNum1(), event.getNum1())) &&
(Objects.equals(this.getNum2(), event.getNum2())));
}
public @Override String toString() {
return new StringJoiner(", ", DummyEvent2.class.getSimpleName() + "[", "]")
.add("num1='" + this.num1 + "'")
.add("num2='" + this.num2 + "'")
.toString();
.add("num1='" + this.num1 + "'")
.add("num2='" + this.num2 + "'")
.toString();
}
}
@@ -302,10 +287,9 @@ class TestTinyEvents {
public @Override int hashCode() {
return Objects.hash(
this.string1,
this.string2,
this.cancelled
);
this.string1,
this.string2,
this.cancelled);
}
public @Override boolean equals(Object object) {
@@ -313,18 +297,17 @@ class TestTinyEvents {
}
public <E extends DummyEvent3> boolean equals(E event) {
return Objects.equals(this.hashCode(), event.hashCode()) || (
(Objects.equals(this.getString1(), event.getString1())) &&
(Objects.equals(this.getString2(), event.getString2()))
);
return Objects.equals(this.hashCode(), event.hashCode())
|| ((Objects.equals(this.getString1(), event.getString1())) &&
(Objects.equals(this.getString2(), event.getString2())));
}
public @Override String toString() {
return new StringJoiner(", ", DummyEvent3.class.getSimpleName() + "[", "]")
.add("string1='" + this.string1 + "'")
.add("string2='" + this.string2 + "'")
.add("cancelled='" + this.cancelled + "'")
.toString();
.add("string1='" + this.string1 + "'")
.add("string2='" + this.string2 + "'")
.add("cancelled='" + this.cancelled + "'")
.toString();
}
@Override
@@ -365,10 +348,9 @@ class TestTinyEvents {
public @Override int hashCode() {
return Objects.hash(
this.num1,
this.num2,
this.cancelled
);
this.num1,
this.num2,
this.cancelled);
}
public @Override boolean equals(Object object) {
@@ -376,18 +358,16 @@ class TestTinyEvents {
}
public <E extends DummyEvent4> boolean equals(E event) {
return Objects.equals(this.hashCode(), event.hashCode()) || (
(Objects.equals(this.getNum1(), event.getNum1())) &&
(Objects.equals(this.getNum2(), event.getNum2()))
);
return Objects.equals(this.hashCode(), event.hashCode()) || ((Objects.equals(this.getNum1(), event.getNum1())) &&
(Objects.equals(this.getNum2(), event.getNum2())));
}
public @Override String toString() {
return new StringJoiner(", ", DummyEvent4.class.getSimpleName() + "[", "]")
.add("num1='" + this.num1 + "'")
.add("num2='" + this.num2 + "'")
.add("cancelled='" + this.cancelled + "'")
.toString();
.add("num1='" + this.num1 + "'")
.add("num2='" + this.num2 + "'")
.add("cancelled='" + this.cancelled + "'")
.toString();
}
@Override