org.apache.juneau.PropertyStoreBuilder Maven / Gradle / Ivy
// ***************************************************************************************************************************
// * 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.juneau;
import static java.util.Collections.*;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.regex.*;
import org.apache.juneau.PropertyStore.*;
import org.apache.juneau.internal.*;
import org.apache.juneau.json.*;
/**
* A builder for {@link PropertyStore} objects.
*/
public class PropertyStoreBuilder {
// Contains a cache of all created PropertyStore objects keyed by hashcode.
// Used to minimize memory consumption by reusing identical PropertyStores.
private static final Map CACHE = new ConcurrentHashMap<>();
// Maps property suffixes (e.g. "lc") to PropertyType (e.g. LIST_CLASS)
static final Map SUFFIX_MAP = new ConcurrentHashMap<>();
static {
for (PropertyType pt : PropertyType.values())
SUFFIX_MAP.put(pt.getSuffix(), pt);
}
private final Map groups = new ConcurrentSkipListMap<>();
// Previously-created property store.
private volatile PropertyStore propertyStore;
// Called by PropertyStore.builder()
PropertyStoreBuilder(PropertyStore ps) {
apply(ps);
}
// Called by PropertyStore.create()
PropertyStoreBuilder() {}
/**
* Creates a new {@link PropertyStore} based on the values in this builder.
*
* @return A new {@link PropertyStore} based on the values in this builder.
*/
public synchronized PropertyStore build() {
// Reused the last one if we haven't change this builder.
if (propertyStore == null)
propertyStore = new PropertyStore(groups);
PropertyStore ps = CACHE.get(propertyStore.hashCode());
if (ps == null)
CACHE.put(propertyStore.hashCode(), propertyStore);
else if (! ps.equals(propertyStore))
throw new RuntimeException("Property store mismatch! This shouldn't happen.");
else
propertyStore = ps;
return propertyStore;
}
/**
* Copies all the values in the specified property store into this builder.
*
* @param copyFrom The property store to copy the values from.
* @return This object (for method chaining).
*/
public synchronized PropertyStoreBuilder apply(PropertyStore copyFrom) {
propertyStore = null;
if (copyFrom != null)
for (Map.Entry e : copyFrom.groups.entrySet()) {
String gName = e.getKey();
PropertyGroupBuilder g1 = this.groups.get(gName);
PropertyGroup g2 = e.getValue();
if (g1 == null)
this.groups.put(gName, g2.builder());
else
g1.apply(g2);
}
return this;
}
/**
* Sets a configuration property value on this object.
*
* @param key
* The configuration property key.
*
(e.g "BeanContext.foo.ss/add.1" )
*
If name ends with /add , then the specified value is added to the existing property value as an entry
* in a SET or LIST property.
*
If name ends with /add.{key} , then the specified value is added to the existing property value as a
* key/value pair in a MAP property.
*
If name ends with /remove , then the specified value is removed from the existing property property
* value in a SET or LIST property.
* @param value
* The new value.
* If null , the property value is deleted.
* In general, the value type can be anything.
* @return This object (for method chaining).
*/
public synchronized PropertyStoreBuilder set(String key, Object value) {
propertyStore = null;
String g = group(key);
int i = key.indexOf('/');
if (i != -1) {
String command = key.substring(i+1), arg = null;
String key2 = key.substring(0, i);
int j = command.indexOf('.');
if (j != -1) {
arg = command.substring(j+1);
command = command.substring(0, j);
}
if ("add".equals(command)) {
return addTo(key2, arg, value);
} else if ("remove".equals(command)) {
if (arg != null)
throw new ConfigException("Invalid key specified: ''{0}''", key);
return removeFrom(key2, value);
} else {
throw new ConfigException("Invalid key specified: ''{0}''", key);
}
}
String n = g.isEmpty() ? key : key.substring(g.length()+1);
PropertyGroupBuilder gb = groups.get(g);
if (gb == null) {
gb = new PropertyGroupBuilder();
groups.put(g, gb);
}
gb.set(n, value);
if (gb.isEmpty())
groups.remove(g);
return this;
}
/**
* Removes the property with the specified key.
*
*
* This is equivalent to calling set(key, null );
*
* @param key The property key.
* @return This object (for method chaining).
*/
public synchronized PropertyStoreBuilder remove(String key) {
propertyStore = null;
return set(key, null);
}
/**
* Convenience method for setting multiple properties in one call.
*
*
* This replaces any previous configuration properties set on this store.
*
* @param newProperties The new properties to set.
* @return This object (for method chaining).
*/
public synchronized PropertyStoreBuilder set(Map newProperties) {
propertyStore = null;
clear();
add(newProperties);
return this;
}
/**
* Convenience method for setting multiple properties in one call.
*
*
* This appends to any previous configuration properties set on this store.
*
* @param newProperties The new properties to set.
* @return This object (for method chaining).
*/
public synchronized PropertyStoreBuilder add(Map newProperties) {
propertyStore = null;
if (newProperties != null)
for (Map.Entry e : newProperties.entrySet())
set(e.getKey(), e.getValue());
return this;
}
/**
* Adds one or more values to a SET, LIST, or MAP property.
*
* @param key The property key.
* @param arg
* The argument.
*
For SETs, this must always be null .
*
For LISTs, this can be null or a numeric index.
* Out-of-range indexes are simply 'adjusted' to the beginning or the end of the list.
* So, for example, a value of "-100" will always just cause the entry to be added to the beginning
* of the list.
*
For MAPs, this can be null if we're adding a map, or a string key if we're adding an entry.
* @param value
* The new value to add to the property.
*
For SETs and LISTs, this can be a single value, Collection, array, or JSON array string.
*
For MAPs, this can be a single value, Map, or JSON object string.
*
null values have the effect of removing entries.
* @return This object (for method chaining).
* @throws ConfigException If property is not a SET/LIST/MAP property, or the argument is invalid.
*/
public synchronized PropertyStoreBuilder addTo(String key, String arg, Object value) {
propertyStore = null;
String g = group(key);
String n = g.isEmpty() ? key : key.substring(g.length()+1);
PropertyGroupBuilder gb = groups.get(g);
if (gb == null) {
gb = new PropertyGroupBuilder();
groups.put(g, gb);
}
gb.addTo(n, arg, value);
if (gb.isEmpty())
groups.remove(g);
return this;
}
/**
* Adds a value to a SET, LIST, or MAP property.
*
*
* Shortcut for calling addTo(key, null , value);
.
*
* @param key The property key.
* @param value
* The new value to add to the property.
*
For SETs and LISTs, this can be a single value, Collection, array, or JSON array string.
*
for MAPs, this can be a single value, Map, or JSON object string.
*
null values have the effect of removing entries.
* @return This object (for method chaining).
* @throws ConfigException If property is not a SET/LIST/MAP property, or the argument is invalid.
*/
public synchronized PropertyStoreBuilder addTo(String key, Object value) {
propertyStore = null;
return addTo(key, null, value);
}
/**
* Removes a value from a SET or LIST property.
*
* @param key The property key.
* @param value The property value in the property.
* @return This object (for method chaining).
* @throws ConfigException If property is not a SET or LIST property.
*/
public synchronized PropertyStoreBuilder removeFrom(String key, Object value) {
propertyStore = null;
String g = group(key);
String n = g.isEmpty() ? key : key.substring(g.length()+1);
PropertyGroupBuilder gb = groups.get(g);
// Create property group anyway to generate a good error message.
if (gb == null)
gb = new PropertyGroupBuilder();
gb.removeFrom(n, value);
if (gb.isEmpty())
groups.remove(g);
return this;
}
/**
* Peeks at a property value.
*
*
* Used for debugging purposes.
*
* @param key The property key.
* @return The property value, or null if it doesn't exist.
*/
public Object peek(String key) {
String g = group(key);
String n = g.isEmpty() ? key : key.substring(g.length()+1);
PropertyGroupBuilder gb = groups.get(g);
// Create property group anyway to generate a good error message.
if (gb == null)
return null;
MutableProperty bp = gb.properties.get(n);
if (bp == null)
return null;
return bp.peek();
}
/**
* Clears all entries in this property store.
*/
public synchronized void clear() {
propertyStore = null;
this.groups.clear();
}
//-------------------------------------------------------------------------------------------------------------------
// PropertyGroupBuilder
//-------------------------------------------------------------------------------------------------------------------
static class PropertyGroupBuilder {
final Map properties = new ConcurrentSkipListMap<>();
PropertyGroupBuilder() {
this(EMPTY_MAP);
}
synchronized void apply(PropertyGroup copyFrom) {
for (Map.Entry e : copyFrom.properties.entrySet()) {
String pName = e.getKey();
MutableProperty p1 = properties.get(pName);
Property p2 = e.getValue();
if (p1 == null)
properties.put(pName, p2.mutable());
else
p1.apply(p2.value);
}
}
PropertyGroupBuilder(Map properties) {
for (Map.Entry p : properties.entrySet())
this.properties.put(p.getKey(), p.getValue().mutable());
}
synchronized void set(String key, Object value) {
MutableProperty p = properties.get(key);
if (p == null) {
p = MutableProperty.create(key, value);
if (! p.isEmpty())
properties.put(key, p);
} else {
p.set(value);
if (p.isEmpty())
properties.remove(key);
else
properties.put(key, p);
}
}
synchronized void addTo(String key, String arg, Object value) {
MutableProperty p = properties.get(key);
if (p == null) {
p = MutableProperty.create(key, null);
p.add(arg, value);
if (! p.isEmpty())
properties.put(key, p);
} else {
p.add(arg, value);
if (p.isEmpty())
properties.remove(key);
else
properties.put(key, p);
}
}
synchronized void removeFrom(String key, Object value) {
MutableProperty p = properties.get(key);
if (p == null) {
// Create property anyway to generate a good error message.
p = MutableProperty.create(key, null);
p.remove(value);
} else {
p.remove(value);
if (p.isEmpty())
properties.remove(key);
else
properties.put(key, p);
}
}
synchronized boolean isEmpty() {
return properties.isEmpty();
}
synchronized PropertyGroup build() {
return new PropertyGroup(properties);
}
}
//-------------------------------------------------------------------------------------------------------------------
// MutableProperty
//-------------------------------------------------------------------------------------------------------------------
static abstract class MutableProperty {
final String name;
final PropertyType type;
MutableProperty(String name, PropertyType type) {
this.name = name;
this.type = type;
}
static MutableProperty create(String name, Object value) {
int i = name.lastIndexOf('.');
String type = i == -1 ? "s" : name.substring(i+1);
PropertyType pt = SUFFIX_MAP.get(type);
if (pt == null)
throw new ConfigException("Invalid type specified on property ''{0}''", name);
switch (pt) {
case STRING:
case BOOLEAN:
case INTEGER:
case CLASS:
case OBJECT: return new MutableSimpleProperty(name, pt, value);
case SET_STRING:
case SET_INTEGER:
case SET_CLASS: return new MutableSetProperty(name, pt, value);
case LIST_STRING:
case LIST_INTEGER:
case LIST_CLASS:
case LIST_OBJECT: return new MutableListProperty(name, pt, value);
case SORTED_MAP_STRING:
case SORTED_MAP_INTEGER:
case SORTED_MAP_CLASS:
case SORTED_MAP_OBJECT: return new MutableMapProperty(name, pt, value);
case ORDERED_MAP_STRING:
case ORDERED_MAP_INTEGER:
case ORDERED_MAP_CLASS:
case ORDERED_MAP_OBJECT: return new MutableLinkedMapProperty(name, pt, value);
default: return new MutableSimpleProperty(name, PropertyType.STRING, value);
}
}
abstract Property build();
abstract boolean isEmpty();
abstract void set(Object value);
abstract void apply(Object value);
abstract Object peek();
void add(String arg, Object value) {
throw new ConfigException("Cannot add value {0} ({1}) to property ''{2}'' ({3}).", string(value), className(value), name, type);
}
void remove(Object value) {
throw new ConfigException("Cannot remove value {0} ({1}) from property ''{2}'' ({3}).", string(value), className(value), name, type);
}
Object convert(Object value) {
return value == null ? null : type.converter.convert(value);
}
}
//-------------------------------------------------------------------------------------------------------------------
// MutableSimpleProperty
//-------------------------------------------------------------------------------------------------------------------
static class MutableSimpleProperty extends MutableProperty {
private Object value;
MutableSimpleProperty(String name, PropertyType type, Object value) {
super(name, type);
set(value);
}
@Override /* MutableProperty */
synchronized Property build() {
return new Property(name, value, type);
}
@Override /* MutableProperty */
synchronized void set(Object value) {
this.value = convert(value);
}
@Override /* MutableProperty */
synchronized void apply(Object value) {
this.value = convert(value);
}
@Override /* MutableProperty */
synchronized boolean isEmpty() {
return this.value == null;
}
@Override /* MutableProperty */
synchronized Object peek() {
return value;
}
}
//-------------------------------------------------------------------------------------------------------------------
// MutableSetProperty
//-------------------------------------------------------------------------------------------------------------------
static class MutableSetProperty extends MutableProperty {
private final Set