Using Markdown for Multiline Properties

md-databaseHave you ever just wanted to load multiline properties values into Java and sank in your seat at the choices available?  I had the exact same ‘sigh’ on a new lightweight project when I needed to store some database statements.  After quickly refusing to accept the usual suspects (Java string concatenation, XML bloat, properties files or JSon escape ugliness) I thought it shouldn’t be too hard to load from a Markdown file.  No frameworks, no more dependencies, just a few lines and simple rule for beautifully maintainable multiline properties.

And it really was just a few lines of code.  But like many such ideas it came together after some not so few lines and considerable simplification effort.  But in the end the MarkdownProperties helper handles various formats.

    /**
     * Regular expression to extract code blocks using markdown ` char tokens
     */
    public static final String PATTERN_CODE_BLOCK = "([^`]*[`]{1,3})([^`]*)([`]{1,3}[^`]*)";
 
    /**
     * Matching helper
     *
     * @param pattern the regular expression to match
     * @param group   the group from the regular expression to extract
     * @param payload the payload to match on
     * @return list of extracted group values
     */
    public static List<String> match(String pattern, int group, String payload) {
        List<String> code = new ArrayList<>();
        Pattern compiled = Pattern.compile(pattern);
        Matcher matcher = compiled.matcher(payload);
        while (matcher.find() && matcher.group(group).length() > 0) {
            code.add(matcher.group(group));
        }
        return code;
    }
 
    /**
     * Loads properties from markdown, based on simple pairing of code blocks.
     *
     * @param markdown Path object to markdown file content.
     * @return map of properties extracted from markdown.
     * @throws IOException
     */
    public static Map<String, String> load(Path markdown) throws IOException {
        Map<String, String> result = new HashMap<>();
        StringBuilder name = new StringBuilder();
        StringBuilder buffer = new StringBuilder();
 
        Files.lines(markdown, StandardCharsets.UTF_8)
                .forEach(line -> {
                    buffer.append(line).append('\n');
                    match(PATTERN_CODE_BLOCK, 2, buffer.toString()).stream()
                            .filter(code -> code.trim().length() >= 0)
                            .forEach(code -> {
                                if (name.length() == 0) {
                                    name.append(code);
                                } else {
                                    result.put(name.toString(), code);
                                    name.setLength(0);
                                }
                                buffer.setLength(0);
                            });
                });
        return result;
    }

So there it is, barely 30 lines of real code (albeit Java8 compressed). Still, the basic principles are simple. We continue to buffer lines from the markdown file until a regular expression matches any code blocks. Then we simply cache the first code block as the name, otherwise we collect the code block as a value under the previously cached name.  That’s it.

As long as your Markdown adheres to paired code blocks for names and values of properties, you can be as expressive and rich in other Markdown formatting and documentation content around those properties.

md-tablesHere’s another example of a table in Markdown which contains your properties.  Again, the parser simply ignores everything that isn’t in a code block, pairing code blocks up into properties as it goes.

I’m sure you can think of many improvements and enhancements but even this is quite usable for loading Markdown resource files from the classpath that contains database statements or other documented properties values without littering your Java code.  The concept is easy to port.

GitHub-Mark-64pxHead on over to my GitHub research project for the working code and test class, including the full test Markdown I’ve used to verify it works.

It's only fair to share...Tweet about this on TwitterShare on LinkedInEmail this to someone

Leave a Reply

Your email address will not be published. Required fields are marked *