langtools/src/jdk.jshell/share/classes/jdk/jshell/TreeDissector.java
author rfield
Tue, 29 Dec 2015 21:27:25 -0800
changeset 34857 14d1224cfed3
parent 33362 65ec6de1d6b4
child 36160 f42d362d0d17
permissions -rw-r--r--
8145239: JShell: throws AssertionError when replace classes with some methods which depends on these classes Reviewed-by: rfield Contributed-by: bitterfoxc@gmail.com

/*
 * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package jdk.jshell;


import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ReturnTree;
import com.sun.source.tree.StatementTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.SourcePositions;
import com.sun.source.util.TreePath;
import com.sun.source.util.Trees;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Type.MethodType;
import com.sun.tools.javac.code.Types;
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
import com.sun.tools.javac.util.JavacMessages;
import com.sun.tools.javac.util.Name;
import static jdk.jshell.Util.isDoIt;
import jdk.jshell.Wrap.Range;
import java.util.List;
import java.util.Locale;
import java.util.function.BinaryOperator;
import java.util.function.Predicate;
import java.util.stream.Stream;
import javax.lang.model.type.TypeMirror;
import jdk.jshell.Util.Pair;

/**
 * Utilities for analyzing compiler API parse trees.
 * @author Robert Field
 */

class TreeDissector {

    private static final String OBJECT_TYPE = "Object";

    static class ExpressionInfo {

        boolean isNonVoid;
        String typeName;
        ExpressionTree tree;
        String signature;
    }

    private final TaskFactory.BaseTask bt;
    private final ClassTree targetClass;
    private final CompilationUnitTree targetCompilationUnit;
    private SourcePositions theSourcePositions = null;

    private TreeDissector(TaskFactory.BaseTask bt, CompilationUnitTree targetCompilationUnit, ClassTree targetClass) {
        this.bt = bt;
        this.targetCompilationUnit = targetCompilationUnit;
        this.targetClass = targetClass;
    }

    static TreeDissector createByFirstClass(TaskFactory.BaseTask bt) {
        Pair<CompilationUnitTree, ClassTree> pair = classes(bt.firstCuTree())
                .findFirst().orElseGet(() -> new Pair<>(bt.firstCuTree(), null));

        return new TreeDissector(bt, pair.first, pair.second);
    }

    private static final Predicate<? super Tree> isClassOrInterface =
            t -> t.getKind() == Tree.Kind.CLASS || t.getKind() == Tree.Kind.INTERFACE;

    private static Stream<Pair<CompilationUnitTree, ClassTree>> classes(CompilationUnitTree cut) {
        return cut == null
                ? Stream.empty()
                : cut.getTypeDecls().stream()
                        .filter(isClassOrInterface)
                        .map(decl -> new Pair<>(cut, (ClassTree)decl));
    }

    private static Stream<Pair<CompilationUnitTree, ClassTree>> classes(Iterable<? extends CompilationUnitTree> cuts) {
        return Util.stream(cuts)
                .flatMap(TreeDissector::classes);
    }

    static TreeDissector createBySnippet(TaskFactory.BaseTask bt, Snippet si) {
        String name = si.className();

        Pair<CompilationUnitTree, ClassTree> pair = classes(bt.cuTrees())
                .filter(p -> p.second.getSimpleName().contentEquals(name))
                .findFirst().orElseThrow(() ->
                        new IllegalArgumentException("Class " + name + " is not found."));

        return new TreeDissector(bt, pair.first, pair.second);
    }

    Types types() {
        return bt.types();
    }

    Trees trees() {
        return bt.trees();
    }

    SourcePositions getSourcePositions() {
        if (theSourcePositions == null) {
            theSourcePositions = trees().getSourcePositions();
        }
        return theSourcePositions;
    }

    int getStartPosition(Tree tree) {
        return (int) getSourcePositions().getStartPosition(targetCompilationUnit, tree);
    }

    int getEndPosition(Tree tree) {
        return (int) getSourcePositions().getEndPosition(targetCompilationUnit, tree);
    }

    Range treeToRange(Tree tree) {
        return new Range(getStartPosition(tree), getEndPosition(tree));
    }

