All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ca.derekcormier.recipe.Cake Maven / Gradle / Ivy

There is a newer version: 0.4.0
Show newest version
package ca.derekcormier.recipe;

import org.apache.commons.lang.StringUtils;

import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonIgnore;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class Cake {
    public static final String SEPARATOR = ".";
    private final Map entries = new HashMap<>();
    private final LinkedList prefixStack = new LinkedList<>();

    public static String key(String...subKeys) {
        if (subKeys.length == 0) {
            throw new IllegalArgumentException("cannot form cake key; no keys supplied");
        }
        Arrays.asList(subKeys).forEach(Cake::validateKey);
        return StringUtils.join(subKeys, Cake.SEPARATOR);
    }

    @SuppressWarnings("unchecked")
    public  T get(String... key) {
        List keys = ((key == null) ? new ArrayList() : Arrays.asList(key)).stream()
            .flatMap(k -> Arrays.stream(k == null ? new String[]{null} : StringUtils.split(k, Cake.SEPARATOR)))
            .collect(Collectors.toList());

        if (keys.isEmpty()) {
            throw new IllegalArgumentException("cannot get value for empty key");
        }

        keys.forEach(Cake::validateKey);
        String fullKey = StringUtils.join(keys, Cake.SEPARATOR);

        // search within current namespace
        String searchKey = getPrefixWithSeparator(prefixStack) + fullKey;
        if (entries.containsKey(searchKey)) {
            return (T)entries.get(searchKey);
        }

        // search within each ancestor namespace up to the root
        LinkedList namespaces = new LinkedList<>(prefixStack);
        while (!namespaces.isEmpty()) {
            namespaces.removeLast();
            searchKey = getPrefixWithSeparator(namespaces) + fullKey;
            if (entries.containsKey(searchKey)) {
                return (T)entries.get(searchKey);
            }
        }

        // search within any other namespace (if unambiguous)
        List candidates = entries.keySet().stream().filter(k -> {
            String[] subkeys = StringUtils.split(k, SEPARATOR);
            if (keys.size() > subkeys.length) {
                return false;
            }
            for (int i = 0; i < keys.size(); i++) {
                if (!keys.get(keys.size() - 1 - i).equals(subkeys[subkeys.length - 1 - i])) {
                    return false;
                }
            }
            return true;
        }).collect(Collectors.toList());

        if (candidates.size() == 1) {
            return (T)entries.get(candidates.get(0));
        }
        else if (candidates.isEmpty()) {
            throw new RuntimeException("cake does not contain key '" + fullKey + "'");
        }
        else {
            throw new RuntimeException("cannot retrieve ambiguous key '" + fullKey + "'");
        }
    }

    @SuppressWarnings("unchecked")
    public  T get(Class clazz, String... key) {
        return clazz.cast(get(key));
    }

    public void publish(String key, Object value) {
        getSubKeysAndValidateFullKey(key);
        String newKey = getPrefixWithSeparator(prefixStack) + key;
        entries.put(newKey, value);
    }

    public void inNamespace(String key, Runnable runnable) {
        List keys = getSubKeysAndValidateFullKey(key);
        keys.forEach(prefixStack::addLast);

        try {
            runnable.run();
        }
        finally {
            keys.forEach(k -> prefixStack.removeLast());
        }
    }

    public String getPublishedKeyForValue(Object value, boolean fullyQualified) {
        List matchingKeys = entries.entrySet().stream().filter(e -> e.getValue().equals(value)).map(Map.Entry::getKey).collect(Collectors.toList());
        if (matchingKeys.size() == 1) {
            if (fullyQualified) {
                return matchingKeys.get(0);
            }
            String[] splitKey = StringUtils.split(matchingKeys.get(0), Cake.SEPARATOR);
            return splitKey[splitKey.length - 1];
        }
        if (matchingKeys.size() > 1) {
            throw new IllegalArgumentException("multiple keys found for object " + value);
        }
        throw new IllegalArgumentException("no key found for object " + value);
    }

    @JsonIgnore
    public String getNamespace() {
        return StringUtils.join(prefixStack, Cake.SEPARATOR);
    }

    public boolean hasContext() {
        try {
            getContext();
            return true;
        }
        catch (RuntimeException e) {
            return false;
        }
    }

    @JsonIgnore
    public  T getContext() {
        if (this.prefixStack.isEmpty()) {
            throw new IllegalStateException("cannot get context in root namespace");
        }
        else {
            String prefix = this.getPrefixWithSeparator(this.prefixStack);
            prefix = prefix.substring(0, prefix.length() - 1);

            if (!entries.containsKey(prefix)) {
                throw new RuntimeException("cake does not contain context value for namespace " + prefix);
            }
            return (T)entries.get(prefix);
        }
    }

    @JsonIgnore
    @SuppressWarnings("unchecked")
    public  T getContext(Class clazz) {
        return clazz.cast(getContext());
    }

    @JsonIgnore
    public  T getOrGetContext(String... key) {
        try {
            return get(key);
        }
        catch (RuntimeException e) {
            return getContext();
        }
    }

    @JsonIgnore
    public  T getOrGetContext(Class clazz, String... key) {
        try {
            return clazz.cast(get(key));
        }
        catch (RuntimeException e) {
            return clazz.cast(getContext());
        }
    }

    private static void validateKey(String key) {
        if (StringUtils.isBlank(key)) {
            throw new IllegalArgumentException("keys cannot be empty");
        }
        if (key.contains(Cake.SEPARATOR)) {
            throw new IllegalArgumentException("keys cannot contain the namespace separator: " + Cake.SEPARATOR);
        }
    }

    private List getSubKeysAndValidateFullKey(String fullKey) {
        List keys = (null == fullKey) ? new ArrayList<>() : Arrays.asList(StringUtils.split(fullKey, Cake.SEPARATOR));
        if (keys.isEmpty() || StringUtils.countMatches(fullKey, Cake.SEPARATOR) != keys.size() - 1) {
            throw new IllegalArgumentException("cannot publish value for empty key");
        }

        keys.forEach(Cake::validateKey);
        return keys;
    }

    private String getPrefixWithSeparator(List namespaces) {
        return StringUtils.join(namespaces, Cake.SEPARATOR) + (namespaces.size() > 0 ? Cake.SEPARATOR : "");
    }

    @JsonAnyGetter
    protected Map getEntries() {
        return entries;
    }

    protected void setEntries(Map entries) {
        this.entries.clear();
        this.entries.putAll(entries);
    }

    @JsonAnySetter
    private void setEntry(String key, Object value) {
        entries.put(key, value);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy