org.diorite.config.impl.ConfigTemplateImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.diorite.config.impl.ConfigTemplateImpl.java

Source

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2016. Diorite (by Bartomiej Mazur (aka GotoFinal))
 *
 * 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 org.diorite.config.impl;

import javax.annotation.Nullable;

import java.lang.invoke.MethodHandle;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;

import org.apache.commons.lang3.tuple.Pair;

import org.diorite.commons.DioriteUtils;
import org.diorite.commons.reflections.DioriteReflectionUtils;
import org.diorite.commons.reflections.MethodInvoker;
import org.diorite.config.ActionMatcherResult;
import org.diorite.config.Config;
import org.diorite.config.ConfigPropertyAction;
import org.diorite.config.ConfigPropertyActionInstance;
import org.diorite.config.ConfigPropertyTemplate;
import org.diorite.config.ConfigTemplate;
import org.diorite.config.MethodSignature;
import org.diorite.config.Property;
import org.diorite.config.annotations.ToKeyMapperFunction;
import org.diorite.config.annotations.ToStringMapperFunction;
import org.diorite.config.impl.actions.ActionsRegistry;
import org.diorite.config.serialization.Serialization;
import org.diorite.config.serialization.comments.DocumentComments;

public class ConfigTemplateImpl<T extends Config> implements ConfigTemplate<T> {
    private final Class<T> type;
    private String name;
    private CharsetEncoder charsetEncoder;
    private CharsetDecoder charsetDecoder;
    private final ConfigImplementationProvider implementationProvider;

    private final List<String> order = new ArrayList<>(10);
    private final Map<String, ConfigPropertyTemplateImpl<?>> mutableProperties = new ConcurrentSkipListMap<>(
            Comparator.comparingInt(this.order::indexOf));
    private final Map<String, ConfigPropertyTemplate<?>> properties = Collections
            .unmodifiableMap(this.mutableProperties);

    private final Map<ConfigPropertyActionInstance, ConfigPropertyTemplate<?>> mutableActions = new ConcurrentHashMap<>(
            15);
    private final Map<ConfigPropertyActionInstance, ConfigPropertyTemplate<?>> actions = Collections
            .unmodifiableMap(this.mutableActions);

    private final Map<MethodSignature, ConfigPropertyActionInstance> actionsDispatcher = new ConcurrentHashMap<>(
            10);

    private final DocumentComments comments;

    public ConfigTemplateImpl(Class<T> type, ConfigImplementationProvider provider) {
        this.type = type;
        this.comments = Serialization.getGlobal().getCommentsManager().getComments(type);
        this.implementationProvider = provider;
        this.name = type.getSimpleName();
        this.charsetEncoder = StandardCharsets.UTF_8.newEncoder().onMalformedInput(CodingErrorAction.REPORT)
                .onUnmappableCharacter(CodingErrorAction.REPORT);
        this.charsetDecoder = StandardCharsets.UTF_8.newDecoder().onMalformedInput(CodingErrorAction.REPORT)
                .onUnmappableCharacter(CodingErrorAction.REPORT);
        this.setupActions();
    }

    private void scanInterface(@Nullable Class<?> type, LinkedList<MethodInvoker> methods) {
        if ((type == null) || (type == Config.class)) {
            return;
        }
        for (Method method : type.getDeclaredMethods()) {
            MethodInvoker methodInvoker = new MethodInvoker(method);
            if (methodInvoker.isPrivate() && (methodInvoker.getParameterCount() == 0)
                    && (methodInvoker.getReturnType() != void.class)
                    && methodInvoker.isAnnotationPresent(Property.class)) {
                methods.add(methodInvoker);
                continue;
            }
            if (methodInvoker.isPublic()) {
                methods.add(methodInvoker);
            }
            if (methodInvoker.isAnnotationPresent(ToKeyMapperFunction.class)
                    || methodInvoker.isAnnotationPresent(ToStringMapperFunction.class)) {
                methods.add(methodInvoker);
            }
        }
        for (Class<?> subType : type.getInterfaces()) {
            this.scanInterface(subType, methods);
        }
    }

