feat(resource): Add StaticResource, StaticResourceService, and StaticResourceServiceImpl for resource management

This commit is contained in:
extio-de
2025-12-24 15:02:45 +01:00
parent 9c375ceb0d
commit 09a1eb405f
5 changed files with 700 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
package de.extio.game_engine.resource;
import java.util.List;
public record StaticResource(List<String> pathSegments, String resourceName) {
}

View File

@@ -0,0 +1,17 @@
package de.extio.game_engine.resource;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
@AutoConfiguration
@ConditionalOnProperty(name = "game-engine.resource.enabled", havingValue = "true", matchIfMissing = true)
public class StaticResourceAutoConfiguration {
@Bean
@ConditionalOnMissingBean
StaticResourceService staticResourceService() {
return new StaticResourceServiceImpl();
}
}

View File

@@ -0,0 +1,25 @@
package de.extio.game_engine.resource;
import java.io.InputStream;
import java.util.List;
import java.util.Optional;
public interface StaticResourceService {
<T> Optional<T> loadByPath(Class<T> clazz, List<String> path, String name);
<T> Optional<T> loadByPath(Class<T> clazz, StaticResource resource);
Optional<InputStream> loadStreamByPath(List<String> path, String name);
Optional<InputStream> loadStreamByPath(StaticResource resource);
boolean exists(List<String> path, String name);
boolean exists(StaticResource resource);
List<String> listPath(List<String> path);
List<String> listPathRecursive(List<String> path);
}

View File

@@ -0,0 +1,213 @@
package de.extio.game_engine.resource;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import de.extio.game_engine.storage.StorageServiceImpl;
import de.extio.game_engine.util.ObjectSerialization;
public class StaticResourceServiceImpl implements StaticResourceService {
private static final Logger LOGGER = LoggerFactory.getLogger(StaticResourceServiceImpl.class);
static Path DATA_LOCATION;
static {
String location = System.getProperty("datalocation");
if (location == null) {
try {
final URL url = StorageServiceImpl.class.getProtectionDomain().getCodeSource().getLocation();
final Path jarPath = Paths.get(url.toURI()).getParent().toAbsolutePath().normalize();
final String currentSubDir = jarPath.getFileName().toString();
if ("bin".equals(currentSubDir) || "target".equals(currentSubDir)) {
location = jarPath.getParent().resolve("data").toString();
}
else {
location = jarPath.resolve("data").toString();
}
}
catch (final Exception e) {
LOGGER.error("Could not determine data location, defaulting to 'data'", e);
location = "data";
}
}
DATA_LOCATION = Paths.get(location).toAbsolutePath().normalize();
LOGGER.info("Data location: {}", DATA_LOCATION);
}
private Path resolvePath(final List<String> path, final String name) {
Path result = DATA_LOCATION;
if (path != null) {
for (final String segment : path) {
result = result.resolve(segment);
}
}
if (name != null) {
result = result.resolve(name);
}
result = result.normalize();
if (!result.startsWith(DATA_LOCATION)) {
throw new IllegalArgumentException("Path escapes data location: " + result);
}
return result;
}
@Override
public <T> Optional<T> loadByPath(final Class<T> clazz, final List<String> path, final String name) {
Objects.requireNonNull(clazz, "clazz");
Objects.requireNonNull(name, "name");
final Path filePath = resolvePath(path, name);
if (!Files.exists(filePath)) {
LOGGER.debug("Resource not found: {}", filePath);
return Optional.empty();
}
if (!Files.isRegularFile(filePath)) {
LOGGER.warn("Path is not a regular file: {}", filePath);
return Optional.empty();
}
try {
try (final var in = Files.newInputStream(filePath)) {
final T obj = ObjectSerialization.deserialize(clazz, in, true, false, null, null, null);
if (obj != null) {
LOGGER.info("Loaded resource from {}", filePath);
}
return Optional.ofNullable(obj);
}
}
catch (final Exception e) {
LOGGER.error("Could not load resource from " + filePath.toString(), e);
return Optional.empty();
}
}
@Override
public <T> Optional<T> loadByPath(final Class<T> clazz, final StaticResource resource) {
Objects.requireNonNull(clazz, "clazz");
Objects.requireNonNull(resource, "resource");
return this.loadByPath(clazz, resource.pathSegments(), resource.resourceName());
}
@Override
public Optional<InputStream> loadStreamByPath(final List<String> path, final String name) {
Objects.requireNonNull(name, "name");
final Path filePath = resolvePath(path, name);
if (!Files.exists(filePath)) {
LOGGER.debug("Resource not found: {}", filePath);
return Optional.empty();
}
if (!Files.isRegularFile(filePath)) {
LOGGER.warn("Path is not a regular file: {}", filePath);
return Optional.empty();
}
try {
final InputStream stream = Files.newInputStream(filePath);
LOGGER.info("Loaded resource stream from {}", filePath);
return Optional.of(stream);
}
catch (final Exception e) {
LOGGER.error("Could not load resource stream from " + filePath.toString(), e);
return Optional.empty();
}
}
@Override
public Optional<InputStream> loadStreamByPath(final StaticResource resource) {
Objects.requireNonNull(resource, "resource");
return this.loadStreamByPath(resource.pathSegments(), resource.resourceName());
}
@Override
public boolean exists(final List<String> path, final String name) {
Objects.requireNonNull(name, "name");
final Path filePath = resolvePath(path, name);
return Files.exists(filePath) && Files.isRegularFile(filePath);
}
@Override
public boolean exists(final StaticResource resource) {
Objects.requireNonNull(resource, "resource");
return this.exists(resource.pathSegments(), resource.resourceName());
}
@Override
public List<String> listPath(final List<String> path) {
final Path dirPath = resolvePath(path, null);
if (!Files.exists(dirPath)) {
LOGGER.debug("Directory not found: {}", dirPath);
return List.of();
}
if (!Files.isDirectory(dirPath)) {
LOGGER.warn("Path is not a directory: {}", dirPath);
return List.of();
}
try (final Stream<Path> stream = Files.list(dirPath)) {
return stream
.filter(Files::isRegularFile)
.map(Path::getFileName)
.map(Path::toString)
.sorted()
.toList();
}
catch (final IOException e) {
LOGGER.error("Could not list directory " + dirPath.toString(), e);
return List.of();
}
}
@Override
public List<String> listPathRecursive(final List<String> path) {
final Path dirPath = resolvePath(path, null);
if (!Files.exists(dirPath)) {
LOGGER.debug("Directory not found: {}", dirPath);
return List.of();
}
if (!Files.isDirectory(dirPath)) {
LOGGER.warn("Path is not a directory: {}", dirPath);
return List.of();
}
try (final Stream<Path> stream = Files.walk(dirPath)) {
return stream
.filter(Files::isRegularFile)
.map(dirPath::relativize)
.map(Path::toString)
.sorted()
.toList();
}
catch (final IOException e) {
LOGGER.error("Could not list directory recursively " + dirPath.toString(), e);
return List.of();
}
}
}