summaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-06-20 20:04:02 +0300
committerPaul Buetow <paul@buetow.org>2025-06-20 20:04:02 +0300
commitf0b58321ae53f330da86c392661354b87bd9a412 (patch)
treec0be72f57094d13cf8bcb53567614258a6eb43fa /src/main
parentc3b95267b24d843897b04d6d6a16f62dc8cf1ed2 (diff)
Modernize codebase to use Java 21 features
- Convert VS3Tupel and VSLamportTime to records for immutability - Use switch expressions with pattern matching in VSTimestampTriggeredEvent - Modernize exception handling with pattern matching in VSErrorHandler - Replace anonymous ActionListener with lambda in VSAboutFrame - Use formatted strings instead of concatenation in VSDummyProtocol - Add sealed hierarchy VSEventType for exhaustive pattern matching - Create VSSimulationConfig record for configuration management - Maintain backward compatibility with deprecated methods All 132 unit tests pass successfully with Java 21 features. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
Diffstat (limited to 'src/main')
-rw-r--r--src/main/java/core/VSSimulationConfig.java106
-rw-r--r--src/main/java/core/time/VSLamportTime.java60
-rw-r--r--src/main/java/events/VSEventType.java71
-rw-r--r--src/main/java/events/implementations/VSTimestampTriggeredEvent.java42
-rw-r--r--src/main/java/exceptions/VSErrorHandler.java45
-rw-r--r--src/main/java/protocols/implementations/VSDummyProtocol.java4
-rw-r--r--src/main/java/utils/VS3Tupel.java69
-rw-r--r--src/main/java/utils/VSAboutFrame.java10
8 files changed, 280 insertions, 127 deletions
diff --git a/src/main/java/core/VSSimulationConfig.java b/src/main/java/core/VSSimulationConfig.java
new file mode 100644
index 0000000..a3b94d9
--- /dev/null
+++ b/src/main/java/core/VSSimulationConfig.java
@@ -0,0 +1,106 @@
+package core;
+
+/**
+ * Configuration record for simulation parameters using Java 21 records.
+ * This immutable configuration object encapsulates all simulation settings.
+ *
+ * <p>Example usage:</p>
+ * <pre>{@code
+ * var config = VSSimulationConfig.defaultConfig();
+ * var customConfig = new VSSimulationConfig(
+ * 5, // processes
+ * 100, // timestep
+ * 10000, // duration
+ * true, // logging enabled
+ * 0.1f // clock variance
+ * );
+ * }</pre>
+ *
+ * @param numProcesses number of processes in the simulation (1-6)
+ * @param timestepMs simulation timestep in milliseconds
+ * @param maxDurationMs maximum simulation duration in milliseconds
+ * @param loggingEnabled whether logging is enabled
+ * @param defaultClockVariance default clock variance for processes
+ *
+ * @author Paul C. Buetow
+ */
+public record VSSimulationConfig(
+ int numProcesses,
+ long timestepMs,
+ long maxDurationMs,
+ boolean loggingEnabled,
+ float defaultClockVariance
+) {
+
+ /**
+ * Validates the configuration parameters.
+ * Called automatically by the record's canonical constructor.
+ */
+ public VSSimulationConfig {
+ if (numProcesses < constants.VSConstants.MIN_PROCESSES ||
+ numProcesses > constants.VSConstants.MAX_PROCESSES) {
+ throw new IllegalArgumentException(
+ "Number of processes must be between %d and %d"
+ .formatted(constants.VSConstants.MIN_PROCESSES,
+ constants.VSConstants.MAX_PROCESSES)
+ );
+ }
+ if (timestepMs <= 0) {
+ throw new IllegalArgumentException("Timestep must be positive");
+ }
+ if (maxDurationMs <= 0) {
+ throw new IllegalArgumentException("Max duration must be positive");
+ }
+ if (defaultClockVariance < -1.0f || defaultClockVariance > 1.0f) {
+ throw new IllegalArgumentException("Clock variance must be between -1.0 and 1.0");
+ }
+ }
+
+ /**
+ * Creates a default configuration suitable for most simulations.
+ *
+ * @return default simulation configuration
+ */
+ public static VSSimulationConfig defaultConfig() {
+ return new VSSimulationConfig(
+ constants.VSConstants.DEFAULT_PROCESSES,
+ 100, // 100ms timestep
+ 60000, // 60 second max duration
+ true, // logging enabled
+ 0.0f // no clock variance
+ );
+ }
+
+ /**
+ * Creates a new configuration with a different number of processes.
+ *
+ * @param newNumProcesses the new number of processes
+ * @return new configuration with updated process count
+ */
+ public VSSimulationConfig withProcesses(int newNumProcesses) {
+ return new VSSimulationConfig(
+ newNumProcesses,
+ timestepMs,
+ maxDurationMs,
+ loggingEnabled,
+ defaultClockVariance
+ );
+ }
+
+ /**
+ * Creates a new configuration with different timing parameters.
+ *
+ * @param newTimestep the new timestep in milliseconds
+ * @param newMaxDuration the new maximum duration in milliseconds
+ * @return new configuration with updated timing
+ */
+ public VSSimulationConfig withTiming(long newTimestep, long newMaxDuration) {
+ return new VSSimulationConfig(
+ numProcesses,
+ newTimestep,
+ newMaxDuration,
+ loggingEnabled,
+ defaultClockVariance
+ );
+ }
+} \ No newline at end of file
diff --git a/src/main/java/core/time/VSLamportTime.java b/src/main/java/core/time/VSLamportTime.java
index be1226b..2973225 100644
--- a/src/main/java/core/time/VSLamportTime.java
+++ b/src/main/java/core/time/VSLamportTime.java
@@ -1,50 +1,54 @@
package core.time;
/**
- * The class VSLamportTime, defines how the lamport timestamps are represented.
+ * Immutable record representing a Lamport timestamp in the distributed system.
+ * Lamport timestamps provide a partial ordering of events in a distributed system.
+ *
+ * <p>Each timestamp contains:</p>
+ * <ul>
+ * <li>Global time - the simulation time when this timestamp was created</li>
+ * <li>Lamport time - the logical clock value following Lamport's algorithm</li>
+ * </ul>
+ *
+ * <p>This record automatically provides {@code equals()}, {@code hashCode()},
+ * and {@code toString()} implementations.</p>
*
+ * @param globalTime the global simulation time when this timestamp was recorded
+ * @param lamportTime the Lamport logical clock value
+ *
* @author Paul C. Buetow
*/
-public class VSLamportTime implements VSTime {
- /** Specified the global time of the lamport timestamp. It's used for
- * correct painting position in the simulator canvas paint area.
- */
- private long globalTime;
-
- /** Specified the process' local lamport time. */
- private long lamportTime;
-
+public record VSLamportTime(long globalTime, long lamportTime) implements VSTime {
+
/**
- * A simple constructor.
+ * Gets the global time (implements VSTime interface).
*
- * @param globalTime The global time.
- * @param lamportTime The local lamport time.
- */
- public VSLamportTime(long globalTime, long lamportTime) {
- this.globalTime = globalTime;
- this.lamportTime = lamportTime;
- }
-
- /* (non-Javadoc)
- * @see core.time.VSTime#getGlobalTime()
+ * @return the global simulation time
*/
+ @Override
public long getGlobalTime() {
return globalTime;
}
-
+
/**
- * Gets the lamport time.
+ * Gets the Lamport time value.
*
- * @return The process' local lamport time
+ * @return the Lamport logical clock value
+ * @deprecated Use {@link #lamportTime()} instead
*/
+ @Deprecated
public long getLamportTime() {
return lamportTime;
}
-
- /* (non-Javadoc)
- * @see core.time.VSTime#toString()
+
+ /**
+ * Returns a string representation of this Lamport timestamp.
+ * Format: "(lamportTime)"
+ *
+ * @return string representation of the Lamport time
*/
+ @Override
public String toString() {
return "(" + lamportTime + ")";
}
-}
+} \ No newline at end of file
diff --git a/src/main/java/events/VSEventType.java b/src/main/java/events/VSEventType.java
new file mode 100644
index 0000000..dc673ed
--- /dev/null
+++ b/src/main/java/events/VSEventType.java
@@ -0,0 +1,71 @@
+package events;
+
+import protocols.VSAbstractProtocol;
+import events.implementations.VSTimestampTriggeredEvent;
+
+/**
+ * Sealed hierarchy representing different types of events in the simulator.
+ * This uses Java 21's sealed classes feature to create a closed type hierarchy
+ * that can be exhaustively matched in switch expressions.
+ *
+ * <p>Example usage with pattern matching:</p>
+ * <pre>{@code
+ * VSEventType eventType = getEventType(event);
+ * String description = switch (eventType) {
+ * case InternalEvent e -> "Internal: " + e.description();
+ * case ProtocolEvent e -> "Protocol: " + e.protocolName();
+ * case TimestampEvent e -> "Timestamp: " + e.condition();
+ * case SystemEvent e -> "System: " + e.eventName();
+ * };
+ * }</pre>
+ *
+ * @since Java 21
+ */
+public sealed interface VSEventType
+ permits VSEventType.InternalEvent,
+ VSEventType.ProtocolEvent,
+ VSEventType.TimestampEvent,
+ VSEventType.SystemEvent {
+
+ /**
+ * Internal events that don't affect timestamps.
+ */
+ record InternalEvent(String description) implements VSEventType {}
+
+ /**
+ * Protocol-related events for distributed algorithms.
+ */
+ record ProtocolEvent(String protocolName, boolean isServer) implements VSEventType {}
+
+ /**
+ * Timestamp-triggered events based on logical time conditions.
+ */
+ record TimestampEvent(String condition, long targetTime) implements VSEventType {}
+
+ /**
+ * System events like process crashes and recoveries.
+ */
+ record SystemEvent(String eventName, int processId) implements VSEventType {}
+
+ /**
+ * Utility method to categorize an event.
+ *
+ * @param event the event to categorize
+ * @return the appropriate event type
+ */
+ static VSEventType categorize(VSAbstractEvent event) {
+ if (event.isInternalEvent()) {
+ return new InternalEvent(event.getShortname());
+ } else if (event instanceof VSAbstractProtocol protocol) {
+ return new ProtocolEvent(protocol.getShortname(), protocol.isServer());
+ } else if (event instanceof VSTimestampTriggeredEvent timestampEvent) {
+ return new TimestampEvent(
+ timestampEvent.getOperator().toString(),
+ timestampEvent.getTargetLamportTime()
+ );
+ } else {
+ return new SystemEvent(event.getShortname(),
+ event.getProcess() != null ? event.getProcess().getProcessNum() : -1);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/events/implementations/VSTimestampTriggeredEvent.java b/src/main/java/events/implementations/VSTimestampTriggeredEvent.java
index c5e5386..278d2f2 100644
--- a/src/main/java/events/implementations/VSTimestampTriggeredEvent.java
+++ b/src/main/java/events/implementations/VSTimestampTriggeredEvent.java
@@ -130,20 +130,13 @@ public abstract class VSTimestampTriggeredEvent extends VSAbstractEvent implemen
protected boolean checkLamportCondition(VSInternalProcess process) {
long currentLamport = process.getLamportTime();
- switch (operator) {
- case EQUAL:
- return currentLamport == targetLamportTime;
- case GREATER_THAN:
- return currentLamport > targetLamportTime;
- case LESS_THAN:
- return currentLamport < targetLamportTime;
- case GREATER_EQUAL:
- return currentLamport >= targetLamportTime;
- case LESS_EQUAL:
- return currentLamport <= targetLamportTime;
- default:
- return false;
- }
+ return switch (operator) {
+ case EQUAL -> currentLamport == targetLamportTime;
+ case GREATER_THAN -> currentLamport > targetLamportTime;
+ case LESS_THAN -> currentLamport < targetLamportTime;
+ case GREATER_EQUAL -> currentLamport >= targetLamportTime;
+ case LESS_EQUAL -> currentLamport <= targetLamportTime;
+ };
}
/**
@@ -156,20 +149,13 @@ public abstract class VSTimestampTriggeredEvent extends VSAbstractEvent implemen
return false;
}
- switch (operator) {
- case EQUAL:
- return vectorTimesEqual(currentVector, targetVectorTime);
- case GREATER_THAN:
- return vectorTimeGreater(currentVector, targetVectorTime, false);
- case LESS_THAN:
- return vectorTimeGreater(targetVectorTime, currentVector, false);
- case GREATER_EQUAL:
- return vectorTimeGreater(currentVector, targetVectorTime, true);
- case LESS_EQUAL:
- return vectorTimeGreater(targetVectorTime, currentVector, true);
- default:
- return false;
- }
+ return switch (operator) {
+ case EQUAL -> vectorTimesEqual(currentVector, targetVectorTime);
+ case GREATER_THAN -> vectorTimeGreater(currentVector, targetVectorTime, false);
+ case LESS_THAN -> vectorTimeGreater(targetVectorTime, currentVector, false);
+ case GREATER_EQUAL -> vectorTimeGreater(currentVector, targetVectorTime, true);
+ case LESS_EQUAL -> vectorTimeGreater(targetVectorTime, currentVector, true);
+ };
}
/**
diff --git a/src/main/java/exceptions/VSErrorHandler.java b/src/main/java/exceptions/VSErrorHandler.java
index 59085f0..3d2e2ca 100644
--- a/src/main/java/exceptions/VSErrorHandler.java
+++ b/src/main/java/exceptions/VSErrorHandler.java
@@ -84,22 +84,16 @@ public final class VSErrorHandler {
* @param exception the exception to display
*/
private static void showErrorDialog(Exception exception) {
- String title = "Simulator Error";
- String message = exception.getMessage();
-
- if (exception instanceof VSSimulatorException) {
- // Use more specific title for our exceptions
- if (exception instanceof VSConfigurationException) {
- title = "Configuration Error";
- } else if (exception instanceof VSProcessException) {
- title = "Process Error";
- } else if (exception instanceof VSProtocolException) {
- title = "Protocol Error";
- } else if (exception instanceof VSSerializationException) {
- title = "Save/Load Error";
- }
- }
+ String title = switch (exception) {
+ case VSConfigurationException e -> "Configuration Error";
+ case VSProcessException e -> "Process Error";
+ case VSProtocolException e -> "Protocol Error";
+ case VSSerializationException e -> "Save/Load Error";
+ case VSSimulatorException e -> "Simulator Error";
+ default -> "Simulator Error";
+ };
+ String message = exception.getMessage();
if (message == null || message.isEmpty()) {
message = "An unexpected error occurred: " + exception.getClass().getSimpleName();
}
@@ -130,19 +124,12 @@ public final class VSErrorHandler {
* @return a VSSimulatorException wrapping the original exception
*/
public static VSSimulatorException wrap(Exception e, String context) {
- if (e instanceof VSSimulatorException) {
- return (VSSimulatorException) e;
- }
-
- // Wrap common exceptions with more specific types
- if (e instanceof java.io.IOException) {
- return new VSSerializationException(context, e);
- } else if (e instanceof NumberFormatException) {
- return new VSConfigurationException("Invalid number format in " + context, e);
- } else if (e instanceof NullPointerException) {
- return new VSSimulatorException("Null value encountered in " + context, e);
- } else {
- return new VSSimulatorException(context + ": " + e.getMessage(), e);
- }
+ return switch (e) {
+ case VSSimulatorException vse -> vse;
+ case java.io.IOException ioe -> new VSSerializationException(context, ioe);
+ case NumberFormatException nfe -> new VSConfigurationException("Invalid number format in " + context, nfe);
+ case NullPointerException npe -> new VSSimulatorException("Null value encountered in " + context, npe);
+ default -> new VSSimulatorException(context + ": " + e.getMessage(), e);
+ };
}
} \ No newline at end of file
diff --git a/src/main/java/protocols/implementations/VSDummyProtocol.java b/src/main/java/protocols/implementations/VSDummyProtocol.java
index e9e6837..0e06ea3 100644
--- a/src/main/java/protocols/implementations/VSDummyProtocol.java
+++ b/src/main/java/protocols/implementations/VSDummyProtocol.java
@@ -49,7 +49,7 @@ public class VSDummyProtocol extends VSAbstractProtocol {
* @see protocols.VSAbstractProtocol#onClientRecv(core.VSMessage)
*/
public void onClientRecv(VSMessage recvMessage) {
- log("onClientRecv("+recvMessage+")");
+ log("onClientRecv(%s)".formatted(recvMessage));
/*
String s = recvMessage.getString("Greeting");
@@ -82,7 +82,7 @@ public class VSDummyProtocol extends VSAbstractProtocol {
* @see protocols.VSAbstractProtocol#onServerRecv(core.VSMessage)
*/
public void onServerRecv(VSMessage recvMessage) {
- log("onServerRecv("+recvMessage+")");
+ log("onServerRecv(%s)".formatted(recvMessage));
}
/* (non-Javadoc)
diff --git a/src/main/java/utils/VS3Tupel.java b/src/main/java/utils/VS3Tupel.java
index ac7ffba..3ff5129 100644
--- a/src/main/java/utils/VS3Tupel.java
+++ b/src/main/java/utils/VS3Tupel.java
@@ -1,58 +1,59 @@
package utils;
/**
- * The class VS3Tupel, an object of this class represents a 3-Tupel of objects.
- * Each object can have its own type.
+ * A generic 3-tuple record that holds three values of potentially different types.
+ * This is an immutable value object that provides automatic implementations of
+ * {@code equals()}, {@code hashCode()}, and {@code toString()}.
+ *
+ * <p>Example usage:</p>
+ * <pre>{@code
+ * VS3Tupel<String, Integer, Boolean> tuple = new VS3Tupel<>("Hello", 42, true);
+ * String first = tuple.a();
+ * Integer second = tuple.b();
+ * Boolean third = tuple.c();
+ * }</pre>
*
+ * @param <A> the type of the first element
+ * @param <B> the type of the second element
+ * @param <C> the type of the third element
+ * @param a the first element
+ * @param b the second element
+ * @param c the third element
+ *
* @author Paul C. Buetow
*/
-public final class VS3Tupel<A,B,C> {
- /** The a. */
- private A a;
-
- /** The b. */
- private B b;
-
- /** The c. */
- private C c;
-
- /**
- * Instantiates a new tupel.
- *
- * @param a the a
- * @param b the b
- * @param c the c
- */
- public VS3Tupel(A a, B b, C c) {
- this.a = a;
- this.b = b;
- this.c = c;
- }
-
+public record VS3Tupel<A, B, C>(A a, B b, C c) {
+
/**
- * Gets the a.
+ * Gets the first element (provided for backward compatibility).
*
- * @return the a
+ * @return the first element
+ * @deprecated Use {@link #a()} instead
*/
+ @Deprecated
public A getA() {
return a;
}
-
+
/**
- * Gets the b.
+ * Gets the second element (provided for backward compatibility).
*
- * @return the b
+ * @return the second element
+ * @deprecated Use {@link #b()} instead
*/
+ @Deprecated
public B getB() {
return b;
}
-
+
/**
- * Gets the c.
+ * Gets the third element (provided for backward compatibility).
*
- * @return the c
+ * @return the third element
+ * @deprecated Use {@link #c()} instead
*/
+ @Deprecated
public C getC() {
return c;
}
-}
+} \ No newline at end of file
diff --git a/src/main/java/utils/VSAboutFrame.java b/src/main/java/utils/VSAboutFrame.java
index e07289f..76f3cbd 100644
--- a/src/main/java/utils/VSAboutFrame.java
+++ b/src/main/java/utils/VSAboutFrame.java
@@ -73,12 +73,10 @@ public class VSAboutFrame extends VSFrame {
JButton closeButton = new JButton(
prefs.getString("lang.close"));
closeButton.setMnemonic(prefs.getInteger("keyevent.close"));
- closeButton.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- String actionCommand = e.getActionCommand();
- if (actionCommand.equals(prefs.getString("lang.close")))
- dispose();
- }
+ closeButton.addActionListener(e -> {
+ String actionCommand = e.getActionCommand();
+ if (actionCommand.equals(prefs.getString("lang.close")))
+ dispose();
});
buttonPane.add(closeButton);