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

kendal.api.impl.AstHelperImpl Maven / Gradle / Ivy

The newest version!
package kendal.api.impl;

import static kendal.utils.AnnotationUtils.isPutOnAnnotation;
import static kendal.utils.Utils.with;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import javax.lang.model.element.Name;

import com.sun.tools.javac.code.Attribute;
import com.sun.tools.javac.code.Scope;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCAnnotation;
import com.sun.tools.javac.tree.JCTree.JCAssign;
import com.sun.tools.javac.tree.JCTree.JCBlock;
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
import com.sun.tools.javac.tree.JCTree.JCExpressionStatement;
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.List;

import kendal.api.AstHelper;
import kendal.api.AstNodeBuilder;
import kendal.api.AstUtils;
import kendal.api.AstValidator;
import kendal.api.exceptions.InvalidArgumentException;
import kendal.api.exceptions.KendalRuntimeException;
import kendal.model.Node;

public class AstHelperImpl implements AstHelper {
    private final Context context;
    private final AstUtils astUtils;
    private final AstValidator astValidator;
    private final AstNodeBuilder astNodeBuilder;

    public AstHelperImpl(Context context) {
        this.context = context;
        astUtils = new AstUtilsImpl(context);
        astValidator = new AstValidatorImpl(astUtils);
        astNodeBuilder = new AstNodeBuilderImpl(context, astUtils, astValidator);
    }

    @Override
    public void addElementToClass(Node clazz, Node element, Mode mode, int offset) {
        // Update Kendal AST:
        clazz.addChild(element, mode, offset);

        // Update javac AST:
        JCClassDecl classDecl = clazz.getObject();
        JCTree elementDecl = element.getObject();
        if (mode == Mode.APPEND) classDecl.defs = append(classDecl.defs, elementDecl, 0);
        else classDecl.defs = prepend(classDecl.defs, elementDecl, 0);
    }

    @Override
    public void addArgToAnnotation(Node annotationNode, Node arg) {
        // Update Kendal AST:
        annotationNode.addChild(arg, Mode.APPEND, 0);

        // Update javac AST:

        annotationNode.getObject().args = annotationNode.getObject().args.append(arg.getObject());
    }

