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

javax0.jamal.snippet.Java Maven / Gradle / Ivy

The newest version!
package javax0.jamal.snippet;

import javax0.jamal.api.BadSyntax;
import javax0.jamal.api.BadSyntaxAt;
import javax0.jamal.api.InnerScopeDependent;
import javax0.jamal.api.Input;
import javax0.jamal.api.Macro;
import javax0.jamal.api.ObjectHolder;
import javax0.jamal.api.Position;
import javax0.jamal.api.Processor;
import javax0.jamal.tools.IndexedPlaceHolders;
import javax0.jamal.tools.IndexedPlaceHolders.ThrowingStringSupplier;
import javax0.jamal.tools.InputHandler;
import javax0.jamal.tools.Scanner;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

import static javax0.jamal.tools.IndexedPlaceHolders.value;
import static javax0.jamal.tools.InputHandler.skipWhiteSpaces;

public class Java {

    public static class ClassMacro implements Macro, InnerScopeDependent, Scanner {
        private static class Trie {
            static final IndexedPlaceHolders formatter = IndexedPlaceHolders.with(
                    // snippet classFormats
                    "$simpleName",
                    "$name",
                    "$canonicalName",
                    "$packageName",
                    "$typeName"
                    // end snippet
            );
        }

        @Override
        public String evaluate(Input in, Processor processor) throws BadSyntax {
            final var scanner = newScanner(in, processor);
            final var format = scanner.str("classFormat", "format").defaultValue("$simpleName");
            scanner.done();
            skipWhiteSpaces(in);
            final var className = in.toString().trim();
            final var klass = classForName(className, processor, getId(), in.getPosition());
            try {
                return Trie.formatter.format(format.get(),
                        klass.getSimpleName(),
                        klass.getName(),
                        klass.getCanonicalName(),
                        klass.getPackageName(),
                        klass.getTypeName()
                );
            } catch (Exception e) {
                throw new BadSyntaxAt(String.format("The class '%s' cannot be formatted in the macro '%s'.", className, getId()), in.getPosition(), e);
            }
        }

        @Override
        public String getId() {
            return "java:class";
        }
    }

    public static class FieldMacro implements Macro, InnerScopeDependent, Scanner {
        private static class Trie {
            static final IndexedPlaceHolders formatter = IndexedPlaceHolders.with(
                    // OTFDC -> of the field's defining class
                    // OTF -> of the field
                    // snippet fieldFormats
                    "$name",// name OTF
                    "$classSimpleName",// simple name OTFDC
                    "$className",// name of the OTFDC
                    "$classCanonicalName",// canonical name OTFDC
                    "$classTypeName",// type name OTFDC
                    "$packageName",// package where the method is
                    "$typeClass",// type OTF
                    "$modifiers",// modifier list of the method
                    "$value"// value OTF in case the field is both `static` and `final`
                    // end snippet
            );
        }

        @Override
        public String evaluate(Input in, Processor processor) throws BadSyntax {
            final var scanner = newScanner(in, processor);
            final var format = scanner.str("fieldFormat", "format").defaultValue("$name");
            scanner.done();
            skipWhiteSpaces(in);
            final var fieldRef = in.toString().trim();
            final var parts = split(in, processor, this);
            final var className = parts[0];
            final var fieldName = parts[1];
            final Class klass;
            klass = classForName(className, processor, getId(), in.getPosition());
            final Field field;
            try {
                field = klass.getDeclaredField(fieldName);
                field.setAccessible(true);
            } catch (Exception e) {
                throw new BadSyntaxAt("The field '" + fieldRef + "' cannot be found on the classpath in the macro '" + getId() + "'.", in.getPosition(), e);
            }
            final ThrowingStringSupplier valueCalculator;
            if (Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers())) {
                valueCalculator = () -> Objects.toString(field.get(null));
            } else {
                valueCalculator = () -> {
                    throw new BadSyntax("Field '" + fieldRef + "' value is not available as it is not 'final' or not 'static'");
                };
            }
            try {
                return Trie.formatter.format(format.get(),
                        value(field.getName()),
                        value(klass.getSimpleName()),
                        value(klass.getName()),
                        value(klass.getCanonicalName()),
                        value(klass.getTypeName()),
                        value(klass.getPackageName()),
                        value(field.getType().getName()),
                        value(Modifier.toString(field.getModifiers())),
                        value(valueCalculator)
                );
            } catch (Exception e) {
                throw new BadSyntax("Cannot evaluate field formatting for '" + fieldRef + "' using '" + format.get() + "'", e);
            }
        }

