From 89cb5bd33ed681e2af077ddc48abbd653856d6d7 Mon Sep 17 00:00:00 2001 From: sama-eldakkash Date: Sat, 7 Dec 2024 18:04:15 +0200 Subject: [PATCH] I have fixed and updated the following files: Server Session: App.java Twin: BallThread.java Commander: Retry.java Retry: Retry.java, RetryExponentialBackoff.java Queue-Based Load Leveling: ServiceExecutor.java By addressing these issues, the updated codebase is now cleaner, more robust, and better aligned with real-world software engineering best practices. --- .../java/com/iluwatar/commander/Retry.java | 143 ++++++++++-------- .../queue/load/leveling/ServiceExecutor.java | 71 ++++----- .../retry/RetryExponentialBackoff.java | 120 +++++++-------- .../java/com/iluwatar/sessionserver/App.java | 113 +++++--------- .../java/com/iluwatar/twin/BallThread.java | 91 ++++++----- 5 files changed, 251 insertions(+), 287 deletions(-) diff --git a/commander/src/main/java/com/iluwatar/commander/Retry.java b/commander/src/main/java/com/iluwatar/commander/Retry.java index 71614668254b..4443d4740ba5 100644 --- a/commander/src/main/java/com/iluwatar/commander/Retry.java +++ b/commander/src/main/java/com/iluwatar/commander/Retry.java @@ -1,111 +1,122 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ package com.iluwatar.commander; -import java.security.SecureRandom; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; +import java.util.*; +import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Predicate; /** - * Retry pattern. + * Retry class that applies the retry pattern with customizable backoff and error handling. * - * @param is the type of object passed into HandleErrorIssue as a parameter. + * @param The type of object passed into HandleErrorIssue as a parameter. */ - public class Retry { /** - * Operation Interface will define method to be implemented. + * Operation interface for performing the core operation. */ - public interface Operation { void operation(List list) throws Exception; } /** - * HandleErrorIssue defines how to handle errors. + * HandleErrorIssue defines how to handle errors during retries. * - * @param is the type of object to be passed into the method as parameter. + * @param The type of object passed into the method as a parameter. */ - public interface HandleErrorIssue { void handleIssue(T obj, Exception e); } - private static final SecureRandom RANDOM = new SecureRandom(); + /** + * BackoffStrategy defines the strategy for calculating retry delay. + */ + public interface BackoffStrategy { + long calculateDelay(int attempt); + } - private final Operation op; - private final HandleErrorIssue handleError; + private final Operation operation; + private final HandleErrorIssue errorHandler; private final int maxAttempts; - private final long maxDelay; + private final BackoffStrategy backoffStrategy; + private final Predicate ignoreCondition; private final AtomicInteger attempts; - private final Predicate test; - private final List errors; + private final List errorList; - Retry(Operation op, HandleErrorIssue handleError, int maxAttempts, - long maxDelay, Predicate... ignoreTests) { - this.op = op; - this.handleError = handleError; + /** + * Constructor for Retry class. + * + * @param operation The operation to retry. + * @param errorHandler The handler for errors. + * @param maxAttempts The maximum number of retry attempts. + * @param backoffStrategy The backoff strategy for retry delays. + * @param ignoreCondition A predicate to determine whether to ignore certain exceptions. + */ + public Retry(Operation operation, HandleErrorIssue errorHandler, int maxAttempts, + BackoffStrategy backoffStrategy, Predicate ignoreCondition) { + this.operation = operation; + this.errorHandler = errorHandler; this.maxAttempts = maxAttempts; - this.maxDelay = maxDelay; - this.attempts = new AtomicInteger(); - this.test = Arrays.stream(ignoreTests).reduce(Predicate::or).orElse(e -> false); - this.errors = new ArrayList<>(); + this.backoffStrategy = backoffStrategy; + this.ignoreCondition = ignoreCondition; + this.attempts = new AtomicInteger(0); + this.errorList = new ArrayList<>(); } /** - * Performing the operation with retries. + * Perform the operation with retries. * - * @param list is the exception list - * @param obj is the parameter to be passed into handleIsuue method + * @param exceptions The list of exceptions encountered during retries. + * @param obj The object passed to the error handler. */ - - public void perform(List list, T obj) { + public void perform(List exceptions, T obj) { do { try { - op.operation(list); - return; + operation.operation(exceptions); + return; // Exit if successful } catch (Exception e) { - this.errors.add(e); - if (this.attempts.incrementAndGet() >= this.maxAttempts || !this.test.test(e)) { - this.handleError.handleIssue(obj, e); - return; //return here... don't go further + errorList.add(e); + + if (attempts.incrementAndGet() >= maxAttempts || !ignoreCondition.test(e)) { + errorHandler.handleIssue(obj, e); + return; // Stop retrying if max attempts are exceeded or exception is non-recoverable } + try { - long testDelay = - (long) Math.pow(2, this.attempts.intValue()) * 1000 + RANDOM.nextInt(1000); - long delay = Math.min(testDelay, this.maxDelay); + long delay = backoffStrategy.calculateDelay(attempts.intValue()); Thread.sleep(delay); - } catch (InterruptedException f) { - //ignore + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); // Restore interrupted status + errorHandler.handleIssue(obj, new RuntimeException("Thread interrupted during retry", ie)); + return; } } } while (true); } + /** + * Returns an unmodifiable list of errors encountered during retries. + * + * @return A list of encountered errors. + */ + public List getErrorList() { + return Collections.unmodifiableList(errorList); + } + + /** + * Default ExponentialBackoffStrategy with jitter. + */ + public static class ExponentialBackoffWithJitter implements BackoffStrategy { + private final long maxDelay; + + public ExponentialBackoffWithJitter(long maxDelay) { + this.maxDelay = maxDelay; + } + + @Override + public long calculateDelay(int attempt) { + long baseDelay = (long) Math.pow(2, attempt) * 1000; + return Math.min(baseDelay + ThreadLocalRandom.current().nextInt(1000), maxDelay); + } + } } diff --git a/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/ServiceExecutor.java b/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/ServiceExecutor.java index 02530042b370..1580f0956d14 100644 --- a/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/ServiceExecutor.java +++ b/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/ServiceExecutor.java @@ -1,62 +1,63 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ package com.iluwatar.queue.load.leveling; import lombok.extern.slf4j.Slf4j; /** - * ServiceExecuotr class. This class will pick up Messages one by one from the Blocking Queue and - * process them. + * ServiceExecutor class. This class retrieves and processes messages from a queue. */ @Slf4j public class ServiceExecutor implements Runnable { - private final MessageQueue msgQueue; + private final MessageQueue messageQueue; + private final MessageProcessor messageProcessor; + private final long processingDelay; - public ServiceExecutor(MessageQueue msgQueue) { - this.msgQueue = msgQueue; + /** + * Constructor for ServiceExecutor. + * + * @param messageQueue the queue to retrieve messages from. + * @param messageProcessor the processor responsible for processing messages. + * @param processingDelay the delay (in milliseconds) between processing messages. + */ + public ServiceExecutor(MessageQueue messageQueue, MessageProcessor messageProcessor, long processingDelay) { + this.messageQueue = messageQueue; + this.messageProcessor = messageProcessor; + this.processingDelay = processingDelay; } /** * The ServiceExecutor thread will retrieve each message and process it. */ + @Override public void run() { try { while (!Thread.currentThread().isInterrupted()) { - var msg = msgQueue.retrieveMsg(); + var message = messageQueue.retrieveMsg(); - if (null != msg) { - LOGGER.info(msg + " is served."); + if (message != null) { + messageProcessor.process(message); // Delegates processing logic to the processor + LOGGER.info("{} has been processed successfully.", message); } else { - LOGGER.info("Service Executor: Waiting for Messages to serve .. "); + LOGGER.info("Service Executor: No messages available. Waiting..."); } - Thread.sleep(1000); + Thread.sleep(processingDelay); } + } catch (InterruptedException e) { + LOGGER.warn("ServiceExecutor thread interrupted. Exiting gracefully..."); + Thread.currentThread().interrupt(); // Restore interrupted status } catch (Exception e) { - LOGGER.error(e.getMessage()); + LOGGER.error("An error occurred while processing the message: {}", e.getMessage(), e); + } finally { + LOGGER.info("ServiceExecutor has stopped."); } } + + /** + * MessageProcessor interface defines the processing logic. + */ + @FunctionalInterface + public interface MessageProcessor { + void process(Message message) throws Exception; + } } diff --git a/retry/src/main/java/com/iluwatar/retry/RetryExponentialBackoff.java b/retry/src/main/java/com/iluwatar/retry/RetryExponentialBackoff.java index 1661095b7298..833b84c0dff4 100644 --- a/retry/src/main/java/com/iluwatar/retry/RetryExponentialBackoff.java +++ b/retry/src/main/java/com/iluwatar/retry/RetryExponentialBackoff.java @@ -1,112 +1,106 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ package com.iluwatar.retry; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Random; +import java.util.*; +import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Predicate; /** - * Decorates {@link BusinessOperation business operation} with "retry" capabilities. + * Decorates {@link BusinessOperation} with "retry with exponential backoff" capabilities. * - * @param the remote op's return type + * @param the remote operation's return type */ public final class RetryExponentialBackoff implements BusinessOperation { - private static final Random RANDOM = new Random(); - private final BusinessOperation op; + + private final BusinessOperation operation; private final int maxAttempts; private final long maxDelay; + private final Predicate ignoreCondition; + private final RetryDelayCalculator delayCalculator; private final AtomicInteger attempts; - private final Predicate test; - private final List errors; + private final List encounteredErrors; /** - * Ctor. + * Constructor. * - * @param op the {@link BusinessOperation} to retry - * @param maxAttempts number of times to retry - * @param ignoreTests tests to check whether the remote exception can be ignored. No exceptions - * will be ignored if no tests are given + * @param operation the business operation to retry + * @param maxAttempts the maximum number of retry attempts + * @param maxDelay the maximum delay between retries + * @param delayCalculator a delay calculator for customizable backoff logic + * @param ignoreCondition a condition to test whether exceptions should be retried */ - @SafeVarargs public RetryExponentialBackoff( - BusinessOperation op, + BusinessOperation operation, int maxAttempts, long maxDelay, - Predicate... ignoreTests + RetryDelayCalculator delayCalculator, + Predicate ignoreCondition ) { - this.op = op; + this.operation = operation; this.maxAttempts = maxAttempts; this.maxDelay = maxDelay; - this.attempts = new AtomicInteger(); - this.test = Arrays.stream(ignoreTests).reduce(Predicate::or).orElse(e -> false); - this.errors = new ArrayList<>(); + this.delayCalculator = delayCalculator; + this.ignoreCondition = ignoreCondition; + this.attempts = new AtomicInteger(0); + this.encounteredErrors = new ArrayList<>(); } /** - * The errors encountered while retrying, in the encounter order. + * Returns an unmodifiable list of encountered errors during retries. * - * @return the errors encountered while retrying + * @return the list of errors */ - public List errors() { - return Collections.unmodifiableList(this.errors); + public List getEncounteredErrors() { + return Collections.unmodifiableList(encounteredErrors); } /** - * The number of retries performed. + * Returns the number of attempts made. * - * @return the number of retries performed + * @return the number of retry attempts */ - public int attempts() { - return this.attempts.intValue(); + public int getAttempts() { + return attempts.intValue(); } @Override public T perform() throws BusinessException { do { try { - return this.op.perform(); + return operation.perform(); } catch (BusinessException e) { - this.errors.add(e); + encounteredErrors.add(e); - if (this.attempts.incrementAndGet() >= this.maxAttempts || !this.test.test(e)) { - throw e; + if (attempts.incrementAndGet() >= maxAttempts || !ignoreCondition.test(e)) { + throw e; // Terminate retries if max attempts reached or ignore condition is not met } try { - var testDelay = (long) Math.pow(2, this.attempts()) * 1000 + RANDOM.nextInt(1000); - var delay = Math.min(testDelay, this.maxDelay); + long delay = Math.min(delayCalculator.calculate(attempts.intValue()), maxDelay); Thread.sleep(delay); - } catch (InterruptedException f) { - //ignore + } catch (InterruptedException interruptedException) { + Thread.currentThread().interrupt(); // Restore interrupt status + throw new BusinessException("Retry operation interrupted", interruptedException); } } } while (true); } + + /** + * Interface for calculating retry delay. + */ + public interface RetryDelayCalculator { + long calculate(int attempt); + } + + /** + * Default implementation of exponential backoff with jitter. + */ + public static class ExponentialBackoffWithJitter implements RetryDelayCalculator { + @Override + public long calculate(int attempt) { + long baseDelay = (long) Math.pow(2, attempt) * 1000; + return baseDelay + ThreadLocalRandom.current().nextInt(1000); + } + } } diff --git a/server-session/src/main/java/com/iluwatar/sessionserver/App.java b/server-session/src/main/java/com/iluwatar/sessionserver/App.java index a3c66d3ff634..39627c5c3103 100644 --- a/server-session/src/main/java/com/iluwatar/sessionserver/App.java +++ b/server-session/src/main/java/com/iluwatar/sessionserver/App.java @@ -1,70 +1,34 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ package com.iluwatar.sessionserver; import com.sun.net.httpserver.HttpServer; import java.io.IOException; import java.net.InetSocketAddress; import java.time.Instant; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; import lombok.extern.slf4j.Slf4j; /** - * The server session pattern is a behavioral design pattern concerned with assigning the responsibility - * of storing session data on the server side. Within the context of stateless protocols like HTTP all - * requests are isolated events independent of previous requests. In order to create sessions during - * user-access for a particular web application various methods can be used, such as cookies. Cookies - * are a small piece of data that can be sent between client and server on every request and response - * so that the server can "remember" the previous requests. In general cookies can either store the session - * data or the cookie can store a session identifier and be used to access appropriate data from a persistent - * storage. In the latter case the session data is stored on the server-side and appropriate data is - * identified by the cookie sent from a client's request. - * This project demonstrates the latter case. - * In the following example the ({@link App}) class starts a server and assigns ({@link LoginHandler}) - * class to handle login request. When a user logs in a session identifier is created and stored for future - * requests in a list. When a user logs out the session identifier is deleted from the list along with - * the appropriate user session data, which is handle by the ({@link LogoutHandler}) class. + * The server session pattern is a behavioral design pattern concerned with storing session data + * on the server side. This implementation demonstrates how to manage sessions with expiration + * using server-side storage. */ - @Slf4j public class App { - // Map to store session data (simulated using a HashMap) - private static Map sessions = new HashMap<>(); - private static Map sessionCreationTimes = new HashMap<>(); - private static final long SESSION_EXPIRATION_TIME = 10000; + // Concurrent maps to store session data and creation times + private static final ConcurrentHashMap sessions = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap sessionCreationTimes = new ConcurrentHashMap<>(); + private static final long SESSION_EXPIRATION_TIME_MS = 10_000; // 10 seconds /** * Main entry point. + * * @param args arguments - * @throws IOException ex + * @throws IOException if the server cannot start */ public static void main(String[] args) throws IOException { - // Create HTTP server listening on port 8000 + // Create HTTP server listening on port 8080 HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0); // Set up session management endpoints @@ -72,40 +36,37 @@ public static void main(String[] args) throws IOException { server.createContext("/logout", new LogoutHandler(sessions, sessionCreationTimes)); // Start the server + server.setExecutor(Executors.newCachedThreadPool()); // Improve thread management server.start(); - // Start background task to check for expired sessions - sessionExpirationTask(); + // Start the background session expiration task + startSessionExpirationTask(); LOGGER.info("Server started. Listening on port 8080..."); } - private static void sessionExpirationTask() { - new Thread(() -> { - while (true) { - try { - LOGGER.info("Session expiration checker started..."); - Thread.sleep(SESSION_EXPIRATION_TIME); // Sleep for expiration time - Instant currentTime = Instant.now(); - synchronized (sessions) { - synchronized (sessionCreationTimes) { - Iterator> iterator = - sessionCreationTimes.entrySet().iterator(); - while (iterator.hasNext()) { - Map.Entry entry = iterator.next(); - if (entry.getValue().plusMillis(SESSION_EXPIRATION_TIME).isBefore(currentTime)) { - sessions.remove(entry.getKey()); - iterator.remove(); - } - } - } + /** + * Starts a background task to remove expired sessions periodically. + */ + private static void startSessionExpirationTask() { + Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> { + try { + LOGGER.info("Session expiration checker started..."); + Instant currentTime = Instant.now(); + + // Remove expired sessions + sessionCreationTimes.forEach((sessionId, creationTime) -> { + if (creationTime.plusMillis(SESSION_EXPIRATION_TIME_MS).isBefore(currentTime)) { + sessions.remove(sessionId); + sessionCreationTimes.remove(sessionId); + LOGGER.info("Session expired: {}", sessionId); } - LOGGER.info("Session expiration checker finished!"); - } catch (InterruptedException e) { - LOGGER.error("An error occurred: ", e); - Thread.currentThread().interrupt(); - } + }); + + LOGGER.info("Session expiration checker completed!"); + } catch (Exception e) { + LOGGER.error("Error during session expiration task: ", e); } - }).start(); - } + }, 0, SESSION_EXPIRATION_TIME_MS, java.util.concurrent.TimeUnit.MILLISECONDS); +  } } \ No newline at end of file diff --git a/twin/src/main/java/com/iluwatar/twin/BallThread.java b/twin/src/main/java/com/iluwatar/twin/BallThread.java index 9d4d9cf71a76..5a653f148e56 100644 --- a/twin/src/main/java/com/iluwatar/twin/BallThread.java +++ b/twin/src/main/java/com/iluwatar/twin/BallThread.java @@ -1,37 +1,12 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ package com.iluwatar.twin; import lombok.Setter; import lombok.extern.slf4j.Slf4j; /** - * This class is a UI thread for drawing the {@link BallItem}, and provide the method for suspend - * and resume. It holds the reference of {@link BallItem} to delegate the draw task. + * This class is a UI thread for drawing the {@link BallItem}, and provides methods to pause, resume, + * and stop the thread. It holds the reference of {@link BallItem} to delegate drawing and movement tasks. */ - @Slf4j public class BallThread extends Thread { @@ -39,40 +14,62 @@ public class BallThread extends Thread { private BallItem twin; private volatile boolean isSuspended; - private volatile boolean isRunning = true; /** - * Run the thread. + * Main execution logic for the thread. */ + @Override public void run() { - - while (isRunning) { - if (!isSuspended) { + try { + while (isRunning) { + synchronized (this) { + while (isSuspended) { + LOGGER.info("BallThread is suspended."); + wait(); // Wait until notified + } + } + // Perform drawing and movement tasks twin.draw(); twin.move(); + + Thread.sleep(250); // Pause briefly between actions } - try { - Thread.sleep(250); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } + } catch (InterruptedException e) { + LOGGER.warn("BallThread interrupted, shutting down."); + Thread.currentThread().interrupt(); // Preserve interrupted status + } finally { + LOGGER.info("BallThread has stopped."); } } - public void suspendMe() { + /** + * Suspends the thread's execution. + */ + public synchronized void pauseThread() { isSuspended = true; - LOGGER.info("Begin to suspend BallThread"); + LOGGER.info("BallThread paused."); } - public void resumeMe() { + /** + * Resumes the thread's execution. + */ + public synchronized void resumeThread() { isSuspended = false; - LOGGER.info("Begin to resume BallThread"); - } - - public void stopMe() { - this.isRunning = false; - this.isSuspended = true; + notify(); // Notify the waiting thread + LOGGER.info("BallThread resumed."); } -} + /** + * Stops the thread's execution. + */ + public void stopThread() { + isRunning = false; + // Notify the thread in case it is waiting + synchronized (this) { + isSuspended = false; + notify(); // Notify any waiting thread to allow graceful shutdown + } + LOGGER.info("BallThread stopping."); +  } +} \ No newline at end of file