1/*
2 * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package jdk.jshell;
27
28import com.sun.source.tree.ReturnTree;
29import com.sun.source.tree.ClassTree;
30import com.sun.source.tree.CompilationUnitTree;
31import com.sun.source.tree.ConditionalExpressionTree;
32import com.sun.source.tree.ExpressionTree;
33import com.sun.source.tree.MethodTree;
34import com.sun.source.tree.Tree;
35import com.sun.source.util.TreePath;
36import com.sun.source.util.TreePathScanner;
37import com.sun.tools.javac.code.Symtab;
38import com.sun.tools.javac.code.Type;
39import com.sun.tools.javac.code.Types;
40import jdk.jshell.TaskFactory.AnalyzeTask;
41
42/**
43 * Compute information about an expression string, particularly its type name.
44 */
45class ExpressionToTypeInfo {
46
47    private static final String OBJECT_TYPE_NAME = "Object";
48
49    final AnalyzeTask at;
50    final CompilationUnitTree cu;
51    final JShell state;
52    final Symtab syms;
53    final Types types;
54
55    private ExpressionToTypeInfo(AnalyzeTask at, CompilationUnitTree cu, JShell state) {
56        this.at = at;
57        this.cu = cu;
58        this.state = state;
59        this.syms = Symtab.instance(at.context);
60        this.types = Types.instance(at.context);
61    }
62
63    public static class ExpressionInfo {
64        ExpressionTree tree;
65        String typeName;
66        boolean isNonVoid;
67    }
68
69    // return mechanism and other general structure from TreePath.getPath()
70    private static class Result extends Error {
71
72        static final long serialVersionUID = -5942088234594905629L;
73        final TreePath expressionPath;
74
75        Result(TreePath path) {
76            this.expressionPath = path;
77        }
78    }
79
80    private static class PathFinder extends TreePathScanner<TreePath, Boolean> {
81
82        // Optimize out imports etc
83        @Override
84        public TreePath visitCompilationUnit(CompilationUnitTree node, Boolean isTargetContext) {
85            return scan(node.getTypeDecls(), isTargetContext);
86        }
87
88        // Only care about members
89        @Override
90        public TreePath visitClass(ClassTree node, Boolean isTargetContext) {
91            return scan(node.getMembers(), isTargetContext);
92        }
93
94        // Only want the doit method where the code is
95        @Override
96        public TreePath visitMethod(MethodTree node, Boolean isTargetContext) {
97            if (Util.isDoIt(node.getName())) {
98                return scan(node.getBody(), true);
99            } else {
100                return null;
101            }
102        }
103
104        @Override
105        public TreePath visitReturn(ReturnTree node, Boolean isTargetContext) {
106            ExpressionTree tree = node.getExpression();
107            TreePath tp = new TreePath(getCurrentPath(), tree);
108            if (isTargetContext) {
109                throw new Result(tp);
110            } else {
111                return null;
112            }
113        }
114    }
115
116    private Type pathToType(TreePath tp) {
117        return (Type) at.trees().getTypeMirror(tp);
118    }
119
120    private Type pathToType(TreePath tp, Tree tree) {
121        if (tree instanceof ConditionalExpressionTree) {
122            // Conditionals always wind up as Object -- this corrects
123            ConditionalExpressionTree cet = (ConditionalExpressionTree) tree;
124            Type tmt = pathToType(new TreePath(tp, cet.getTrueExpression()));
125            Type tmf = pathToType(new TreePath(tp, cet.getFalseExpression()));
126            if (!tmt.isPrimitive() && !tmf.isPrimitive()) {
127                Type lub = types.lub(tmt, tmf);
128                // System.err.printf("cond ? %s : %s  --  lub = %s\n",
129                //             varTypeName(tmt), varTypeName(tmf), varTypeName(lub));
130                return lub;
131            }
132        }
133        return pathToType(tp);
134    }
135
136    /**
137     * Entry method: get expression info
138     * @param code the expression as a string
139     * @param state a JShell instance
140     * @return type information
141     */
142    public static ExpressionInfo expressionInfo(String code, JShell state) {
143        if (code == null || code.isEmpty()) {
144            return null;
145        }
146        try {
147            OuterWrap codeWrap = state.outerMap.wrapInTrialClass(Wrap.methodReturnWrap(code));
148            AnalyzeTask at = state.taskFactory.new AnalyzeTask(codeWrap);
149            CompilationUnitTree cu = at.firstCuTree();
150            if (at.hasErrors() || cu == null) {
151                return null;
152            }
153            return new ExpressionToTypeInfo(at, cu, state).typeOfExpression();
154        } catch (Exception ex) {
155            return null;
156        }
157    }
158
159    private ExpressionInfo typeOfExpression() {
160        return treeToInfo(findExpressionPath());
161    }
162
163    private TreePath findExpressionPath() {
164        try {
165            new PathFinder().scan(new TreePath(cu), false);
166        } catch (Result result) {
167            return result.expressionPath;
168        }
169        return null;
170    }
171
172    private ExpressionInfo treeToInfo(TreePath tp) {
173        if (tp != null) {
174            Tree tree = tp.getLeaf();
175            if (tree instanceof ExpressionTree) {
176                ExpressionInfo ei = new ExpressionInfo();
177                ei.tree = (ExpressionTree) tree;
178                Type type = pathToType(tp, tree);
179                if (type != null) {
180                    switch (type.getKind()) {
181                        case VOID:
182                        case NONE:
183                        case ERROR:
184                        case OTHER:
185                            break;
186                        case NULL:
187                            ei.isNonVoid = true;
188                            ei.typeName = OBJECT_TYPE_NAME;
189                            break;
190                        default: {
191                            ei.isNonVoid = true;
192                            ei.typeName = varTypeName(type);
193                            if (ei.typeName == null) {
194                                ei.typeName = OBJECT_TYPE_NAME;
195                            }
196                            break;
197                        }
198                    }
199                }
200                return ei;
201            }
202        }
203        return null;
204    }
205
206    private String varTypeName(Type type) {
207        try {
208            TypePrinter tp = new VarTypePrinter(at.messages(),
209                    state.maps::fullClassNameAndPackageToClass, syms, types);
210            return tp.toString(type);
211        } catch (Exception ex) {
212            return null;
213        }
214    }
215
216}
217