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

javarepl.Evaluator Maven / Gradle / Ivy

There is a newer version: 431
Show newest version
package javarepl;

import com.googlecode.totallylazy.*;
import com.googlecode.totallylazy.Option;
import com.googlecode.totallylazy.annotations.multimethod;
import com.googlecode.totallylazy.functions.Reducer;
import javarepl.expressions.*;
import javarepl.expressions.Type;
import javarepl.expressions.Value;

import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import java.io.File;
import java.io.FileNotFoundException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.regex.MatchResult;

import static com.googlecode.totallylazy.Either.left;
import static com.googlecode.totallylazy.Either.right;
import static com.googlecode.totallylazy.Files.*;
import static com.googlecode.totallylazy.Option.*;
import static com.googlecode.totallylazy.Sequences.empty;
import static com.googlecode.totallylazy.Sequences.sequence;
import static com.googlecode.totallylazy.predicates.Not.not;
import static com.googlecode.totallylazy.predicates.Predicates.equalTo;
import static com.googlecode.totallylazy.predicates.Predicates.where;
import static java.io.File.pathSeparator;
import static javarepl.Evaluation.evaluation;
import static javarepl.EvaluationClassLoader.evaluationClassLoader;
import static javarepl.EvaluationContext.evaluationContext;
import static javarepl.Result.noResult;
import static javarepl.Utils.randomIdentifier;
import static javarepl.Utils.urlAsFilePath;
import static javarepl.expressions.Patterns.*;
import static javarepl.rendering.EvaluationClassRenderer.renderExpressionClass;
import static javarepl.rendering.EvaluationClassRenderer.renderMethodSignatureDetection;
import static javarepl.rendering.ExpressionSourceRenderer.renderExpressionSource;
import static javarepl.rendering.ExpressionTokenRenderer.EXPRESSION_TOKEN;
import static javax.tools.ToolProvider.getSystemJavaCompiler;

public class Evaluator {
    private EvaluationClassLoader classLoader;
    private EvaluationContext context;

    public Evaluator(EvaluationContext context, EvaluationClassLoader classLoader) {
        initializeEvaluator(context, classLoader);
    }

    public Either evaluate(final String expr) {
        return parseExpression(expr).flatMap(
                expression -> {
                    Either resultForValue = evaluate(expression);
                    if (resultForValue.isLeft() && resultForValue.left() instanceof ExpressionCompilationException && expression instanceof Value) {
                        Either resultForStatement = evaluate(new Statement(expr));
                        return resultForStatement.isLeft() && resultForStatement.left() instanceof ExpressionCompilationException
                                ? left(new ExpressionCompilationException(sequence(resultForStatement.left().getMessage(), resultForValue.left().getMessage()).unique().toString("\n\n")))
                                : resultForStatement;
                    }

                    return resultForValue;
                });
    }

    public Either parseExpression(String expression) {
        if (isValidImport(expression))
            return createImport(expression);

        if (isValidType(expression))
            return createTypeExpression(expression);

        if (isValidMethod(expression))
            return createMethodExpression(expression);

        if (isValidAssignmentWithType(expression))
            return createAssignmentWithType(expression);

        if (isValidAssignment(expression))
            return createAssignmentExpression(expression);

        return createValueExpression(expression);
    }

    public void addResults(Sequence result) {
        context = context.addResults(result);
    }

    private Either createImport(String expression) {
        return right((Expression) new Import(expression, importPattern.match(expression).group(1)));
    }

    private Either createAssignmentWithType(String expression) {
        try {
            MatchResult match = Patterns.assignmentWithTypeNamePattern.match(expression);
            java.lang.reflect.Method declaredMethod = detectMethod(match.group(1) + " " + randomIdentifier("method") + "(){}");
            return right((Expression) new AssignmentWithType(expression, declaredMethod.getGenericReturnType(), match.group(2), match.group(3)));
        } catch (Exception e) {
            return left(Utils.unwrapException(e));
        }
    }

