
de.codecamp.messages.shared.bundle.BundleFile Maven / Gradle / Ivy
package de.codecamp.messages.shared.bundle;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.configuration2.PropertiesConfiguration;
import org.apache.commons.configuration2.PropertiesConfiguration.JupIOFactory;
import org.apache.commons.configuration2.PropertiesConfigurationLayout;
import org.apache.commons.configuration2.io.FileHandler;
import org.apache.commons.lang3.StringUtils;
import de.codecamp.messages.shared.bundle.MessageBundleManager.BundleFileCoordinates;
/**
* Represents one file (i.e. one locale) of a message bundle.
*
* @param
* the file reference
*/
public class BundleFile
implements
Comparable>
{
private final FileSystemAdapter, F> fileSystem;
private final F file;
private final String bundleName;
private final Locale locale;
private final String displayPath;
private boolean readOnly = false;
private final PropertiesConfiguration properties;
private final FileHandler fileHandler;
private boolean loaded = false;
private boolean needsSaving;
private Map lineNumbersCache;
public BundleFile(FileSystemAdapter, F> fileSystem, F file, Charset encoding,
BundleFileCoordinates coordinates, String displayPath)
{
this.bundleName = coordinates.getBundleName();
this.locale = coordinates.getLocale();
this.fileSystem = fileSystem;
this.file = file;
this.displayPath = displayPath;
this.properties = new PropertiesConfiguration();
boolean escapeUnicode = !encoding.contains(StandardCharsets.UTF_8);
this.properties.setIOFactory(new JupIOFactory(escapeUnicode));
this.properties.setLayout(new SortedPropertiesConfigurationLayout());
/*
* Loading does not determine the line separator. So there's no way to (easily) preserve the
* line separator, and saving without setting it will use the platform default.
*/
properties.getLayout().setLineSeparator("\n");
properties.getLayout().setForceSingleLine(true);
properties.getLayout().setGlobalSeparator(" = ");
fileHandler = new FileHandler(properties);
fileHandler.setEncoding(encoding.name());
}
public F getLocation()
{
return file;
}
public String getDisplayPath()
{
return displayPath;
}
public String getBundleName()
{
return bundleName;
}
public Locale getLocale()
{
return locale;
}
public boolean isReadOnly()
{
return readOnly;
}
void setReadOnly(boolean readOnly)
{
this.readOnly = readOnly;
}
boolean isLoaded()
{
return loaded;
}
public boolean isEmpty()
{
checkLoaded();
return properties.isEmpty();
}
public Set getKeys()
{
checkLoaded();
return new HashSet<>(properties.getLayout().getKeys());
}
public boolean hasMessageKey(String messageKey)
{
checkLoaded();
return properties.containsKey(messageKey);
}
public boolean isMessageEmpty(String messageKey)
{
return !hasMessage(messageKey);
}
public boolean hasMessage(String messageKey)
{
return StringUtils.isNotEmpty(getMessage(messageKey));
}
public String getMessage(String messageKey)
{
checkLoaded();
return properties.getString(messageKey);
}
public void setMessage(String messageKey, String message)
{
checkLoaded();
checkReadOnly();
Object oldMessage = properties.getProperty(messageKey);
properties.setProperty(messageKey, message);
if (!Objects.equals(oldMessage, message))
{
needsSaving = true;
lineNumbersCache = null;
}
}
public String removeMessage(String messageKey)
{
checkLoaded();
checkReadOnly();
String message = null;
if (properties.containsKey(messageKey))
{
message = properties.getString(messageKey);
properties.clearProperty(messageKey);
needsSaving = true;
lineNumbersCache = null;
}
return message;
}
public String getComment(String messageKey)
{
checkLoaded();
return properties.getLayout().getComment(messageKey);
}
public void setComment(String messageKey, String comment)
{
checkLoaded();
checkReadOnly();
String oldComment = properties.getLayout().getComment(messageKey);
properties.getLayout().setComment(messageKey, comment);
if (!Objects.equals(oldComment, comment))
{
needsSaving = true;
lineNumbersCache = null;
}
}
public Integer findLineNumberOfKey(String messageKey)
throws BundleException
{
updateLineNumberOfKeys();
return lineNumbersCache.get(messageKey);
}
public Map findLineNumberOfKeys(Collection messageKeys)
throws BundleException
{
updateLineNumberOfKeys();
Map lineNumbers = new HashMap<>();
for (String messageKey : messageKeys)
{
Integer lineNumber = lineNumbersCache.get(messageKey);
if (lineNumber != null)
lineNumbers.put(messageKey, lineNumber);
}
return lineNumbers;
}
private void updateLineNumberOfKeys()
throws BundleException
{
checkLoaded();
if (needsSaving())
throw new IllegalStateException("Save file before querying line numbers.");
// cache is already filled and up-to-date
if (lineNumbersCache != null)
return;
Map lineNumbers = new HashMap<>();
List lines = new ArrayList<>();
try (InputStream in = fileSystem.newInputStream(getLocation()))
{
BufferedReader reader =
new BufferedReader(new InputStreamReader(in, fileHandler.getEncoding()));
String line;
while ((line = reader.readLine()) != null)
{
lines.add(line);
}
}
catch (Exception ex)
{
String msg = "Failed to load message bundle file '%s'.";
msg = String.format(msg, getDisplayPath());
throw new BundleException(msg, ex);
}
Pattern pattern = Pattern.compile(
"^\\s*(" + "([\\p{L}_$][\\p{L}\\p{N}_$]*\\.)*[\\p{L}_$][\\p{L}\\p{N}_$]*" + ")\\s*=.*");
for (int i = 0; i < lines.size(); i++)
{
String line = lines.get(i);
Matcher matcher = pattern.matcher(line);
if (matcher.matches())
{
lineNumbers.put(matcher.group(1), i + 1);
}
}
lineNumbersCache = lineNumbers;
}
public boolean exists()
{
return fileSystem.exists(getLocation());
}
public boolean needsSaving()
{
return loaded && needsSaving;
}
public boolean needsSorting()
{
List keysSorted = new ArrayList<>(properties.getLayout().getKeys());
List keysOriginal = new ArrayList<>(
((SortedPropertiesConfigurationLayout) properties.getLayout()).getUnsortedKeys());
return !keysSorted.equals(keysOriginal);
}
public boolean save()
throws BundleException
{
return save(false);
}
public boolean save(boolean force)
throws BundleException
{
if (isReadOnly() || (!force && !needsSaving()))
return false;
checkReadOnly();
try
{
fileSystem.createParentDirectories(getLocation());
}
catch (Exception ex)
{
String msg = "Failed to create directories for '%s'.";
msg = String.format(msg, getLocation());
throw new BundleException(msg, ex);
}
try (OutputStream out = fileSystem.newOutputStream(getLocation()))
{
fileHandler.save(out);
needsSaving = false;
return true;
}
catch (Exception ex)
{
String msg = "Failed to save message bundle file '%s'.";
msg = String.format(msg, getDisplayPath());
throw new BundleException(msg, ex);
}
}
private void checkLoaded()
{
if (!loaded)
throw new IllegalStateException("Bundle file has not been loaded.");
}
protected void ensureLoaded()
throws BundleException
{
if (loaded)
return;
if (!exists())
{
loaded = true;
needsSaving = true;
return;
}
try (InputStream in = fileSystem.newInputStream(getLocation()))
{
fileHandler.load(in);
}
catch (Exception ex)
{
String msg = "Failed to load message bundle file '%s'.";
msg = String.format(msg, getDisplayPath());
throw new BundleException(msg, ex);
}
loaded = true;
needsSaving = false;
}
public void delete()
throws BundleException
{
checkReadOnly();
try
{
fileSystem.deleteIfExists(getLocation());
}
catch (Exception ex)
{
String msg = "Failed to delete empty message bundle file '%s'.";
msg = String.format(msg, getDisplayPath());
throw new BundleException(msg, ex);
}
}
private void checkReadOnly()
{
if (readOnly)
{
throw new IllegalStateException(
String.format("Message bundle '%s' is read-only.", bundleName));
}
}
@Override
public int compareTo(BundleFile o)
{
int result = getBundleName().compareTo(o.getBundleName());
if (result != 0)
return result;
return getLocale().toLanguageTag().compareTo(o.getLocale().toLanguageTag());
}
static class SortedPropertiesConfigurationLayout
extends PropertiesConfigurationLayout
{
@Override
public Set getKeys()
{
return new TreeSet<>(super.getKeys());
}
public Set getUnsortedKeys()
{
return super.getKeys();
}
};
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy