diff options
| author | Paul Buetow <paul@buetow.org> | 2025-06-20 20:04:02 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2025-06-20 20:04:02 +0300 |
| commit | f0b58321ae53f330da86c392661354b87bd9a412 (patch) | |
| tree | c0be72f57094d13cf8bcb53567614258a6eb43fa /src/main | |
| parent | c3b95267b24d843897b04d6d6a16f62dc8cf1ed2 (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.java | 106 | ||||
| -rw-r--r-- | src/main/java/core/time/VSLamportTime.java | 60 | ||||
| -rw-r--r-- | src/main/java/events/VSEventType.java | 71 | ||||
| -rw-r--r-- | src/main/java/events/implementations/VSTimestampTriggeredEvent.java | 42 | ||||
| -rw-r--r-- | src/main/java/exceptions/VSErrorHandler.java | 45 | ||||
| -rw-r--r-- | src/main/java/protocols/implementations/VSDummyProtocol.java | 4 | ||||
| -rw-r--r-- | src/main/java/utils/VS3Tupel.java | 69 | ||||
| -rw-r--r-- | src/main/java/utils/VSAboutFrame.java | 10 |
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); |
