com.force.i18n.settings.BasePropertyFile Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of grammaticus Show documentation
Show all versions of grammaticus Show documentation
Localization Framework that allows grammatically correct renaming of nouns
/*
* Copyright (c) 2017, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
package com.force.i18n.settings;
import static com.force.i18n.commons.util.settings.IniFileUtil.intern;
import java.io.*;
import java.util.*;
import java.util.Map.Entry;
import org.xml.sax.Locator;
import com.force.i18n.I18nJavaUtil;
import com.force.i18n.commons.text.TextUtil;
import com.force.i18n.commons.util.settings.BaseNonConfigIniFile;
import com.force.i18n.commons.util.settings.SettingsUtil;
/**
* This class parses XML configuration files and puts the data into a data
* structure that can be used by the app to retrieve info.
*
* Comments can be entered using standard XML comment syntax.
*
References
* <Value>s may contain substitutions of other
* <ParamName>,<Value> pairs. Substitutions are indicated by strings
* of the form: When the parser sees such strings, they are replaced with the
* selected <SectionName>.<ParamName> pair. References are in the
* form ${<section>.<param>}
*
* Forward and multi-level references are allowed, but circular are not.
*
* Values may also contain references to system parameters.
*
Example
*
* <section name="sectionName">
*
* <!-- Standard Parameter -->
* <param name="paramName1"> paramValue </param>
*
* <!-- Parameter Reference -->
* <param name="paramName2"> ${sectionName.paramName1} </param>
*
* <!-- Property Reference -->
* <param name="paramName3"> #{user.property} </param>
*
* </section>
*
*
* @author nveeser, btsai
*/
public class BasePropertyFile implements BaseNonConfigIniFile, Serializable {
private static final long serialVersionUID = 1L;
public static final String MISSING_LABEL = I18nJavaUtil.getProperty("missingLabelPrefix");
// Objects where we store the data and metadata
protected PropertyFileData data;
protected final Map> metaData;
// Who uses this?
protected long lastModified = -1;
public BasePropertyFile(int initialCapacity) {
this(initialCapacity, new MapPropertyFileData());
}
public BasePropertyFile() {
this(new MapPropertyFileData());
}
protected BasePropertyFile(PropertyFileData data) {
this(16, data);
}
protected BasePropertyFile(int initialCapacity, PropertyFileData data) {
this.data = data;
this.metaData = new HashMap<>(initialCapacity);
}
public BasePropertyFile(Parser p) throws IOException {
this();
p.load(this.data, this.metaData);
}
protected BasePropertyFile(Parser p, PropertyFileData data) throws IOException {
this(data);
p.load(this.data, this.metaData);
}
@Override
public Set>> entrySet() {
return this.data.entrySet();
}
public Set sectionNames() {
return this.data.getSectionNames();
}
public void remove(String sectionName, String paramName) {
this.data.remove(sectionName, paramName);
}
public void removeSection(String sectionName) {
this.data.removeSection(sectionName);
}
public boolean containsSection(String section) {
return this.data.containsSection(section);
}
public boolean containsParam(String section, String param) {
return this.data.contains(section, param);
}
/**
* @return Whether the section contains an enum list with the baseParam.
* @param section the label section
* @param baseParam the prefix for all the keys in the label section.
*/
public boolean containsListParam(String section, String baseParam) {
// check if the list param exists by attempting to get the first one
return inner_get(section, makeListEntryParam(baseParam, 0), false) != null;
}
/**
* @return all values of parameters that were in the section for a build config
* file
* @param section the label section
*/
public List getListConfigValues(String section) throws SettingsSectionNotFoundException {
return getList(section, section);
}
/**
* @return all values of parameters that begin with baseParam_xx, where xx is a
* number starting from 0.
* @param section the label section
* @param baseParam the prefix for all the keys in the label section.
*/
public List getList(String section, String baseParam) throws SettingsSectionNotFoundException {
// try to get the first one as a way to find whether the section exists
inner_get(section, makeListEntryParam(baseParam, 0), true);
return getParamList(section, baseParam);
}
/**
* @return all values of parameters that begin with baseParam_xx, where xx is a
* number starting from 0.
* @param section the label section
* @param baseParam the prefix for all the keys in the label section.
* @param ifNull what to return if there are no parameters
*/
@Override
public List getList(String section, String baseParam, List ifNull) {
// try to get the first one as a way to find whether the section exists
try {
if (inner_get(section, makeListEntryParam(baseParam, 0), false) == null) {
return ifNull;
}
} catch (SettingsSectionNotFoundException x) {
// won't happen
throw new RuntimeException(x);
}
List result = getParamList(section, baseParam);
if (result.isEmpty()) {
return ifNull;
}
return result;
}
/**
* @return a List of all values whose parameters start with
* baseParam_x
, where x is number starting from 0.
* @param section the label section
* @param baseParam the prefix for all the keys in the label section.
*/
private List getParamList(String section, String baseParam) {
List list = new ArrayList<>();
for (int index = 0; true; ++index) {
String result = getString(section, makeListEntryParam(baseParam, index), null);
if (result == null) {
break;
}
list.add(result);
}
return list;
}
/**
* @return the number of enumItems within an enum list param.
* @param section the label section
* @param baseParam the prefix for all the keys in the label section.
*/
public int getNumListEntryParams(String section, String baseParam) {
for (int i = 0; true; i++) {
String result = getString(section, makeListEntryParam(baseParam, i), null);
if (result == null) {
return i;
}
}
}
public static String makeListEntryParam(String baseParam, int index) {
StringBuilder param = new StringBuilder(baseParam.length() + 5);
param.append(baseParam);
param.append("_");
param.append(index);
return param.toString();
}
/**
* @return a set of all params in the specified section. You'd best be not
* messing with it.
* @param section the label section
*/
public Set getParams(String section) throws SettingsSectionNotFoundException {
Set result = getParams(section, null);
if (result == null) {
throw new SettingsSectionNotFoundException("PropertyFile - section " + section + " not found.");
}
return result;
}
protected Object inner_get(String section, String param, boolean throwSettingsSectionNotFoundException) throws SettingsSectionNotFoundException {
if (!containsSection(section)) {
if (throwSettingsSectionNotFoundException) {
throw new SettingsSectionNotFoundException("PropertyFile - section " + section + " not found.");
}
return null;
}
if (param == null) {
throw new NullPointerException();
}
return this.data.get(section, param);
}
/**
* @return a set of all params in the specified section. You'd best be not
* messing with it.
* @param section the label section
* @param ifNull the params to use if the section is missing or has no keys
*/
public Set getParams(String section, Set ifNull) {
Map theSect = this.data.getSection(section);
if (theSect == null) {
return ifNull;
}
return theSect.keySet();
}
@Override
public Object get(String section, String param, Object ifNull) {
Object result;
try {
result = inner_get(section, param, false);
} catch (SettingsSectionNotFoundException x) {
// won't ever happen
throw new RuntimeException(x);
}
if (result == null) {
return ifNull;
}
return result;
}
public Object get(String section, String param, boolean allowLabelException) throws ParameterNotFoundException, SettingsSectionNotFoundException {
Object result = inner_get(section, param, true);
if (result == null) {
return processMissingLabel("PropertyFile - val " + param + " not found in section " + section, allowLabelException);
}
return result;
}
/**
* Get the value for a given SectName.ParamName pair.
*
* @param section
* the section name of the desired variable
* @param param
* the parameter name with the the given section
* @return the value of the parameter
*/
public Object get(String section, String param) throws ParameterNotFoundException, SettingsSectionNotFoundException {
return get(section, param, false);
}
/**
* Will either throw an exception or send an error depending on whether we
* are in production / test running more or note.
*
* @param message
* @return
* @throws ParameterNotFoundException
*/
private String processMissingLabel(String message) throws ParameterNotFoundException {
return processMissingLabel(message, false);
}
/**
* Will either throw an exception or send an error depending on whether we
* are in production / test running more or note.
*
* @param message
* @return
* @throws ParameterNotFoundException
*/
private String processMissingLabel(String message, boolean allowLabelException) throws ParameterNotFoundException {
if (!isProductionMode() || allowLabelException) {
throw new ParameterNotFoundException(message);
} else {
return MISSING_LABEL + message;
}
}
public String getString(String section, String param) throws ParameterNotFoundException, SettingsSectionNotFoundException {
return (String) this.get(section, param);
}
public String getStringThrow(String section, String param) throws ParameterNotFoundException, SettingsSectionNotFoundException {
return (String) this.get(section, param, true);
}
/**
* Censors the value before returning it to the caller. However, if the value is null, then we will not
* run the censoring logic and just return the passed in ifNull value directly.
*
* Note: Do _NOT_ rely the censored value we return always being the same. The exact censored output
* may change over time.
*
* @param section the section for the label
* @param param the key for the label
* @param ifNull default value to return if null (not set).
* @return some number of 'x'ses if the value should be censored.
*/
@Override
public String getCensoredString(String section, String param, String ifNull) {
String value = this.getString(section, param, null);
if(value == null) {
return ifNull;
}
return SettingsUtil.censorValue(section, param, value);
}
@Override
public String getString(String section, String param, String ifNull) {
return (String) this.get(section, param, ifNull);
}
public int getInt(String section, String param) throws ParameterNotFoundException, SettingsSectionNotFoundException {
return Integer.parseInt(getString(section, param));
}
@Override
public int getInt(String section, String param, int ifNull) {
try {
return Integer.parseInt(getString(section, param, null));
} catch (Exception x) {
return ifNull;
}
}
public float getFloat(String section, String param) throws SettingsSectionNotFoundException, ParameterNotFoundException {
return Float.parseFloat(getString(section, param));
}
@Override
public float getFloat(String section, String param, float ifNull) {
try {
return Float.parseFloat(getString(section, param, null));
} catch (Exception x) {
return ifNull;
}
}
public boolean getBoolean(String section, String param) throws ParameterNotFoundException, SettingsSectionNotFoundException {
return stringToBoolean(getString(section, param));
}
@Override
public boolean getBoolean(String section, String param, boolean ifNull) {
if (ifNull) {
return stringToBoolean(getString(section, param, "true"));
}
return stringToBoolean(getString(section, param, "false"));
}
public Object put(String section, String param, Object value) {
return this.data.put(section, param, value);
}
/**
* @return an unmodifiable Map
of all the values for a
* particular sectionName. If, that section does not exist, will return
* null
.
* @param sectionName the label section
*/
public Map getSection(String sectionName) {
return this.data.getSection(sectionName);
}
/**
* Output XML data to a stream output is for set.dtd. This will not give an
* exact copy of what was read in.
* @param os the output stream to write to
* @throws IOException if an error happens while streaming
*/
public void outputXML(OutputStream os) throws IOException {
outputXML(os, false);
}
/**
* Outputs XML data to a stream output, either censored or uncensored version depending on
* doCensor's value.
*
* Note: outputValueXML vs outputValueXMLCensored will go away in 152 when configuration
* is split out from labels / motifs.
*
* @param os the output stream to write to
* @param doCensor will censor values like password if set to true
* @throws IOException if an error happens while streaming
*/
public void outputXML(OutputStream os, boolean doCensor) throws IOException {
OutputStreamWriter osw = new OutputStreamWriter(os, "utf-8");
osw.write("\n");
osw.write("\n");
for (String sectionName : new TreeSet(this.data.getSectionNames())) {
osw.write("\t\n");
Map sectionMap = this.data.getSection(sectionName);
for (String paramName : new TreeSet(sectionMap.keySet())) {
osw.write("\t\t");
if(doCensor) {
outputValueXMLCensored(osw, sectionName, paramName, sectionMap.get(paramName));
} else {
outputValueXML(sectionMap.get(paramName), osw);
}
osw.write("\n");
}
osw.write("\t \n");
}
osw.write(" \n");
osw.flush();
}
protected void outputValueXML(Object value, OutputStreamWriter os) throws IOException {
if (value != null) {
if (value instanceof String) {
os.write(TextUtil.escapeToXml((String)value, true, true));
} else {
os.write(value.getClass() + ":" + TextUtil.escapeToXml(value.toString(), true, true));
}
}
}
protected void outputValueXMLCensored(OutputStreamWriter os, String sectionName, String paramName, Object value) throws IOException {
if (value != null) {
if (value instanceof String) {
os.write(TextUtil.escapeToXml(SettingsUtil.censorValue(sectionName, paramName, (String)value), true, true));
} else {
os.write(value.getClass() + ":" + TextUtil.escapeToXml(SettingsUtil.censorValue(sectionName, paramName, value.toString()), true, true));
}
}
}
public final long getLastModified() {
return lastModified;
}
public final void setLastModified(long lastModified) {
this.lastModified = lastModified;
}
public void doSubstitutions() throws ParameterNotFoundException, IOException {
doSubstitutions(null);
}
public void doSubstitutions(Object referenceConfig) throws ParameterNotFoundException, IOException {
// we need to queue up our modifications, because some implementations
// don't support
// swapping values on their Iterators (e.g. SharedKeyMap)
Map> modifications = new HashMap>(128);
for (Map.Entry> sectionEntry : this.data.entrySet()) {
String sectionKey = sectionEntry.getKey();
for (Map.Entry parameterEntry : sectionEntry.getValue().entrySet()) {
String parameterName = parameterEntry.getKey();
Object originalValue = parameterEntry.getValue();
Object newValue;
try {
newValue = substitute(sectionKey, parameterName, originalValue, referenceConfig);
} catch (SubstitutionException nfe) {
String sourceFile;
try {
MetaDataInfo info = metaData.get(sectionKey).get(parameterName);
sourceFile = info.getSourceFile().toString();
} catch (NullPointerException e) {
sourceFile = "(unknown)";
}
String message = "label substitution exception in file: " + sourceFile + ": section: "
+ sectionKey + " label: " + parameterName + " on value: " + nfe.getValue() + " for value: "
+ nfe.getParam();
newValue = processMissingLabel(message);
}
if (newValue != originalValue) {
Map sectionMods = modifications.get(sectionKey);
if (sectionMods == null) {
// lazy create
sectionMods = new HashMap(8);
modifications.put(sectionKey, sectionMods);
}
sectionMods.put(intern(parameterName), newValue);
}
}
}
for (Map.Entry> sectionEntry : modifications.entrySet()) {
String sectionName = sectionEntry.getKey();
for (Map.Entry entry : sectionEntry.getValue().entrySet()) {
this.data.put(sectionName, entry.getKey(), entry.getValue());
}
}
}
/**
* Allow child implementations to substitute values based on an external set of params
* @param sectionName the section of the param being replace
* @param paramName the key of the param being replace
* @param val the value being replaced
* @param referenceConfig an object being passed in to parsing for
* @return the val with substrings substituted if need be
* @throws IOException in case there are any IO Exceptions
* @throws SubstitutionException in case the val has a malformed section/param
*/
protected Object substitute(String sectionName, String paramName, Object val, Object referenceConfig) throws IOException, SubstitutionException {
return val;
}
public static class SubstitutionException extends Exception {
private static final long serialVersionUID = 1L;
private final String param;
private final Object val;
public SubstitutionException(String section, String param, Object val) {
super("Could not find property: " + section + "." + param);
this.param = section + "." + param;
this.val = val;
}
public SubstitutionException(String property, Object val) {
super("Could not find property: " + property);
this.param = property;
this.val = val;
}
public String getParam() { return this.param; }
public Object getValue() { return this.val; }
}
protected PropertyFileData getPropertyFileData() {
return this.data;
}
/**
* Allows for some implementations to save a great deal of memory by sharing
* maps across various language versions of the "same" properties/labels.
* @param seedKeyMap the shared seedKeyMap to share keys to reduce memory
*/
public void attachSharedKeyMap(SharedKeyMap> seedKeyMap) {
if (seedKeyMap == null) {
return;
}
this.data.shareKeys(seedKeyMap);
}
public boolean isProductionMode() {
return true;
}
public static class MetaDataInfo {
private Locator locator;
private File sourceFile;
private boolean read = false;
private boolean deprecated = false;
public Locator getLocator() {
return this.locator;
}
public void setLocator(Locator locator) {
this.locator = locator;
}
public boolean isDeprecated() {
return this.deprecated;
}
public void setDeprecated(boolean deprecated) {
this.deprecated = deprecated;
}
public boolean isRead() {
return this.read;
}
public void setRead(boolean read) {
this.read = read;
}
public File getSourceFile() {
return this.sourceFile;
}
public void setSourceFile(File sourceFile) {
this.sourceFile = sourceFile;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o instanceof MetaDataInfo) {
MetaDataInfo mdi = (MetaDataInfo)o;
return this.locator.equals(mdi.locator) && this.sourceFile.equals(mdi.sourceFile)
&& this.deprecated == mdi.deprecated;
}
return false;
}
@Override
public int hashCode() {
int hash = 17;
hash = 37 * hash + ((null != locator) ? locator.hashCode() : 0);
hash = 37 * hash + ((null != sourceFile) ? sourceFile.hashCode() : 0);
hash = 37 * hash + ((this.deprecated) ? 1 : 0);
return hash;
}
}
/**
* Defaults to false if the string does not match one of the following
* - 1
* - true
* - yes
* - on
* @param booleanValue the string to test
* @return the booleanValue string as a boolean
*/
public static boolean stringToBoolean(String booleanValue) {
return "1".equals(booleanValue)
|| "true".equals(booleanValue)
|| "yes".equals(booleanValue)
|| "on".equals(booleanValue);
}
/**
* Interface used by PropertyFile and its subclasses to parse and load its
* internal map data structure (eg. from a file).
*
* @author nveeser
*/
public interface Parser {
void load(PropertyFileData data, Map> metaData) throws IOException;
// used for motif parsing - it helps determine the date that will be used in urls
long getFileLastModified();
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy