[configuration] Bubbling now works

This commit is contained in:
Roel Spilker committed 2014-01-09 23:16:18 +01:00
1 parent a23f56c21b
commit 96e1c9e02e
7 files changed
+309 -175

No files matched your search

@@ -0,0 +1,76 @@
/*
* Copyright (C) 2014 The Project Lombok Authors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package lombok.core.configuration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import lombok.core.configuration.ConfigurationSource.ListModification;
import lombok.core.configuration.ConfigurationSource.Result;
public class BubblingConfigurationResolver implements ConfigurationResolver {
private final List<ConfigurationSource> sources;
public BubblingConfigurationResolver(List<ConfigurationSource> sources) {
this.sources = sources;
}
@SuppressWarnings("unchecked")
@Override
public <T> T resolve(ConfigurationKey<T> key) {
boolean isList = key.getType().isList();
List<ListModification> listModifications = null;
for (ConfigurationSource source : sources) {
Result<T> result = source.resolve(key);
if (result == null) continue;
if (isList) {
if (listModifications == null) {
listModifications = new ArrayList<ListModification>((List<ListModification>)result.getValue());
} else {
listModifications.addAll(0, (List<ListModification>)result.getValue());
}
}
if (result.isAuthoritative()) {
if (isList) {
break;
}
return result.getValue();
}
}
if (!isList) {
return null;
}
if (listModifications == null) {
return (T) Collections.emptyList();
}
List<Object> listValues = new ArrayList<Object>();
for (ListModification modification : listModifications) {
listValues.remove(modification.getValue());
if (modification.isAdded()) {
listValues.add(modification.getValue());
}
}
return (T) listValues;
}
}
@@ -82,7 +82,6 @@ public final class ConfigurationDataType {
SIMPLE_TYPES = map;
}
private static ConfigurationValueParser enumParser(Object enumType) {
@SuppressWarnings("rawtypes") final Class rawType = (Class)enumType;
return new ConfigurationValueParser(){
@@ -0,0 +1,32 @@
/*
* Copyright (C) 2014 The Project Lombok Authors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package lombok.core.configuration;
public interface ConfigurationErrorReporter {
ConfigurationErrorReporter CONSOLE = new ConfigurationErrorReporter() {
@Override public void report(String error) {
System.err.println(error);
}
};
void report(String error);
}
@@ -22,8 +22,8 @@
package lombok.core.configuration;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
/**
* Describes a configuration key and its type.
@@ -34,7 +34,7 @@ import java.util.Map;
* </pre>
*/
public abstract class ConfigurationKey<T> {
private static final Map<String, ConfigurationDataType> registeredKeys = new HashMap<String, ConfigurationDataType>();
private static final TreeMap<String, ConfigurationDataType> registeredKeys = new TreeMap<String, ConfigurationDataType>(String.CASE_INSENSITIVE_ORDER);
private static Map<String, ConfigurationDataType> copy;
private final String keyName;
@@ -57,7 +57,7 @@ public abstract class ConfigurationKey<T> {
return type;
}
@Override
@Override
public final int hashCode() {
final int prime = 31;
int result = 1;
@@ -69,7 +69,7 @@ public abstract class ConfigurationKey<T> {
/**
* Two configuration are considered equal if and only if their {@code keyName} and {@code type} are equal.
*/
@Override
@Override
public final boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof ConfigurationKey)) return false;
@@ -86,9 +86,10 @@ public abstract class ConfigurationKey<T> {
/**
* Returns a copy of the currently registered keys.
*/
@SuppressWarnings("unchecked")
public static Map<String, ConfigurationDataType> registeredKeys() {
synchronized (registeredKeys) {
if (copy == null) copy = Collections.unmodifiableMap(new HashMap<String, ConfigurationDataType>(registeredKeys));
if (copy == null) copy = Collections.unmodifiableMap((Map<String, ConfigurationDataType>) registeredKeys.clone());
return copy;
}
}
@@ -0,0 +1,67 @@
/*
* Copyright (C) 2014 The Project Lombok Authors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package lombok.core.configuration;
public interface ConfigurationSource {
<T> Result<T> resolve(ConfigurationKey<T> key);
public static final class Result<T> {
private final T value;
private final boolean authoritative;
public Result(T value, boolean authoritative) {
this.value = value;
this.authoritative = authoritative;
}
public T getValue() {
return value;
}
public boolean isAuthoritative() {
return authoritative;
}
@Override public String toString() {
return String.valueOf(value) + (authoritative ? " (set)" : " (delta)");
}
}
public static class ListModification {
private final Object value;
private final boolean added;
public ListModification(Object value, boolean added) {
this.value = value;
this.added = added;
}
public Object getValue() {
return value;
}
public boolean isAdded() {
return added;
}
}
}
@@ -0,0 +1,128 @@
/*
* Copyright (C) 2014 The Project Lombok Authors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package lombok.core.configuration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class StringConfigurationSource implements ConfigurationSource {
private static final Pattern LINE = Pattern.compile("(?:clear\\s+([^=]+))|(?:(\\S*?)\\s*([-+]?=)\\s*(.*?))");
private final Map<String, Result<?>> values;
public static ConfigurationSource forString(String content) {
return forString(content, ConfigurationErrorReporter.CONSOLE);
}
public static ConfigurationSource forString(String content, ConfigurationErrorReporter reporter) {
if (reporter == null) throw new NullPointerException("reporter");
Map<String, Result<?>> values = new TreeMap<String, Result<?>>(String.CASE_INSENSITIVE_ORDER);
Map<String, ConfigurationDataType> registeredKeys = ConfigurationKey.registeredKeys();
for (String line : content.trim().split("\\s*\\n\\s*")) {
if (line.isEmpty() || line.startsWith("#")) continue;
Matcher matcher = LINE.matcher(line);
String operator = null;
String keyName = null;
String value;
if (matcher.matches()) {
if (matcher.group(1) == null) {
keyName = matcher.group(2);
operator = matcher.group(3);
value = matcher.group(4);
} else {
keyName = matcher.group(1);
operator = "clear";
value = null;
}
ConfigurationDataType type = registeredKeys.get(keyName);
if (type == null) {
reporter.report("Unknown key '" + keyName + "' on line: " + line);
} else {
boolean listOperator = operator.equals("+=") || operator.equals("-=");
if (listOperator && !type.isList()) {
reporter.report("'" + keyName + "' is not a list and doesn't support " + operator + " (only = and clear): " + line);
} else if (operator.equals("=") && type.isList()) {
reporter.report("'" + keyName + "' is a list and cannot be assigned to (use +=, -= and clear instead): " + line);
} else {
processResult(values, keyName, operator, value, type, reporter);
}
}
} else {
reporter.report("No valid line: " + line);
}
}
return new StringConfigurationSource(values);
}
private StringConfigurationSource(Map<String, Result<?>> values) {
this.values = new TreeMap<String, Result<?>>(String.CASE_INSENSITIVE_ORDER);
for (Entry<String, Result<?>> entry : values.entrySet()) {
Result<?> result = entry.getValue();
if (result.getValue() instanceof List<?>) {
this.values.put(entry.getKey(), new Result<List<?>>(Collections.unmodifiableList((List<?>) result.getValue()), result.isAuthoritative()));
} else {
this.values.put(entry.getKey(), result);
}
}
}
private static void processResult(Map<String, Result<?>> values, String keyName, String operator, String value, ConfigurationDataType type, ConfigurationErrorReporter reporter) {
Object element = null;
if (value != null) try {
element = type.getParser().parse(value);
} catch (Exception e) {
reporter.report("Error while parsing the value for '" + keyName + "' value '" + value + "' (should be a " + type.getParser().description() + ")");
return;
}
if (operator.equals("clear") || operator.equals("=")) {
if (element == null && type.isList()) {
element = new ArrayList<ListModification>();
}
values.put(keyName, new Result<Object>(element, true));
} else {
Result<?> result = values.get(keyName);
@SuppressWarnings("unchecked")
List<ListModification> list = result == null ? new ArrayList<ListModification>() : (List<ListModification>) result.getValue();
if (result == null) values.put(keyName, new Result<Object>(list, false));
list.add(new ListModification(element, operator.equals("+=")));
}
}
@SuppressWarnings("unchecked")
@Override
public <T> Result<T> resolve(ConfigurationKey<T> key) {
return (Result<T>) values.get(key.getKeyName());
}
}
@@ -1,169 +0,0 @@
/*
* Copyright (C) 2013 The Project Lombok Authors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package lombok.core.configuration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class StringResolver implements ConfigurationResolver {
private static final Pattern LINE = Pattern.compile("(?:clear\\s+([^=]+))|(?:(\\S*?)\\s*([-+]?=)\\s*(.*?))");
private final Map<String, Result> values = new TreeMap<String, Result>(String.CASE_INSENSITIVE_ORDER);
private static class Result {
Object value;
boolean owned;
public Result(Object value, boolean owned) {
this.value = value;
this.owned = owned;
}
@Override public String toString() {
return String.valueOf(value) + (owned ? " (set)" : " (delta)");
}
}
public StringResolver(String content) {
Map<String, ConfigurationDataType> registeredKeys = ConfigurationKey.registeredKeys();
for (String line : content.trim().split("\\s*\\n\\s*")) {
if (line.isEmpty() || line.startsWith("#")) continue;
Matcher matcher = LINE.matcher(line);
String operator = null;
String keyName = null;
String value;
if (matcher.matches()) {
if (matcher.group(1) == null) {
keyName = matcher.group(2);
operator = matcher.group(3);
value = matcher.group(4);
} else {
keyName = matcher.group(1);
operator = "clear";
value = null;
}
ConfigurationDataType type = registeredKeys.get(keyName);
if (type == null) {
System.out.println("Unknown key " + keyName);
} else {
boolean listOperator = operator.equals("+=") || operator.equals("-=");
if (listOperator && !type.isList()) {
System.out.println(keyName + " is not a list");
} else if (operator.equals("=") && type.isList()) {
System.out.println(keyName + " IS a list");
} else {
processResult(keyName, operator, value, type);
}
}
} else {
System.out.println("no match:" + line);
}
}
for (Result r : values.values()) {
if (r.value instanceof List<?>) {
r.value = Collections.unmodifiableList(((List<?>) r.value));
}
}
}
private void processResult(String keyName, String operator, String value, ConfigurationDataType type) {
Object element = null;
if (value != null) try {
element = type.getParser().parse(value);
}
catch (Exception e) {
// log the wrong value
return;
}
if (operator.equals("clear") || operator.equals("=")) {
if (element == null && type.isList()) {
element = new ArrayList<Object>();
}
values.put(keyName, new Result(element, true));
} else {
Result result = values.get(keyName);
@SuppressWarnings("unchecked")
List<Object> list = result == null ? new ArrayList<Object>() : (List<Object>) result.value;
if (result == null) values.put(keyName, new Result(list, false));
list.remove(element);
if (operator.equals("+=")) list.add(element);
}
}
@SuppressWarnings("unchecked")
@Override public <T> T resolve(ConfigurationKey<T> key) {
Result result = values.get(key.getKeyName());
T value = result == null ? null: (T)result.value;
if (value == null && key.getType().isList()) {
value = (T)Collections.emptyList();
}
return value;
}
enum Flag {
WARNING, ERROR
}
private static final ConfigurationKey<String> AAP = new ConfigurationKey<String>("aap") {};
private static final ConfigurationKey<List<String>> NOOT = new ConfigurationKey<List<String>>("noot") {};
private static final ConfigurationKey<Integer> MIES = new ConfigurationKey<Integer>("mies") {};
private static final ConfigurationKey<Boolean> WIM = new ConfigurationKey<Boolean>("wim") {};
private static final ConfigurationKey<TypeName> ZUS = new ConfigurationKey<TypeName>("zus") {};
private static final ConfigurationKey<Flag> JET = new ConfigurationKey<Flag>("jet") {};
public static void main(String[] args) {
print("aap=text\nnoot+=first\nmies=5\nwim=true\nzus=foo.bar.Baz\njet=error");
print("noot+=first\nnoot+=second\n");
print("clear noot");
print("noot+=before-clear\nclear noot\nnoot+=first\nnoot+=second\nnoot+=third\nnoot+=first\nnoot-=second\n");
}
private static void print(String content) {
StringResolver resolver = new StringResolver(content);
System.out.println("\n\n================================================");
System.out.println(content);
System.out.println("================================================\n");
System.out.println(resolver.values);
System.out.println("================================================\n");
String aap = resolver.resolve(AAP);
System.out.println("aap: "+ aap);
List<String> noot = resolver.resolve(NOOT);
System.out.println("noot: "+ noot);
Integer mies = resolver.resolve(MIES);
System.out.println("mies: "+ mies);
Boolean wim = resolver.resolve(WIM);
System.out.println("wim: "+ wim);
TypeName zus = resolver.resolve(ZUS);
System.out.println("zus: "+ zus);
Flag jet = resolver.resolve(JET);
System.out.println("jet: "+ jet);
}
}