    Range treeListToRange(List<? extends Tree> treeList) {
        int start = Integer.MAX_VALUE;
        int end = -1;
        for (Tree t : treeList) {
            int tstart = getStartPosition(t);
            int tend = getEndPosition(t);
            if (tstart < start) {
                start = tstart;
            }
            if (tend > end) {
                end = tend;
            }
        }
        if (start == Integer.MAX_VALUE) {
            return null;
        }
        return new Range(start, end);
    }

    Tree firstClassMember() {
        if (targetClass != null) {
            //TODO: missing classes
            for (Tree mem : targetClass.getMembers()) {
                if (mem.getKind() == Tree.Kind.VARIABLE) {
                    return mem;
                }
                if (mem.getKind() == Tree.Kind.METHOD) {
                    MethodTree mt = (MethodTree) mem;
                    if (!isDoIt(mt.getName()) && !mt.getName().toString().equals("<init>")) {
                        return mt;
                    }
                }
            }
        }
        return null;
    }

    StatementTree firstStatement() {
        if (targetClass != null) {
            for (Tree mem : targetClass.getMembers()) {
                if (mem.getKind() == Tree.Kind.METHOD) {
                    MethodTree mt = (MethodTree) mem;
                    if (isDoIt(mt.getName())) {
                        List<? extends StatementTree> stmts = mt.getBody().getStatements();
                        if (!stmts.isEmpty()) {
                            return stmts.get(0);
                        }
                    }
                }
            }
        }
        return null;
    }

    VariableTree firstVariable() {
        if (targetClass != null) {
            for (Tree mem : targetClass.getMembers()) {
                if (mem.getKind() == Tree.Kind.VARIABLE) {
                    VariableTree vt = (VariableTree) mem;
                    return vt;
                }
            }
        }
        return null;
    }


    ExpressionInfo typeOfReturnStatement(JavacMessages messages, BinaryOperator<String> fullClassNameAndPackageToClass) {
        ExpressionInfo ei = new ExpressionInfo();
        Tree unitTree = firstStatement();
        if (unitTree instanceof ReturnTree) {
            ei.tree = ((ReturnTree) unitTree).getExpression();
            if (ei.tree != null) {
                TreePath viPath = trees().getPath(targetCompilationUnit, ei.tree);
                if (viPath != null) {
                    TypeMirror tm = trees().getTypeMirror(viPath);
                    if (tm != null) {
                        Type type = (Type)tm;
                        TypePrinter tp = new TypePrinter(messages, fullClassNameAndPackageToClass, type);
                        ei.typeName = tp.visit(type, Locale.getDefault());
                        switch (tm.getKind()) {
                            case VOID:
                            case NONE:
                            case ERROR:
                            case OTHER:
                                break;
                            case NULL:
                                ei.isNonVoid = true;
                                ei.typeName = OBJECT_TYPE;
                                break;
                            default: {
                                ei.isNonVoid = true;
                                break;

                            }
                        }
                    }
                }
            }
        }
        return ei;
    }

    String typeOfMethod() {
        Tree unitTree = firstClassMember();
        if (unitTree instanceof JCMethodDecl) {
            JCMethodDecl mtree = (JCMethodDecl) unitTree;
            Type mt = types().erasure(mtree.type);
            if (mt instanceof MethodType) {
                return signature(types(), (MethodType) mt);
            }
        }
        return null;
    }

    static String signature(Types types, MethodType mt) {
        TDSignatureGenerator sg = new TDSignatureGenerator(types);
        sg.assembleSig(mt);
        return sg.toString();
    }

    /**
     * Signature Generation
     */
    private static class TDSignatureGenerator extends Types.SignatureGenerator {

        /**
         * An output buffer for type signatures.
         */
        StringBuilder sb = new StringBuilder();

        TDSignatureGenerator(Types types) {
            super(types);
        }

        @Override
        protected void append(char ch) {
            sb.append(ch);
        }

        @Override
        protected void append(byte[] ba) {
            sb.append(new String(ba));
        }

        @Override
        protected void append(Name name) {
            sb.append(name);
        }

        @Override
        public String toString() {
            return sb.toString();
        }
    }
}