    private Either createAssignmentExpression(String expression) {
        MatchResult match = Patterns.assignmentPattern.match(expression);
        return right((Expression) new Assignment(expression, match.group(1), match.group(2)));
    }

    private Either createTypeExpression(String expression) {
        MatchResult match = typePattern.match(expression);
        return right((Expression) new Type(expression, option(match.group(1)), match.group(2)));
    }

    private Either createValueExpression(String expression) {
        return right((Expression) new Value(expression));
    }

    public Option lastSource() {
        return context.lastSource();
    }

    public Sequence results() {
        return context.results();
    }

    public Option result(String name) {
        return context.result(name);
    }

    public  Sequence expressionsOfType(Class type) {
        return context.expressionsOfType(type);
    }

    public Sequence expressions() {
        return context.expressions();
    }

    public EvaluationContext context() {
        return context;
    }

    public void reset() {
        clearOutputDirectory();
        EvaluationContext evaluationContext = evaluationContext();
        initializeEvaluator(evaluationContext, evaluationClassLoader(evaluationContext));
    }

    private void initializeEvaluator(EvaluationContext evaluationContext, EvaluationClassLoader evaluationClassLoader) {
        context = evaluationContext;
        classLoader = evaluationClassLoader;
    }

    public final Either tryEvaluate(String expression) {
        Evaluator localEvaluator = new Evaluator(context, evaluationClassLoader(context));
        return localEvaluator.evaluate(expression);
    }

    public final Option typeOfExpression(String expression) {
        Either evaluation = tryEvaluate(expression);

        Option expressionType = none();
        if (evaluation.isRight()) {
            Option result = evaluation.right().result();
            if (!result.isEmpty()) {
                expressionType = some(result.get().type());
            }
        }

        return expressionType;
    }

    public final Option classFrom(String expression) {
        try {
            return some(detectClass(expression));
        } catch (Throwable e) {
            return none();
        }
    }

    public void clearOutputDirectory() {
        deleteFiles(context.outputDirectory());
        delete(context.outputDirectory());
    }

    public void addClasspathUrl(URL classpathUrl) {
        classLoader.registerURL(classpathUrl);
    }

    public File outputDirectory() {
        return context.outputDirectory();
    }

    @multimethod
    private Either evaluate(Expression expression) {
        return new multi() {
        }.>methodOption(expression).getOrElse(evaluateExpression(expression));
    }

    @multimethod
    private Either evaluate(Type expression) {
        if (getSystemJavaCompiler() == null) {
            return left((Throwable) new FileNotFoundException("Java compiler not found." +
                    "This can occur when JavaREPL was run with JRE instead of JDK or JDK is not configured correctly."));
        }

        try {
            File outputPath = directory(context.outputDirectory(), expression.typePackage().getOrElse("").replace('.', File.separatorChar));
            File outputJavaFile = file(outputPath, expression.type() + ".java");

            String sources = renderExpressionClass(context, expression.type(), expression)
                    .replace(EXPRESSION_TOKEN, renderExpressionSource(expression));

            Files.write(sources.getBytes(), outputJavaFile);
            compile(outputJavaFile);

            classLoader.loadClass(expression.canonicalName());

            context = context.addExpression(expression).lastSource(sources);

            return right(evaluation(expression, noResult()));
        } catch (Exception e) {
            return left(Utils.unwrapException(e));
        }
    }

    private Either createMethodExpression(String expression) {
        try {
            java.lang.reflect.Method declaredMethod = detectMethod(expression);
            return right((Expression) new Method(expression, declaredMethod.getGenericReturnType(), declaredMethod.getName(), sequence(declaredMethod.getParameterTypes())));
        } catch (Exception e) {
            return left(Utils.unwrapException(e));
        }
    }

