jodd.props.PropsData Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jodd-all Show documentation
Show all versions of jodd-all Show documentation
Jodd bundle - all classes in one jar
// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
package jodd.props;
import jodd.template.StringTemplateParser;
import jodd.util.StringPool;
import jodd.util.StringUtil;
import jodd.util.Wildcard;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.function.Function;
/**
* Props data storage for base and profile properties.
* Properties can be lookuped and modified only through this
* class.
*/
public class PropsData implements Cloneable {
private static final int MAX_INNER_MACROS = 100;
private static final String APPEND_SEPARATOR = ",";
protected final HashMap baseProperties;
protected final HashMap> profileProperties;
protected PropsEntry first;
protected PropsEntry last;
/**
* If set, duplicate props will be appended to the end, separated by comma.
*/
protected boolean appendDuplicateProps;
/**
* When set, missing macros will be replaces with an empty string.
*/
protected boolean ignoreMissingMacros;
/**
* When set, empty properties will be skipped.
*/
protected boolean skipEmptyProps = true;
public PropsData() {
this(new HashMap<>(), new HashMap<>());
}
protected PropsData(final HashMap properties, final HashMap> profiles) {
this.baseProperties = properties;
this.profileProperties = profiles;
}
@Override
public PropsData clone() {
final HashMap> newProfiles = new HashMap<>();
final HashMap newBase = new HashMap<>(baseProperties);
for (final Map.Entry> entry : profileProperties.entrySet()) {
final Map map = new HashMap<>(entry.getValue().size());
map.putAll(entry.getValue());
newProfiles.put(entry.getKey(), map);
}
final PropsData pd = new PropsData(newBase, newProfiles);
pd.appendDuplicateProps = appendDuplicateProps;
pd.ignoreMissingMacros = ignoreMissingMacros;
pd.skipEmptyProps = skipEmptyProps;
return pd;
}
// ---------------------------------------------------------------- misc
/**
* Puts key-value pair into the map, with respect of appending duplicate properties
*/
protected void put(final String profile, final Map map, final String key, final String value, final boolean append) {
String realValue = value;
if (append || appendDuplicateProps) {
PropsEntry pv = map.get(key);
if (pv != null) {
realValue = pv.value + APPEND_SEPARATOR + realValue;
}
}
PropsEntry propsEntry = new PropsEntry(key, realValue, profile, this);
// update position pointers
if (first == null) {
first = propsEntry;
} else {
last.next = propsEntry;
}
last = propsEntry;
// add to the map
map.put(key, propsEntry);
}
// ---------------------------------------------------------------- properties
/**
* Counts base properties.
*/
public int countBaseProperties() {
return baseProperties.size();
}
/**
* Adds base property.
*/
public void putBaseProperty(final String key, final String value, final boolean append) {
put(null, baseProperties, key, value, append);
}
/**
* Returns base property or null
if it doesn't exist.
*/
public PropsEntry getBaseProperty(final String key) {
return baseProperties.get(key);
}
// ---------------------------------------------------------------- profiles
/**
* Counts profile properties. Note: this method is not
* that easy on execution.
*/
public int countProfileProperties() {
final HashSet profileKeys = new HashSet<>();
for (final Map map : profileProperties.values()) {
for (final String key : map.keySet()) {
if (!baseProperties.containsKey(key)) {
profileKeys.add(key);
}
}
}
return profileKeys.size();
}
/**
* Adds profile property.
*/
public void putProfileProperty(final String key, final String value, final String profile, final boolean append) {
Map map = profileProperties.computeIfAbsent(profile, k -> new HashMap<>());
put(profile, map, key, value, append);
}
/**
* Returns profile property.
*/
public PropsEntry getProfileProperty(final String profile, final String key) {
final Map profileMap = profileProperties.get(profile);
if (profileMap == null) {
return null;
}
return profileMap.get(key);
}
// ---------------------------------------------------------------- lookup
/**
* Lookup props value through profiles and base properties.
* Returns {@code null} if value not found.
*/
protected String lookupValue(final String key, final String... profiles) {
if (profiles != null) {
for (String profile : profiles) {
if (profile == null) {
continue;
}
while (true) {
final Map profileMap = this.profileProperties.get(profile);
if (profileMap != null) {
final PropsEntry value = profileMap.get(key);
if (value != null) {
return value.getValue(profiles);
}
}
// go back with profile
final int ndx = profile.lastIndexOf('.');
if (ndx == -1) {
break;
}
profile = profile.substring(0, ndx);
}
}
}
final PropsEntry value = getBaseProperty(key);
if (value == null) {
return null;
}
return value.getValue(profiles);
}
// ---------------------------------------------------------------- resolve
/**
* Resolves all macros in this props set. Called on property lookup.
*/
public String resolveMacros(String value, final String... profiles) {
// create string template parser that will be used internally
StringTemplateParser stringTemplateParser = new StringTemplateParser();
stringTemplateParser.setResolveEscapes(false);
if (!ignoreMissingMacros) {
stringTemplateParser.setReplaceMissingKey(false);
} else {
stringTemplateParser.setReplaceMissingKey(true);
stringTemplateParser.setMissingKeyReplacement(StringPool.EMPTY);
}
final Function macroResolver = macroName -> {
String[] lookupProfiles = profiles;
int leftIndex = macroName.indexOf('<');
if (leftIndex != -1) {
int rightIndex = macroName.indexOf('>');
String profiles1 = macroName.substring(leftIndex + 1, rightIndex);
macroName = macroName.substring(0, leftIndex).concat(macroName.substring(rightIndex + 1));
lookupProfiles = StringUtil.splitc(profiles1, ',');
StringUtil.trimAll(lookupProfiles);
}
return lookupValue(macroName, lookupProfiles);
};
// start parsing
int loopCount = 0;
while (loopCount++ < MAX_INNER_MACROS) {
final String newValue = stringTemplateParser.parse(value, macroResolver);
if (newValue.equals(value)) {
break;
}
if (skipEmptyProps) {
if (newValue.length() == 0) {
return null;
}
}
value = newValue;
}
return value;
}
// ---------------------------------------------------------------- extract
/**
* Extracts props to target map. This is all-in-one method, that does many things at once.
*/
public Map extract(Map target, final String[] profiles, final String[] wildcardPatterns, String prefix) {
if (target == null) {
target = new HashMap();
}
// make sure prefix ends with a dot
if (prefix != null) {
if (!StringUtil.endsWithChar(prefix, '.')) {
prefix += StringPool.DOT;
}
}
if (profiles != null) {
for (String profile : profiles) {
while (true) {
final Map map = this.profileProperties.get(profile);
if (map != null) {
extractMap(target, map, profiles, wildcardPatterns, prefix);
}
final int ndx = profile.indexOf('.');
if (ndx == -1) {
break;
}
profile = profile.substring(0, ndx);
}
}
}
extractMap(target, this.baseProperties, profiles, wildcardPatterns, prefix);
return target;
}
@SuppressWarnings("unchecked")
protected void extractMap(
final Map target,
final Map map,
final String[] profiles,
final String[] wildcardPatterns,
final String prefix
) {
for (Map.Entry entry : map.entrySet()) {
String key = entry.getKey();
if (wildcardPatterns != null) {
if (Wildcard.matchOne(key, wildcardPatterns) == -1) {
continue;
}
}
// shorten the key
if (prefix != null) {
if (!key.startsWith(prefix)) {
continue;
}
key = key.substring(prefix.length());
}
// only append if target DOES NOT contain the key
if (!target.containsKey(key)) {
target.put(key, entry.getValue().getValue(profiles));
}
}
}
}