/*
* Copyright [2014] [Christian Loehnert, krampenschiesser@gmail.com]
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.ks.i18n;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
/**
* Ignores ":" and "=" at the end of a string.
* Also uses the UTF8Control
*/
class ResourceBundleWrapper extends ResourceBundle {
private static final Logger log = LoggerFactory.getLogger(ResourceBundleWrapper.class);
private final ResourceBundle bundle;
private final String path;
private final File missingKeyFile;
private final ResourceBundle fallback;
public ResourceBundleWrapper(ResourceBundle bundle, ResourceBundle fallback, String path) {
this.fallback = fallback == null ? bundle : fallback;
String tempDir = System.getProperty("java.io.tmpdir");
String pathname = tempDir + File.separator + "idnadrev_missing_keys.properties";
File missing = null;
try {
missing = new File(pathname);
if (missing.exists()) {
missing.delete();
}
missing.createNewFile();
} catch (IOException e) {
log.error("Could not create tempfile {} for missing properties", pathname, e);
missing = null;
}
this.missingKeyFile = missing;
this.bundle = bundle;
this.path = path;
}
@Override
public boolean containsKey(String key) {
if (key.endsWith(":")) {
key = key.substring(0, key.length() - 1);
} else if (key.endsWith("=")) {
key = key.substring(0, key.length() - 1);
}
boolean isContained = getBundle().containsKey(key);
if (!isContained && hasFallback()) {
isContained = getFallback().containsKey(key);
}
if (!isContained) {
log.warn("Key \"{}\" not found in properties: {}", key, path);
return true;
}
return true;
}
@Override
public Set<String> keySet() {
HashSet<String> retval = new HashSet<>();
retval.addAll(getBundle().keySet());
if (hasFallback()) {
retval.addAll(getFallback().keySet());
}
return retval;
}
@Override
public Enumeration<String> getKeys() {
Enumeration<String> keys = getBundle().getKeys();
if (hasFallback()) {
Enumeration<String> fallbackKeys = getFallback().getKeys();
return new Enumeration<String>() {
@Override
public boolean hasMoreElements() {
if (!keys.hasMoreElements()) {
return fallbackKeys.hasMoreElements();
}
return keys.hasMoreElements();
}
@Override
public String nextElement() {
if (!keys.hasMoreElements()) {
return fallbackKeys.nextElement();
}
return keys.nextElement();
}
};
} else {
return keys;
}
}
@Override
protected Object handleGetObject(String key) {
String ending = null;
if (key.endsWith(":")) {
ending = ":";
} else if (key.endsWith("=")) {
ending = "=";
}
if (ending != null) {
key = key.substring(0, key.length() - ending.length());
}
try {
Method method = ResourceBundle.class.getDeclaredMethod("handleGetObject", String.class);
method.setAccessible(true);
Object retval = method.invoke(getBundle(), key);
if (retval == null && hasFallback()) {
retval = method.invoke(getFallback(), key);
}
if ((ending != null) && (retval instanceof String)) {
retval = retval + ending;
}
if (retval == null) {
log.warn("Key \"{}\" not found in properties:{}", key, path);
appendToMissingKeyFile(key);
return "?" + key + "?";
}
return retval;
} catch (Exception e) {
log.error("Could not invoke delegate method.", e);
}
return null;
}
protected void appendToMissingKeyFile(String key) throws IOException {
if (missingKeyFile == null) {
return;
}
try (FileWriter writer = new FileWriter(missingKeyFile, true)) {
StringBuilder builder = new StringBuilder(key);
int indexOf = key.lastIndexOf(".");
if (indexOf > 0) {
builder.append(" = ").append(key.substring(indexOf + 1));
} else {
builder.append(" = ").append(key);
}
builder.append("\n");
writer.append(builder.toString());
}
}
@Override
public int hashCode() {
return getBundle().hashCode();
}
@Override
public boolean equals(Object obj) {
return getBundle().equals(obj);
}
@Override
public String toString() {
return getBundle().toString();
}
@Override
public Locale getLocale() {
return getBundle().getLocale();
}
public File getMissingKeyFile() {
return missingKeyFile;
}
protected boolean hasFallback() {
return fallback != null;
}
public ResourceBundle getFallback() {
return fallback;
}
protected ResourceBundle getBundle() {
String baseBundleName = bundle.getBaseBundleName();
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
boolean isNext = false;
String callerClassName = null;
for (StackTraceElement stackTraceElement : stackTrace) {
String className = stackTraceElement.getClassName();
if (className.equals(Thread.class.getName()) || className.equals(ResourceBundle.class.getName()) || className.equals(getClass().getName()) || className.equals(Localized.class.getName())) {
continue;
} else if (className.contains("$")) {
continue;
} else {
callerClassName = className;
break;
}
}
log.trace("Found caller class {}, basename={}", callerClassName, baseBundleName);
Locale locale = Locale.getDefault();
String substring = callerClassName.substring(0, callerClassName.lastIndexOf('.') + 1);
UTF8Control control = new UTF8Control();
String baseName = substring + Localized.FILENAME;
URL resource = getClass().getClassLoader().getResource(control.getResourceName(baseName, locale));
if (resource == null) {
resource = getClass().getClassLoader().getResource(control.getResourceName(baseName, control.getFallbackLocale(baseName, locale)));
}
if (resource != null) {
ResourceBundle localBundle = ResourceBundle.getBundle(baseName, locale, control);
log.trace("Found local bundle {}", baseName);
if (localBundle != null) {
return localBundle;
}
}
return this.bundle;
}
}