    private java.lang.reflect.Method detectMethod(String expression) throws Exception {
        final String className = randomIdentifier("Method");
        final File outputJavaFile = file(context.outputDirectory(), className + ".java");

        final String sources = renderMethodSignatureDetection(context, className, expression);
        Files.write(sources.getBytes(), outputJavaFile);

        compile(outputJavaFile);

        Class expressionClass = classLoader.loadClass(className);

        return expressionClass.getDeclaredMethods()[0];
    }

    private Class detectClass(String expression) throws Exception {
        final String className = randomIdentifier("Class");
        final File outputJavaFile = file(context.outputDirectory(), className + ".java");

        final String sources = renderMethodSignatureDetection(context, className, "public " + expression + " detectClass(){return null;}");
        Files.write(sources.getBytes(), outputJavaFile);

        compile(outputJavaFile);

        Class expressionClass = classLoader.loadClass(className);

        return expressionClass.getDeclaredMethods()[0].getReturnType();
    }

    private Either evaluateExpression(final Expression expression) {
        final String className = randomIdentifier("Evaluation");

        try {
            EvaluationContext newContext = context.removeExpressionWithKey(expression.key());

            File outputJavaFile = file(context.outputDirectory(), className + ".java");
            final String sources = renderExpressionClass(newContext, className, expression)
                    .replace(EXPRESSION_TOKEN, renderExpressionSource(expression));

            Files.write(sources.getBytes(), outputJavaFile);

            compile(outputJavaFile);

            Class expressionClass = classLoader.loadClass(className);
            Constructor constructor = expressionClass.getDeclaredConstructor(EvaluationContext.class);

            final Object expressionInstance = constructor.newInstance(newContext);

            java.lang.reflect.Method method = expressionClass.getMethod("evaluate");
            Object resultObject = method.invoke(expressionInstance);

            Sequence modifiedResults = modifiedResults(expressionInstance);

            if (resultObject != null || !method.getReturnType().equals(void.class)) {
                Result result = Result.result(nextResultKeyFor(expression), resultObject, typeFor(expression));
                context = newContext.addExpression(expression).addResults(modifiedResults.append(result)).lastSource(sources);
                return right(evaluation(expression, some(result)));
            } else {
                context = newContext.addExpression(expression).addResults(modifiedResults).lastSource(sources);
                return right(evaluation(expression, noResult()));
            }
        } catch (Throwable e) {
            return left(Utils.unwrapException(e));
        }
    }

    private Sequence modifiedResults(final Object expressionInstance) {
        return sequence(expressionInstance.getClass().getDeclaredFields())
                .reduceLeft(new Reducer>() {
                    public Sequence call(Sequence results, Field field) throws Exception {
                        Option result = result(field.getName()).filter(where(Result::value, not(equalTo(field.get(expressionInstance)))));

                        if (result.isEmpty())
                            return results;

                        return results.append(Result.result(field.getName(), field.get(expressionInstance)));

                    }

                    public Sequence identity() {
                        return empty(Result.class);
                    }
                });
    }

    private String nextResultKeyFor(Expression expression) {
        return (expression instanceof Value)
                ? context.nextResultKey()
                : expression.key();
    }

    private Option typeFor(Expression expression) {
        return (expression instanceof AssignmentWithType)
                ? Option.some(((AssignmentWithType) expression).type())
                : Option.none();
    }

    private void compile(File file) throws Exception {
        String classpath =
                sequence(System.getProperty("java.class.path"))
                        .join(sequence(classLoader.getURLs()).map(urlAsFilePath()))
                        .toString(pathSeparator);
        JavaCompiler compiler = getSystemJavaCompiler();
        DiagnosticCollector diagnostics = new DiagnosticCollector();
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);
        Iterable compilationUnits = fileManager.getJavaFileObjectsFromFiles(sequence(file));
        JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, sequence("-cp", classpath), null, compilationUnits);

        try {
            if (!task.call())
                throw new ExpressionCompilationException(file, diagnostics.getDiagnostics());
        } finally {
            fileManager.close();
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy