Added ability to load in BundledResource for localization

This commit is contained in:
lieght
2025-10-01 16:55:40 +02:00
parent 71dab89dc8
commit 8ae7510219
12 changed files with 157 additions and 90 deletions

View File

@@ -3,30 +3,27 @@ package org.toop.framework.asset;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.framework.asset.events.AssetEvents;
import org.toop.framework.asset.resources.BaseResource;
import org.toop.framework.asset.resources.*;
import org.toop.framework.eventbus.EventFlow;
import org.reflections.Reflections;
import java.io.File;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import org.reflections.Reflections;
import org.toop.framework.asset.resources.FileExtension;
import org.toop.framework.asset.resources.FontAsset;
import org.toop.framework.eventbus.EventFlow;
public class AssetLoader {
private static final Logger logger = LogManager.getLogger(AssetLoader.class);
private final List<Asset<? extends BaseResource>> assets = new CopyOnWriteArrayList<>();
private static final List<Asset<? extends BaseResource>> assets = new CopyOnWriteArrayList<>();
private final Map<String, Function<File, ? extends BaseResource>> registry = new ConcurrentHashMap<>();
private volatile int loadedCount = 0;
private volatile int totalCount = 0;
private final AtomicInteger loadedCount = new AtomicInteger(0);
private int totalCount = 0;
public AssetLoader(File rootFolder) {
autoRegisterResources(); // make sure resources are registered!
autoRegisterResources();
List<File> foundFiles = new ArrayList<>();
fileSearcher(rootFolder, foundFiles);
this.totalCount = foundFiles.size();
@@ -38,19 +35,19 @@ public class AssetLoader {
}
public double getProgress() {
return (this.totalCount == 0) ? 1.0 : (this.loadedCount / (double) this.totalCount);
return (totalCount == 0) ? 1.0 : (loadedCount.get() / (double) totalCount);
}
public int getLoadedCount() {
return this.loadedCount;
return loadedCount.get();
}
public int getTotalCount() {
return this.totalCount;
return totalCount;
}
public List<Asset<? extends BaseResource>> getAssets() {
return new ArrayList<>(this.assets);
return new ArrayList<>(assets);
}
public <T extends BaseResource> void register(String extension, Function<File, T> factory) {
@@ -59,8 +56,7 @@ public class AssetLoader {
private <T extends BaseResource> T resourceMapper(File file, Class<T> type) {
String ext = getExtension(file.getName());
Function<File, ? extends BaseResource> factory = this.registry.get(ext);
Function<File, ? extends BaseResource> factory = registry.get(ext);
if (factory == null) return null;
BaseResource resource = factory.apply(file);
@@ -70,32 +66,49 @@ public class AssetLoader {
"File " + file.getName() + " is not of type " + type.getSimpleName()
);
}
return type.cast(resource);
}
private void loader(List<File> files) {
Map<String, BundledResource> bundledResources = new HashMap<>();
for (File file : files) {
BaseResource resource = resourceMapper(file, BaseResource.class);
if (resource != null) {
Asset<? extends BaseResource> asset = new Asset<>(file.getName(), resource);
this.assets.add(asset);
if (resource instanceof FontAsset fontAsset) {
fontAsset.load();
switch (resource) {
case null -> {
continue;
}
case BundledResource br -> {
String key = resource.getClass().getName() + ":" + br.getBaseName();
if (bundledResources.containsKey(key)) {
bundledResources.get(key).loadFile(file);
resource = (BaseResource) bundledResources.get(key);
} else {
br.loadFile(file);
bundledResources.put(key, br);
}
}
case FontAsset fontAsset -> fontAsset.load();
default -> {
}
logger.info("Loaded {} from {}", resource.getClass().getSimpleName(), file.getAbsolutePath());
this.loadedCount++; // TODO: Fix non atmomic operation
new EventFlow()
.addPostEvent(new AssetEvents.LoadingProgressUpdate(this.loadedCount, this.totalCount))
.postEvent();
}
BaseResource finalResource = resource;
boolean alreadyAdded = assets.stream()
.anyMatch(a -> a.getResource() == finalResource);
if (!alreadyAdded) {
assets.add(new Asset<>(file.getName(), resource));
}
logger.info("Loaded {} from {}", resource.getClass().getSimpleName(), file.getAbsolutePath());
loadedCount.incrementAndGet();
new EventFlow()
.addPostEvent(new AssetEvents.LoadingProgressUpdate(loadedCount.get(), totalCount))
.postEvent();
}
logger.info("Loaded {} assets", files.size());
}
private void fileSearcher(final File folder, List<File> foundFiles) {
for (File fileEntry : Objects.requireNonNull(folder.listFiles())) {
if (fileEntry.isDirectory()) {
@@ -114,7 +127,7 @@ public class AssetLoader {
if (!cls.isAnnotationPresent(FileExtension.class)) continue;
FileExtension annotation = cls.getAnnotation(FileExtension.class);
for (String ext : annotation.value()) {
this.registry.put(ext, file -> {
registry.put(ext, file -> {
try {
return cls.getConstructor(File.class).newInstance(file);
} catch (Exception e) {
@@ -125,6 +138,13 @@ public class AssetLoader {
}
}
private static String getBaseName(String fileName) {
int underscoreIndex = fileName.indexOf('_');
int dotIndex = fileName.lastIndexOf('.');
if (underscoreIndex > 0) return fileName.substring(0, underscoreIndex);
return fileName.substring(0, dotIndex);
}
public static String getExtension(String name) {
int i = name.lastIndexOf('.');
return (i > 0) ? name.substring(i + 1) : "";

View File

@@ -61,4 +61,22 @@ public class AssetManager {
assets.put(asset.getName(), asset);
}
// public static LocalizationAsset getLocalization(Locale locale) {
// for (Asset<? extends BaseResource> asset : assets.values()) {
// if (asset.getResource() instanceof LocalizationAsset locAsset) {
// if (!locAsset.isLoaded()) locAsset.load();
// if (locAsset.hasLocale(locale)) return locAsset;
// }
// }
// // fallback NL
// Locale fallback = Locale.forLanguageTag("nl");
// for (Asset<? extends BaseResource> asset : assets.values()) {
// if (asset.getResource() instanceof LocalizationAsset locAsset) {
// if (!locAsset.isLoaded()) locAsset.load();
// if (locAsset.hasLocale(fallback)) return locAsset;
// }
// }
// throw new NoSuchElementException("No localization asset available for locale: " + locale + " or fallback: nl");
// }
}

View File

@@ -0,0 +1,15 @@
package org.toop.framework.asset.resources;
import java.io.File;
public interface BundledResource {
/**
* Load or merge an additional file into this resource.
*/
void loadFile(File file);
/**
* Return a base name for grouping multiple files into this single resource.
*/
String getBaseName();
}

View File

@@ -5,9 +5,10 @@ import java.nio.charset.StandardCharsets;
import java.util.*;
@FileExtension({"properties"})
public class LocalizationAsset extends BaseResource implements LoadableResource {
public class LocalizationAsset extends BaseResource implements LoadableResource, BundledResource {
private final Map<Locale, ResourceBundle> bundles = new HashMap<>();
private boolean isLoaded = false;
private final Locale fallback = Locale.forLanguageTag("");
public LocalizationAsset(File file) {
super(file);
@@ -15,53 +16,57 @@ public class LocalizationAsset extends BaseResource implements LoadableResource
@Override
public void load() {
// Convention: file names like messages_en.properties, ui_de.properties, etc.
String baseName = getBaseName(getFile().getName());
// Scan the parent folder for all matching *.properties with same basename
File folder = getFile().getParentFile();
File[] files = folder.listFiles((dir, name) ->
name.startsWith(baseName) && name.endsWith(".properties"));
if (files != null) {
for (File f : files) {
Locale locale = extractLocale(f.getName(), baseName);
try (InputStreamReader reader =
new InputStreamReader(new FileInputStream(f), StandardCharsets.UTF_8)) {
this.bundles.put(locale, new PropertyResourceBundle(reader));
} catch (IOException e) {
throw new RuntimeException("Failed to load localization file: " + f, e);
}
}
}
this.isLoaded = true;
loadFile(getFile());
isLoaded = true;
}
@Override
public void unload() {
this.bundles.clear();
this.isLoaded = false;
bundles.clear();
isLoaded = false;
}
@Override
public boolean isLoaded() {
return this.isLoaded;
return isLoaded;
}
public String getString(String key, Locale locale) {
ResourceBundle bundle = this.bundles.get(locale);
Locale target = findBestLocale(locale);
ResourceBundle bundle = bundles.get(target);
if (bundle == null) throw new MissingResourceException(
"No bundle for locale: " + locale, getClass().getName(), key);
"No bundle for locale: " + target, getClass().getName(), key);
return bundle.getString(key);
}
public boolean hasLocale(Locale locale) {
return this.bundles.containsKey(locale);
private Locale findBestLocale(Locale locale) {
if (bundles.containsKey(locale)) return locale;
for (Locale l : bundles.keySet()) {
if (l.getLanguage().equals(locale.getLanguage())) return l;
}
return fallback;
}
public Set<Locale> getAvailableLocales() {
return Collections.unmodifiableSet(this.bundles.keySet());
return Collections.unmodifiableSet(bundles.keySet());
}
@Override
public void loadFile(File file) {
String baseName = getBaseName(file.getName());
try (InputStreamReader reader =
new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)) {
Locale locale = extractLocale(file.getName(), baseName);
bundles.put(locale, new PropertyResourceBundle(reader));
} catch (IOException e) {
throw new RuntimeException("Failed to load localization file: " + file, e);
}
isLoaded = true;
}
@Override
public String getBaseName() {
return getBaseName(getFile().getName());
}
private String getBaseName(String fileName) {
@@ -80,6 +85,6 @@ public class LocalizationAsset extends BaseResource implements LoadableResource
String localePart = fileName.substring(underscoreIndex + 1, dotIndex);
return Locale.forLanguageTag(localePart.replace('_', '-'));
}
return Locale.getDefault(); // fallback
return fallback;
}
}
}