        @Override
        public String getId() {
            return "java:field";
        }
    }

    public static class MethodMacro implements Macro, InnerScopeDependent, Scanner {
        private static class Trie {
            static final IndexedPlaceHolders formatter = IndexedPlaceHolders.with(
                    // OTMDC -> of the method's defining class
                    // OTM -> of the method
                    // snippet methodFormats
                    "$classSimpleName",// simple name OTMDC
                    "$className",// name of the OTMDC
                    "$classCanonicalName",// canonical name OTMDC
                    "$classTypeName",// type name OTMDC
                    "$packageName",// package where the method is
                    "$name",// name OTM
                    "$typeClass",// return type OTM
                    "$exceptions",// comma separated values of the exception types the method throws
                    "$parameterTypes",// comma separated parameter types
                    "$parameterCount",// number of the parameters in decimal format
                    "$modifiers"// modifier list of the method
                    // end snippet
            );
        }

        @Override
        public String evaluate(Input in, Processor processor) throws BadSyntax {
            final var pos = in.getPosition();
            final var scanner = newScanner(in, processor);
            final var format = scanner.str("methodFormat", "format").defaultValue("$name");
            scanner.done();
            final var parts = split(in, processor, this);
            final var className = parts[0];
            final var methodName = parts[1];
            final Class klass;
            klass = classForName(className, processor, getId(), in.getPosition());

            final var method = Arrays.stream(klass.getDeclaredMethods()).filter(m -> m.getName().equals(methodName)).findAny().orElseThrow(
                    () -> new BadSyntaxAt("The method '" + methodName + "' cannot be found in the class '" + className + "' in the macro '" + getId() + "'.", in.getPosition())
            );
            try {
                return Trie.formatter.format(format.get(),
                        klass.getSimpleName(),
                        klass.getName(),
                        klass.getCanonicalName(),
                        klass.getTypeName(),
                        klass.getPackageName(),
                        method.getName(),
                        method.getReturnType().getName(),
                        Arrays.stream(method.getExceptionTypes()).map(Class::getName).collect(Collectors.joining(",")),
                        Arrays.stream(method.getParameterTypes()).map(Class::getName).collect(Collectors.joining(",")),
                        Objects.toString(method.getParameterCount()),
                        Modifier.toString(method.getModifiers()
                        )
                );
            } catch (Exception e) {//can't really happen as we only have string values for the placeholders
                throw new BadSyntaxAt("There is an exception formatting the method '" + methodName + "' ", pos, e);
            }
        }

        @Override
        public String getId() {
            return "java:method";
        }
    }

    private static Optional getHolder(final Processor processor, final String id) throws BadSyntax {
        final var result = processor.getRegister()
                .getUserDefined(id)
                .filter(p -> p instanceof ObjectHolder)
                .map(p -> (ObjectHolder) p)
                .map(ObjectHolder::getObject)
                .filter(o -> o instanceof CompileJavaMacros.Holder)
                .map(o -> (CompileJavaMacros.Holder) o);
        if (result.isEmpty()) {
            throw new BadSyntax("Macro " + id + " is not defined or not a class store. Were Java classes specified with 'java:sources'?");
        } else {
            return result;
        }
    }

    private static Class classForName(final String className, final Processor processor, final String macroId, final Position pos) throws BadSyntax {
        try {
            return Class.forName(className);
        } catch (ClassNotFoundException e) {
            final var loaded = getHolder(processor, CompileJavaMacros.LOADED_CLASSES);
            try {
                return loaded.get().loaded.get(className);
            } catch (ClassNotFoundException ex) {
                throw new BadSyntaxAt("The class '" + className + "' cannot be found on the classpath in the macro '" + macroId + "'.", pos);
            }
        }

    }

    private static String[] split(Input in, Processor processor, Macro macro) throws BadSyntax {
        final var trimmed = in.toString().trim();
        final int fieldStart, classEnd;
        final String className, fieldName;
        if (!trimmed.isEmpty() && Character.isAlphabetic(trimmed.charAt(0))) {
            int j = trimmed.indexOf('#');
            if (j != -1) {
                fieldStart = j + 1;
                classEnd = j;
            } else {
                j = trimmed.indexOf("::");
                if (j != -1) {
                    fieldStart = j + 2;
                    classEnd = j;
                } else {
                    throw new BadSyntaxAt("Macro '" + macro.getId() + "' needs a class and a method/field name separated by '#' or '::'", in.getPosition());
                }
            }
            className = trimmed.substring(0, classEnd);
            fieldName = trimmed.substring(fieldStart);
        } else {
            final var parts = InputHandler.getParts(in, processor, 2);
            BadSyntaxAt.when(parts.length < 2, "Macro '" + macro.getId() + "' needs exactly two arguments and got " + parts.length + " from '" + in + "'", in.getPosition());
            className = parts[0];
            fieldName = parts[1];
        }
        return new String[]{className, fieldName};
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy