diff --git a/pom.xml b/pom.xml index ef13866d842a5d564ae2dc676d6415db6f852b64..81ce2385023cf40136ee4c7709d7d1838caf6b8b 100644 --- a/pom.xml +++ b/pom.xml @@ -45,7 +45,7 @@ <artifactId>javafx-maven-plugin</artifactId> <version>0.0.8</version> <configuration> - <mainClass>edu.ntnu.idatt2003.Main</mainClass> + <mainClass>edu.ntnu.idatt2003.launcher.Main</mainClass> </configuration> </plugin> <plugin> diff --git a/src/main/java/edu/ntnu/idatt2003/controller/GameStateManager.java b/src/main/java/edu/ntnu/idatt2003/controller/GameStateManager.java new file mode 100644 index 0000000000000000000000000000000000000000..28793ec3c787c6fe4eea66d34e97bf89f9385815 --- /dev/null +++ b/src/main/java/edu/ntnu/idatt2003/controller/GameStateManager.java @@ -0,0 +1,76 @@ +package edu.ntnu.idatt2003.controller; + +import edu.ntnu.idatt2003.model.ChaosGame; +import edu.ntnu.idatt2003.model.ChaosGameDescriptionFactory; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Manages the game state by providing methods to save and load the game state from a file. + */ +public class GameStateManager { + + private static final String SERIALIZED_GAME_PATH = "src/main/resources/savedGameState.ser"; + private static final Logger LOGGER = Logger.getLogger(GameStateManager.class.getName()); + + /** + * Saves the current game state to a serialized file. + * + * @param game The current game state to be saved. + */ + public static void saveGameState(ChaosGame game) { + LOGGER.log(Level.INFO, "Saving game state."); + try (ObjectOutputStream oos = new ObjectOutputStream( + new FileOutputStream(SERIALIZED_GAME_PATH))) { + oos.writeObject(game); + LOGGER.log(Level.INFO, "Game state saved successfully in {0}", SERIALIZED_GAME_PATH); + } catch (IOException e) { + LOGGER.log(Level.WARNING, "Failed to save game state.", e); + } + } + + /** + * Loads the game state from a serialized file. If the file does not exist or loading fails, + * a new game state is created. + * + * @return The loaded game state, or a new game state if the file does not exist or loading fails. + */ + public static ChaosGame loadGameState() { + LOGGER.log(Level.INFO, "Loading game state."); + File file = new File(SERIALIZED_GAME_PATH); + if (file.exists()) { + try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) { + ChaosGame loadedGame = (ChaosGame) ois.readObject(); + LOGGER.log(Level.INFO, "Game state loaded successfully."); + return loadedGame; + } catch (IOException | ClassNotFoundException e) { + LOGGER.log(Level.WARNING, "Failed to load game state. Creating new game.", e); + } + } else { + LOGGER.log(Level.WARNING, "No saved game state found. Creating new game."); + } + return createNewGame(); + } + + /** + * Creates a new game state with default settings. + * + * @return The newly created game state. + */ + private static ChaosGame createNewGame() { + ChaosGame newGame = new ChaosGame( + ChaosGameDescriptionFactory.get( + ChaosGameDescriptionFactory.DescriptionTypeEnum.SIERPINSKI_TRIANGLE), + 650, 650); + newGame.setDescriptionName( + ChaosGameDescriptionFactory.DescriptionTypeEnum.SIERPINSKI_TRIANGLE.toString()); + return newGame; + } +} + diff --git a/src/main/java/edu/ntnu/idatt2003/controller/MainPageController.java b/src/main/java/edu/ntnu/idatt2003/controller/MainPageController.java index 5de111797fdbf1e262b35d513a203870f47b8990..d1785753c7312f9337f3b48a278e89fa6fa6a23b 100644 --- a/src/main/java/edu/ntnu/idatt2003/controller/MainPageController.java +++ b/src/main/java/edu/ntnu/idatt2003/controller/MainPageController.java @@ -1,35 +1,27 @@ package edu.ntnu.idatt2003.controller; -import edu.ntnu.idatt2003.model.AffineTransform2D; import edu.ntnu.idatt2003.model.ChaosGame; import edu.ntnu.idatt2003.model.ChaosGameDescription; import edu.ntnu.idatt2003.model.ChaosGameDescriptionFactory; import edu.ntnu.idatt2003.model.ChaosGameFileHandler; import edu.ntnu.idatt2003.model.Complex; import edu.ntnu.idatt2003.model.JuliaTransform; -import edu.ntnu.idatt2003.model.Matrix2x2; import edu.ntnu.idatt2003.model.Transform2D; import edu.ntnu.idatt2003.model.Vector2d; +import edu.ntnu.idatt2003.utils.LoggerUtil; +import edu.ntnu.idatt2003.utils.TransformationParser; import edu.ntnu.idatt2003.view.MainPageView; import java.io.File; -import java.io.FileInputStream; import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.InputMismatchException; import java.util.List; -import java.util.logging.FileHandler; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.logging.SimpleFormatter; -import java.util.stream.DoubleStream; import java.util.stream.Stream; /** @@ -43,22 +35,8 @@ public class MainPageController { private final List<String> customFractalNames; private boolean addingCustomFractal; private static final String FRACTAL_PATH = "src/main/resources/fractals/"; - private static final String SERIALIZED_GAME_PATH = "src/main/resources/savedGameState.ser"; - private static final Logger LOGGER = Logger.getLogger(MainPageController.class.getName()); + private static final Logger LOGGER = LoggerUtil.setupLogger(MainPageController.class.getName()); - static { - try { - new File("logs").mkdirs(); - - FileHandler fileHandler = new FileHandler("logs/application.log", false); - fileHandler.setFormatter(new SimpleFormatter()); - fileHandler.setLevel(Level.WARNING); - LOGGER.addHandler(fileHandler); - LOGGER.setLevel(Level.ALL); - } catch (IOException e) { - e.printStackTrace(); - } - } /** * The constructor for the MainPageController class. @@ -66,13 +44,13 @@ public class MainPageController { * and renders the view. */ public MainPageController() { - this.game = loadGameState(); + this.game = GameStateManager.loadGameState(); this.view = new MainPageView(this); this.game.registerObserver(view); this.customFractalNames = new ArrayList<>(getAllCustomFractalsNames()); this.addingCustomFractal = false; this.view.render(); - Runtime.getRuntime().addShutdownHook(new Thread(this::saveGameState)); + Runtime.getRuntime().addShutdownHook(new Thread(() -> GameStateManager.saveGameState(game))); LOGGER.log(Level.INFO, "MainPageController initialized successfully."); } @@ -130,49 +108,6 @@ public class MainPageController { return addingCustomFractal; } - /** - * Get the list of coordinate-arrays of the game. - * - * @return the list of coordinate-arrays of the game. - */ - public List<double[]> getTransformList() { - if (fractalIsJulia()) { - return getTransformListJulia(); - } else { - return getTransformListAffine(); - } - } - - private List<double[]> getTransformListJulia() { - List<double[]> transformList = new ArrayList<>(); - transformList.add(((JuliaTransform) game.getTransformList().get(0)).getPointAsList()); - return transformList; - } - - private List<double[]> getTransformListAffine() { - List<double[]> transformList = new ArrayList<>(); - for (Transform2D transform : game.getTransformList()) { - transformList.add(DoubleStream.concat(DoubleStream.of(((AffineTransform2D) transform) - .getMatrixCoordsList()), DoubleStream.of(((AffineTransform2D) transform) - .getVectorCoordsList())).toArray()); - } - return transformList; - } - - /** - * Check if the current fractal is a Julia fractal. If it is, - * return true and false otherwise. - * - * @return true if the fractal is a Julia fractal, false otherwise. - */ - public boolean fractalIsJulia() { - try { - return game.getTransformList().get(0) instanceof JuliaTransform; - } catch (IndexOutOfBoundsException e) { - return false; - } - } - /** * Run the chaos game simulation for the specified number of steps. If * the number of steps is negative, the canvas will be cleared. @@ -211,43 +146,20 @@ public class MainPageController { */ public void uploadFile(File file) { LOGGER.log(Level.INFO, "Uploading file: {0}", file.getName()); - if (validateFile(file) - && (!Files.exists(Path.of(FRACTAL_PATH + file.getName())) - || view.askConfirmation("File already exists. Do you want to overwrite it?"))) { - storeFile(file); - LOGGER.log(Level.INFO, "File {0} uploaded successfully.", file.getName()); - view.showAlert("File " + file.getName() + " uploaded successfully."); - } - } - - private boolean validateFile(File file) { - try { - ChaosGameFileHandler.readFromFile(file); - return true; - } catch (InputMismatchException | FileNotFoundException e) { - view.showAlert(e.getMessage()); - LOGGER.log(Level.WARNING, "Error uploading file. File was not uploaded."); - return false; + if (ChaosGameFileHandler.validateFile(file) + && (!Files.exists(Path.of(ChaosGameFileHandler.FRACTAL_PATH + file.getName())) + || view.askConfirmation("File already exists. Do you want to overwrite it?"))) { + try { + ChaosGameFileHandler.storeFile(file); + LOGGER.log(Level.INFO, "File {0} uploaded successfully.", file.getName()); + view.showAlert("File " + file.getName() + " uploaded successfully."); + } catch (IOException e) { + view.showAlert("Error storing file. Please try again."); + LOGGER.log(Level.WARNING, "Error storing file. File was not stored."); + } } } - /** - * Stores the file in the resources/transformations folder of the project. - * - * @param file The file to store. - */ - private void storeFile(File file) { - try { - String projectPath = System.getProperty("user.dir"); - String destinationPath = projectPath + File.separator - + FRACTAL_PATH + file.getName(); - Files.copy(file.toPath(), Path.of(destinationPath), StandardCopyOption.REPLACE_EXISTING); - LOGGER.log(Level.INFO, "File stored successfully in {0}", destinationPath); - } catch (IOException e) { - view.showAlert("Error storing file. Please try again."); - LOGGER.log(Level.WARNING, "Error storing file. File was not stored."); - } - } /** * Change the fractal-type of the chaos game. @@ -285,47 +197,6 @@ public class MainPageController { } } - private void saveGameState() { - LOGGER.log(Level.INFO, "Saving game state."); - game.removeObserver(view); - try (ObjectOutputStream oos = new ObjectOutputStream( - new FileOutputStream(SERIALIZED_GAME_PATH))) { - oos.writeObject(game); - LOGGER.log(Level.INFO, "Game state saved successfully in {0}", SERIALIZED_GAME_PATH); - } catch (IOException e) { - LOGGER.log(Level.WARNING, "Failed to save game state. Next time the application is" - + " started, the game will be launched in same game state as this time."); - } - } - - /** - * Load the game state from the serialized file to restore progress. - * If the file does not exist, a new game state is created. - * - * @return The loaded game state, or a new game state if the file does not exist. - */ - public ChaosGame loadGameState() { - LOGGER.log(Level.INFO, "Loading game state."); - File file = new File(SERIALIZED_GAME_PATH); - if (file.exists()) { - try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) { - ChaosGame loadedGame = (ChaosGame) ois.readObject(); - LOGGER.log(Level.INFO, "Game state loaded successfully."); - return loadedGame; - } catch (IOException | ClassNotFoundException e) { - LOGGER.log(Level.WARNING, "Failed to load game state. Creating new game."); - } - } else { - LOGGER.log(Level.WARNING, "No saved game state found. Creating new game."); - } - ChaosGame newGame = new ChaosGame(ChaosGameDescriptionFactory - .get(ChaosGameDescriptionFactory.DescriptionTypeEnum.SIERPINSKI_TRIANGLE), - 650, 650); - newGame.setDescriptionName(ChaosGameDescriptionFactory.DescriptionTypeEnum - .SIERPINSKI_TRIANGLE.toString()); - return newGame; - } - /** * Adds a new custom fractal by creating a ChaosGameDescription based on the * parameters which is written to a file in the fractals' directory. @@ -341,9 +212,9 @@ public class MainPageController { try { ChaosGameDescription newChaosGameDescription = new ChaosGameDescription( - getVector2dFromStringList(minCoords), - getVector2dFromStringList(maxCoords), - getTransformListFromStringList(transformations) + TransformationParser.getVector2dFromStringList(minCoords), + TransformationParser.getVector2dFromStringList(maxCoords), + TransformationParser.getTransformListFromStringList(transformations) ); if (!Files.exists(Path.of(FRACTAL_PATH + fractalName + ".txt")) || view.askConfirmation("A custom fractal with the same name already exists. " @@ -372,88 +243,6 @@ public class MainPageController { LOGGER.log(Level.INFO, "File saved successfully in {0}", file.getAbsolutePath()); } - /** - * Creates a Vector2d object from a string array, containing the x and y coordinates. - * - * @param vector the string array containing the x and y coordinates - * @return the Vector2d object created from the string array - */ - private Vector2d getVector2dFromStringList(String[] vector) { - try { - return new Vector2d(Double.parseDouble(vector[0]), Double.parseDouble(vector[1])); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("Invalid coordinates. Please enter valid integers."); - } - } - - /** - * Creates a list of Transform2D objects from a list of string arrays. - * - * @param transform the list of string arrays containing the transformation parameters - * @return the list of Transform2D objects created from the string arrays - */ - private List<Transform2D> getTransformListFromStringList(List<String[]> transform) - throws NumberFormatException { - try { - List<Transform2D> transformList = new ArrayList<>(); - for (String[] transformation : transform) { - if (transformation.length == 2) { - transformList.addAll(parseJuliaTransform(transformation)); - } else if (transformation.length == 6) { - transformList.add(parseAffineTransform(transformation)); - } - } - return transformList; - } catch (NumberFormatException e) { - throw new IllegalArgumentException("Invalid coordinates. Please " - + "enter valid decimal numbers."); - } - - } - - /** - * Parses the Julia transformations and returns a List of Julia Transformations. - * - * @param transformation the string array containing the transformation parameters - * @return the list of Julia Transformations - */ - private List<Transform2D> parseJuliaTransform(String[] transformation) { - return List.of( - new JuliaTransform( - new Complex( - Double.parseDouble(transformation[0]), - Double.parseDouble(transformation[1]) - ), - 1), - new JuliaTransform( - new Complex( - Double.parseDouble(transformation[0]), - Double.parseDouble(transformation[1]) - ), - 1) - ); - } - - /** - * Parses the Affine transformations and returns a List of Julia Transformations. - * - * @param transformation the string array containing the transformation parameters - * @return the list of Affine Transformations - */ - private AffineTransform2D parseAffineTransform(String[] transformation) { - return new AffineTransform2D( - new Matrix2x2( - Double.parseDouble(transformation[0]), - Double.parseDouble(transformation[1]), - Double.parseDouble(transformation[2]), - Double.parseDouble(transformation[3]) - ), - new Vector2d( - Double.parseDouble(transformation[4]), - Double.parseDouble(transformation[5]) - )); - - } /** * Retrieves a list of all custom fractal files in the fractals directory. diff --git a/src/main/java/edu/ntnu/idatt2003/launcher/CLILauncher.java b/src/main/java/edu/ntnu/idatt2003/launcher/CLILauncher.java new file mode 100644 index 0000000000000000000000000000000000000000..40e804870da7f53574e2a02f7b8b0e05924a335f --- /dev/null +++ b/src/main/java/edu/ntnu/idatt2003/launcher/CLILauncher.java @@ -0,0 +1,16 @@ + +package edu.ntnu.idatt2003.launcher; + +import edu.ntnu.idatt2003.view.CommandLineInterface; + +/** + * Launcher for the command line interface + * To read or write from file write src/main/resources/fractals/filename(with .txt) + */ + +public class CLILauncher { + public static void main(String[] args) { + CommandLineInterface menuDrivenCommandLine = new CommandLineInterface(); + menuDrivenCommandLine.start(); + } +} diff --git a/src/main/java/edu/ntnu/idatt2003/Main.java b/src/main/java/edu/ntnu/idatt2003/launcher/Main.java similarity index 96% rename from src/main/java/edu/ntnu/idatt2003/Main.java rename to src/main/java/edu/ntnu/idatt2003/launcher/Main.java index 20e09cc4070d5d3a4564cfa5ffb3f18b9a7d4df1..1c9edf412fa54b3091f02ff46feff204458413ab 100644 --- a/src/main/java/edu/ntnu/idatt2003/Main.java +++ b/src/main/java/edu/ntnu/idatt2003/launcher/Main.java @@ -1,4 +1,4 @@ -package edu.ntnu.idatt2003; +package edu.ntnu.idatt2003.launcher; import edu.ntnu.idatt2003.controller.MainPageController; import javafx.application.Application; diff --git a/src/main/java/edu/ntnu/idatt2003/model/ChaosGameDescriptionFactory.java b/src/main/java/edu/ntnu/idatt2003/model/ChaosGameDescriptionFactory.java index 270e83644f9a44798ec97eaa32a1d55433701568..696e272d23921e9ae0195101e518733b6ad838c5 100644 --- a/src/main/java/edu/ntnu/idatt2003/model/ChaosGameDescriptionFactory.java +++ b/src/main/java/edu/ntnu/idatt2003/model/ChaosGameDescriptionFactory.java @@ -187,7 +187,7 @@ public class ChaosGameDescriptionFactory { public static ChaosGameDescription getCustom(String transformationName) throws FileNotFoundException{ try { - String filePath = "src/main/resources/transformations/" + transformationName + ".txt"; + String filePath = "src/main/resources/fractals/" + transformationName + ".txt"; return readFractal(filePath); } catch (FileNotFoundException e) { throw new FileNotFoundException("File " + e.getMessage()); diff --git a/src/main/java/edu/ntnu/idatt2003/model/ChaosGameFileHandler.java b/src/main/java/edu/ntnu/idatt2003/model/ChaosGameFileHandler.java index cbdc3813ae28eca2ef971b36f809aacfc1c15129..73db0d7dd6c18a37476d5e08363dbde8a0c35974 100644 --- a/src/main/java/edu/ntnu/idatt2003/model/ChaosGameFileHandler.java +++ b/src/main/java/edu/ntnu/idatt2003/model/ChaosGameFileHandler.java @@ -6,7 +6,9 @@ import java.io.FileNotFoundException; import java.io.FileWriter; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.InputMismatchException; import java.util.List; @@ -22,6 +24,8 @@ import java.util.Scanner; */ public class ChaosGameFileHandler { + public static final String FRACTAL_PATH = "src/main/resources/fractals/"; + /** * Private constructor to prevent instantiation. */ @@ -163,4 +167,32 @@ public class ChaosGameFileHandler { } } + /** + * Validates if the file exists. + * + * @param file the file to validate + * + * @return a boolean value + */ + + public static boolean validateFile(File file) { + try { + ChaosGameFileHandler.readFromFile(file); + return true; + } catch (InputMismatchException | FileNotFoundException e) { + return false; + } + } + + /** + * Stores the file in the resources/transformations folder of the project. + * + * @param file The file to store. + */ + public static void storeFile(File file) throws IOException { + String projectPath = System.getProperty("user.dir"); + String destinationPath = projectPath + File.separator + FRACTAL_PATH + file.getName(); + Files.copy(file.toPath(), Path.of(destinationPath), StandardCopyOption.REPLACE_EXISTING); + } + } diff --git a/src/main/java/edu/ntnu/idatt2003/utils/LoggerUtil.java b/src/main/java/edu/ntnu/idatt2003/utils/LoggerUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..10b2fae8f0e4ce8b599cb0268534a36518d716ef --- /dev/null +++ b/src/main/java/edu/ntnu/idatt2003/utils/LoggerUtil.java @@ -0,0 +1,38 @@ +package edu.ntnu.idatt2003.utils; + +import java.io.File; +import java.io.IOException; +import java.util.logging.FileHandler; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.logging.SimpleFormatter; + +/** + * Utility class for setting up a Logger. + */ +public class LoggerUtil { + + /** + * Sets up a logger for the specified class name. + * + * @param className the name of the class for which the logger is being set up + * @return the configured Logger instance + */ + public static Logger setupLogger(String className) { + Logger logger = Logger.getLogger(className); + try { + File logDirectory = new File("logs"); + if (!logDirectory.exists() && !logDirectory.mkdirs()) { + System.err.println("Failed to create log directory."); + } + FileHandler fileHandler = new FileHandler("logs/application.log", false); + fileHandler.setFormatter(new SimpleFormatter()); + fileHandler.setLevel(Level.WARNING); + logger.addHandler(fileHandler); + logger.setLevel(Level.ALL); + } catch (IOException e) { + e.printStackTrace(); + } + return logger; + } +} diff --git a/src/main/java/edu/ntnu/idatt2003/utils/TransformationParser.java b/src/main/java/edu/ntnu/idatt2003/utils/TransformationParser.java new file mode 100644 index 0000000000000000000000000000000000000000..20944e7ebefd81e1922b137c1ed37e51004e122a --- /dev/null +++ b/src/main/java/edu/ntnu/idatt2003/utils/TransformationParser.java @@ -0,0 +1,161 @@ +package edu.ntnu.idatt2003.utils; + +import edu.ntnu.idatt2003.model.AffineTransform2D; +import edu.ntnu.idatt2003.model.ChaosGame; +import edu.ntnu.idatt2003.model.Complex; +import edu.ntnu.idatt2003.model.JuliaTransform; +import edu.ntnu.idatt2003.model.Matrix2x2; +import edu.ntnu.idatt2003.model.Transform2D; +import edu.ntnu.idatt2003.model.Vector2d; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.DoubleStream; + +/** + * Utility class for handling transformations in the ChaosGame. + */ +public class TransformationParser { + + /** + * private constructor to stop initialization + */ + private TransformationParser() { + } + + /** + * Returns the list of coordinate arrays of the game. + * + * @param game the ChaosGame instance + * @return the list of coordinate arrays of the game + */ + public static List<double[]> getTransformList(ChaosGame game) { + if (fractalIsJulia(game)) { + return getTransformListJulia(game); + } else { + return getTransformListAffine(game); + } + } + + /** + * Returns the list of coordinate arrays for a Julia fractal. + * + * @param game the ChaosGame instance + * @return the list of coordinate arrays for a Julia fractal + */ + private static List<double[]> getTransformListJulia(ChaosGame game) { + List<double[]> transformList = new ArrayList<>(); + transformList.add(((JuliaTransform) game.getTransformList().getFirst()).getPointAsList()); + return transformList; + } + + /** + * Returns the list of coordinate arrays for an affine fractal. + * + * @param game the ChaosGame instance + * @return the list of coordinate arrays for an affine fractal + */ + private static List<double[]> getTransformListAffine(ChaosGame game) { + List<double[]> transformList = new ArrayList<>(); + for (Transform2D transform : game.getTransformList()) { + transformList.add(DoubleStream.concat(DoubleStream.of( + ((AffineTransform2D) transform).getMatrixCoordsList()), + DoubleStream.of(((AffineTransform2D) transform).getVectorCoordsList())).toArray()); + } + return transformList; + } + + /** + * Checks if the current fractal is a Julia fractal. + * + * @param game the ChaosGame instance + * @return true if the fractal is a Julia fractal, false otherwise + */ + public static boolean fractalIsJulia(ChaosGame game) { + try { + return game.getTransformList().getFirst() instanceof JuliaTransform; + } catch (IndexOutOfBoundsException e) { + return false; + } + } + + /** + * Creates a Vector2d object from a string array containing the x and y coordinates. + * + * @param vector the string array containing the x and y coordinates + * @return the Vector2d object created from the string array + * @throws IllegalArgumentException if the coordinates are invalid + */ + public static Vector2d getVector2dFromStringList(String[] vector) { + try { + return new Vector2d(Double.parseDouble(vector[0]), Double.parseDouble(vector[1])); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid coordinates. Please enter valid numbers."); + } + } + + /** + * Creates a list of Transform2D objects from a list of string arrays. + * + * @param transform the list of string arrays containing the transformation parameters + * @return the list of Transform2D objects created from the string arrays + * @throws IllegalArgumentException if the coordinates are invalid + */ + public static List<Transform2D> getTransformListFromStringList(List<String[]> transform) { + try { + List<Transform2D> transformList = new ArrayList<>(); + for (String[] transformation : transform) { + if (transformation.length == 2) { + transformList.addAll(parseJuliaTransform(transformation)); + } else if (transformation.length == 6) { + transformList.add(parseAffineTransform(transformation)); + } + } + return transformList; + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid coordinates. Please enter valid numbers."); + } + } + + /** + * Parses the Julia transformations and returns a list of JuliaTransform objects. + * + * @param transformation the string array containing the transformation parameters + * @return the list of JuliaTransform objects + */ + private static List<Transform2D> parseJuliaTransform(String[] transformation) { + return List.of( + new JuliaTransform( + new Complex( + Double.parseDouble(transformation[0]), + Double.parseDouble(transformation[1]) + ), + 1), + new JuliaTransform( + new Complex( + Double.parseDouble(transformation[0]), + Double.parseDouble(transformation[1]) + ), + 1) + ); + } + + /** + * Parses the affine transformations and returns an AffineTransform2D object. + * + * @param transformation the string array containing the transformation parameters + * @return the AffineTransform2D object + */ + private static AffineTransform2D parseAffineTransform(String[] transformation) { + return new AffineTransform2D( + new Matrix2x2( + Double.parseDouble(transformation[0]), + Double.parseDouble(transformation[1]), + Double.parseDouble(transformation[2]), + Double.parseDouble(transformation[3]) + ), + new Vector2d( + Double.parseDouble(transformation[4]), + Double.parseDouble(transformation[5]) + )); + } +} diff --git a/src/main/java/edu/ntnu/idatt2003/view/CommandLineInterface.java b/src/main/java/edu/ntnu/idatt2003/view/CommandLineInterface.java new file mode 100644 index 0000000000000000000000000000000000000000..8b73135a3d2581264b199551d9f401c76bbbaff5 --- /dev/null +++ b/src/main/java/edu/ntnu/idatt2003/view/CommandLineInterface.java @@ -0,0 +1,270 @@ +package edu.ntnu.idatt2003.view; + +import static edu.ntnu.idatt2003.view.CommandLineMenuRenderer.READ_FILE; +import static edu.ntnu.idatt2003.view.CommandLineMenuRenderer.EXIT; +import static edu.ntnu.idatt2003.view.CommandLineMenuRenderer.RUN_ITERATIONS; +import static edu.ntnu.idatt2003.view.CommandLineMenuRenderer.SHOW_CANVAS; +import static edu.ntnu.idatt2003.view.CommandLineMenuRenderer.WRITE_FILE; +import static edu.ntnu.idatt2003.view.CommandLineMenuRenderer.WRITE_NEW_DESCRIPTION; + +import edu.ntnu.idatt2003.model.AffineTransform2D; +import edu.ntnu.idatt2003.model.ChaosGame; +import edu.ntnu.idatt2003.model.ChaosGameDescription; +import edu.ntnu.idatt2003.model.ChaosGameFileHandler; +import edu.ntnu.idatt2003.model.Complex; +import edu.ntnu.idatt2003.model.JuliaTransform; +import edu.ntnu.idatt2003.model.Matrix2x2; +import edu.ntnu.idatt2003.model.Transform2D; +import edu.ntnu.idatt2003.model.Vector2d; +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + +/** + * Represents a command-line interface for the ChaosGame application. + * This class handles user interactions through the console, allowing + * the user to read and write descriptions from/to files, run iterations, + * show the canvas, and create new descriptions. + */ +public class CommandLineInterface { + CommandLineMenuRenderer menuRenderer = new CommandLineMenuRenderer(); + ChaosGameDescription description; + ChaosGame chaosGame; + private final Scanner scanner = new Scanner(System.in); + + /** + * Starts the user interface. Asks the user for input and performs the corresponding action. + * Breaks the loop when the user chooses to exit, which is when the user inputs "6". + * Invalid input will result in a message. + */ + public void start() { + String choice; + do { + menuRenderer.showMenu(); + choice = getUserChoice(); + switch (choice) { + case READ_FILE: + readDescriptionFromFile(); + break; + case WRITE_FILE: + writeDescriptionToFile(); + break; + case RUN_ITERATIONS: + runIterations(); + break; + case SHOW_CANVAS: + showCanvas(); + break; + case WRITE_NEW_DESCRIPTION: + createNewDescription(); + break; + default: + System.out.println("Invalid choice"); + break; + } + } while (!choice.equals(EXIT)); + } + + /** + * Gets the user's choice from the console. + * + * @return The user's choice as a String. + */ + public static String getUserChoice() { + Scanner scanner = new Scanner(System.in); + return scanner.nextLine(); + } + + /** + * Gets the user's input from the console, ensuring it is not empty. + * + * @return The user's input as a String. + */ + public String textInput() { + String text; + do { + text = scanner.nextLine(); + if (text.isEmpty()) { + System.out.println("Input cannot be empty. Please try again."); + } + } while (text.isEmpty()); + return text; + } + + /** + * Gets the user's numeric input from the console, ensuring it is a valid number and not empty. + * + * @return The user's input as an int. + */ + public int numberInput() { + int number; + while (true) { + String input = scanner.nextLine(); + if (!input.isEmpty()) { + try { + number = Integer.parseInt(input); + break; + } catch (NumberFormatException e) { + System.out.println("Invalid input. Please enter a valid number."); + } + } else { + System.out.println("Input cannot be empty. Please try again."); + } + } + return number; + } + + /** + * Gets the user's vector input from the console, ensuring it contains two valid numbers. + * + * @return The user's input as a Vector2d. + */ + public Vector2d vectorInput() { + while (true) { + String input = scanner.nextLine(); + if (!input.isEmpty()) { + String[] parts = input.split(" "); + if (parts.length == 2) { + try { + double x = Double.parseDouble(parts[0]); + double y = Double.parseDouble(parts[1]); + return new Vector2d(x, y); + } catch (NumberFormatException e) { + System.out.println("Invalid input. Please enter two valid numbers separated by a space."); + } + } else { + System.out.println("Invalid input. Please enter exactly two numbers separated by a space."); + } + } else { + System.out.println("Input cannot be empty. Please try again."); + } + } + } + + public Matrix2x2 matrixInput() { + while (true) { + System.out.println("Enter matrix elements (a b c d):"); + String input = scanner.nextLine(); + if (!input.isEmpty()) { + String[] parts = input.split(" "); + if (parts.length == 4) { + try { + double a = Double.parseDouble(parts[0]); + double b = Double.parseDouble(parts[1]); + double c = Double.parseDouble(parts[2]); + double d = Double.parseDouble(parts[3]); + return new Matrix2x2(a, b, c, d); + } catch (NumberFormatException e) { + System.out.println("Invalid input. Please enter four valid numbers separated by spaces."); + } + } else { + System.out.println("Invalid input. Please enter exactly four numbers separated by spaces."); + } + } else { + System.out.println("Input cannot be empty. Please try again."); + } + } + } + + /** + * Reads a ChaosGameDescription from a file specified by the user. + * Sets the description and initializes the chaosGame with the new description. + */ + public void readDescriptionFromFile() { + menuRenderer.enterPath(); + String pathToFile = textInput(); + try { + ChaosGameDescription newDescription = ChaosGameFileHandler.readFromFile(pathToFile); + this.description = newDescription; + this.chaosGame = new ChaosGame(description, 30, 30); + System.out.println("File read and description set successfully"); + } catch (FileNotFoundException e) { + System.out.println("File not found: " + pathToFile); + } + } + + /** + * Writes the current ChaosGameDescription to a file specified by the user. + */ + public void writeDescriptionToFile() { + menuRenderer.enterPath(); + String pathToFile = textInput(); + ChaosGameFileHandler.writeToFile(description, pathToFile); + } + + /** + * Runs iterations for the chaos game. Asks the user for the number of steps to run the simulation. + */ + public void runIterations() { + if (chaosGame == null || description == null) { + System.out.println("Description or chaosGame is null"); + return; + } + menuRenderer.enterSteps(); + int steps = numberInput(); + chaosGame.runStepsAndUpdateTotal(steps); + } + + /** + * Displays the canvas for the current chaos game. + */ + public void showCanvas() { + if (chaosGame == null) { + System.out.println("ChaosGame is null"); + return; + } + chaosGame.getCanvas().showCanvas(); + } + + /** + * Creates a new ChaosGameDescription based on user input. + */ + public void createNewDescription() { + System.out.println("Enter minimum coordinates (x y):"); + Vector2d minCoords = vectorInput(); + + System.out.println("Enter maximum coordinates (x y):"); + Vector2d maxCoords = vectorInput(); + + List<Transform2D> transformations = new ArrayList<>(); + System.out.println("Enter the number of transformations:"); + int numTransformations = numberInput(); + + for (int i = 0; i < numTransformations; i++) { + System.out.println("Enter transformation type (affine/julia):"); + String type = textInput(); + if (type.equalsIgnoreCase("affine")) { + Matrix2x2 matrix = matrixInput(); + System.out.println("Enter vector elements (e f):"); + Vector2d vector = vectorInput(); + transformations.add(new AffineTransform2D(matrix, vector)); + } else if (type.equalsIgnoreCase("julia")) { + System.out.println("Enter real and imaginary parts of the complex number:"); + double real = numberInput(); + double imaginary = numberInput(); + transformations.add(new JuliaTransform(new Complex(real, imaginary), 1)); + transformations.add(new JuliaTransform(new Complex(real, imaginary), -1)); + } else { + System.out.println("Invalid transformation type. Please enter 'affine' or 'julia'."); + i--; + } + } + + + ChaosGameDescription newDescription = new ChaosGameDescription(minCoords, maxCoords, transformations); + this.description = newDescription; + this.chaosGame = new ChaosGame(description, 30, 30); + System.out.println("New description created successfully"); + } + + /** + * The main method to run the command-line interface. + * + * @param args Command line arguments + */ + public static void main(String[] args) { + CommandLineInterface cli = new CommandLineInterface(); + cli.start(); + } +} \ No newline at end of file diff --git a/src/main/java/edu/ntnu/idatt2003/view/TextRenderer.java b/src/main/java/edu/ntnu/idatt2003/view/CommandLineMenuRenderer.java similarity index 66% rename from src/main/java/edu/ntnu/idatt2003/view/TextRenderer.java rename to src/main/java/edu/ntnu/idatt2003/view/CommandLineMenuRenderer.java index 9bc904284d8a84d088ab679c69bf45ec6d7b2f27..a864df131652f746b2781cc516b17cfd2fcf3ce3 100644 --- a/src/main/java/edu/ntnu/idatt2003/view/TextRenderer.java +++ b/src/main/java/edu/ntnu/idatt2003/view/CommandLineMenuRenderer.java @@ -7,31 +7,44 @@ package edu.ntnu.idatt2003.view; * */ -public class TextRenderer { +public class CommandLineMenuRenderer { public static final String READ_FILE = "1"; public static final String WRITE_FILE = "2"; public static final String RUN_ITERATIONS = "3"; public static final String SHOW_CANVAS = "4"; - public static final String EXIT = "5"; + public static final String WRITE_NEW_DESCRIPTION = "5"; + public static final String EXIT = "6"; + + /** + * shows the menu + */ public void showMenu() { System.out.println("Menu:"); System.out.println("1. Read description from file"); - System.out.println("2. Write description to file"); + System.out.println("2. Write current description to file"); System.out.println("3. Run ChaosGame a given number of steps"); System.out.println("4. Show Canvas"); - System.out.println("5. Exit"); + System.out.println("5. Write New Description"); + System.out.println("6. Exit"); } + /** + * shows the enter path message + */ + public void enterPath() { System.out.println("Enter path to file:"); } + /** + * shows the enter steps message + */ + public void enterSteps() { System.out.println("Enter number of steps:"); } - } diff --git a/src/main/java/edu/ntnu/idatt2003/view/MainPageView.java b/src/main/java/edu/ntnu/idatt2003/view/MainPageView.java index 6a5e295e8e98b690c17f979a959fc81070f0c463..ce866efc9a09faf94d205d07f318b134ee9dc711 100644 --- a/src/main/java/edu/ntnu/idatt2003/view/MainPageView.java +++ b/src/main/java/edu/ntnu/idatt2003/view/MainPageView.java @@ -7,6 +7,7 @@ import static edu.ntnu.idatt2003.view.components.TextBoxFactory.createTextBox; import static edu.ntnu.idatt2003.view.components.TextFieldFactory.createTextField; import edu.ntnu.idatt2003.controller.MainPageController; +import edu.ntnu.idatt2003.utils.TransformationParser; import edu.ntnu.idatt2003.model.ChaosGameDescriptionFactory; import edu.ntnu.idatt2003.model.ChaosGameObserver; import edu.ntnu.idatt2003.utils.Sizes; @@ -132,7 +133,7 @@ public class MainPageView extends Scene implements ChaosGameObserver { dynamicJuliaContainer.setAlignment(Pos.BOTTOM_CENTER); TextField x0Field = createTextField("x: ", 100, 20); TextField x1Field = createTextField("y: ", 100, 20); - if (controller.fractalIsJulia()) { + if (TransformationParser.fractalIsJulia(controller.getGame())) { VBox juliaInformationContainer = new VBox(DEFAULT_SPACING); HBox.setHgrow(juliaInformationContainer, Priority.ALWAYS); juliaInformationContainer.getChildren().addAll( @@ -297,7 +298,7 @@ public class MainPageView extends Scene implements ChaosGameObserver { private TransformationType getTransformationComboBoxValue() { if (controller.isAddingCustomFractal()) { return selectedTransformation; - } else if (controller.fractalIsJulia()) { + } else if (TransformationParser.fractalIsJulia(controller.getGame())) { return TransformationType.JULIA; } else { return TransformationType.AFFINE; @@ -332,7 +333,7 @@ public class MainPageView extends Scene implements ChaosGameObserver { private List<String[]> getJuliaTransformation(VBox transformationVbox) { List<String[]> list = new ArrayList<>(); if (!transformationVbox.getChildren().isEmpty()) { - HBox juliaFields = (HBox) transformationVbox.getChildren().get(0); + HBox juliaFields = (HBox) transformationVbox.getChildren().getFirst(); list.add(new String[]{((TextField) juliaFields.getChildren().get(1)).getText(), ((TextField) juliaFields.getChildren().get(2)).getText()}); } @@ -392,7 +393,7 @@ public class MainPageView extends Scene implements ChaosGameObserver { ); } if (!controller.isAddingCustomFractal()) { - for (double[] coords : controller.getTransformList()) { + for (double[] coords : TransformationParser.getTransformList(controller.getGame())) { vbox.getChildren().add(createTextBoxWithTextFieldsContainer(DEFAULT_SPACING, textBoxText, 55, 20, coords)); } diff --git a/src/main/java/edu/ntnu/idatt2003/view/UI.java b/src/main/java/edu/ntnu/idatt2003/view/UI.java deleted file mode 100644 index 036465a0fb45affecfb85cd0ec7cee1cc7787969..0000000000000000000000000000000000000000 --- a/src/main/java/edu/ntnu/idatt2003/view/UI.java +++ /dev/null @@ -1,176 +0,0 @@ -package edu.ntnu.idatt2003.view; - -import static edu.ntnu.idatt2003.view.TextRenderer.READ_FILE; -import static edu.ntnu.idatt2003.view.TextRenderer.EXIT; -import static edu.ntnu.idatt2003.view.TextRenderer.RUN_ITERATIONS; -import static edu.ntnu.idatt2003.view.TextRenderer.SHOW_CANVAS; -import static edu.ntnu.idatt2003.view.TextRenderer.WRITE_FILE; - -import edu.ntnu.idatt2003.model.ChaosGame; -import edu.ntnu.idatt2003.model.ChaosGameDescription; -import edu.ntnu.idatt2003.model.ChaosGameFileHandler; -import java.io.FileNotFoundException; -import java.util.Scanner; - -/** - * Represents a user interface. - * Contains methods for starting the UI, reading a description from a file, writing a description to a file, - * running iterations and showing the canvas. - * Includes a scanner for user input, and instances of TextRenderer, ChaosGameFileHandler, ChaosGame and ChaosGameDescription. - * Goal: act as a view for a user interface. - */ - -public class UI { - TextRenderer textRenderer = new TextRenderer(); - ChaosGameDescription description; - ChaosGame chaosGame; - private final Scanner scanner = new Scanner(System.in); - - /** - * Starts the user interface. Asks the user for input and performs the corresponding action. - * Breaks the loop when the user chooses to exit, which is when the user inputs "5". - * Invalid input will result in a message. - */ - - public void start() { - String choice; - do { - textRenderer.showMenu(); - choice = getUserChoice(); - switch (choice) { - case READ_FILE: - readDescriptionFromFile(); - break; - case WRITE_FILE: - writeDescriptionToFile(); - break; - case RUN_ITERATIONS: - runIterations(); - break; - case SHOW_CANVAS: - showCanvas(); - break; - default: - System.out.println("Invalid choice"); - break; - } - } while (!choice.equals(EXIT)); - } - - /** - * Gets the user's choice. - * - * @return The user's choice. - */ - - public static String getUserChoice() { - Scanner scanner = new Scanner(System.in); - return scanner.nextLine(); - } - - /** - * Gets the user's input, and checks if it is empty. - * - * @return The user's input. - */ - - public String textInput() { - String text; - do { - text = scanner.nextLine(); - if (text.isEmpty()) { - System.out.println("Input cannot be empty. Please try again."); - } - } while (text.isEmpty()); - return text; - } - - /** - * Gets the user's input, and checks if it is a number and whether it is empty. - * - * @return The user's input. - */ - - - public int numberInput() { - int number; - while (true) { - String input = scanner.nextLine(); - if (!input.isEmpty()) { - try { - number = Integer.parseInt(input); - break; - } catch (NumberFormatException e) { - System.out.println("Invalid input. Please enter a valid number."); - } - } else { - System.out.println("Input cannot be empty. Please try again."); - } - } - return number; - } - - /** - * Reads a description from a file. - * Asks the user for the path to the file, and reads the already existing description from the file. - * Sets the description and chaosGame to the new description. - * If the file is not found, a message will be shown. - */ - - - public void readDescriptionFromFile(){ - textRenderer.enterPath(); - String pathToFile = textInput(); - ChaosGameDescription newDescription; - try { - newDescription = ChaosGameFileHandler.readFromFile(pathToFile); - System.out.println("file read successfully"); - } catch (FileNotFoundException e) { - throw new RuntimeException("File '" + pathToFile + "' not found." + e.getMessage()); - } - this.description = newDescription; - chaosGame = new ChaosGame(description, 30, 30); - System.out.println("description set successfully"); - } - - /** - * Writes a description to a file. - * Asks the user for the path to the file, and writes the description to the file. - * If the file is not found, a message will be shown. - */ - - public void writeDescriptionToFile() { - textRenderer.enterPath(); - String pathToFile = textInput(); - ChaosGameFileHandler.writeToFile(description, pathToFile); - } - - /** - * Runs iterations. - * Asks the user for the number of steps to run the simulation. - * Runs the simulation for the specified number of steps. - */ - - public void runIterations() { - if(chaosGame == null || description == null) { - System.out.println("description or chaosGame is null"); - return; - } - textRenderer.enterSteps(); - int steps = numberInput(); - chaosGame.runStepsAndUpdateTotal(steps); - } - - /** - * Shows the canvas, and ensures that chaosGame is not null. - */ - - public void showCanvas() { - if(chaosGame == null) { - System.out.println("chaosGame is null"); - return; - } - chaosGame.getCanvas().showCanvas(); - } - -} diff --git a/src/test/java/edu/ntnu/idatt2003/controller/GameStateManagerTest.java b/src/test/java/edu/ntnu/idatt2003/controller/GameStateManagerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..2e12822483c33641460c12c310eb36e5f8612a22 --- /dev/null +++ b/src/test/java/edu/ntnu/idatt2003/controller/GameStateManagerTest.java @@ -0,0 +1,102 @@ +package edu.ntnu.idatt2003.controller; + +import edu.ntnu.idatt2003.model.ChaosGame; +import edu.ntnu.idatt2003.model.ChaosGameDescriptionFactory; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.io.TempDir; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Test class for the GameStateManager class. + * This class contains tests for verifying the functionality of the GameStateManager. + */ +public class GameStateManagerTest { + + @TempDir + Path tempDir; + + private Path tempFilePath; + private ChaosGame game; + + @BeforeEach + public void setUp() { + tempFilePath = tempDir.resolve("savedGameStateTest.ser"); + + game = new ChaosGame( + ChaosGameDescriptionFactory.get( + ChaosGameDescriptionFactory.DescriptionTypeEnum.SIERPINSKI_TRIANGLE), + 650, 650); + game.setDescriptionName( + ChaosGameDescriptionFactory.DescriptionTypeEnum.SIERPINSKI_TRIANGLE.toString()); + } + + @Nested + @DisplayName("Positive Tests") + class positiveTest { + + /** + * Tests that the game state is loaded correctly. + */ + @Test + @DisplayName("Load game state") + public void testLoadGameState() { + GameStateManager.saveGameState(game); + + ChaosGame loadedGame = GameStateManager.loadGameState(); + assertNotNull(loadedGame, "Loaded game state should not be null"); + assertEquals(game.getDescriptionName(), loadedGame.getDescriptionName(), + "Loaded game state should match saved state"); + } + + /** + * Tests that a new game state is created when no saved game state exists. + */ + @Test + @DisplayName("Load new game state when no saved state exists") + public void testLoadNewGameStateWhenNoSavedStateExists() { + assertNotNull(game, "New game state should not be null"); + assertEquals("SIERPINSKI_TRIANGLE", game.getDescriptionName(), + "New game state should have default description"); + } + + + } + + @Nested + @DisplayName("Negative tests") + class negativeTests { + + /** + * Tests that an IOException is handled gracefully when saving the game state. + */ + @Test + @DisplayName("Handle IOException during save") + public void testHandleIOExceptionDuringSave() { + + assertDoesNotThrow(() -> GameStateManager.saveGameState(game), + "Saving game state should not throw an exception"); + } + + /** + * Tests that an IOException is handled gracefully when loading the game state. + */ + @Test + @DisplayName("Handle IOException during load") + public void testHandleIOExceptionDuringLoad() { + try { + Files.write(tempFilePath, new byte[]{0, 1, 2, 3}); + } catch (IOException e) { + fail("Unexpected exception writing to temp file: " + e.getMessage()); + } + + ChaosGame loadedGame = assertDoesNotThrow(() -> GameStateManager.loadGameState(), + "Loading game state should not throw an exception"); + assertNotNull(loadedGame, "Loaded game state should not be null despite IOException"); + } +} + +} diff --git a/src/test/java/edu/ntnu/idatt2003/model/ChaosGameDescriptionFactoryTest.java b/src/test/java/edu/ntnu/idatt2003/model/ChaosGameDescriptionFactoryTest.java index 6be5ff0047ca1696ca9a45ce197629caff61b67c..2fb09763ab72296e9facc809cb598e247f049aa1 100644 --- a/src/test/java/edu/ntnu/idatt2003/model/ChaosGameDescriptionFactoryTest.java +++ b/src/test/java/edu/ntnu/idatt2003/model/ChaosGameDescriptionFactoryTest.java @@ -23,6 +23,8 @@ public class ChaosGameDescriptionFactoryTest { /** * Tests getting the Sierpinski Triangle description. + * This test checks if the Sierpinski Triangle description is correctly created with the expected minimum and maximum coordinates + * and the correct number and types of transformations. */ @Test @DisplayName("Get Sierpinski Triangle Test") @@ -43,6 +45,8 @@ public class ChaosGameDescriptionFactoryTest { /** * Tests getting the Barnsley Fern description. + * This test checks if the Barnsley Fern description is correctly created with the expected minimum and maximum coordinates + * and the correct number and types of transformations. */ @Test @DisplayName("Get Barnsley Fern Test") @@ -64,6 +68,8 @@ public class ChaosGameDescriptionFactoryTest { /** * Tests getting the Julia transformation description. + * This test checks if the Julia transformation description is correctly created with the expected minimum and maximum coordinates + * and the correct number and types of transformations. */ @Test @DisplayName("Get Julia Transformation Test") @@ -80,6 +86,48 @@ public class ChaosGameDescriptionFactoryTest { assertInstanceOf(JuliaTransform.class, transforms.get(0)); assertInstanceOf(JuliaTransform.class, transforms.get(1)); } + + /** + * Tests getting the Levy C Curve description. + * This test checks if the Levy C Curve description is correctly created with the expected minimum and maximum coordinates + * and the correct number and types of transformations. + */ + @Test + @DisplayName("Get Levy C Curve Test") + void testGetLevyCCurve() { + ChaosGameDescription description = ChaosGameDescriptionFactory.get(ChaosGameDescriptionFactory.DescriptionTypeEnum.LEVY_C_CURVE); + + assertNotNull(description); + assertEquals(new Vector2d(-1, -0.5).toString(), description.getMinCoords().toString()); + assertEquals(new Vector2d(2, 1.5).toString(), description.getMaxCoords().toString()); + + List<Transform2D> transforms = description.getTransform(); + assertEquals(2, transforms.size()); + + assertInstanceOf(AffineTransform2D.class, transforms.get(0)); + assertInstanceOf(AffineTransform2D.class, transforms.get(1)); + } + + /** + * Tests getting the Dragon Curve description. + * This test checks if the Dragon Curve description is correctly created with the expected minimum and maximum coordinates + * and the correct number and types of transformations. + */ + @Test + @DisplayName("Get Dragon Curve Test") + void testGetDragonCurve() { + ChaosGameDescription description = ChaosGameDescriptionFactory.get(ChaosGameDescriptionFactory.DescriptionTypeEnum.DRAGON_CURVE); + + assertNotNull(description); + assertEquals(new Vector2d(-7, 0).toString(), description.getMinCoords().toString()); + assertEquals(new Vector2d(6, 11).toString(), description.getMaxCoords().toString()); + + List<Transform2D> transforms = description.getTransform(); + assertEquals(2, transforms.size()); + + assertInstanceOf(AffineTransform2D.class, transforms.get(0)); + assertInstanceOf(AffineTransform2D.class, transforms.get(1)); + } } /** @@ -91,6 +139,8 @@ public class ChaosGameDescriptionFactoryTest { /** * Tests getting a custom transformation file that does not exist. + * This test checks if a FileNotFoundException is thrown when trying to get a custom transformation + * that does not exist. */ @Test @DisplayName("Get Custom Transformation File Not Found Test") @@ -99,7 +149,7 @@ public class ChaosGameDescriptionFactoryTest { ChaosGameDescriptionFactory.getCustom("non_existent_transformation") ); - String expectedMessage = "File src/main/resources/transformations/non_existent_transformation.txt not found."; + String expectedMessage = "File src/main/resources/fractals/non_existent_transformation.txt not found."; String actualMessage = exception.getMessage(); assertTrue(actualMessage.contains(expectedMessage)); @@ -110,4 +160,3 @@ public class ChaosGameDescriptionFactoryTest { - diff --git a/src/test/java/edu/ntnu/idatt2003/model/ChaosGameFileHandlerTest.java b/src/test/java/edu/ntnu/idatt2003/model/ChaosGameFileHandlerTest.java index 7cf83cbc10af3d499a1c2630e94501c9c15f6807..b217ea5a5838fed2cf532dab0c8027bd65baf1fe 100644 --- a/src/test/java/edu/ntnu/idatt2003/model/ChaosGameFileHandlerTest.java +++ b/src/test/java/edu/ntnu/idatt2003/model/ChaosGameFileHandlerTest.java @@ -1,13 +1,13 @@ package edu.ntnu.idatt2003.model; import org.junit.jupiter.api.*; - import java.io.FileNotFoundException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.InputMismatchException; import java.util.List; + import static org.junit.jupiter.api.Assertions.*; /** @@ -126,36 +126,35 @@ class ChaosGameFileHandlerTest { class NegativeTests { /** - * Tests reading from a non-existing file. - * Verifies that a FileNotFoundException is thrown. + * Tests reading from a non-existing file. Verifies that a FileNotFoundException is thrown. */ @Test @DisplayName("Test readChaosGameDescription with non-existing file") void testReadChaosGameDescriptionWithNonExistingFile() { assertThrows(FileNotFoundException.class, () -> ChaosGameFileHandler - .readFromFile("non-existing-file.txt")); + .readFromFile("non-existing-file.txt")); } /** - * Test reading from a file with an unknown transformation type. - * Verifies that an IllegalArgumentException is thrown. + * Test reading from a file with an unknown transformation type. Verifies that an + * IllegalArgumentException is thrown. */ @Test @DisplayName("Test readChaosGameDescription with unknown transformation type") void testReadChaosGameDescriptionWithUnknownTransformationType() { assertThrows(IllegalArgumentException.class, () -> ChaosGameFileHandler - .readFromFile("src/test/resources/invalidNameExample.txt")); + .readFromFile("src/test/resources/invalidNameExample.txt")); } /** - * Tests reading from a file with an invalid number of arguments. - * Verifies that an IllegalArgumentException is thrown. + * Tests reading from a file with an invalid number of arguments. Verifies that an + * IllegalArgumentException is thrown. */ @Test @DisplayName("Test readChaosGameDescription with invalid number of arguments") void testReadChaosGameDescriptionWithInvalidNumberOfArguments() { assertThrows(InputMismatchException.class, () -> ChaosGameFileHandler - .readFromFile("src/test/resources/invalidFormatExample.txt")); + .readFromFile("src/test/resources/invalidFormatExample.txt")); } } } diff --git a/src/test/java/edu/ntnu/idatt2003/utils/LoggerUtilTest.java b/src/test/java/edu/ntnu/idatt2003/utils/LoggerUtilTest.java new file mode 100644 index 0000000000000000000000000000000000000000..8081c04ea279091485befe73a7bb96d60a9ababe --- /dev/null +++ b/src/test/java/edu/ntnu/idatt2003/utils/LoggerUtilTest.java @@ -0,0 +1,54 @@ +package edu.ntnu.idatt2003.utils; + +import org.junit.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.File; +import java.util.logging.FileHandler; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Test class for the LoggerUtil class. + * This class contains tests for verifying the configuration and functionality of the LoggerUtil. + */ +public class LoggerUtilTest { + + private static final String LOG_FILE_PATH = "src/test/resources"; + private static Logger logger; + + /** + * Sets up the test environment before each test. + * Initializes the logger and ensures that the log file is deleted before each test. + */ + @BeforeEach + public void setUp() { + logger = LoggerUtil.setupLogger(LoggerUtilTest.class.getName()); + } + + /** + * Tests that the logger is configured correctly. + * Verifies that the logger is not null, the log file is created, the logger has a FileHandler, + * and the logger level is set to ALL. + */ + @Test + @DisplayName("Logger is configured correctly") + public void testLoggerConfiguration() { + assertNotNull(logger); + + File logFile = new File(LOG_FILE_PATH); + assertTrue(logFile.exists(), "Log file should exist"); + + boolean hasFileHandler = false; + for (var handler : logger.getHandlers()) { + if (handler instanceof FileHandler) { + hasFileHandler = true; + break; + } + } + assertTrue(hasFileHandler, "Logger should have a FileHandler"); + + assertEquals(Level.ALL, logger.getLevel(), "Logger level should be ALL"); + } +} \ No newline at end of file diff --git a/src/test/java/edu/ntnu/idatt2003/utils/TransformationUtilTest.java b/src/test/java/edu/ntnu/idatt2003/utils/TransformationUtilTest.java new file mode 100644 index 0000000000000000000000000000000000000000..ede8f67f92a7b4615a2546d2c3c200feb631c327 --- /dev/null +++ b/src/test/java/edu/ntnu/idatt2003/utils/TransformationUtilTest.java @@ -0,0 +1,153 @@ +package edu.ntnu.idatt2003.utils; + +import edu.ntnu.idatt2003.model.AffineTransform2D; +import edu.ntnu.idatt2003.model.ChaosGame; +import edu.ntnu.idatt2003.model.ChaosGameDescription; +import edu.ntnu.idatt2003.model.Complex; +import edu.ntnu.idatt2003.model.JuliaTransform; +import edu.ntnu.idatt2003.model.Matrix2x2; +import edu.ntnu.idatt2003.model.Transform2D; +import edu.ntnu.idatt2003.model.Vector2d; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.ArrayList; +import java.util.List; + +public class TransformationUtilTest { + + private ChaosGame juliaGame; + private ChaosGame affineGame; + private List<String[]> juliaTransformStrings; + private List<String[]> affineTransformStrings; + + @BeforeEach + public void setUp() { + List<Transform2D> juliaTransforms = new ArrayList<>(); + juliaTransforms.add(new JuliaTransform(new Complex(0.355, 0.355), 1)); + juliaGame = new ChaosGame(new ChaosGameDescription( + new Vector2d(0,0), new Vector2d(1,1),juliaTransforms), 800, 800); + + List<Transform2D> affineTransforms = new ArrayList<>(); + affineTransforms.add(new AffineTransform2D( + new Matrix2x2(0.5, 0, 0, 0.5), new Vector2d(1, 1))); + affineGame = new ChaosGame(new ChaosGameDescription( + new Vector2d(0,0), new Vector2d(1,1),affineTransforms), 800, 800); + + juliaTransformStrings = new ArrayList<>(); + juliaTransformStrings.add(new String[]{"0.355", "0.355"}); + + affineTransformStrings = new ArrayList<>(); + affineTransformStrings.add(new String[]{"0.5", "0", "0", "0.5", "1", "1"}); + } + + @Nested + @DisplayName("Positive Tests") + class PositiveTests { + + /** + * Tests that the transformation list for a Julia fractal is returned correctly. + */ + @Test + @DisplayName("Get transformation list for Julia fractal") + public void testGetTransformListJulia() { + List<double[]> transformList = TransformationParser.getTransformList(juliaGame); + assertEquals(1, transformList.size()); + assertArrayEquals(new double[]{0.355, 0.355}, transformList.getFirst()); + } + + /** + * Tests that the transformation list for an affine fractal is returned correctly. + */ + @Test + @DisplayName("Get transformation list for affine fractal") + public void testGetTransformListAffine() { + List<double[]> transformList = TransformationParser.getTransformList(affineGame); + assertEquals(1, transformList.size()); + assertArrayEquals(new double[]{0.5, 0, 0, 0.5, 1, 1}, transformList.getFirst()); + } + + /** + * Tests that the method correctly identifies a Julia fractal. + */ + @Test + @DisplayName("Check if fractal is Julia") + public void testFractalIsJulia() { + assertTrue(TransformationParser.fractalIsJulia(juliaGame)); + } + + /** + * Tests that the method correctly identifies when the fractal is not a Julia fractal. + */ + @Test + @DisplayName("Check if fractal is not Julia") + public void testFractalIsNotJulia() { + assertFalse(TransformationParser.fractalIsJulia(affineGame)); + } + + /** + * Tests the conversion from a string array to a Vector2d object. + */ + @Test + @DisplayName("Convert string array to Vector2d") + public void testGetVector2dFromStringList() { + String[] vector = {"1.0", "2.0"}; + Vector2d vec = TransformationParser.getVector2dFromStringList(vector); + assertEquals(1.0, vec.getX0()); + assertEquals(2.0, vec.getX1()); + } + + /** + * Tests the creation of JuliaTransform objects from string arrays. + */ + @Test + @DisplayName("Create JuliaTransform list from string arrays") + public void testGetTransformListFromStringListJulia() { + List<Transform2D> transforms = TransformationParser.getTransformListFromStringList(juliaTransformStrings); + assertEquals(2, transforms.size()); + assertTrue(transforms.get(0) instanceof JuliaTransform); + assertTrue(transforms.get(1) instanceof JuliaTransform); + } + + /** + * Tests the creation of AffineTransform2D objects from string arrays. + */ + @Test + @DisplayName("Create AffineTransform2D list from string arrays") + public void testGetTransformListFromStringListAffine() { + List<Transform2D> transforms = TransformationParser.getTransformListFromStringList(affineTransformStrings); + assertEquals(1, transforms.size()); + assertTrue(transforms.getFirst() instanceof AffineTransform2D); + } + } + + @Nested + @DisplayName("Negative Tests") + class NegativeTests { + + /** + * Tests that an IllegalArgumentException is thrown for invalid input when converting string array to Vector2d. + */ + @Test + @DisplayName("Invalid input for converting string array to Vector2d") + public void testGetVector2dFromStringListInvalid() { + String[] vector = {"abc", "2.0"}; + assertThrows(IllegalArgumentException.class, () -> TransformationParser.getVector2dFromStringList(vector)); + } + + /** + * Tests that an IllegalArgumentException is thrown for invalid input when creating Transform2D list from string arrays. + */ + @Test + @DisplayName("Invalid input for creating Transform2D list from string arrays") + public void testGetTransformListFromStringListInvalid() { + List<String[]> transformStrings = new ArrayList<>(); + transformStrings.add(new String[]{"abc", "0", "0", "0.5", "1", "1"}); + assertThrows(IllegalArgumentException.class, () -> TransformationParser.getTransformListFromStringList(transformStrings)); + } + } +}