Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .idea/encodings.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

101 changes: 91 additions & 10 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,35 +15,107 @@

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<maven.compiler.source>23</maven.compiler.source>
<maven.compiler.target>23</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.12.1</version>
<scope>test</scope>
</dependency>
</dependencies>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.11.4</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.15.2</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.15.2</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>5.2.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.servlet.jsp.jstl</groupId>
<artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>jakarta.servlet.jsp.jstl</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.18.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.18.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.18.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.17</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.18</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<finalName>final-project-module-3</finalName>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<finalName>quest_training</finalName>
<pluginManagement>
<!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.4.0</version>
</plugin>
<!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
<!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.1</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<release>23</release>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
Expand All @@ -63,5 +135,14 @@
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<release>23</release>
</configuration>
</plugin>
</plugins>
</build>
</project>
5 changes: 5 additions & 0 deletions src/main/java/quiz/command/Command.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package quiz.command;

public interface Command {
void execute();
}
25 changes: 25 additions & 0 deletions src/main/java/quiz/command/RedoCommand.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package quiz.command;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import quiz.history.QuestionsHistoryManager;

public class RedoCommand implements Command {
private static final Logger logger = LoggerFactory.getLogger(RedoCommand.class);

private final QuestionsHistoryManager memento;

public RedoCommand(QuestionsHistoryManager memento) {
if (memento == null) {
logger.error("Memento cannot be null");
throw new IllegalArgumentException("Memento cannot be null");
}

this.memento = memento;
}

@Override
public void execute() {
memento.backup();
}
}
25 changes: 25 additions & 0 deletions src/main/java/quiz/command/UndoCommand.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package quiz.command;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import quiz.history.QuestionsHistoryManager;

public class UndoCommand implements Command {
private static final Logger logger = LoggerFactory.getLogger(UndoCommand.class);

private final QuestionsHistoryManager memento;

public UndoCommand(QuestionsHistoryManager memento) {
if (memento == null) {
logger.error("Memento cannot be null");
throw new IllegalArgumentException("Memento cannot be null");
}

this.memento = memento;
}

@Override
public void execute() {
memento.restore();
}
}
5 changes: 5 additions & 0 deletions src/main/java/quiz/deserializer/Deserializer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package quiz.deserializer;

public interface Deserializer<T> {
T deserializeFromFile(String filePath, Class<T> clazz);
}
80 changes: 80 additions & 0 deletions src/main/java/quiz/deserializer/DeserializerBase.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package quiz.deserializer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.Path;

public abstract class DeserializerBase<T> implements Deserializer<T> {
private static final Logger logger = LoggerFactory.getLogger(DeserializerBase.class);

protected void validateFilePath(String filePath) {
if (filePath == null || filePath.isBlank()) {
logger.error("File path cannot be null or empty");
throw new IllegalArgumentException("File path cannot be null or empty");
}
}

protected void validateClass(Class<T> clazz) {
if (clazz == null) {
logger.error("Target class cannot be null");
throw new IllegalArgumentException("Target class cannot be null");
}

if (clazz.isInterface()) {
throw new IllegalArgumentException("Target class cannot be an interface");
}

if (Modifier.isAbstract(clazz.getModifiers())) {
throw new IllegalArgumentException("Target class cannot be abstract");
}
}

protected InputStream loadResourceAsStream(String filePath) throws IOException {
InputStream fileStream = loadFromFileSystem(filePath);
if (fileStream != null) {
return fileStream;
}

return loadFromClasspath(filePath);
}

protected InputStream loadFromFileSystem(String filePath) throws IOException {
validateFilePath(filePath);

Path path = Path.of(filePath).normalize();

if (!Files.exists(path)) {
logger.debug("File not found in filesystem: {}", path);
return null;
}

if (!Files.isReadable(path)) {
logger.error("File exists but is not readable: {}", path);
throw new IOException("File exists but is not readable: " + path);
}

logger.debug("Loading file from filesystem: {}", path);
return Files.newInputStream(path);
}

protected InputStream loadFromClasspath(String resourcePath) {
validateFilePath(resourcePath);

InputStream inputStream = Thread.currentThread()
.getContextClassLoader()
.getResourceAsStream(resourcePath);

if (inputStream == null) {
logger.error("Resource not found in classpath: {}", resourcePath);
throw new IllegalStateException("Resource not found in classpath: " + resourcePath);
}

logger.debug("Loading resource from classpath: {}", resourcePath);
return inputStream;
}
}
34 changes: 34 additions & 0 deletions src/main/java/quiz/deserializer/DeserializerObjectFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package quiz.deserializer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import quiz.deserializer.json.DeserializerJSONObjectMapper;

public class DeserializerObjectFactory {
private static final Logger logger = LoggerFactory.getLogger(DeserializerObjectFactory.class);

public <T> T deserializerAndCreateObject(String filePath, Class<T> clazz) {
Deserializer<T> deserializer = getDeserializer(filePath);
return deserializer.deserializeFromFile(filePath, clazz);
}

private <T> Deserializer<T> getDeserializer(String filePath) {
// Поки що тільки JSON
String extension = getFileExtension(filePath);
return switch (extension) {
case "json" -> new DeserializerJSONObjectMapper<>();
default -> {
logger.error("Unsupported file format: {}", filePath);
throw new IllegalArgumentException("Only JSON format is supported");
}
};
}

private String getFileExtension(String filePath) {
if (filePath == null || filePath.isBlank()) {
return "";
}
int lastDotIndex = filePath.lastIndexOf('.');
return (lastDotIndex > 0) ? filePath.substring(lastDotIndex + 1) : "";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package quiz.deserializer.json;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import quiz.deserializer.DeserializerBase;

import java.io.IOException;
import java.io.InputStream;

public class DeserializerJSONObjectMapper<T> extends DeserializerBase<T> {
private static final Logger logger = LoggerFactory.getLogger(DeserializerJSONObjectMapper.class);

private final ObjectMapper mapper;

public DeserializerJSONObjectMapper() {
this.mapper = new ObjectMapper();
}

DeserializerJSONObjectMapper(ObjectMapper mapper) {
this.mapper = mapper;
}

@Override
public T deserializeFromFile(String filePath, Class<T> clazz) {
logger.debug("Attempting to deserialize JSON file {} to {}", filePath, clazz.getName());

validateFilePath(filePath);
validateClass(clazz);

try (InputStream inputStream = loadResourceAsStream(filePath))
{
T object = mapper.readValue(inputStream, clazz);
if (object == null) {
logger.error("Deserialization failed: got null from file '{}'", filePath);
throw new IllegalStateException("Deserialization returned null from: " + filePath);
}
logger.debug("Successfully deserialized from JSON file '{}' into {}", filePath, object.getClass());
return object;
} catch (IOException e) {
logger.error("Error reading JSON file: {}", filePath);
throw new IllegalStateException("Error reading JSON file: " + filePath, e);
}
}
}
Loading