    private void scanStringMapper(MethodInvoker methodInvoker, Map<String, MethodInvoker> toStringMappers) {
        if ((methodInvoker.getParameterCount() != 1) || (methodInvoker.getReturnType() != String.class)) {
            throw new IllegalStateException(
                    "This isn't valid to string mapper function, valid function must have signature matching: 'String "
                            + "anyName(KeyType)'");
        }
        String property = methodInvoker.getAnnotation(ToStringMapperFunction.class).property();
        if (toStringMappers.put(property, methodInvoker) != null) {
            throw new IllegalStateException("Duplicated " + ToStringMapperFunction.class.getSimpleName() + " for '"
                    + property + "' in: " + this.type);
        }
    }

    private void scanKeyMapper(MethodInvoker methodInvoker, Map<String, MethodInvoker> toKeyMappers) {

        if ((methodInvoker.getParameterCount() != 0) || (methodInvoker.getParameterTypes()[0] != String.class)
                || (methodInvoker.getReturnType() == void.class)) {
            throw new IllegalStateException(
                    "This isn't valid to key mapper function, valid function must have signature matching: 'KeyType "
                            + "anyName(String)'");
        }
        String property = methodInvoker.getAnnotation(ToKeyMapperFunction.class).property();
        if (toKeyMappers.put(property, methodInvoker) != null) {
            throw new IllegalStateException("Duplicated " + ToKeyMapperFunction.class.getSimpleName() + " for '"
                    + property + "' in: " + this.type);
        }
    }

