blob: 47137effc793f0ed74d589e4dae92e5c02c6d230 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.phoenix.util;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
/**
*
* Read-only properties that avoids unnecessary synchronization in
* java.util.Properties.
*
*
* @since 1.2.2
*/
public class ReadOnlyProps implements Iterable<Entry<String, String>> {
public static final ReadOnlyProps EMPTY_PROPS = new ReadOnlyProps();
private final Map<String, String> props;
public ReadOnlyProps(ReadOnlyProps defaultProps, Iterator<Entry<String, String>> iterator) {
Map<String, String> map = new HashMap<String,String>(defaultProps.asMap());
while (iterator.hasNext()) {
Entry<String,String> entry = iterator.next();
map.put(entry.getKey(), entry.getValue());
}
this.props = ImmutableMap.copyOf(map);
}
public ReadOnlyProps(Iterator<Entry<String, String>> iterator) {
this(EMPTY_PROPS, iterator);
}
private ReadOnlyProps() {
this.props = Collections.emptyMap();
}
public ReadOnlyProps(Map<String, String> props) {
this.props = ImmutableMap.copyOf(props);
}
private ReadOnlyProps(ReadOnlyProps defaultProps, Properties overrides) {
Map<String,String> combinedProps = Maps.newHashMapWithExpectedSize(defaultProps.props.size() + overrides.size());
combinedProps.putAll(defaultProps.props);
for (Entry<Object, Object> entry : overrides.entrySet()) {
String key = entry.getKey().toString();
String value = entry.getValue().toString();
combinedProps.put(key, value);
}
this.props = ImmutableMap.copyOf(combinedProps);
}
private static Pattern varPat = Pattern.compile("\\$\\{[^\\}\\$\u0020]+\\}");
private static int MAX_SUBST = 20;
private String substituteVars(String expr) {
if (expr == null) {
return null;
}
Matcher match = varPat.matcher("");
String eval = expr;
for(int s=0; s<MAX_SUBST; s++) {
match.reset(eval);
if (!match.find()) {
return eval;
}
String var = match.group();
var = var.substring(2, var.length()-1); // remove ${ .. }
String val = null;
try {
val = System.getProperty(var);
} catch(SecurityException se) {
}
if (val == null) {
val = getRaw(var);
}
if (val == null) {
return eval; // return literal ${var}: var is unbound
}
// substitute
eval = eval.substring(0, match.start())+val+eval.substring(match.end());
}
throw new IllegalStateException("Variable substitution depth too large: "
+ MAX_SUBST + " " + expr);
}
/**
* Get the value of the <code>name</code> property, without doing
* <a href="#VariableExpansion">variable expansion</a>.
*
* @param name the property name.
* @return the value of the <code>name</code> property,
* or null if no such property exists.
*/
public String getRaw(String name) {
return props.get(name);
}
public String getRaw(String name, String defaultValue) {
String value = getRaw(name);
if (value == null) {
return defaultValue;
}
return value;
}
/**
* Get the value of the <code>name</code> property. If no such property
* exists, then <code>defaultValue</code> is returned.
*
* @param name property name.
* @param defaultValue default value.
* @return property value, or <code>defaultValue</code> if the property
* doesn't exist.
*/
public String get(String name, String defaultValue) {
return substituteVars(getRaw(name, defaultValue));
}
/**
* Get the value of the <code>name</code> property, <code>null</code> if
* no such property exists.
*
* Values are processed for <a href="#VariableExpansion">variable expansion</a>
* before being returned.
*
* @param name the property name.
* @return the value of the <code>name</code> property,
* or null if no such property exists.
*/
public String get(String name) {
return substituteVars(getRaw(name));
}
private String getHexDigits(String value) {
boolean negative = false;
String str = value;
String hexString = null;
if (value.startsWith("-")) {
negative = true;
str = value.substring(1);
}
if (str.startsWith("0x") || str.startsWith("0X")) {
hexString = str.substring(2);
if (negative) {
hexString = "-" + hexString;
}
return hexString;
}
return null;
}
/**
* Get the value of the <code>name</code> property as a <code>boolean</code>.
* If no such property is specified, or if the specified value is not a valid
* <code>boolean</code>, then <code>defaultValue</code> is returned.
*
* @param name property name.
* @param defaultValue default value.
* @return property value as a <code>boolean</code>,
* or <code>defaultValue</code>.
*/
public boolean getBoolean(String name, boolean defaultValue) {
String valueString = get(name);
if ("true".equals(valueString))
return true;
else if ("false".equals(valueString))
return false;
else return defaultValue;
}
/**
* Get the value of the <code>name</code> property as an <code>int</code>.
*
* If no such property exists, or if the specified value is not a valid
* <code>int</code>, then <code>defaultValue</code> is returned.
*
* @param name property name.
* @param defaultValue default value.
* @return property value as an <code>int</code>,
* or <code>defaultValue</code>.
*/
public int getInt(String name, int defaultValue) {
String valueString = get(name);
if (valueString == null)
return defaultValue;
try {
String hexString = getHexDigits(valueString);
if (hexString != null) {
return Integer.parseInt(hexString, 16);
}
return Integer.parseInt(valueString);
} catch (NumberFormatException e) {
return defaultValue;
}
}
/**
* Get the value of the <code>name</code> property as a <code>long</code>.
* If no such property is specified, or if the specified value is not a valid
* <code>long</code>, then <code>defaultValue</code> is returned.
*
* @param name property name.
* @param defaultValue default value.
* @return property value as a <code>long</code>,
* or <code>defaultValue</code>.
*/
public long getLong(String name, long defaultValue) {
String valueString = get(name);
if (valueString == null)
return defaultValue;
try {
String hexString = getHexDigits(valueString);
if (hexString != null) {
return Long.parseLong(hexString, 16);
}
return Long.parseLong(valueString);
} catch (NumberFormatException e) {
return defaultValue;
}
}
/**
* Get the value of the <code>name</code> property as a <code>float</code>.
* If no such property is specified, or if the specified value is not a valid
* <code>float</code>, then <code>defaultValue</code> is returned.
*
* @param name property name.
* @param defaultValue default value.
* @return property value as a <code>float</code>,
* or <code>defaultValue</code>.
*/
public float getFloat(String name, float defaultValue) {
String valueString = get(name);
if (valueString == null)
return defaultValue;
try {
return Float.parseFloat(valueString);
} catch (NumberFormatException e) {
return defaultValue;
}
}
/**
* Get the properties as a <code>Map<String,String></code>
*
* @return Map<String,String>
*/
public Map<String,String> asMap() {
return props;
}
@Override
public Iterator<Entry<String, String>> iterator() {
return props.entrySet().iterator();
}
public boolean isEmpty() {
return props.isEmpty();
}
/**
* Constructs new map only if necessary for adding the override properties.
* @param overrides Map of properties to override current properties.
* @return new ReadOnlyProps if in applying the overrides there are
* modifications to the current underlying Map, otherwise returns this.
*/
public ReadOnlyProps addAll(Properties overrides) {
for (Entry<Object, Object> entry : overrides.entrySet()) {
String key = entry.getKey().toString();
String value = entry.getValue().toString();
String oldValue = props.get(key);
if (!Objects.equal(oldValue, value)) {
return new ReadOnlyProps(this, overrides);
}
}
return this;
}
}