Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/**
* 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.tomitribe.util;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
/**
* The purpose of this class is to provide a more strongly typed version of a
* java.util.Properties object. So far it is a read only view of the properties
* and does not set data into the underlying Properties instance.
*
* Similar to java.util.Properties it will delegate to a "parent" instance when
* a property is not found. If a property is found but its value cannot be parsed
* as the desired data type, the parent's value is used.
*
* By default this object will log nothing, but if a Log implementation is set the
* Options class will log three kinds of statements:
*
* - When a property is not found: the property name and default value in use along
* with all possible values (enums only). Debug level.
* - When a property is found: the property name and value. Info level.
* - When a property value cannot be parsed: the property name and invalid value. Warn level.
*
* Logging the user supplied values onto INFO is really nice as it shows up in the standard
* log output and allows us to easily see which values the user has changed from the default.
* It's rather impossible to diagnose issues without this information.
*
* ENUM SETS:
*
* Properties that accept a Set of enum values automatically accept ALL and NONE in
* addition to the explicitly created enum items.
*
* Using ALL. This allows users to have an easy way to imply "all" without having to
* hardcode an the entire list of enum items and protects against the case where that
* list may grow in the future.
*
* Using NONE. This allows users an alternative to using an empty string when explicitly
* specifying that none of the options should be used.
*
* In the internal code, this allows us to have these concepts in all enum options
* without us having to add NONE or ALL enum items explicitly which leads to strange code.
*
* Additionally TRUE is an alias for ALL and FALSE an alias for NONE. This allows options
* that used to support only true/false values to be further defined in the future without
* breaking compatibility.
*/
public class Options {
private final Options parent;
private final Properties properties;
public Options(final Properties properties) {
this(properties, new NullOptions());
}
public Options(final Properties properties, final Options parent) {
this.parent = parent;
this.properties = properties;
}
public Options getParent() {
return parent;
}
public Properties getProperties() {
return properties;
}
public void setLogger(final Log logger) {
parent.setLogger(logger);
}
public Log getLogger() {
return parent.getLogger();
}
public boolean has(final String property) {
return properties.containsKey(property) || parent.has(property);
}
public String get(final String property, final String defaultValue) {
final String value = properties.getProperty(property);
return value != null ? log(property, value) : parent.get(property, defaultValue);
}
public T get(final String property, final T defaultValue) {
if (defaultValue == null) throw new NullPointerException("defaultValue");
final String value = properties.getProperty(property);
if (value == null || "".equals(value)) {
return parent.get(property, defaultValue);
}
try {
final Class> type = defaultValue.getClass();
final Constructor> constructor = type.getConstructor(String.class);
final T t = (T) constructor.newInstance(value);
return log(property, t);
} catch (final Exception e) {
e.printStackTrace();
warn(property, value, e);
return parent.get(property, defaultValue);
}
}
public int get(final String property, final int defaultValue) {
final String value = properties.getProperty(property);
if (value == null || "".equals(value)) {
return parent.get(property, defaultValue);
}
try {
return log(property, Integer.parseInt(value));
} catch (final NumberFormatException e) {
warn(property, value, e);
return parent.get(property, defaultValue);
}
}
public long get(final String property, final long defaultValue) {
final String value = properties.getProperty(property);
if (value == null || "".equals(value)) {
return parent.get(property, defaultValue);
}
try {
return log(property, Long.parseLong(value));
} catch (final NumberFormatException e) {
warn(property, value, e);
return parent.get(property, defaultValue);
}
}
public boolean get(final String property, final boolean defaultValue) {
final String value = properties.getProperty(property);
if (value == null || "".equals(value)) {
return parent.get(property, defaultValue);
}
try {
return log(property, Boolean.parseBoolean(value));
} catch (final NumberFormatException e) {
warn(property, value, e);
return parent.get(property, defaultValue);
}
}
public Class> get(final String property, final Class> defaultValue) {
final String className = properties.getProperty(property);
if (className == null) return parent.get(property, defaultValue);
final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
try {
return log(property, classLoader.loadClass(className));
} catch (final Exception e) {
getLogger().warning("Could not load " + property + " : " + className, e);
return parent.get(property, defaultValue);
}
}
public > T get(final String property, final T defaultValue) {
final String value = properties.getProperty(property);
if (value == null || "".equals(value)) {
return parent.get(property, defaultValue);
}
if (defaultValue == null) {
throw new IllegalArgumentException("Must supply a default for property " + property);
}
final Class enumType = (Class) defaultValue.getClass();
try {
return log(property, valueOf(enumType, value.toUpperCase()));
} catch (final IllegalArgumentException e) {
warn(property, value);
return parent.get(property, defaultValue);
}
}
public > Set getAll(final String property, final T... defaultValue) {
final EnumSet defaults = EnumSet.copyOf(Arrays.asList(defaultValue));
return getAll(property, defaults);
}
public > Set getAll(final String property, final Set defaultValue) {
final Class enumType;
try {
final T t = defaultValue.iterator().next();
enumType = (Class) t.getClass();
} catch (final Exception e) {
throw new IllegalArgumentException("Must supply a default for property " + property);
}
return getAll(property, defaultValue, enumType);
}
public > Set getAll(final String property, final Class enumType) {
return getAll(property, Collections.EMPTY_SET, enumType);
}
protected > Set getAll(final String property, final Set defaultValue,
final Class enumType) {
final String value = properties.getProperty(property);
if (value == null || "".equals(value)) {
return parent.getAll(property, defaultValue, enumType);
}
// Shorthand for specifying ALL or NONE for any option
// that allows for multiple values of the enum
if ("all".equalsIgnoreCase(value) || "true".equalsIgnoreCase(value)) {
log(property, value);
return EnumSet.allOf(enumType);
} else if ("none".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value)) {
log(property, value);
return EnumSet.noneOf(enumType);
}
try {
final String[] values = value.split(",");
final EnumSet set = EnumSet.noneOf(enumType);
for (String s : values) {
s = s.trim();
set.add(valueOf(enumType, s.toUpperCase()));
}
return logAll(property, set);
} catch (final IllegalArgumentException e) {
warn(property, value);
return parent.getAll(property, defaultValue, enumType);
}
}
/**
* Use this instead of Enum.valueOf() when you want to ensure that the
* the enum values are case insensitive.
*
* @param enumType
* @param name
* @param
* @return
*/
public static > T valueOf(final Class enumType, final String name) {
final Map map = new HashMap();
for (final T t : enumType.getEnumConstants()) {
map.put(t.name().toUpperCase(), t);
}
final T value = map.get(name.toUpperCase());
// Call Enum.valueOf for the clean exception
if (value == null || "".equals(value)) {
Enum.valueOf(enumType, name);
}
return value;
}
protected void warn(final String property, final String value) {
getLogger().warning("Cannot parse supplied value \"" + value + "\" for option \"" + property + "\"");
}
protected void warn(final String property, final String value, final Exception e) {
getLogger().warning("Cannot parse supplied value \"" + value + "\" for option \"" + property + "\"", e);
}
protected V log(final String property, final V value) {
if (!getLogger().isInfoEnabled()) return value;
if (value instanceof Class) {
final Class clazz = (Class) value;
getLogger().info("Using \'" + property + "=" + clazz.getName() + "\'");
} else {
getLogger().info("Using \'" + property + "=" + value + "\'");
}
return value;
}
public > Set logAll(final String property, final Set value) {
if (!getLogger().isInfoEnabled()) return value;
getLogger().info("Using \'" + property + "=" + join(", ", lowercase(value)) + "\'");
return value;
}
protected static > String[] lowercase(final T... items) {
final String[] values = new String[items.length];
for (int i = 0; i < items.length; i++) {
values[i] = items[i].name().toLowerCase();
}
return values;
}
protected static > String[] lowercase(final Collection items) {
final String[] values = new String[items.size()];
int i = 0;
for (final T item : items) {
values[i++] = item.name().toLowerCase();
}
return values;
}
protected static > String possibleValues(final V v) {
final Class extends Enum> enumType = v.getClass();
return possibleValues(enumType);
}
protected static String possibleValues(final Class extends Enum> enumType) {
return join(", ", lowercase(enumType.getEnumConstants()));
}
public static String join(final String delimiter, final Object... collection) {
final StringBuilder sb = new StringBuilder();
for (final Object obj : collection) {
sb.append(obj).append(delimiter);
}
if (collection.length > 0) {
sb.delete(sb.length() - delimiter.length(), sb.length());
}
return sb.toString();
}
protected static class NullOptions extends Options {
private Log logger;
public NullOptions() {
super(null, null);
this.logger = new NullLog();
}
@Override
public Log getLogger() {
return logger;
}
@Override
public void setLogger(final Log logger) {
this.logger = logger;
}
@Override
public boolean has(final String property) {
return false;
}
@Override
public T get(final String property, final T defaultValue) {
return log(property, defaultValue);
}
@Override
public int get(final String property, final int defaultValue) {
return log(property, defaultValue);
}
@Override
public long get(final String property, final long defaultValue) {
return log(property, defaultValue);
}
@Override
public boolean get(final String property, final boolean defaultValue) {
return log(property, defaultValue);
}
@Override
public > T get(final String property, final T defaultValue) {
return log(property, defaultValue);
}
@Override
public > Set getAll(final String property, final T... defaultValue) {
return EnumSet.copyOf(Arrays.asList(defaultValue));
}
@Override
protected > Set getAll(final String property, final Set defaults,
final Class enumType) {
if (getLogger().isDebugEnabled()) {
String possibleValues = " Possible values are: " + possibleValues(enumType);
possibleValues += " or NONE or ALL";
final String defaultValues;
if (defaults.size() == 0) {
defaultValues = "NONE";
} else if (defaults.size() == enumType.getEnumConstants().length) {
defaultValues = "ALL";
} else {
defaultValues = join(", ", lowercase(defaults));
}
getLogger().debug("Using default \'" + property + "=" + defaultValues + "\'" + possibleValues);
}
return defaults;
}
@Override
public String get(final String property, final String defaultValue) {
return log(property, defaultValue);
}
@Override
public Class> get(final String property, final Class> defaultValue) {
return log(property, defaultValue);
}
protected V log(final String property, final V value) {
if (getLogger().isDebugEnabled()) {
if (value instanceof Enum) {
final Enum anEnum = (Enum) value;
getLogger().debug("Using default \'" + property + "=" + anEnum.name().toLowerCase() + "\'. Possible values are: " + possibleValues(anEnum));
} else if (value instanceof Class) {
final Class clazz = (Class) value;
getLogger().debug("Using default \'" + property + "=" + clazz.getName() + "\'");
} else if (value != null) {
logger.debug("Using default \'" + property + "=" + value + "\'");
}
}
return value;
}
}
public interface Log {
boolean isDebugEnabled();
boolean isInfoEnabled();
boolean isWarningEnabled();
void warning(String message, Throwable t);
void warning(String message);
void debug(String message, Throwable t);
void debug(String message);
void info(String message, Throwable t);
void info(String message);
}
public static class NullLog implements Log {
public boolean isDebugEnabled() {
return false;
}
public boolean isInfoEnabled() {
return false;
}
public boolean isWarningEnabled() {
return false;
}
public void warning(final String message, final Throwable t) {
}
public void warning(final String message) {
}
public void debug(final String message, final Throwable t) {
}
public void debug(final String message) {
}
public void info(final String message, final Throwable t) {
}
public void info(final String message) {
}
}
}