    private String extractName(MethodInvoker methodInvoker) {
        String name;
        Property annotation = methodInvoker.getAnnotation(Property.class);
        if (annotation.name().isEmpty()) {
            name = methodInvoker.getName();
        } else {
            name = annotation.name();
        }
        if (this.mutableProperties.containsKey(name)) {
            throw new RuntimeException(
                    "Duplicated property found in " + this.type + ": " + name + ", " + methodInvoker.getMethod());
        }
        return name;
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    private void scanMethods(LinkedList<MethodInvoker> methods, Map<String, MethodInvoker> toKeyMappers,
            Map<String, MethodInvoker> toStringMappers, Set<String> knownProperties) {
        int sizeBefore = methods.size();
        for (Iterator<MethodInvoker> iterator = methods.iterator(); iterator.hasNext();) {
            MethodInvoker methodInvoker = iterator.next();
            ConfigPropertyTemplateImpl template;
            Class<?> returnType = methodInvoker.getReturnType();
            Type genericReturnType = methodInvoker.getGenericReturnType();
            String name;
            Method method = methodInvoker.getMethod();
            if (methodInvoker.isPrivate()) {
                if (methodInvoker.isAnnotationPresent(ToKeyMapperFunction.class)) {
                    this.scanKeyMapper(methodInvoker, toKeyMappers);
                    iterator.remove();
                    continue;
                }
                if (methodInvoker.isAnnotationPresent(ToStringMapperFunction.class)) {
                    this.scanStringMapper(methodInvoker, toStringMappers);
                    iterator.remove();
                    continue;
                }
                name = this.extractName(methodInvoker);

                methodInvoker.ensureAccessible();
                if (!knownProperties.add(name)) {
                    throw new IllegalStateException("Duplicated property: " + name);
                }
                template = new ConfigPropertyTemplateImpl(this, returnType, genericReturnType, name,
                        cfg -> methodInvoker.invoke(cfg), method);
            } else {
                Pair<ConfigPropertyAction, ActionMatcherResult> resultPair = ActionsRegistry.findMethod(method,
                        knownProperties::contains);
                if (resultPair == null) {
                    iterator.remove();
                    continue;
                }
                ConfigPropertyAction propertyAction = resultPair.getLeft();
                if (!propertyAction.getActionName().equals("get") && methodInvoker.isDefault()) {
                    throw new RuntimeException("Unexpected default implementation of: " + method);
                }
                ActionMatcherResult matcherResult = resultPair.getRight();
                if (!matcherResult.isValidatedName()) {
                    continue; // wait for validation.
                }
                name = matcherResult.getPropertyName();

                ConfigPropertyTemplateImpl<?> oldTemplate = this.mutableProperties.get(name);
                if (oldTemplate != null) {
                    template = oldTemplate;
                } else {
                    if (!propertyAction.declaresProperty()) {
                        continue;
                    }
                    if (!knownProperties.add(name)) {
                        throw new IllegalStateException("Duplicated property: " + name);
                    }
                    template = new ConfigPropertyTemplateImpl(this, returnType, genericReturnType, name,
                            cfg -> null, method);
                }

                if (propertyAction.getActionName().equals("get") && methodInvoker.isDefault()) {
                    this.defaultValueFromDefaultMethod(methodInvoker, template);
                }

                MethodSignature methodSignature = new MethodSignature(method);
                PropertyActionKey propertyActionKey = new PropertyActionKey(propertyAction, methodSignature);
                this.mutableActions.put(propertyActionKey, template);
                this.actionsDispatcher.put(methodSignature, propertyActionKey);
            }
            this.order.add(name);
            this.mutableProperties.put(name, template);
            iterator.remove();
        }
        if (methods.isEmpty()) {
            return;
        }
        if (sizeBefore == methods.size()) {
            throw new IllegalStateException(
                    "Can't create config template, can't find how to implement: " + methods);
        }
        this.scanMethods(methods, toKeyMappers, toStringMappers, knownProperties);
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    private void defaultValueFromDefaultMethod(MethodInvoker methodInvoker, ConfigPropertyTemplateImpl template) {
        Method method = methodInvoker.getMethod();
        methodInvoker.ensureAccessible();
        try {
            MethodHandle methodHandle = DioriteReflectionUtils.createLookup(method.getDeclaringClass(), -1)
                    .unreflectSpecial(method, method.getDeclaringClass());
            template.setDefaultValueSupplier(cfg -> {
                try {
                    return methodHandle.invoke(cfg);
                } catch (Throwable t) {
                    throw DioriteUtils.sneakyThrow(t);
                }
            });
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    private synchronized void setupActions() {
        LinkedList<MethodInvoker> methods = new LinkedList<>();
        this.scanInterface(this.type, methods);

        Map<String, MethodInvoker> toKeyMappers = new HashMap<>(methods.size());
        Map<String, MethodInvoker> toStringMappers = new HashMap<>(methods.size());
        this.scanMethods(new LinkedList<>(methods), toKeyMappers, toStringMappers, new HashSet<>(10));

        for (Entry<String, MethodInvoker> entry : toKeyMappers.entrySet()) {
            ConfigPropertyTemplateImpl<?> propertyTemplate = this.mutableProperties.get(entry.getKey());
            if (propertyTemplate == null) {
                throw new IllegalStateException("Unknown property: " + entry.getKey());
            }
            propertyTemplate.setToKeyMapper(entry.getValue());
        }
        for (Entry<String, MethodInvoker> entry : toStringMappers.entrySet()) {
            ConfigPropertyTemplateImpl<?> propertyTemplate = this.mutableProperties.get(entry.getKey());
            if (propertyTemplate == null) {
                throw new IllegalStateException("Unknown property: " + entry.getKey());
            }
            propertyTemplate.setToStringMapper(entry.getValue());
        }

        for (ConfigPropertyTemplateImpl<?> template : this.mutableProperties.values()) {
            template.init();
        }
    }

    @Override
    public Class<T> getConfigType() {
        return this.type;
    }

    @Override
    public DocumentComments getComments() {
        return this.comments;
    }

    @Override
    public Map<String, ? extends ConfigPropertyTemplate<?>> getProperties() {
        return this.properties;
    }

    @Override
    public Map<? extends ConfigPropertyActionInstance, ? extends ConfigPropertyTemplate<?>> getActionsMap() {
        return this.actions;
    }

    @Override
    @Nullable
    public ConfigPropertyTemplate<?> getTemplateFor(String property) {
        return this.properties.get(property);
    }

    @Override
    @Nullable
    public ConfigPropertyTemplate<?> getTemplateFor(ConfigPropertyActionInstance action) {
        return this.actions.get(action);
    }

    @Override
    public ConfigPropertyActionInstance getActionFor(MethodSignature method) {
        return this.actionsDispatcher.get(method);
    }

    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public CharsetEncoder getDefaultEncoder() {
        return this.charsetEncoder;
    }

    @Override
    public void setDefaultEncoder(CharsetEncoder encoder) {
        this.charsetEncoder = encoder;
    }

    @Override
    public CharsetDecoder getDefaultDecoder() {
        return this.charsetDecoder;
    }

    @Override
    public void setDefaultDecoder(CharsetDecoder decoder) {
        this.charsetDecoder = decoder;
    }

    @Override
    public T create() {
        return this.implementationProvider.createImplementation(this.type, this);
    }
}