|
|
@@ -7,25 +7,14 @@
|
|
|
|
|
|
package net.ranides.assira.config;
|
|
|
|
|
|
-import java.util.ArrayList;
|
|
|
-import java.util.Arrays;
|
|
|
import java.util.Collection;
|
|
|
-import java.util.Collections;
|
|
|
import java.util.HashMap;
|
|
|
-import java.util.LinkedHashMap;
|
|
|
-import java.util.List;
|
|
|
import java.util.Map;
|
|
|
-import java.util.regex.Matcher;
|
|
|
-import java.util.regex.Pattern;
|
|
|
-import net.ranides.assira.collection.SingleCollection;
|
|
|
-import net.ranides.assira.collection.list.ListAdapter;
|
|
|
import net.ranides.assira.events.Event;
|
|
|
import net.ranides.assira.events.EventBinding;
|
|
|
import net.ranides.assira.events.EventListener;
|
|
|
-import net.ranides.assira.generic.TypeToken;
|
|
|
-import net.ranides.assira.text.LexicalCast;
|
|
|
-import net.ranides.assira.text.StringUtils;
|
|
|
-import net.ranides.assira.text.Strings;
|
|
|
+import net.ranides.assira.events.EventRouter;
|
|
|
+import net.ranides.assira.events.EventRouterDispatcher;
|
|
|
|
|
|
/**
|
|
|
*
|
|
|
@@ -34,346 +23,120 @@ import net.ranides.assira.text.Strings;
|
|
|
@SuppressWarnings({
|
|
|
"SIC_INNER_SHOULD_BE_STATIC_ANON"
|
|
|
})
|
|
|
-public abstract class AbstractConfiguration implements Configuration {
|
|
|
-
|
|
|
- protected enum NoResult { CODE };
|
|
|
-
|
|
|
-
|
|
|
- public AbstractConfiguration() {
|
|
|
- // do nothing
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public Branch branch(String path) {
|
|
|
- return new BasicBranch(path, get(path));
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public List<Branch> branchList(String path) {
|
|
|
- List<?> list = getList(path);
|
|
|
- List<Branch> result = new ArrayList<Branch>();
|
|
|
- for(Object item : list) {
|
|
|
- String home = path+"[" + result.size() + "]";
|
|
|
- result.add(new BasicBranch(home, item));
|
|
|
+public abstract class AbstractConfiguration extends AbstractBranch implements Configuration {
|
|
|
+
|
|
|
+ protected final EventRouter dispatcher;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Pełna ścieżka podana podczas tworzenia konfiguracji
|
|
|
+ */
|
|
|
+ protected final String resource;
|
|
|
+ /**
|
|
|
+ * Rodzaj podanej ścieżki: "file" lub "url". W przypadku ścieżki nie określającej
|
|
|
+ * protokołu przyjmowany jest typ "file". W przypadku adresu URL konieczne jest
|
|
|
+ * podanie najpierw protokołu "url:" a następnie pełnego adresu, włącznie z
|
|
|
+ * konkretnym prefiksem określającym rodzaj transportu (np "http:", "ftp:", ...).
|
|
|
+ */
|
|
|
+ protected final String protocol;
|
|
|
+ /**
|
|
|
+ * Część pozostała po usunięciu rodzaju ze ścieżki rodzaju.
|
|
|
+ */
|
|
|
+ protected final String location;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Pamięć podręczna na przechowywane wartości konfiguracyjne.
|
|
|
+ */
|
|
|
+ protected Map<String, Object> values;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * <p>
|
|
|
+ * Tworzy nową konfigurację powiązaną z zasobem wskazywanym przez ścieżkę.
|
|
|
+ * Konfiguracja może być ładowana i zapisywana z/do plików lokalnych.
|
|
|
+ * Może też być ładowana w trybie tylko-do-odczytu z zewnętrznego źródła
|
|
|
+ * określonego za pomocą URL.
|
|
|
+ * </p>
|
|
|
+ * <pre>
|
|
|
+ * {@code
|
|
|
+ * "/user/home/config.json"
|
|
|
+ * "file:/user/home/config.json"
|
|
|
+ * "file:d:/app/config.json"
|
|
|
+ * "url:http://example.com/config.json"
|
|
|
+ * }</pre>
|
|
|
+ * @param path
|
|
|
+ */
|
|
|
+ public AbstractConfiguration(String path) {
|
|
|
+ this.dispatcher = new EventRouterDispatcher();
|
|
|
+ this.values = new HashMap<String, Object>();
|
|
|
+ this.resource = path;
|
|
|
+
|
|
|
+ String[] params = path.split(":",2);
|
|
|
+ if(params.length == 2) {
|
|
|
+ protocol = (params.length == 2) ? params[0] : "file";
|
|
|
+ location = params[1];
|
|
|
+ } else {
|
|
|
+ protocol = "file";
|
|
|
+ location = params[0];
|
|
|
}
|
|
|
- return result;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public final Map<String,Object> asFlatMap() {
|
|
|
- return flat(asMap(), "");
|
|
|
+ public String resource() {
|
|
|
+ return resource;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public final <T> T get(Class<T> type, String path, T ddefault) throws ConfigException {
|
|
|
- return LexicalCast.cast(get(path, ddefault), type);
|
|
|
+ public Map<String,Object> asMap() {
|
|
|
+ return values;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public final <T> T get(Class<T> type, String path) throws ConfigException {
|
|
|
- return LexicalCast.cast(get(path), type);
|
|
|
+ public Object get(String path, Object ddefault) throws ConfigException {
|
|
|
+ return get(values, path, ddefault);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public final <T> T get(TypeToken<T> type, String path, T ddefault) throws ConfigException {
|
|
|
- return LexicalCast.cast(get(path, ddefault), type);
|
|
|
+ public void put(String path, Object value) throws ConfigException {
|
|
|
+ put(values, path, value);
|
|
|
}
|
|
|
|
|
|
- @Override
|
|
|
- public final <T> T get(TypeToken<T> type, String path) throws ConfigException {
|
|
|
- return LexicalCast.cast(get(path), type);
|
|
|
- }
|
|
|
|
|
|
- @Override
|
|
|
- public final Object get(String path) throws ConfigException {
|
|
|
- return get(path, NoResult.CODE);
|
|
|
- }
|
|
|
+/* ************************************************************************** */
|
|
|
|
|
|
- @Override
|
|
|
- public final List<?> getList(String path) throws ConfigException {
|
|
|
- Object value = get(path, Collections.emptyList());
|
|
|
- if(value instanceof List) {
|
|
|
- return (List<?>)value;
|
|
|
- } else {
|
|
|
- return Arrays.asList(value);
|
|
|
- }
|
|
|
- }
|
|
|
|
|
|
@Override
|
|
|
- public final <T> List<T> getList(Class<T> type, String path) {
|
|
|
- Object value = get(path, Collections.emptyList());
|
|
|
- if(value instanceof List) {
|
|
|
- return new CastList<T>(type, (List<?>)value);
|
|
|
- } else {
|
|
|
- return SingleCollection.of(LexicalCast.cast(value, type));
|
|
|
- }
|
|
|
+ public <T extends Event> void addEventListener(Class<T> event, EventListener<? super T> listener) {
|
|
|
+ dispatcher.addEventListener(event, listener);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public final <T> List<T> getList(TypeToken<T> type, String path) {
|
|
|
- Object value = get(path, Collections.emptyList());
|
|
|
- if(value instanceof List) {
|
|
|
- return new TokenCastList<T>(type, (List<?>)value);
|
|
|
- } else {
|
|
|
- return SingleCollection.of(LexicalCast.cast(value, type));
|
|
|
- }
|
|
|
+ public <T extends Event> void removeEventListener(Class<T> event, EventListener<? super T> listener) {
|
|
|
+ dispatcher.removeEventListener(event, listener);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public final <T> void put(Class<T> type, String path, Object value) throws ConfigException {
|
|
|
- put(path, LexicalCast.cast(value, type));
|
|
|
+ public void removeAllEventListeners() {
|
|
|
+ dispatcher.removeAllEventListeners();
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public final <T> void put(TypeToken<T> type, String path, Object value) throws ConfigException {
|
|
|
- put(path, LexicalCast.cast(value, type));
|
|
|
+ public Collection<EventBinding<?>> getEventListeners() {
|
|
|
+ return dispatcher.getEventListeners();
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public final <T> void putList(Class<T> type, String path, List<?> value) throws ConfigException {
|
|
|
- put(path, new CastList<T>(type, value));
|
|
|
+ public boolean signalEvent(Event event) {
|
|
|
+ return dispatcher.signalEvent(event);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public final <T> void putList(TypeToken<T> type, String path, List<?> value) throws ConfigException {
|
|
|
- put(path, new TokenCastList<T>(type, value));
|
|
|
+ public void dispose() {
|
|
|
+ dispatcher.dispose();
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public final void putList(String path, List<?> value) throws ConfigException {
|
|
|
- put(path, value);
|
|
|
- }
|
|
|
-
|
|
|
- @SuppressWarnings("unchecked")
|
|
|
- protected void put(Object context, String path, Object value) throws ConfigException {
|
|
|
- String[] keys = path.split("\\.");
|
|
|
-
|
|
|
- Object target = context;
|
|
|
- for(int i=0, n=keys.length-1; i<n; i++) {
|
|
|
- String key = keys[i];
|
|
|
- if(target instanceof Map) {
|
|
|
- Map<Object,Object> map = (Map)target;
|
|
|
- if( !map.containsKey(key) ) {
|
|
|
- map.put(key, target = new HashMap<Object, Object>());
|
|
|
- } else {
|
|
|
- target = map.get(key);
|
|
|
- }
|
|
|
- } else {
|
|
|
- throw new ConfigException("property not found: " + StringUtils.join(keys, "."));
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if(target instanceof Map) {
|
|
|
- Map<Object,Object> map = (Map)target;
|
|
|
- map.put(keys[keys.length-1], value);
|
|
|
- } else {
|
|
|
- throw new ConfigException("can't change property: " + path);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- protected Object get(Object context, String path, Object ddefault) throws ConfigException {
|
|
|
- Object now = context;
|
|
|
- for(String key : path.split("\\.") ) {
|
|
|
- Matcher hit = Pattern.compile("(.*)\\[([0-9]+)\\]").matcher(key);
|
|
|
- if(hit.find()) {
|
|
|
- now = next(now, hit.group(1), Integer.parseInt(hit.group(2)));
|
|
|
- } else {
|
|
|
- now = next(now, key, -1);
|
|
|
- }
|
|
|
- if(now == BasicConfiguration.NoResult.CODE) {
|
|
|
- return reportNotFound(path, ddefault);
|
|
|
- }
|
|
|
- }
|
|
|
- return now;
|
|
|
- }
|
|
|
-
|
|
|
- protected Object reportNotFound(String path, Object ddefault) {
|
|
|
- if(NoResult.CODE == ddefault) { // NOPMD
|
|
|
- throw new ConfigException("property not found: " + path);
|
|
|
- } else {
|
|
|
- return ddefault;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private Object next(Object context, String key, int index) {
|
|
|
- if(!(context instanceof Map)) {
|
|
|
- return BasicConfiguration.NoResult.CODE;
|
|
|
- }
|
|
|
- final Map<?,?> now = (Map<?,?>)context;
|
|
|
- if(!now.containsKey(key)) {
|
|
|
- return BasicConfiguration.NoResult.CODE;
|
|
|
- }
|
|
|
- Object value = now.get(key);
|
|
|
- if(index == -1) {
|
|
|
- return value;
|
|
|
- }
|
|
|
- if(value instanceof List) {
|
|
|
- List<?> list = (List)value;
|
|
|
- return index < list.size() ? list.get(index) : BasicConfiguration.NoResult.CODE;
|
|
|
- } else {
|
|
|
- return (index == 0) ? value : BasicConfiguration.NoResult.CODE;
|
|
|
- }
|
|
|
+ public void handleEvent(Event event) {
|
|
|
+ dispatcher.handleEvent(event);
|
|
|
}
|
|
|
|
|
|
- protected Map<String,Object> flat(Object source, String seed) {
|
|
|
- return flat(new LinkedHashMap<String, Object>(), source, seed);
|
|
|
- }
|
|
|
-
|
|
|
- private Map<String,Object> flat(Map<String,Object> target, Object source, String seed) {
|
|
|
- if(source instanceof List) {
|
|
|
- int index = 0;
|
|
|
- for(Object item : (List<?>)source) {
|
|
|
- flat(target, item, seed+"[" + index+ "]");
|
|
|
- index++;
|
|
|
- }
|
|
|
- return target;
|
|
|
- }
|
|
|
-
|
|
|
- if(source instanceof Map) {
|
|
|
- String key = Strings.isEmpty(seed) ? "" : seed + ".";
|
|
|
- for (Map.Entry<?, ?> entry : ((Map<?,?>)source).entrySet()) {
|
|
|
- flat(target, entry.getValue(), key + entry.getKey());
|
|
|
- }
|
|
|
- return target;
|
|
|
- }
|
|
|
-
|
|
|
- target.put(seed, source);
|
|
|
- return target;
|
|
|
- }
|
|
|
-
|
|
|
- private static class CastList<T> extends ListAdapter<T, Object> {
|
|
|
-
|
|
|
- private final Class<T> type;
|
|
|
|
|
|
- @SuppressWarnings("unchecked")
|
|
|
- public CastList(Class<T> type, List<?> content) {
|
|
|
- super((List<Object>)content);
|
|
|
- this.type = type;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public T apply(Object source) {
|
|
|
- return LexicalCast.cast(source, type);
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- private static class TokenCastList<T> extends ListAdapter<T, Object> {
|
|
|
-
|
|
|
- private final TypeToken<T> type;
|
|
|
-
|
|
|
- @SuppressWarnings("unchecked")
|
|
|
- public TokenCastList(TypeToken<T> type, List<?> content) {
|
|
|
- super((List<Object>)content);
|
|
|
- this.type = type;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public T apply(Object source) {
|
|
|
- return LexicalCast.cast(source, type);
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- public class BasicBranch extends AbstractConfiguration {
|
|
|
-
|
|
|
- private static final String MSG_NO_BRANCH = "Not supported by branch.";
|
|
|
-
|
|
|
- private final String home;
|
|
|
- private Object context;
|
|
|
-
|
|
|
- public BasicBranch(String home, Object context) {
|
|
|
- this.home = home;
|
|
|
- initContext(context);
|
|
|
- }
|
|
|
-
|
|
|
- private void initContext(Object context) {
|
|
|
- if(context == null) {
|
|
|
- this.context = AbstractConfiguration.this.get(home);
|
|
|
- } else {
|
|
|
- this.context = context;
|
|
|
- }
|
|
|
- if( !(this.context instanceof Map) ) {
|
|
|
- throw new ConfigException("path is not branch: " + home);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public Branch branch(String path) {
|
|
|
- return AbstractConfiguration.this.branch(home + "." + path);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public List<Branch> branchList(String path) {
|
|
|
- return AbstractConfiguration.this.branchList(home + "." + path);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public String resource() {
|
|
|
- return AbstractConfiguration.this.resource();
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public Map<String, Object> asMap() {
|
|
|
- return flat(context, "");
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public Object get(String path, Object ddefault) throws ConfigException {
|
|
|
- return get(context, path, ddefault);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void put(String path, Object value) throws ConfigException {
|
|
|
- put(context, path, value);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public Configuration load() throws ConfigException {
|
|
|
- AbstractConfiguration.this.load();
|
|
|
- initContext(null);
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public Configuration save() throws ConfigException {
|
|
|
- AbstractConfiguration.this.save();
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public <T extends Event> void addEventListener(Class<T> event, final EventListener<? super T> listener) {
|
|
|
- throw new UnsupportedOperationException(MSG_NO_BRANCH);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public <T extends Event> void removeEventListener(Class<T> event, EventListener<? super T> listener) {
|
|
|
- throw new UnsupportedOperationException(MSG_NO_BRANCH);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void removeAllEventListeners() {
|
|
|
- throw new UnsupportedOperationException(MSG_NO_BRANCH);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public Collection<EventBinding<?>> getEventListeners() {
|
|
|
- throw new UnsupportedOperationException(MSG_NO_BRANCH);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public boolean signalEvent(Event event) {
|
|
|
- throw new UnsupportedOperationException(MSG_NO_BRANCH);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void dispose() {
|
|
|
- throw new UnsupportedOperationException(MSG_NO_BRANCH);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void handleEvent(Event event) {
|
|
|
- throw new UnsupportedOperationException(MSG_NO_BRANCH);
|
|
|
- }
|
|
|
- }
|
|
|
}
|