java/de/jflex/testing/javaast/BasicJavaInterpreter.java (140 lines of code) (raw):

/* * Copyright (C) 2020 Google, LLC. * SPDX-License-Identifier: BSD-3-Clause */ package de.jflex.testing.javaast; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.truth.Truth.assertThat; import static java.util.stream.Collectors.joining; import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import com.google.common.io.Files; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTParser; import org.eclipse.jdt.core.dom.ArrayInitializer; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.FieldDeclaration; import org.eclipse.jdt.core.dom.InfixExpression; import org.eclipse.jdt.core.dom.NumberLiteral; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.StringLiteral; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.dom.VariableDeclaration; import org.eclipse.jdt.core.dom.VariableDeclarationFragment; public class BasicJavaInterpreter { private BasicJavaInterpreter() {} public static ImmutableMap<String, Object> parseJavaClass(File javaSrc) throws IOException { assertThat(javaSrc.exists()).isTrue(); TypeDeclaration classDeclaration = doParseJavaClass(javaSrc); ImmutableMap<SimpleName, Object> fields = BasicJavaInterpreter.interpretValues(BasicJavaInterpreter.getJavaFields(classDeclaration)); ImmutableMap.Builder<String, Object> classData = ImmutableMap.builder(); for (Map.Entry<SimpleName, Object> field : fields.entrySet()) { classData.put(field.getKey().toString(), field.getValue()); } return classData.build(); } private static TypeDeclaration doParseJavaClass(File javaSrc) throws IOException { ASTParser parser = ASTParser.newParser(AST.JLS15); char[] source = Files.asCharSource(javaSrc, StandardCharsets.UTF_8).read().toCharArray(); parser.setSource(source); parser.setResolveBindings(true); CompilationUnit result = (CompilationUnit) parser.createAST(null); TypeDeclaration classDeclaration = (TypeDeclaration) result.types().get(0); return classDeclaration; } private static ImmutableMap<SimpleName, VariableDeclarationFragment> getJavaFields( TypeDeclaration classDeclaration) { ImmutableMap.Builder<SimpleName, VariableDeclarationFragment> map = ImmutableMap.builder(); FieldDeclaration[] javaFields = classDeclaration.getFields(); for (FieldDeclaration fd : javaFields) { // A field has only one fragment Object f = fd.fragments().get(0); if (f instanceof VariableDeclaration) { VariableDeclaration field = (VariableDeclaration) f; map.put(field.getName(), (VariableDeclarationFragment) field); } } return map.build(); } private static ImmutableMap<SimpleName, Object> interpretValues( ImmutableMap<SimpleName, VariableDeclarationFragment> javaFields) { return ImmutableMap.copyOf( Maps.transformValues( javaFields, variableDecl -> interpretValue(variableDecl.getInitializer()))); } private static Object interpretValue(ASTNode expr) { if (expr instanceof StringLiteral) { // "abc" return interpretString((StringLiteral) expr); } else if (expr instanceof NumberLiteral) { // a = 0x123; return interpretNumber(expr.toString()); } else if (expr instanceof ArrayInitializer) { // a = {foo, bar}; return interpretArray((ArrayInitializer) expr); } else if (expr instanceof InfixExpression) { // a = "b" + "c" Object infixEval = interpretInfix((InfixExpression) expr).evaluate(); return infixEval; } else { throw new UnsupportedOperationException("Unimplemented field type " + expr); } } private static Infix interpretInfix(InfixExpression expr) { return Infix.create( expr.getOperator(), expr.getLeftOperand(), expr.getRightOperand(), expr.extendedOperands()); } private static long interpretNumber(String string) { if (string.startsWith("0x")) { return Long.parseLong(string.substring(2), 16); } else { return Long.parseLong(string); } } private static ImmutableList<String> interpretArray(ArrayInitializer expr) { ImmutableList.Builder<String> list = ImmutableList.builder(); for (Object stringLiteral : expr.expressions()) { list.add((String) interpretValue((ASTNode) stringLiteral)); } return list.build(); } private static String interpretString(StringLiteral expr) { return expr.getLiteralValue(); } @AutoValue abstract static class Infix { abstract InfixExpression.Operator operator(); abstract List<Expression> expressions(); public static Infix create( InfixExpression.Operator operator, Expression leftOperand, Expression rightOperand, List<Expression> extendedOperands) { return new AutoValue_BasicJavaInterpreter_Infix( operator, ImmutableList.<Expression>builder() .add(leftOperand) .add(rightOperand) .addAll(extendedOperands) .build()); } public Object evaluate() { if (operator().toString().equals("+")) { if (expressions().stream().allMatch(e -> e instanceof StringLiteral)) { return expressions().stream() .map(x -> interpretString((StringLiteral) x)) .collect(joining()); } else { throw new UnsupportedOperationException( "Operator not implemented on " + expressions().stream() .map(x -> x.getClass().getSimpleName()) .collect(toImmutableList())); } } else { throw new UnsupportedOperationException("Operator not implemented: " + operator()); } } } }