diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index 786b7ee..0000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,71 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: "CodeQL" - -on: - push: - branches: [ master ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ master ] - schedule: - - cron: '36 12 * * 3' - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'java' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] - # Learn more: - # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v1 - - # â„šī¸ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 diff --git a/README.md b/README.md index ce46d41..33ba121 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,23 @@ It looks better to organize config in a YAML file since it makes sense to mainta **YamlConfig** helps read configuration for a java project from a YAML config file and access them via dotted notation. +## ⚠ 1.3.0 Breaking changes: + + +Version **1.3.0** introduces breaking changes as follows: + +``` +YamlConfig config = YamlConfig.load(resource); +YamlConfig config = YamlConfig.load(yaml, resource); +``` + +is now: + +``` +YamlConfig config = new YamlConfig(resource); +YamlConfig config = new YamlConfig(yaml, resource); +``` + ## Features - Uses SnakeYAML for reading YAML, so it can handle any data recognizable by SnakeYAML. - Ease of access using dotted notation to read properties @@ -19,13 +36,13 @@ If you use Maven for Dependency management, you can include this using below dep com.github.jsixface yamlconfig - 1.2.0 + 1.3.0 ``` Or if you use Gradle, you can include this using below dependency. ``` -implementation 'com.github.jsixface:yamlconfig:1.2.0' +implementation 'com.github.jsixface:yamlconfig:1.3.0' ``` ## Usage - internal Yaml @@ -36,7 +53,7 @@ InputStream resource = getClass() .getClassLoader() .getResourceAsStream("config.yml"); -YamlConfig config = YamlConfig.load(resource); +YamlConfig config = new YamlConfig(resource); ``` Assume the contents of `config.yml` is as below: @@ -78,7 +95,7 @@ InputStream resource = getClass() .getResourceAsStream("config.yml"); # pass the lot to YamlConfig -YamlConfig config = YamlConfig.load(yaml, resource); +YamlConfig config = new YamlConfig(yaml, resource); # and now you can use fully qualified dot notation keys: config.getString("service.db.someKey"); diff --git a/src/main/java/com/github/jsixface/YamlConfig.java b/src/main/java/com/github/jsixface/YamlConfig.java index d59121a..87d3fab 100644 --- a/src/main/java/com/github/jsixface/YamlConfig.java +++ b/src/main/java/com/github/jsixface/YamlConfig.java @@ -20,65 +20,57 @@ import java.io.InputStream; import java.io.Reader; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Map; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; public class YamlConfig { - private Object content; private final Pattern arrayKeyPattern = Pattern.compile("^([a-zA-Z][a-zA-Z0-9]+)\\[([0-9]+)]$"); - - private YamlConfig() { - } + private final Pattern keyPattern = Pattern.compile("^([a-zA-Z][a-zA-Z0-9]+)\\[([0-9]+)]$"); + private final Object content; /** * Create configuration from Reader + * * @param reader the reader to read config from - * @return YamlConfig instance */ - public static YamlConfig load(Reader reader) { - YamlConfig instance = new YamlConfig(); - Yaml yml = new Yaml(); - instance.content = yml.load(reader); - return instance; + public YamlConfig(Reader reader) { + final Yaml yaml = new Yaml(); + this.content = yaml.load(reader); } /** * Create configuration from input stream - * @param in the Input stream to read from - * @return YamlConfig instance + * + * @param inputStream the Input stream to read from */ - public static YamlConfig load(InputStream in) { - YamlConfig instance = new YamlConfig(); - Yaml yml = new Yaml(); - instance.content = yml.load(in); - return instance; + public YamlConfig(InputStream inputStream) { + final Yaml yaml = new Yaml(); + this.content = yaml.load(inputStream); } /** * Create configuration from input stream, using your yaml instance - * @param yaml the Yaml instance to use - * @param in the Input stream to read from - * @return YamlConfig instance + * + * @param yaml the Yaml instance to use + * @param inputStream the Input stream to read from */ - public static YamlConfig load(Yaml yaml, InputStream in) { - YamlConfig instance = new YamlConfig(); - instance.content = yaml.load(in); - return instance; + public YamlConfig(Yaml yaml, InputStream inputStream) { + this.content = yaml.load(inputStream); } /** * Gets the String value for the specified key from the config. * - * @param key Key in dotted notation like first.second[2].third - * @return The String value of property.
null if the key is not present - * or not a leaf node. Boolean or Integer or other format - * are converted to String. + * @param key key in dotted notation like first.second[2].third + * @return the String value of property. + *

+ * null if the key is not present or not a leaf node. + *

+ * Boolean or Integer or another format is converted to String. */ public String getString(String key) { - Object foundNode = getNode(key, content); + Object foundNode = getNode(key); if (foundNode != null && !(foundNode instanceof Collection)) { return foundNode.toString(); } @@ -88,45 +80,98 @@ public String getString(String key) { /** * Gets the Integer value for the specified key from the config. * - * @param key Key in dotted notation like first.second[2].third - * @return The Integer value of property.
null if the key is not present - * or not a leaf node. + * @param key key in dotted notation like first.second[2].third + * @return the Integer value of property. + *

+ * null if the key is not present or not a leaf node. */ public Integer getInt(String key) { - Object foundNode = getNode(key, content); - if (foundNode instanceof Integer) { - return (Integer) foundNode; + Object node = getNode(key); + if (node instanceof Integer) { + return (Integer) node; + } + return null; + } + + /** + * Gets a string list for the specified key from the config. + * + * @param key key in dotted notation like first.second + * @return the type list value of property. + *

+ * null if the key is not present or not a leaf node. + */ + public ArrayList getStringList(String key) { + return getList(key, String.class); + } + + /** + * Gets a generic list for the specified key from the config. + * + * @param key key in dotted notation like first.second + * @return the generic list. + *

+ * null if the key is not present or not a leaf node. + */ + public ArrayList getList(String key, Class type) { + final ArrayList node = (ArrayList) getNode(key); + final ArrayList typeList = new ArrayList<>(); + if (node != null) { + try { + node.forEach(o -> typeList.add(type.cast(o))); + return typeList; + } catch (ClassCastException exception) { + return null; + } } return null; } - private Object getNode(String key, Object foundNode) { - String[] parts = decompose(key); + /** + * Gets a node by the key. + * The key follows this pattern: my.key[index].entry + * + * @param key The key to find + * @return the found node or null if not found + */ + private Object getNode(String key) { + final String[] parts = splitByDot(key); + + Object node = content; for (String part : parts) { int arrayNum = -1; - Matcher matcher = arrayKeyPattern.matcher(part); - if (matcher.matches()) { - part = matcher.group(1); - arrayNum = Integer.parseInt(matcher.group(2)); + final Matcher arrayKeyPatternMatcher = arrayKeyPattern.matcher(part); + final Matcher keyPatternMatcher = keyPattern.matcher(part); + if (arrayKeyPatternMatcher.matches()) { + part = arrayKeyPatternMatcher.group(1); + arrayNum = Integer.parseInt(arrayKeyPatternMatcher.group(2)); + } else if (keyPatternMatcher.matches()) { + part = keyPatternMatcher.group(1); } - if (foundNode instanceof Map) { - if (((Map) foundNode).containsKey(part)) { - foundNode = ((Map) foundNode).get(part); + if (node instanceof Map) { + if (((Map) node).containsKey(part)) { + node = ((Map) node).get(part); if (arrayNum >= 0) { - if (foundNode instanceof ArrayList - && ((ArrayList) foundNode).size() > arrayNum) { - foundNode = ((ArrayList) foundNode).get(arrayNum); - } else - return null; + if (node instanceof ArrayList && ((ArrayList) node).size() > arrayNum) { + node = ((ArrayList) node).get(arrayNum); + } } - } else + } else { return null; + } } } - return foundNode; + return node; } - private String[] decompose(String key) { + + /** + * Splits a key by the dot character. + * + * @param key The key to split + * @return the split key path. + */ + private String[] splitByDot(String key) { return key.split("\\."); } } diff --git a/src/test/java/com/github/jsixface/YamlConfigTest.java b/src/test/java/com/github/jsixface/YamlConfigTest.java index a356d21..6f40bc4 100644 --- a/src/test/java/com/github/jsixface/YamlConfigTest.java +++ b/src/test/java/com/github/jsixface/YamlConfigTest.java @@ -16,62 +16,68 @@ package com.github.jsixface; +import org.junit.Before; import org.junit.Test; import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; import static org.junit.Assert.*; public class YamlConfigTest { private final InputStream resource = getClass().getClassLoader().getResourceAsStream("test.yml"); + private YamlConfig config; + + @Before + public void loadResource() { + config = new YamlConfig(resource); + assertNotNull(resource); + } @Test - public void load() { + public void getStringList() { + final ArrayList value = config.getList("services.list", String.class); + assertFalse(value.isEmpty()); } @Test public void getStringArray() { - YamlConfig config = YamlConfig.load(resource); - String value = config.getString("services.names[1].first"); + final String value = config.getString("services.names[1].first"); assertNotNull(value); assertEquals("Andrew", value); } @Test public void getStringOutOfIndex() { - YamlConfig config = YamlConfig.load(resource); - String value = config.getString("services.names[3].first"); + final String value = config.getString("services.names[3].first"); assertNull(value); } @Test public void getStringInvalidKey() { - YamlConfig config = YamlConfig.load(resource); - String value = config.getString("services.test.first"); + final String value = config.getString("services.test.first"); assertNull(value); } @Test public void getStringNumber() { - YamlConfig config = YamlConfig.load(resource); - String value = config.getString("version"); + final String value = config.getString("version"); assertNotNull(value); assertEquals("3", value); } @Test public void getString() { - YamlConfig config = YamlConfig.load(resource); - String value = config.getString("services.db.image"); + final String value = config.getString("services.db.image"); assertNotNull(value); assertEquals("mysql", value); } @Test public void getInt() { - YamlConfig config = YamlConfig.load(resource); - Integer value = config.getInt("version"); - assertNotNull(value); - assertEquals(Integer.valueOf(3), value); + final int value = config.getInt("version"); + assertEquals(3, value); } } \ No newline at end of file diff --git a/src/test/java/com/github/jsixface/YamlConfigWithEnvOverridesTest.java b/src/test/java/com/github/jsixface/YamlConfigWithEnvOverridesTest.java index 277ff46..ffcd574 100644 --- a/src/test/java/com/github/jsixface/YamlConfigWithEnvOverridesTest.java +++ b/src/test/java/com/github/jsixface/YamlConfigWithEnvOverridesTest.java @@ -37,17 +37,15 @@ * key: ${ENV_KEY:-someDefault} */ public class YamlConfigWithEnvOverridesTest { - public static final String ENV_OVERRIDE_VALUE = "my db image env value"; - private final InputStream resource = getClass().getClassLoader().getResourceAsStream("testWithEnvOverrides.yml"); + private final InputStream resource = getClass().getClassLoader().getResourceAsStream("testWithEnvOverrides.yml"); private YamlConfig config; @Before public void setUp() { Yaml yaml = givenYamlInstanceWithEnvScalar(); - - config = YamlConfig.load(yaml, resource); + config = new YamlConfig(yaml, resource); } private Yaml givenYamlInstanceWithEnvScalar() { diff --git a/src/test/resources/test.yml b/src/test/resources/test.yml index c8d6a7a..0d9fb45 100644 --- a/src/test/resources/test.yml +++ b/src/test/resources/test.yml @@ -19,4 +19,7 @@ services: - first: James last: Justinson - first: Andrew - last: Armstrong \ No newline at end of file + last: Armstrong + list: + - "First" + - "Last" \ No newline at end of file