commit abfd8ce9988e6e5b92241cb32d46b072fcb73696 Author: Exlll Date: Mon Jan 16 02:11:55 2017 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..790e8e0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +**/.idea +**/out +**/build + +**/.gradle \ No newline at end of file diff --git a/ConfigLib-Bukkit/src/main/java/de/exlll/configlib/ConfigLibPlugin.java b/ConfigLib-Bukkit/src/main/java/de/exlll/configlib/ConfigLibPlugin.java new file mode 100644 index 0000000..57f2abd --- /dev/null +++ b/ConfigLib-Bukkit/src/main/java/de/exlll/configlib/ConfigLibPlugin.java @@ -0,0 +1,6 @@ +package de.exlll.configlib; + +import org.bukkit.plugin.java.JavaPlugin; + +public class ConfigLibPlugin extends JavaPlugin { +} diff --git a/ConfigLib-Bukkit/src/main/resources/plugin.yml b/ConfigLib-Bukkit/src/main/resources/plugin.yml new file mode 100644 index 0000000..ccccd1c --- /dev/null +++ b/ConfigLib-Bukkit/src/main/resources/plugin.yml @@ -0,0 +1,5 @@ +name: ConfigLib +author: Exlll + +version: 1.0 +main: de.exlll.configlib.ConfigLibPlugin \ No newline at end of file diff --git a/ConfigLib-Bungee/src/main/java/de/exlll/configlib/ConfigLibPlugin.java b/ConfigLib-Bungee/src/main/java/de/exlll/configlib/ConfigLibPlugin.java new file mode 100644 index 0000000..bc93e0c --- /dev/null +++ b/ConfigLib-Bungee/src/main/java/de/exlll/configlib/ConfigLibPlugin.java @@ -0,0 +1,6 @@ +package de.exlll.configlib; + +import net.md_5.bungee.api.plugin.Plugin; + +public class ConfigLibPlugin extends Plugin { +} diff --git a/ConfigLib-Bungee/src/main/resources/plugin.yml b/ConfigLib-Bungee/src/main/resources/plugin.yml new file mode 100644 index 0000000..ccccd1c --- /dev/null +++ b/ConfigLib-Bungee/src/main/resources/plugin.yml @@ -0,0 +1,5 @@ +name: ConfigLib +author: Exlll + +version: 1.0 +main: de.exlll.configlib.ConfigLibPlugin \ No newline at end of file diff --git a/ConfigLib-Core/src/main/java/de/exlll/configlib/Comment.java b/ConfigLib-Core/src/main/java/de/exlll/configlib/Comment.java new file mode 100644 index 0000000..6429b7b --- /dev/null +++ b/ConfigLib-Core/src/main/java/de/exlll/configlib/Comment.java @@ -0,0 +1,12 @@ +package de.exlll.configlib; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Comment { + String[] value(); +} diff --git a/ConfigLib-Core/src/main/java/de/exlll/configlib/Comments.java b/ConfigLib-Core/src/main/java/de/exlll/configlib/Comments.java new file mode 100644 index 0000000..029b683 --- /dev/null +++ b/ConfigLib-Core/src/main/java/de/exlll/configlib/Comments.java @@ -0,0 +1,48 @@ +package de.exlll.configlib; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Field; +import java.util.*; + +import static java.util.stream.Collectors.toMap; + +final class Comments { + private final List classComments; + private final Map> commentsByFieldNames; + + Comments(List classComments, + Map> commentsByFieldName) { + this.classComments = classComments; + this.commentsByFieldNames = commentsByFieldName; + } + + public static Comments from(FilteredFieldStreamSupplier supplier) { + Objects.requireNonNull(supplier); + + List classComments = getComments(supplier.getSupplyingClass()); + Map> commentsByFieldNames = supplier + .get() + .filter(Comments::hasCommentAnnotation) + .collect(toMap(Field::getName, Comments::getComments)); + return new Comments(classComments, commentsByFieldNames); + } + + public static List getComments(AnnotatedElement element) { + Comment comment = element.getAnnotation(Comment.class); + return (comment != null) ? + Arrays.asList(comment.value()) : + Collections.emptyList(); + } + + public static boolean hasCommentAnnotation(AnnotatedElement element) { + return element.isAnnotationPresent(Comment.class); + } + + public List getClassComments() { + return classComments; + } + + public Map> getCommentsByFieldNames() { + return commentsByFieldNames; + } +} diff --git a/ConfigLib-Core/src/main/java/de/exlll/configlib/Configuration.java b/ConfigLib-Core/src/main/java/de/exlll/configlib/Configuration.java new file mode 100644 index 0000000..c5743fa --- /dev/null +++ b/ConfigLib-Core/src/main/java/de/exlll/configlib/Configuration.java @@ -0,0 +1,44 @@ +package de.exlll.configlib; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import java.util.Objects; + +public abstract class Configuration { + private final Path configPath; + private final Comments comments; + private final FieldMapper fieldMapper; + + public Configuration(Path configPath) { + Objects.requireNonNull(configPath); + this.configPath = configPath; + FilteredFieldStreamSupplier ffss = new FilteredFieldStreamSupplier( + getClass(), ConfigurationFieldFilter.INSTANCE); + this.comments = Comments.from(ffss); + this.fieldMapper = new FieldMapper(ffss); + } + + public final void save() throws IOException { + createParentDirectories(); + + Map valuesByFieldNames = fieldMapper + .mapFieldNamesToValues(this); + String dump = YamlSerializer.serialize(valuesByFieldNames); + ConfigurationWriter writer = new ConfigurationWriter(configPath, comments); + writer.write(dump); + } + + private void createParentDirectories() throws IOException { + Path parentDirectory = configPath.getParent(); + Files.createDirectories(parentDirectory); + } + + public final void load() throws IOException { + // TODO save if not existent + String dump = new ConfigurationReader(configPath).read(); + Map valuesByFieldNames = YamlSerializer.deserialize(dump); + fieldMapper.mapValuesToFields(valuesByFieldNames, this); + } +} diff --git a/ConfigLib-Core/src/main/java/de/exlll/configlib/ConfigurationFieldFilter.java b/ConfigLib-Core/src/main/java/de/exlll/configlib/ConfigurationFieldFilter.java new file mode 100644 index 0000000..23bde43 --- /dev/null +++ b/ConfigLib-Core/src/main/java/de/exlll/configlib/ConfigurationFieldFilter.java @@ -0,0 +1,18 @@ +package de.exlll.configlib; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.function.Predicate; + +enum ConfigurationFieldFilter implements Predicate { + INSTANCE { + @Override + public boolean test(Field field) { + int modifiers = field.getModifiers(); + boolean fst = Modifier.isFinal(modifiers) || + Modifier.isStatic(modifiers) || + Modifier.isTransient(modifiers); + return !(field.isSynthetic() || fst); + } + }; +} diff --git a/ConfigLib-Core/src/main/java/de/exlll/configlib/ConfigurationReader.java b/ConfigLib-Core/src/main/java/de/exlll/configlib/ConfigurationReader.java new file mode 100644 index 0000000..1646a55 --- /dev/null +++ b/ConfigLib-Core/src/main/java/de/exlll/configlib/ConfigurationReader.java @@ -0,0 +1,19 @@ +package de.exlll.configlib; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Scanner; + +class ConfigurationReader { + private final Path path; + + ConfigurationReader(Path path) { + this.path = path; + } + + String read() throws IOException { + try (Scanner scanner = new Scanner(path)) { + return scanner.useDelimiter("\\z").next(); + } + } +} diff --git a/ConfigLib-Core/src/main/java/de/exlll/configlib/ConfigurationWriter.java b/ConfigLib-Core/src/main/java/de/exlll/configlib/ConfigurationWriter.java new file mode 100644 index 0000000..f70c952 --- /dev/null +++ b/ConfigLib-Core/src/main/java/de/exlll/configlib/ConfigurationWriter.java @@ -0,0 +1,77 @@ +package de.exlll.configlib; + +import java.io.IOException; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +final class ConfigurationWriter { + private final Path configPath; + private final Comments comments; + private Writer writer; + + ConfigurationWriter(Path configPath, Comments comments) { + this.configPath = configPath; + this.comments = comments; + } + + void write(String dump) throws IOException { + writer = Files.newBufferedWriter(configPath); + + writeClassComments(); + writeDump(dump); + + writer.close(); + } + + private void writeClassComments() throws IOException { + List classComments = comments.getClassComments(); + if (!classComments.isEmpty()) { + writeComments(classComments); + writer.write('\n'); + } + } + + private void writeComments(List comments) throws IOException { + for (String comment : comments) { + writer.write("# "); + writer.write(comment); + writer.write('\n'); + } + } + + private void writeDump(String dump) throws IOException { + String[] dumpLines = dump.split("\n"); + for (String dumpLine : dumpLines) { + writeLine(dumpLine); + } + } + + private void writeLine(String line) throws IOException { + writeFieldComment(line); + writer.write(line); + writer.write('\n'); + } + + private void writeFieldComment(String line) throws IOException { + if (!line.contains(":")) { + return; + } + Optional> cmts = getFieldComments(line); + if (cmts.isPresent()) { + writeComments(cmts.get()); + } + } + + private Optional> getFieldComments(String line) { + return comments.getCommentsByFieldNames() + .entrySet() + .stream() + .filter(entry -> line.startsWith(entry.getKey() + ":")) + .map(Map.Entry::getValue) + .findAny(); + } +} diff --git a/ConfigLib-Core/src/main/java/de/exlll/configlib/FieldMapper.java b/ConfigLib-Core/src/main/java/de/exlll/configlib/FieldMapper.java new file mode 100644 index 0000000..1e5ea6c --- /dev/null +++ b/ConfigLib-Core/src/main/java/de/exlll/configlib/FieldMapper.java @@ -0,0 +1,75 @@ +package de.exlll.configlib; + +import java.lang.reflect.Field; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +final class FieldMapper { + private final FilteredFieldStreamSupplier streamSupplier; + + FieldMapper(FilteredFieldStreamSupplier streamSupplier) { + Objects.requireNonNull(streamSupplier); + this.streamSupplier = streamSupplier; + } + +// TODO remove if unneccessary +// Map mapFieldNamesToFields() { +// Collector> fieldCollector = Collectors.toMap( +// Field::getName, +// Function.identity(), +// (field1, field2) -> field1, +// LinkedHashMap::new +// ); +// +// return streamSupplier.get().collect(fieldCollector); +// } + + Map mapFieldNamesToValues(Object instance) { + Map valuesByFieldNames = new LinkedHashMap<>(); + List fields = streamSupplier.get().collect(Collectors.toList()); + + for (Field field : fields) { + Object value = getValue(field, instance); + valuesByFieldNames.put(field.getName(), value); + } + + return valuesByFieldNames; + } + + private Object getValue(Field field, Object instance) { + try { + field.setAccessible(true); + return field.get(instance); + } catch (IllegalAccessException e) { + /* cannot happen */ + throw new AssertionError(e); + } + } + + void mapValuesToFields(Map valuesByFieldNames, Object instance) { + List fields = streamSupplier.get().collect(Collectors.toList()); + + for (Field field : fields) { + String fieldName = field.getName(); + if (valuesByFieldNames.containsKey(fieldName)) { + Object value = valuesByFieldNames.get(fieldName); + setField(field, instance, value); + } + } + } + + private void setField(Field field, Object instance, Object value) { + try { + field.setAccessible(true); + field.set(instance, value); + } catch (IllegalAccessException e) { + /* This exception is only thrown when the field is "static final". + * Since this library filters "static final" fields out, the + * exception is never thrown in production. */ + throw new RuntimeException(e); + } + } +} diff --git a/ConfigLib-Core/src/main/java/de/exlll/configlib/FilteredFieldStreamSupplier.java b/ConfigLib-Core/src/main/java/de/exlll/configlib/FilteredFieldStreamSupplier.java new file mode 100644 index 0000000..eb7e4ad --- /dev/null +++ b/ConfigLib-Core/src/main/java/de/exlll/configlib/FilteredFieldStreamSupplier.java @@ -0,0 +1,32 @@ +package de.exlll.configlib; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Objects; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.Stream; + +final class FilteredFieldStreamSupplier implements Supplier> { + private final Class cls; + private final Predicate fieldFilter; + private final Supplier> streamSupplier; + + FilteredFieldStreamSupplier(Class cls, Predicate fieldFilter) { + Objects.requireNonNull(cls); + Objects.requireNonNull(fieldFilter); + this.cls = cls; + this.fieldFilter = fieldFilter; + Field[] fields = cls.getDeclaredFields(); + streamSupplier = () -> Arrays.stream(fields).filter(fieldFilter); + } + + @Override + public Stream get() { + return streamSupplier.get(); + } + + public Class getSupplyingClass() { + return cls; + } +} diff --git a/ConfigLib-Core/src/main/java/de/exlll/configlib/YamlSerializer.java b/ConfigLib-Core/src/main/java/de/exlll/configlib/YamlSerializer.java new file mode 100644 index 0000000..0118d57 --- /dev/null +++ b/ConfigLib-Core/src/main/java/de/exlll/configlib/YamlSerializer.java @@ -0,0 +1,33 @@ +package de.exlll.configlib; + +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.parser.ParserException; + +import java.util.Map; + +enum YamlSerializer { + ; + private static final Yaml yaml = createYaml(); + + private static Yaml createYaml() { + DumperOptions options = new DumperOptions(); + options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + options.setIndent(2); + return new Yaml(options); + } + + static String serialize(Map mapToDump) { + return yaml.dump(mapToDump); + } + + /** + * @throws ParserException if invalid YAML + * @throws ClassCastException if parsed Object is not a {@code Map} + */ + static Map deserialize(String stringToLoad) { + @SuppressWarnings("unchecked") + Map map = (Map) yaml.load(stringToLoad); + return map; + } +} diff --git a/ConfigLib-Core/src/test/java/ConfigLibTestSuite.java b/ConfigLib-Core/src/test/java/ConfigLibTestSuite.java new file mode 100644 index 0000000..64201cd --- /dev/null +++ b/ConfigLib-Core/src/test/java/ConfigLibTestSuite.java @@ -0,0 +1,17 @@ +import de.exlll.configlib.*; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + CommentsTest.class, + ConfigurationFieldFilterTest.class, + ConfigurationReaderTest.class, + ConfigurationTest.class, + ConfigurationWriterTest.class, + FieldMapperTest.class, + FilteredFieldStreamSupplierTest.class, + YamlSerializerTest.class +}) +public class ConfigLibTestSuite { +} diff --git a/ConfigLib-Core/src/test/java/de/exlll/configlib/CommentsTest.java b/ConfigLib-Core/src/test/java/de/exlll/configlib/CommentsTest.java new file mode 100644 index 0000000..d367d4d --- /dev/null +++ b/ConfigLib-Core/src/test/java/de/exlll/configlib/CommentsTest.java @@ -0,0 +1,88 @@ +package de.exlll.configlib; + +import com.google.common.jimfs.Jimfs; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.lang.reflect.Field; +import java.nio.file.FileSystem; +import java.util.*; +import java.util.function.Predicate; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +public class CommentsTest { + @Rule + public ExpectedException exception = ExpectedException.none(); + + private static final FileSystem fs = Jimfs.newFileSystem(); + private static final Predicate TRUE = x -> true; + + @Test + public void factoryRequiresNonNullSupplier() throws Exception { + exception.expect(NullPointerException.class); + Comments.from(null); + } + + @Test + public void factoryReturnsNotNull() throws Exception { + Comments comments = Comments.from(new FilteredFieldStreamSupplier( + getClass(), TRUE)); + assertThat(comments, notNullValue()); + } + + @Test + public void factoryReturnsCommentsWithClassComments() throws Exception { + Comments comments = Comments.from(new FilteredFieldStreamSupplier( + TestClassWithComment.class, TRUE)); + + List classComments = Collections.singletonList("This is a class comment."); + assertThat(comments.getClassComments(), is(classComments)); + } + + @Test + public void factoryReturnsCommentsWithFilteredFieldComments() throws Exception { + Comments comments = Comments.from(new FilteredFieldStreamSupplier( + TestClass.class, TRUE)); + + /* Fields which don't have a Comment annotation are filtered out. */ + Map> commentsByFieldName = new HashMap<>(); + commentsByFieldName.put("field1", Collections.singletonList("Field1")); + commentsByFieldName.put("field2", Arrays.asList("Field2", "field2")); + + assertThat(comments.getCommentsByFieldNames(), is(commentsByFieldName)); + } + + @Test + public void getCommentsReturnsEmptyListIfNotCommentsPresent() throws Exception { + List comments = Comments.getComments(TestClassWithoutComment.class); + + assertThat(comments, is(Collections.emptyList())); + } + + @Test + public void getCommentsReturnsCommentsAsList() throws Exception { + List comments = Comments.getComments(TestClassWithComment.class); + + assertThat(comments, is(Collections.singletonList("This is a class comment."))); + } + + + @Comment("This is a class comment.") + private static final class TestClassWithComment { + } + + private static final class TestClassWithoutComment { + } + + private static final class TestClass { + @Comment("Field1") + private int field1; + @Comment({"Field2", "field2"}) + private int field2; + private int field3; + } +} \ No newline at end of file diff --git a/ConfigLib-Core/src/test/java/de/exlll/configlib/ConfigurationFieldFilterTest.java b/ConfigLib-Core/src/test/java/de/exlll/configlib/ConfigurationFieldFilterTest.java new file mode 100644 index 0000000..ce0ba42 --- /dev/null +++ b/ConfigLib-Core/src/test/java/de/exlll/configlib/ConfigurationFieldFilterTest.java @@ -0,0 +1,68 @@ +package de.exlll.configlib; + +import org.junit.Before; +import org.junit.Test; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +public class ConfigurationFieldFilterTest { + private static final Supplier> streamSupplier = new FilteredFieldStreamSupplier( + TestClass.class, ConfigurationFieldFilter.INSTANCE); + private Stream fieldSupplier; + + @Before + public void setUp() throws Exception { + fieldSupplier = streamSupplier.get(); + } + + @Test + public void filterFiltersSyntheticFields() throws Exception { + Supplier> streamSupplier = new FilteredFieldStreamSupplier( + TestClassSynthetic.class, ConfigurationFieldFilter.INSTANCE); + + streamSupplier.get().forEach(field -> assertThat(field.isSynthetic(), is(false))); + } + + @Test + public void filterFiltersFinalFields() throws Exception { + fieldSupplier.forEach(field -> assertThat( + Modifier.isFinal(field.getModifiers()), is(false))); + } + + @Test + public void filterFiltersStaticFields() throws Exception { + fieldSupplier.forEach(field -> assertThat( + Modifier.isStatic(field.getModifiers()), is(false))); + } + + @Test + public void filterFiltersTransientFields() throws Exception { + fieldSupplier.forEach(field -> assertThat( + Modifier.isTransient(field.getModifiers()), is(false))); + } + + private static final class TestClass { + /* filtered fields */ + private static int a; + private final int b = 1; + private transient int c; + + /* not filtered fields */ + private int d; + protected int e; + int f; + public int g; + double h; + volatile int i; + } + + private final class TestClassSynthetic { + int a; + } +} \ No newline at end of file diff --git a/ConfigLib-Core/src/test/java/de/exlll/configlib/ConfigurationReaderTest.java b/ConfigLib-Core/src/test/java/de/exlll/configlib/ConfigurationReaderTest.java new file mode 100644 index 0000000..5eceb92 --- /dev/null +++ b/ConfigLib-Core/src/test/java/de/exlll/configlib/ConfigurationReaderTest.java @@ -0,0 +1,32 @@ +package de.exlll.configlib; + +import com.google.common.jimfs.Jimfs; +import org.junit.Before; +import org.junit.Test; + +import java.io.Writer; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +public class ConfigurationReaderTest { + private static final FileSystem fs = Jimfs.newFileSystem(); + private ConfigurationReader reader; + + @Before + public void setUp() throws Exception { + Path configPath = fs.getPath("/config.yml"); + Writer writer = Files.newBufferedWriter(configPath); + writer.write(TestConfiguration.CONFIG_AS_STRING); + writer.close(); + reader = new ConfigurationReader(configPath); + } + + @Test + public void readFileReadsFile() throws Exception { + assertThat(reader.read(), is(TestConfiguration.CONFIG_AS_STRING)); + } +} \ No newline at end of file diff --git a/ConfigLib-Core/src/test/java/de/exlll/configlib/ConfigurationTest.java b/ConfigLib-Core/src/test/java/de/exlll/configlib/ConfigurationTest.java new file mode 100644 index 0000000..c7d52c3 --- /dev/null +++ b/ConfigLib-Core/src/test/java/de/exlll/configlib/ConfigurationTest.java @@ -0,0 +1,91 @@ +package de.exlll.configlib; + +import com.google.common.jimfs.Jimfs; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +public class ConfigurationTest { + @Rule + public ExpectedException exception = ExpectedException.none(); + + private static final FileSystem fs = Jimfs.newFileSystem(); + private Path filePath; + + @Before + public void setUp() throws Exception { + filePath = fs.getPath("/dir1/dir2/file1"); + Files.deleteIfExists(filePath); + Files.deleteIfExists(filePath.getParent()); + } + + @Test + public void constructorRequiresNonNullPath() throws Exception { + exception.expect(NullPointerException.class); + new Configuration(null) { + }; + } + + @Test + public void saveCreatesParentDirectories() throws Exception { + Path dirPath = filePath.getParent(); + + assertThat(Files.notExists(dirPath), is(true)); + + new Configuration(filePath) { + }.save(); + + assertThat(Files.exists(dirPath), is(true)); + } + + @Test + public void saveDoesntThrowExceptionIfDirectoryAlreadyCreated() throws Exception { + Configuration configuration = new Configuration(filePath) { + }; + configuration.save(); + configuration.save(); + } + + @Test + public void save() throws Exception { + new TestConfiguration(filePath).save(); + + String config = new ConfigurationReader(filePath).read(); + + assertThat(config, is(TestConfiguration.CONFIG_AS_STRING)); + } + + @Test + public void load() throws Exception { + new OriginalTestClass(filePath).save(); + ChangedTestClass cls = new ChangedTestClass(filePath); + cls.load(); + + assertThat(cls.a, is(0)); + assertThat(cls.b, is(1)); + assertThat(cls.c, is(4)); + } + + private static final class OriginalTestClass extends Configuration { + private int a = 0; + private int b = 1; + + public OriginalTestClass(Path configPath) {super(configPath);} + } + + private static final class ChangedTestClass extends Configuration { + private int a = 2; + private int b = 3; + private int c = 4; + + public ChangedTestClass(Path configPath) {super(configPath);} + } +} \ No newline at end of file diff --git a/ConfigLib-Core/src/test/java/de/exlll/configlib/ConfigurationWriterTest.java b/ConfigLib-Core/src/test/java/de/exlll/configlib/ConfigurationWriterTest.java new file mode 100644 index 0000000..7e251f6 --- /dev/null +++ b/ConfigLib-Core/src/test/java/de/exlll/configlib/ConfigurationWriterTest.java @@ -0,0 +1,40 @@ +package de.exlll.configlib; + +import com.google.common.jimfs.Jimfs; +import org.junit.Before; +import org.junit.Test; + +import java.nio.file.FileSystem; +import java.nio.file.Path; +import java.util.Map; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +public class ConfigurationWriterTest { + private static final FileSystem fs = Jimfs.newFileSystem(); + private Path configPath; + private ConfigurationWriter writer; + private String dump; + + @Before + public void setUp() throws Exception { + configPath = fs.getPath("/config.yml"); + FilteredFieldStreamSupplier streamSupplier = new FilteredFieldStreamSupplier( + TestConfiguration.class, ConfigurationFieldFilter.INSTANCE); + Comments comments = Comments.from(streamSupplier); + FieldMapper mapper = new FieldMapper(streamSupplier); + Map valuesByFieldNames = mapper.mapFieldNamesToValues( + new TestConfiguration(configPath)); + dump = YamlSerializer.serialize(valuesByFieldNames); + writer = new ConfigurationWriter(configPath, comments); + } + + @Test + public void write() throws Exception { + writer.write(dump); + String read = new ConfigurationReader(configPath).read(); + + assertThat(read, is(TestConfiguration.CONFIG_AS_STRING)); + } +} \ No newline at end of file diff --git a/ConfigLib-Core/src/test/java/de/exlll/configlib/FieldMapperTest.java b/ConfigLib-Core/src/test/java/de/exlll/configlib/FieldMapperTest.java new file mode 100644 index 0000000..3ae8025 --- /dev/null +++ b/ConfigLib-Core/src/test/java/de/exlll/configlib/FieldMapperTest.java @@ -0,0 +1,70 @@ +package de.exlll.configlib; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.*; + +public class FieldMapperTest { + @Rule + public ExpectedException exception = ExpectedException.none(); + + private static final FilteredFieldStreamSupplier streamSupplier = + new FilteredFieldStreamSupplier(TestClass.class, x -> true); + private static final FieldMapper mapper = new FieldMapper(streamSupplier); + + @Test + public void constructorRequiresNonNullSupplier() throws Exception { + exception.expect(NullPointerException.class); + new FieldMapper(null); + } + +// @Test +// public void mapFieldNamesToFields() throws Exception { +// Map fieldsByFieldNames = new LinkedHashMap<>(); +// fieldsByFieldNames.put("field1", TestClass.class.getDeclaredField("field1")); +// fieldsByFieldNames.put("field2", TestClass.class.getDeclaredField("field2")); +// fieldsByFieldNames.put("field3", TestClass.class.getDeclaredField("field3")); +// assertThat(fieldsByFieldNames, is(mapper.mapFieldNamesToFields())); +// } + + @Test + public void mapFieldNamesToValues() throws Exception { + TestClass testClass = new TestClass(); + Map valuesByFieldNames = new LinkedHashMap<>(); + + valuesByFieldNames.put("field1", "field1"); + valuesByFieldNames.put("field2", 2); + valuesByFieldNames.put("field3", "field3"); + + assertThat(valuesByFieldNames, is(mapper.mapFieldNamesToValues(testClass))); + } + + @Test + public void mapValuesToFields() throws Exception { + Map valuesByFieldNames = new HashMap<>(); + valuesByFieldNames.put("field1", "new"); + valuesByFieldNames.put("field2", 10); + + TestClass testClass = new TestClass(); + mapper.mapValuesToFields(valuesByFieldNames, testClass); + + assertThat(testClass.field1, is("new")); + assertThat(testClass.field2, is(10)); + assertThat(testClass.field3, is("field3")); + } + + private static final class TestClass { + @Comment("Comment1") + private String field1 = "field1"; + @Comment({"Comment2", "Comment3"}) + private int field2 = 2; + private String field3 = "field3"; + } +} \ No newline at end of file diff --git a/ConfigLib-Core/src/test/java/de/exlll/configlib/FilteredFieldStreamSupplierTest.java b/ConfigLib-Core/src/test/java/de/exlll/configlib/FilteredFieldStreamSupplierTest.java new file mode 100644 index 0000000..962eab5 --- /dev/null +++ b/ConfigLib-Core/src/test/java/de/exlll/configlib/FilteredFieldStreamSupplierTest.java @@ -0,0 +1,59 @@ +package de.exlll.configlib; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +public class FilteredFieldStreamSupplierTest { + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Test + public void constructorRequiresNonNullPredicate() throws Exception { + exception.expect(NullPointerException.class); + new FilteredFieldStreamSupplier(getClass(), null); + } + + @Test + public void constructorRequiresNonNullClass() throws Exception { + exception.expect(NullPointerException.class); + new FilteredFieldStreamSupplier(null, field -> true); + } + + @Test + public void supplierReturnsStream() throws Exception { + Supplier> supplier = new FilteredFieldStreamSupplier( + getClass(), field -> true); + + Stream fieldStream = supplier.get(); + + assertThat(fieldStream, is(notNullValue())); + } + + @Test + public void supplierApplysFilter() throws Exception { + Supplier> supplier = new FilteredFieldStreamSupplier( + TestClass.class, field -> !Modifier.isPublic(field.getModifiers())); + + Stream fieldStream = supplier.get(); + + assertThat(fieldStream.count(), is(3L)); + } + + private static final class TestClass { + public int i; + protected int j; + int k; + private int l; + } + +} \ No newline at end of file diff --git a/ConfigLib-Core/src/test/java/de/exlll/configlib/TestConfiguration.java b/ConfigLib-Core/src/test/java/de/exlll/configlib/TestConfiguration.java new file mode 100644 index 0000000..f7a70a6 --- /dev/null +++ b/ConfigLib-Core/src/test/java/de/exlll/configlib/TestConfiguration.java @@ -0,0 +1,144 @@ +package de.exlll.configlib; + +import java.nio.file.Path; +import java.util.*; + +@Comment({ + "This is a test configuration.", + "This comment is applied to a class." +}) +final class TestConfiguration extends Configuration { + static final String CONFIG_AS_STRING = "# This is a test configuration.\n" + + "# This comment is applied to a class.\n" + + "\n" + + "# This comment is applied to a field.\n" + + "# It has more than 1 line.\n" + + "port: -1\n" + + "localhost: localhost\n" + + "modifier: 3.14\n" + + "# This comment is applied to a field.\n" + + "allowedIps:\n" + + "- 127.0.0.1\n" + + "- 127.0.0.2\n" + + "- 127.0.0.3\n" + + "intsByStrings:\n" + + " third: 3\n" + + " first: 1\n" + + " second: 2\n" + + "stringListsByString:\n" + + " za:\n" + + " - z1\n" + + " - z2\n" + + " ya:\n" + + " - y1\n" + + " - y2\n" + + " xa:\n" + + " - x1\n" + + " - x2\n"; + + @Comment({ + "This comment is applied to a field.", + "It has more than 1 line." + }) + private int port = -1; + private String localhost = "localhost"; + private double modifier = 3.14; + @Comment("This comment is applied to a field.") + private List allowedIps = new ArrayList<>(); + private Map intsByStrings = new HashMap<>(); + private Map> stringListsByString = new HashMap<>(); + + + public TestConfiguration(Path path) { + super(path); + allowedIps.add("127.0.0.1"); + allowedIps.add("127.0.0.2"); + allowedIps.add("127.0.0.3"); + + intsByStrings.put("first", 1); + intsByStrings.put("second", 2); + intsByStrings.put("third", 3); + + stringListsByString.put("xa", Arrays.asList("x1", "x2")); + stringListsByString.put("ya", Arrays.asList("y1", "y2")); + stringListsByString.put("za", Arrays.asList("z1", "z2")); + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public String getLocalhost() { + return localhost; + } + + public void setLocalhost(String localhost) { + this.localhost = localhost; + } + + public double getModifier() { + return modifier; + } + + public void setModifier(double modifier) { + this.modifier = modifier; + } + + public List getAllowedIps() { + return allowedIps; + } + + public void setAllowedIps(List allowedIps) { + this.allowedIps = allowedIps; + } + + public Map getIntsByStrings() { + return intsByStrings; + } + + public void setIntsByStrings(Map intsByStrings) { + this.intsByStrings = intsByStrings; + } + + public Map> getStringListsByString() { + return stringListsByString; + } + + public void setStringListsByString( + Map> stringListsByString) { + this.stringListsByString = stringListsByString; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TestConfiguration that = (TestConfiguration) o; + + if (port != that.port) return false; + if (Double.compare(that.modifier, modifier) != 0) return false; + if (!localhost.equals(that.localhost)) return false; + if (!allowedIps.equals(that.allowedIps)) return false; + if (!intsByStrings.equals(that.intsByStrings)) return false; + return stringListsByString.equals(that.stringListsByString); + } + + @Override + public int hashCode() { + int result; + long temp; + result = port; + result = 31 * result + localhost.hashCode(); + temp = Double.doubleToLongBits(modifier); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + result = 31 * result + allowedIps.hashCode(); + result = 31 * result + intsByStrings.hashCode(); + result = 31 * result + stringListsByString.hashCode(); + return result; + } +} \ No newline at end of file diff --git a/ConfigLib-Core/src/test/java/de/exlll/configlib/YamlSerializerTest.java b/ConfigLib-Core/src/test/java/de/exlll/configlib/YamlSerializerTest.java new file mode 100644 index 0000000..86fe27e --- /dev/null +++ b/ConfigLib-Core/src/test/java/de/exlll/configlib/YamlSerializerTest.java @@ -0,0 +1,35 @@ +package de.exlll.configlib; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.yaml.snakeyaml.parser.ParserException; + +import java.util.HashMap; +import java.util.Map; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +public class YamlSerializerTest { + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Test + public void desirializeRequiresValidYaml() throws Exception { + exception.expect(ParserException.class); + YamlSerializer.deserialize("{a"); + } + + @Test + public void desirializeReturnsMaps() throws Exception { + Map actual = YamlSerializer + .deserialize("a: 1\nb: c"); + + Map expected = new HashMap<>(); + expected.put("a", 1); + expected.put("b", "c"); + + assertThat(actual, is(expected)); + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..efd0292 --- /dev/null +++ b/build.gradle @@ -0,0 +1,56 @@ +group 'de.exlll' +version '1.0-SNAPSHOT' + +subprojects { + apply plugin: 'java' + + repositories { mavenCentral() } + + dependencies { + testCompile group: 'junit', name: 'junit', version: '4.12' + testCompile group: 'org.hamcrest', name: 'hamcrest-all', version: '1.3' + testCompile group: 'com.google.jimfs', name: 'jimfs', version: '1.1' + } +} + +project(':config-lib-core') { + dependencies { + compile group: 'org.yaml', name: 'snakeyaml', version: '1.17' + } +} + +project(':config-lib-bukkit') { + repositories { + maven { + url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' + } + } + dependencies { + compile project(':config-lib-core') + compile group: 'org.bukkit', name: 'bukkit', version: '1.11.2-R0.1-SNAPSHOT' + } + jar { + baseName = 'ConfigLib.bukkit' + from { + project(':config-lib-core').sourceSets.main.output + } + } +} + +project(':config-lib-bungee') { + repositories { + maven { + url 'https://oss.sonatype.org/content/repositories/snapshots' + } + } + dependencies { + compile project(':config-lib-core') + compile group: 'net.md-5', name: 'bungeecord-api', version: '1.10-SNAPSHOT' + } + jar { + baseName = 'ConfigLib.bungee' + from { + project(':config-lib-core').sourceSets.main.output + } + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..6ffa237 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..2926ac4 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sat Jan 14 21:57:25 CET 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-all.zip diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..9aa616c --- /dev/null +++ b/gradlew @@ -0,0 +1,169 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..40b9d3f --- /dev/null +++ b/settings.gradle @@ -0,0 +1,9 @@ +rootProject.name = 'config-lib' +include 'ConfigLib-Core' +findProject(':ConfigLib-Core')?.name = 'config-lib-core' +include 'ConfigLib-Bukkit' +findProject(':ConfigLib-Bukkit')?.name = 'config-lib-bukkit' +include 'ConfigLib-Bungee' +findProject(':ConfigLib-Bungee')?.name = 'config-lib-bungee' +rootProject.name = 'ConfigLib' +