    public void replaceNode(Node parent,
                            Node oldNode,
                            Node newNode) {
        // Update Kendal AST
        if (!parent.getChildren().remove(oldNode)) {
            throw new InvalidArgumentException("oldNode does not belong to children collection!");
        }
        parent.getChildren().add(newNode);

        // Update javac AST
        try {
            for (Field field : parent.getObject().getClass().getFields()) {
                field.setAccessible(true);
                Object obj = field.get(parent.getObject());
                if (obj == null) {
                    continue;
                }
                if (obj == oldNode.getObject()) {
                    field.set(parent.getObject(), newNode.getObject());
                    return;
                }
                if (obj.getClass().isArray()) {
                    for (int i = 0; i < Array.getLength(obj); i++) {
                        if (Array.get(obj, i) == oldNode.getObject()) {
                            Array.set(obj, i, newNode.getObject());
                            return;
                        }
                    }
                } else if (obj instanceof com.sun.tools.javac.util.List) {
                    Object[] array = ((com.sun.tools.javac.util.List) obj).toArray();
                    for (int i = 0; i < array.length; i++) {
                        if (array[i] == oldNode.getObject()) {
                            array[i] = newNode.getObject();
                            field.set(parent.getObject(), com.sun.tools.javac.util.List.from(array));
                            return;
                        }
                    }
                }
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        throw new KendalRuntimeException(String.format("Failed to replace child %s of %s", oldNode.getObject(), parent.getObject()));
    }

    @Override
    public  void addExpressionStatementToMethod(Node method,
            Node expressionStatement, Mode mode, int offset) {
        commonAddExpressionStatementToMethod(method, expressionStatement, mode, offset);
    }

    @Override
    public  void addExpressionStatementToConstructor(Node method,
            Node expressionStatement, Mode mode, int offset) {
        Node body = method.getChildrenOfType(JCBlock.class).get(0);
        if (mode == Mode.PREPEND && offset == 0 && bodyContainsSuperInvocation(body)) {
            offset++;
        }

        commonAddExpressionStatementToMethod(method, expressionStatement, mode, offset);
    }

    private  void commonAddExpressionStatementToMethod(Node method,
            Node expressionStatement, Mode mode, int offset) {
        // Update Kendal AST:
        Node body = method.getChildrenOfType(JCBlock.class).get(0);
        body.addChild(expressionStatement, mode, offset);

        // Update javac AST:
        JCMethodDecl methodDecl = method.getObject();
        with(methodDecl.body, b -> {
            if (mode == Mode.APPEND) b.stats = append(b.stats, expressionStatement.getObject(), offset);
            else b.stats = prepend(b.stats, expressionStatement.getObject(), offset);
        });
    }

    private boolean bodyContainsSuperInvocation(Node body) {
        if (!body.getChildren().isEmpty() && body.getChildren().get(0).is(JCExpressionStatement.class)) {
            JCExpressionStatement firstLineExpStat = (JCExpressionStatement) body.getChildren().get(0).getObject();
            if (firstLineExpStat.expr instanceof JCMethodInvocation) {
                JCMethodInvocation methodInv = (JCMethodInvocation) firstLineExpStat.expr;
                return methodInv.meth.toString().equals("super");
            }
            return false;
        }
        return false;
    }

    @Override
    public Node findFieldByNameAndType(Node classDeclNode, Name name) {
        return classDeclNode.getChildren().stream()
                .filter(node -> node.is(JCVariableDecl.class) && ((JCVariableDecl) node.getObject()).name.equals(name))
                .map(node -> (Node) node)
                .findAny().orElse(null);
    }

    @Override
    public Context getContext() {
        return context;
    }

    @Override
    public AstNodeBuilder getAstNodeBuilder() {
        return astNodeBuilder;
    }

    @Override
    public AstValidator getAstValidator() {
        return astValidator;
    }

    @Override
    public AstUtils getAstUtils() {
        return astUtils;
    }

    @Override
    public Map getAnnotationSourceMap(Collection annotationNodes, String sourceQualifiedName) {
        // annotation node -> base annotation node
        Map annotationToSourceMap = annotationNodes.stream()
                .map(node -> ((Node) node))
                .collect(HashMap::new, (m,v) -> {
                    if (v.getObject().type.tsym.getQualifiedName().contentEquals(sourceQualifiedName)) {
                        m.put(v, v);
                    } else {
                        m.put(v, null);
                    }
                }, HashMap::putAll);
        // annotation name -> annotation JCClassDecl
        Map> annotationTypesMap = new HashMap<>();
        annotationNodes.forEach(node -> {
            if (isPutOnAnnotation(node)) {
                annotationTypesMap.put(((JCClassDecl) node.getParent().getObject()).sym.type.tsym.getQualifiedName().toString(), node.getParent());
            }
        });

        // indirect annotation name -> source annotation node
        Map> typesToSource = new HashMap<>();

        Set nodesWithoutSource = annotationToSourceMap.entrySet()
                .stream()
                .filter(entry -> entry.getValue() == null).map(Map.Entry::getKey)
                .collect(Collectors.toSet());
        while (!nodesWithoutSource.isEmpty()) {
            Set assignedNodes = new HashSet<>();
            nodesWithoutSource.forEach(node -> {
                String indirectAnnotationName = node.getObject().type.tsym.getQualifiedName().toString();
                Node indirectAnnotationType = annotationTypesMap.get(indirectAnnotationName);
                indirectAnnotationType.getChildren().stream()
                        .filter(n -> n.is(JCAnnotation.class))
                        .forEach(jcAnnotationNode -> {
                    String annotationName = jcAnnotationNode.getObject().type.tsym.getQualifiedName().toString();
                    if (annotationName.equals(sourceQualifiedName)) {
                        annotationToSourceMap.put(node, jcAnnotationNode);
                        assignedNodes.add(node);
                        typesToSource.put(node.getObject().type.tsym.getQualifiedName().toString(), jcAnnotationNode);
                    } else if (typesToSource.containsKey(annotationName)) {
                        annotationToSourceMap.put(node, typesToSource.get(annotationName));
                        assignedNodes.add(node);
                        typesToSource.put(node.getObject().type.tsym.getQualifiedName().toString(), jcAnnotationNode);
                    }
                });
            });
            nodesWithoutSource.removeAll(assignedNodes);
            assignedNodes.clear();
        }

        return annotationToSourceMap;
    }

    @Override
    public Map getAnnotationValues(Node annotationNode) {
        Map values = new HashMap<>();
        Attribute.Compound attribute = annotationNode.getObject().attribute;
        // gather defaults
        for(Scope.Entry entry = ((Symbol.ClassSymbol) attribute.type.tsym).members_field.elems; entry != null; entry = entry.sibling) {
            if (entry.sym instanceof Symbol.MethodSymbol) {
                Attribute defaultValue = ((Symbol.MethodSymbol) entry.sym).defaultValue;
                values.put(entry.sym.name.toString(), defaultValue == null ? null : defaultValue.getValue());
            }
        }

        // override with actual
        attribute.values.forEach(val -> values.put(val.fst.name.toString(), val.snd.getValue()));

        return values;
    }

    private  List append(List defs, T element, int offset) {
        return add(defs.size() - offset, defs, element);
    }

    private  List prepend(List defs, T element, int offset) {
        return add(offset, defs, element);
    }

    private  List add(int offset, List defs, T element) {
        java.util.List list = StreamSupport.stream(defs.spliterator(), false).collect(Collectors.toList());
        list.add(offset, element);
        return astUtils.toJCList(list);
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy