net.udidb.engine.ops.impls.help.HelpMessageProvider.java Source code

Java tutorial

Introduction

Here is the source code for net.udidb.engine.ops.impls.help.HelpMessageProvider.java

Source

/*
 * Copyright (c) 2011-2015, Dan McNulty
 * All rights reserved.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

package net.udidb.engine.ops.impls.help;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import org.apache.commons.lang3.tuple.Pair;
import org.reflections.ReflectionUtils;
import org.reflections.Reflections;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.util.ClasspathHelper;

import com.google.common.collect.ImmutableMap;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.inject.name.Named;

import net.sourcecrumbs.api.Range;
import net.udidb.engine.ops.Operation;
import net.udidb.engine.ops.annotations.DisplayName;
import net.udidb.engine.ops.annotations.GlobalOperation;
import net.udidb.engine.ops.annotations.HelpMessage;
import net.udidb.engine.ops.annotations.HelpMessages;
import net.udidb.engine.ops.annotations.Operand;
import net.udidb.expr.Expression;

/**
 * A class that provides access to help messages for operations
 *
 * @author mcnulty
 */
@Singleton
public class HelpMessageProvider {

    private static final String NEWLINE = System.lineSeparator();

    private final Map<String, HelpMessageDescriptor> helpMessageDescriptors = new TreeMap<>();

    private final Map<String, Class<?>> operandTypeHelpMixins = ImmutableMap.<String, Class<?>>builder()
            .put(Expression.class.getCanonicalName(), ExpressionHelpMixIn.class)
            .put(Range.class.getCanonicalName(), RangeHelpMixIn.class).build();

    private static class HelpMessageDescriptor {
        public String shortMessage;

        public String longMessage;

        public boolean global;
    }

    @Inject
    public HelpMessageProvider(@Named("OP_PACKAGES") String[] opPackages) {

        Set<URL> packages = new HashSet<>();
        for (String opPackage : opPackages) {
            packages.addAll(ClasspathHelper.forPackage(opPackage));
        }
        Reflections reflections = new Reflections(packages, new SubTypesScanner());
        for (Class<? extends Operation> opClass : reflections.getSubTypesOf(Operation.class)) {
            if (Modifier.isAbstract(opClass.getModifiers()))
                continue;

            HelpMessage[] helpMessages = opClass.getAnnotationsByType(HelpMessage.class);
            DisplayName displayName = opClass.getAnnotation(DisplayName.class);

            if (helpMessages.length == 0 || displayName == null) {
                throw new RuntimeException(opClass.getSimpleName() + " is an invalid Operation");
            }

            String name = displayName.value();

            HelpMessageDescriptor descriptor = new HelpMessageDescriptor();

            descriptor.global = opClass.isAnnotationPresent(GlobalOperation.class);
            descriptor.shortMessage = selectMessage(helpMessages);
            descriptor.longMessage = createLongMessage(name, descriptor.shortMessage, opClass);

            helpMessageDescriptors.put(name, descriptor);
        }
    }

    private static String selectMessage(HelpMessage[] helpMessages) {

        HelpMessage helpMessage = HelpMessages.Helpers.fromLocale(helpMessages, Locale.getDefault());
        if (helpMessage == null) {
            return "missing help message for Locale " + Locale.getDefault();
        }

        return helpMessage.value();
    }

    private String createLongMessage(String name, String shortMessage, Class<? extends Operation> opClass) {
        List<Pair<Field, Operand>> operands = ReflectionUtils
                .getAllFields(opClass, ReflectionUtils.withAnnotation(Operand.class)).stream()
                .map(f -> Pair.of(f, f.getAnnotation(Operand.class))).filter(p -> p.getRight() != null)
                .sorted((p1, p2) -> Integer.compare(p1.getRight().order(), p2.getRight().order()))
                .collect(Collectors.toList());

        String header = name + " " + operands.stream().map(p -> getOperandIdentifier(p.getLeft(), p.getRight()))
                .collect(Collectors.joining(" "));

        String operandDescriptions = operands.stream()
                .map(p -> String.format("%15s -- %s", p.getLeft().getName(), getOperandDescription(p.getLeft())))
                .collect(Collectors.joining("\n"));

        return header + "\n\n" + shortMessage + "\n\n" + operandDescriptions;
    }

    private String getOperandDescription(Field field) {
        HelpMessage[] helpMessages = field.getAnnotationsByType(HelpMessage.class);
        String message;
        if (helpMessages.length != 0) {
            message = selectMessage(helpMessages);
        } else {
            message = null;
        }

        if (message == null) {
            Class<?> mixInClass = operandTypeHelpMixins.get(field.getType().getCanonicalName());
            if (mixInClass != null) {
                HelpMessage[] mixInMessages = mixInClass.getAnnotationsByType(HelpMessage.class);
                message = selectMessage(mixInMessages);
            }
        }

        return message;
    }

    private String getOperandIdentifier(Field field, Operand operand) {
        String identifier;
        if (operand.restOfLine()) {
            identifier = field.getName() + "...";
        } else {
            identifier = field.getName();
        }

        if (operand.optional()) {
            return "[" + identifier + "]";
        }

        return "<" + identifier + ">";
    }

    /**
     * @param opName the Operation value
     * @return the short message; null if the operation is unknown
     */
    public String getShortMessage(String opName) {
        HelpMessageDescriptor messages = helpMessageDescriptors.get(opName);
        if (messages != null) {
            return messages.shortMessage;
        }

        return null;
    }

    /**
     * @param opName the Operation value
     * @return the long message; null if the operation is unknown
     */
    public String getLongMessage(String opName) {
        HelpMessageDescriptor messages = helpMessageDescriptors.get(opName);
        if (messages != null) {
            return messages.longMessage;
        }

        return null;
    }

    public String getGlobalLongMessage(String opName) {
        HelpMessageDescriptor messages = helpMessageDescriptors.get(opName);
        if (messages != null) {
            if (messages.global) {
                return messages.longMessage;
            }
        }

        return null;
    }

    /**
     * Appends the short message for all register operations, one per line
     *
     * @param builder the StringBuilder to append to
     */
    public void getAllShortMessages(StringBuilder builder) {
        getAllShortMessages(builder, e -> true);
    }

    private void getAllShortMessages(StringBuilder builder,
            Predicate<Map.Entry<String, HelpMessageDescriptor>> predicate) {

        boolean first = true;
        for (Map.Entry<String, HelpMessageDescriptor> entry : helpMessageDescriptors.entrySet()) {

            if (!predicate.test(entry)) {
                continue;
            }

            if (!first) {
                builder.append(NEWLINE);
            } else {
                first = false;
            }

            builder.append(entry.getKey()).append(" -- ").append(entry.getValue().shortMessage);
        }
    }

    /**
     * Appends the short message for all registered, global-only operations, one per line
     *
     * @param builder the StringBuilder to append to
     */
    public void getAllGlobalShortMessages(StringBuilder builder) {
        getAllShortMessages(builder, e -> e.getValue().global);
    }
}