1/*
2 * Copyright (c) 2014, 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 */
25package jdk.jshell;
26
27import java.util.ArrayList;
28import java.util.Collection;
29import java.util.Collections;
30import java.util.List;
31import java.util.Locale;
32import java.util.regex.Matcher;
33import java.util.regex.Pattern;
34import java.util.stream.Collectors;
35import javax.lang.model.element.Modifier;
36import com.sun.source.tree.ArrayTypeTree;
37import com.sun.source.tree.AssignmentTree;
38import com.sun.source.tree.ClassTree;
39import com.sun.source.tree.ExpressionTree;
40import com.sun.source.tree.IdentifierTree;
41import com.sun.source.tree.MethodTree;
42import com.sun.source.tree.ModifiersTree;
43import com.sun.source.tree.Tree;
44import com.sun.source.tree.VariableTree;
45import com.sun.tools.javac.tree.JCTree;
46import com.sun.tools.javac.tree.Pretty;
47import java.io.IOException;
48import java.io.StringWriter;
49import java.io.Writer;
50import java.util.LinkedHashSet;
51import java.util.Set;
52import jdk.jshell.ExpressionToTypeInfo.ExpressionInfo;
53import jdk.jshell.Key.ErroneousKey;
54import jdk.jshell.Key.MethodKey;
55import jdk.jshell.Key.TypeDeclKey;
56import jdk.jshell.Snippet.Kind;
57import jdk.jshell.Snippet.SubKind;
58import jdk.jshell.TaskFactory.AnalyzeTask;
59import jdk.jshell.TaskFactory.BaseTask;
60import jdk.jshell.TaskFactory.CompileTask;
61import jdk.jshell.TaskFactory.ParseTask;
62import jdk.jshell.Wrap.Range;
63import jdk.jshell.Snippet.Status;
64import jdk.jshell.spi.ExecutionControl.ClassBytecodes;
65import jdk.jshell.spi.ExecutionControl.ClassInstallException;
66import jdk.jshell.spi.ExecutionControl.EngineTerminationException;
67import jdk.jshell.spi.ExecutionControl.InternalException;
68import jdk.jshell.spi.ExecutionControl.NotImplementedException;
69import jdk.jshell.spi.ExecutionControl.ResolutionException;
70import jdk.jshell.spi.ExecutionControl.RunException;
71import jdk.jshell.spi.ExecutionControl.UserException;
72import static java.util.stream.Collectors.toList;
73import static java.util.stream.Collectors.toSet;
74import static java.util.Collections.singletonList;
75import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
76import static jdk.jshell.Util.DOIT_METHOD_NAME;
77import static jdk.jshell.Util.PREFIX_PATTERN;
78import static jdk.jshell.Util.expunge;
79import static jdk.jshell.Snippet.SubKind.SINGLE_TYPE_IMPORT_SUBKIND;
80import static jdk.jshell.Snippet.SubKind.SINGLE_STATIC_IMPORT_SUBKIND;
81import static jdk.jshell.Snippet.SubKind.TYPE_IMPORT_ON_DEMAND_SUBKIND;
82import static jdk.jshell.Snippet.SubKind.STATIC_IMPORT_ON_DEMAND_SUBKIND;
83
84/**
85 * The Evaluation Engine. Source internal analysis, wrapping control,
86 * compilation, declaration. redefinition, replacement, and execution.
87 *
88 * @author Robert Field
89 */
90class Eval {
91
92    private static final Pattern IMPORT_PATTERN = Pattern.compile("import\\p{javaWhitespace}+(?<static>static\\p{javaWhitespace}+)?(?<fullname>[\\p{L}\\p{N}_\\$\\.]+\\.(?<name>[\\p{L}\\p{N}_\\$]+|\\*))");
93
94    // for uses that should not change state -- non-evaluations
95    private boolean preserveState = false;
96
97    private int varNumber = 0;
98
99    private final JShell state;
100
101    Eval(JShell state) {
102        this.state = state;
103    }
104
105    /**
106     * Evaluates a snippet of source.
107     *
108     * @param userSource the source of the snippet
109     * @return the list of primary and update events
110     * @throws IllegalStateException
111     */
112    List<SnippetEvent> eval(String userSource) throws IllegalStateException {
113        List<SnippetEvent> allEvents = new ArrayList<>();
114        for (Snippet snip : sourceToSnippets(userSource)) {
115            if (snip.kind() == Kind.ERRONEOUS) {
116                state.maps.installSnippet(snip);
117                allEvents.add(new SnippetEvent(
118                        snip, Status.NONEXISTENT, Status.REJECTED,
119                        false, null, null, null));
120            } else {
121                allEvents.addAll(declare(snip, snip.syntheticDiags()));
122            }
123        }
124        return allEvents;
125    }
126
127    /**
128     * Converts the user source of a snippet into a Snippet list -- Snippet will
129     * have wrappers.
130     *
131     * @param userSource the source of the snippet
132     * @return usually a singleton list of Snippet, but may be empty or multiple
133     */
134    List<Snippet> sourceToSnippetsWithWrappers(String userSource) {
135        List<Snippet> snippets = sourceToSnippets(userSource);
136        for (Snippet snip : snippets) {
137            if (snip.outerWrap() == null) {
138                snip.setOuterWrap(
139                        (snip.kind() == Kind.IMPORT)
140                                ? state.outerMap.wrapImport(snip.guts(), snip)
141                                : state.outerMap.wrapInTrialClass(snip.guts())
142                );
143            }
144        }
145        return snippets;
146    }
147
148    /**
149     * Converts the user source of a snippet into a Snippet object (or list of
150     * objects in the case of: int x, y, z;).  Does not install the Snippets
151     * or execute them.  Does not change any state.
152     *
153     * @param userSource the source of the snippet
154     * @return usually a singleton list of Snippet, but may be empty or multiple
155     */
156    List<Snippet> toScratchSnippets(String userSource) {
157        try {
158            preserveState = true;
159            return sourceToSnippets(userSource);
160        } finally {
161            preserveState = false;
162        }
163    }
164
165    /**
166     * Converts the user source of a snippet into a Snippet object (or list of
167     * objects in the case of: int x, y, z;).  Does not install the Snippets
168     * or execute them.
169     *
170     * @param userSource the source of the snippet
171     * @return usually a singleton list of Snippet, but may be empty or multiple
172     */
173    private List<Snippet> sourceToSnippets(String userSource) {
174        String compileSource = Util.trimEnd(new MaskCommentsAndModifiers(userSource, false).cleared());
175        if (compileSource.length() == 0) {
176            return Collections.emptyList();
177        }
178        ParseTask pt = state.taskFactory.parse(compileSource);
179        List<? extends Tree> units = pt.units();
180        if (units.isEmpty()) {
181            return compileFailResult(pt, userSource, Kind.ERRONEOUS);
182        }
183        Tree unitTree = units.get(0);
184        if (pt.getDiagnostics().hasOtherThanNotStatementErrors()) {
185            return compileFailResult(pt, userSource, kindOfTree(unitTree));
186        }
187
188        // Erase illegal/ignored modifiers
189        compileSource = new MaskCommentsAndModifiers(compileSource, true).cleared();
190
191        state.debug(DBG_GEN, "Kind: %s -- %s\n", unitTree.getKind(), unitTree);
192        switch (unitTree.getKind()) {
193            case IMPORT:
194                return processImport(userSource, compileSource);
195            case VARIABLE:
196                return processVariables(userSource, units, compileSource, pt);
197            case EXPRESSION_STATEMENT:
198                return processExpression(userSource, compileSource);
199            case CLASS:
200                return processClass(userSource, unitTree, compileSource, SubKind.CLASS_SUBKIND, pt);
201            case ENUM:
202                return processClass(userSource, unitTree, compileSource, SubKind.ENUM_SUBKIND, pt);
203            case ANNOTATION_TYPE:
204                return processClass(userSource, unitTree, compileSource, SubKind.ANNOTATION_TYPE_SUBKIND, pt);
205            case INTERFACE:
206                return processClass(userSource, unitTree, compileSource, SubKind.INTERFACE_SUBKIND, pt);
207            case METHOD:
208                return processMethod(userSource, unitTree, compileSource, pt);
209            default:
210                return processStatement(userSource, compileSource);
211        }
212    }
213
214    private List<Snippet> processImport(String userSource, String compileSource) {
215        Wrap guts = Wrap.simpleWrap(compileSource);
216        Matcher mat = IMPORT_PATTERN.matcher(compileSource);
217        String fullname;
218        String name;
219        boolean isStatic;
220        if (mat.find()) {
221            isStatic = mat.group("static") != null;
222            name = mat.group("name");
223            fullname = mat.group("fullname");
224        } else {
225            // bad import -- fake it
226            isStatic = compileSource.contains("static");
227            name = fullname = compileSource;
228        }
229        String fullkey = (isStatic ? "static-" : "") + fullname;
230        boolean isStar = name.equals("*");
231        String keyName = isStar
232                ? fullname
233                : name;
234        SubKind snippetKind = isStar
235                ? (isStatic ? STATIC_IMPORT_ON_DEMAND_SUBKIND : TYPE_IMPORT_ON_DEMAND_SUBKIND)
236                : (isStatic ? SINGLE_STATIC_IMPORT_SUBKIND : SINGLE_TYPE_IMPORT_SUBKIND);
237        Snippet snip = new ImportSnippet(state.keyMap.keyForImport(keyName, snippetKind),
238                userSource, guts, fullname, name, snippetKind, fullkey, isStatic, isStar);
239        return singletonList(snip);
240    }
241
242    private static class EvalPretty extends Pretty {
243
244        private final Writer out;
245
246        public EvalPretty(Writer writer, boolean bln) {
247            super(writer, bln);
248            this.out = writer;
249        }
250
251        /**
252         * Print string, DO NOT replacing all non-ascii character with unicode
253         * escapes.
254         */
255        @Override
256        public void print(Object o) throws IOException {
257            out.write(o.toString());
258        }
259
260        static String prettyExpr(JCTree tree, boolean bln) {
261            StringWriter out = new StringWriter();
262            try {
263                new EvalPretty(out, bln).printExpr(tree);
264            } catch (IOException e) {
265                throw new AssertionError(e);
266            }
267            return out.toString();
268        }
269    }
270
271    private List<Snippet> processVariables(String userSource, List<? extends Tree> units, String compileSource, ParseTask pt) {
272        List<Snippet> snippets = new ArrayList<>();
273        TreeDissector dis = TreeDissector.createByFirstClass(pt);
274        for (Tree unitTree : units) {
275            VariableTree vt = (VariableTree) unitTree;
276            String name = vt.getName().toString();
277            String typeName = EvalPretty.prettyExpr((JCTree) vt.getType(), false);
278            Tree baseType = vt.getType();
279            TreeDependencyScanner tds = new TreeDependencyScanner();
280            tds.scan(baseType); // Not dependent on initializer
281            StringBuilder sbBrackets = new StringBuilder();
282            while (baseType instanceof ArrayTypeTree) {
283                //TODO handle annotations too
284                baseType = ((ArrayTypeTree) baseType).getType();
285                sbBrackets.append("[]");
286            }
287            Range rtype = dis.treeToRange(baseType);
288            Range runit = dis.treeToRange(vt);
289            runit = new Range(runit.begin, runit.end - 1);
290            ExpressionTree it = vt.getInitializer();
291            Range rinit = null;
292            int nameMax = runit.end - 1;
293            SubKind subkind;
294            if (it != null) {
295                subkind = SubKind.VAR_DECLARATION_WITH_INITIALIZER_SUBKIND;
296                rinit = dis.treeToRange(it);
297                nameMax = rinit.begin - 1;
298            } else {
299                subkind = SubKind.VAR_DECLARATION_SUBKIND;
300            }
301            int nameStart = compileSource.lastIndexOf(name, nameMax);
302            if (nameStart < 0) {
303                throw new AssertionError("Name '" + name + "' not found");
304            }
305            int nameEnd = nameStart + name.length();
306            Range rname = new Range(nameStart, nameEnd);
307            Wrap guts = Wrap.varWrap(compileSource, rtype, sbBrackets.toString(), rname, rinit);
308            DiagList modDiag = modifierDiagnostics(vt.getModifiers(), dis, true);
309            Snippet snip = new VarSnippet(state.keyMap.keyForVariable(name), userSource, guts,
310                    name, subkind, typeName,
311                    tds.declareReferences(), modDiag);
312            snippets.add(snip);
313        }
314        return snippets;
315    }
316
317    private List<Snippet> processExpression(String userSource, String compileSource) {
318        String name = null;
319        ExpressionInfo ei = ExpressionToTypeInfo.expressionInfo(compileSource, state);
320        ExpressionTree assignVar;
321        Wrap guts;
322        Snippet snip;
323        if (ei != null && ei.isNonVoid) {
324            String typeName = ei.typeName;
325            SubKind subkind;
326            if (ei.tree instanceof IdentifierTree) {
327                IdentifierTree id = (IdentifierTree) ei.tree;
328                name = id.getName().toString();
329                subkind = SubKind.VAR_VALUE_SUBKIND;
330
331            } else if (ei.tree instanceof AssignmentTree
332                    && (assignVar = ((AssignmentTree) ei.tree).getVariable()) instanceof IdentifierTree) {
333                name = assignVar.toString();
334                subkind = SubKind.ASSIGNMENT_SUBKIND;
335            } else {
336                subkind = SubKind.OTHER_EXPRESSION_SUBKIND;
337            }
338            if (shouldGenTempVar(subkind)) {
339                if (preserveState) {
340                    name = "$$";
341                } else {
342                    if (state.tempVariableNameGenerator != null) {
343                        name = state.tempVariableNameGenerator.get();
344                    }
345                    while (name == null || state.keyMap.doesVariableNameExist(name)) {
346                        name = "$" + ++varNumber;
347                    }
348                }
349                guts = Wrap.tempVarWrap(compileSource, typeName, name);
350                Collection<String> declareReferences = null; //TODO
351                snip = new VarSnippet(state.keyMap.keyForVariable(name), userSource, guts,
352                        name, SubKind.TEMP_VAR_EXPRESSION_SUBKIND, typeName, declareReferences, null);
353            } else {
354                guts = Wrap.methodReturnWrap(compileSource);
355                snip = new ExpressionSnippet(state.keyMap.keyForExpression(name, typeName), userSource, guts,
356                        name, subkind);
357            }
358        } else {
359            guts = Wrap.methodWrap(compileSource);
360            if (ei == null) {
361                // We got no type info, check for not a statement by trying
362                AnalyzeTask at = trialCompile(guts);
363                if (at.getDiagnostics().hasNotStatement()) {
364                    guts = Wrap.methodReturnWrap(compileSource);
365                    at = trialCompile(guts);
366                }
367                if (at.hasErrors()) {
368                    return compileFailResult(at, userSource, Kind.EXPRESSION);
369                }
370            }
371            snip = new StatementSnippet(state.keyMap.keyForStatement(), userSource, guts);
372        }
373        return singletonList(snip);
374    }
375
376    private List<Snippet> processClass(String userSource, Tree unitTree, String compileSource, SubKind snippetKind, ParseTask pt) {
377        TreeDependencyScanner tds = new TreeDependencyScanner();
378        tds.scan(unitTree);
379
380        TreeDissector dis = TreeDissector.createByFirstClass(pt);
381
382        ClassTree klassTree = (ClassTree) unitTree;
383        String name = klassTree.getSimpleName().toString();
384        DiagList modDiag = modifierDiagnostics(klassTree.getModifiers(), dis, false);
385        TypeDeclKey key = state.keyMap.keyForClass(name);
386        // Corralling mutates.  Must be last use of pt, unitTree, klassTree
387        Wrap corralled = new Corraller(key.index(), pt.getContext()).corralType(klassTree);
388
389        Wrap guts = Wrap.classMemberWrap(compileSource);
390        Snippet snip = new TypeDeclSnippet(key, userSource, guts,
391                name, snippetKind,
392                corralled, tds.declareReferences(), tds.bodyReferences(), modDiag);
393        return singletonList(snip);
394    }
395
396    private List<Snippet> processStatement(String userSource, String compileSource) {
397        Wrap guts = Wrap.methodWrap(compileSource);
398        // Check for unreachable by trying
399        AnalyzeTask at = trialCompile(guts);
400        if (at.hasErrors()) {
401            if (at.getDiagnostics().hasUnreachableError()) {
402                guts = Wrap.methodUnreachableSemiWrap(compileSource);
403                at = trialCompile(guts);
404                if (at.hasErrors()) {
405                    if (at.getDiagnostics().hasUnreachableError()) {
406                        // Without ending semicolon
407                        guts = Wrap.methodUnreachableWrap(compileSource);
408                        at = trialCompile(guts);
409                    }
410                    if (at.hasErrors()) {
411                        return compileFailResult(at, userSource, Kind.STATEMENT);
412                    }
413                }
414            } else {
415                return compileFailResult(at, userSource, Kind.STATEMENT);
416            }
417        }
418        Snippet snip = new StatementSnippet(state.keyMap.keyForStatement(), userSource, guts);
419        return singletonList(snip);
420    }
421
422    private AnalyzeTask trialCompile(Wrap guts) {
423        OuterWrap outer = state.outerMap.wrapInTrialClass(guts);
424        return state.taskFactory.new AnalyzeTask(outer);
425    }
426
427    private List<Snippet> processMethod(String userSource, Tree unitTree, String compileSource, ParseTask pt) {
428        TreeDependencyScanner tds = new TreeDependencyScanner();
429        tds.scan(unitTree);
430        TreeDissector dis = TreeDissector.createByFirstClass(pt);
431
432        MethodTree mt = (MethodTree) unitTree;
433        String name = mt.getName().toString();
434        String parameterTypes
435                = mt.getParameters()
436                .stream()
437                .map(param -> dis.treeToRange(param.getType()).part(compileSource))
438                .collect(Collectors.joining(","));
439        Tree returnType = mt.getReturnType();
440        DiagList modDiag = modifierDiagnostics(mt.getModifiers(), dis, true);
441        MethodKey key = state.keyMap.keyForMethod(name, parameterTypes);
442        // Corralling mutates.  Must be last use of pt, unitTree, mt
443        Wrap corralled = new Corraller(key.index(), pt.getContext()).corralMethod(mt);
444
445        if (modDiag.hasErrors()) {
446            return compileFailResult(modDiag, userSource, Kind.METHOD);
447        }
448        Wrap guts = Wrap.classMemberWrap(compileSource);
449        Range typeRange = dis.treeToRange(returnType);
450        String signature = "(" + parameterTypes + ")" + typeRange.part(compileSource);
451
452        Snippet snip = new MethodSnippet(key, userSource, guts,
453                name, signature,
454                corralled, tds.declareReferences(), tds.bodyReferences(), modDiag);
455        return singletonList(snip);
456    }
457
458    private Kind kindOfTree(Tree tree) {
459        switch (tree.getKind()) {
460            case IMPORT:
461                return Kind.IMPORT;
462            case VARIABLE:
463                return Kind.VAR;
464            case EXPRESSION_STATEMENT:
465                return Kind.EXPRESSION;
466            case CLASS:
467            case ENUM:
468            case ANNOTATION_TYPE:
469            case INTERFACE:
470                return Kind.TYPE_DECL;
471            case METHOD:
472                return Kind.METHOD;
473            default:
474                return Kind.STATEMENT;
475        }
476    }
477
478    /**
479     * The snippet has failed, return with the rejected snippet
480     *
481     * @param xt the task from which to extract the failure diagnostics
482     * @param userSource the incoming bad user source
483     * @return a rejected snippet
484     */
485    private List<Snippet> compileFailResult(BaseTask xt, String userSource, Kind probableKind) {
486        return compileFailResult(xt.getDiagnostics(), userSource, probableKind);
487    }
488
489    /**
490     * The snippet has failed, return with the rejected snippet
491     *
492     * @param diags the failure diagnostics
493     * @param userSource the incoming bad user source
494     * @return a rejected snippet
495     */
496    private List<Snippet> compileFailResult(DiagList diags, String userSource, Kind probableKind) {
497        ErroneousKey key = state.keyMap.keyForErroneous();
498        Snippet snip = new ErroneousSnippet(key, userSource, null,
499                probableKind, SubKind.UNKNOWN_SUBKIND);
500        snip.setFailed(diags);
501
502        // Install  wrapper for query by SourceCodeAnalysis.wrapper
503        String compileSource = Util.trimEnd(new MaskCommentsAndModifiers(userSource, true).cleared());
504        OuterWrap outer;
505        switch (probableKind) {
506            case IMPORT:
507                outer = state.outerMap.wrapImport(Wrap.simpleWrap(compileSource), snip);
508                break;
509            case EXPRESSION:
510                outer = state.outerMap.wrapInTrialClass(Wrap.methodReturnWrap(compileSource));
511                break;
512            case VAR:
513            case TYPE_DECL:
514            case METHOD:
515                outer = state.outerMap.wrapInTrialClass(Wrap.classMemberWrap(compileSource));
516                break;
517            default:
518                outer = state.outerMap.wrapInTrialClass(Wrap.methodWrap(compileSource));
519                break;
520        }
521        snip.setOuterWrap(outer);
522
523        return singletonList(snip);
524    }
525
526    /**
527     * Should a temp var wrap the expression. TODO make this user configurable.
528     *
529     * @param snippetKind
530     * @return
531     */
532    private boolean shouldGenTempVar(SubKind snippetKind) {
533        return snippetKind == SubKind.OTHER_EXPRESSION_SUBKIND;
534    }
535
536    List<SnippetEvent> drop(Snippet si) {
537        Unit c = new Unit(state, si);
538        Set<Unit> outs;
539        if (si instanceof PersistentSnippet) {
540            Set<Unit> ins = c.dependents().collect(toSet());
541            outs = compileAndLoad(ins);
542        } else {
543            outs = Collections.emptySet();
544        }
545        return events(c, outs, null, null);
546    }
547
548    private List<SnippetEvent> declare(Snippet si, DiagList generatedDiagnostics) {
549        Unit c = new Unit(state, si, null, generatedDiagnostics);
550        Set<Unit> ins = new LinkedHashSet<>();
551        ins.add(c);
552        Set<Unit> outs = compileAndLoad(ins);
553
554        if (!si.status().isDefined()
555                && si.diagnostics().isEmpty()
556                && si.unresolved().isEmpty()) {
557            // did not succeed, but no record of it, extract from others
558            si.setDiagnostics(outs.stream()
559                    .flatMap(u -> u.snippet().diagnostics().stream())
560                    .collect(Collectors.toCollection(DiagList::new)));
561        }
562
563        // If appropriate, execute the snippet
564        String value = null;
565        JShellException exception = null;
566        if (si.status().isDefined()) {
567            if (si.isExecutable()) {
568                try {
569                    value = state.executionControl().invoke(si.classFullName(), DOIT_METHOD_NAME);
570                    value = si.subKind().hasValue()
571                            ? expunge(value)
572                            : "";
573                } catch (ResolutionException ex) {
574                    DeclarationSnippet sn = (DeclarationSnippet) state.maps.getSnippetDeadOrAlive(ex.id());
575                    exception = new UnresolvedReferenceException(sn, translateExceptionStack(ex));
576                } catch (UserException ex) {
577                    exception = new EvalException(ex.getMessage(),
578                            ex.causeExceptionClass(),
579                            translateExceptionStack(ex));
580                } catch (RunException ex) {
581                    // StopException - no-op
582                } catch (InternalException ex) {
583                    state.debug(ex, "invoke");
584                } catch (EngineTerminationException ex) {
585                    state.closeDown();
586                }
587            } else if (si.subKind() == SubKind.VAR_DECLARATION_SUBKIND) {
588                switch (((VarSnippet) si).typeName()) {
589                    case "byte":
590                    case "short":
591                    case "int":
592                    case "long":
593                        value = "0";
594                        break;
595                    case "float":
596                    case "double":
597                        value = "0.0";
598                        break;
599                    case "boolean":
600                        value = "false";
601                        break;
602                    case "char":
603                        value = "''";
604                        break;
605                    default:
606                        value = "null";
607                        break;
608                }
609            }
610        }
611        return events(c, outs, value, exception);
612    }
613
614    private boolean interestingEvent(SnippetEvent e) {
615        return e.isSignatureChange()
616                    || e.causeSnippet() == null
617                    || e.status() != e.previousStatus()
618                    || e.exception() != null;
619    }
620
621    private List<SnippetEvent> events(Unit c, Collection<Unit> outs, String value, JShellException exception) {
622        List<SnippetEvent> events = new ArrayList<>();
623        events.add(c.event(value, exception));
624        events.addAll(outs.stream()
625                .filter(u -> u != c)
626                .map(u -> u.event(null, null))
627                .filter(this::interestingEvent)
628                .collect(Collectors.toList()));
629        events.addAll(outs.stream()
630                .flatMap(u -> u.secondaryEvents().stream())
631                .filter(this::interestingEvent)
632                .collect(Collectors.toList()));
633        //System.err.printf("Events: %s\n", events);
634        return events;
635    }
636
637    private Set<OuterWrap> outerWrapSet(Collection<Unit> units) {
638        return units.stream()
639                .map(u -> u.snippet().outerWrap())
640                .collect(toSet());
641    }
642
643    private Set<Unit> compileAndLoad(Set<Unit> ins) {
644        if (ins.isEmpty()) {
645            return ins;
646        }
647        Set<Unit> replaced = new LinkedHashSet<>();
648        // Loop until dependencies and errors are stable
649        while (true) {
650            state.debug(DBG_GEN, "compileAndLoad  %s\n", ins);
651
652            ins.stream().forEach(Unit::initialize);
653            ins.stream().forEach(u -> u.setWrap(ins, ins));
654            AnalyzeTask at = state.taskFactory.new AnalyzeTask(outerWrapSet(ins));
655            ins.stream().forEach(u -> u.setDiagnostics(at));
656
657            // corral any Snippets that need it
658            AnalyzeTask cat;
659            if (ins.stream().anyMatch(u -> u.corralIfNeeded(ins))) {
660                // if any were corralled, re-analyze everything
661                cat = state.taskFactory.new AnalyzeTask(outerWrapSet(ins));
662                ins.stream().forEach(u -> u.setCorralledDiagnostics(cat));
663            } else {
664                cat = at;
665            }
666            ins.stream().forEach(u -> u.setStatus(cat));
667            // compile and load the legit snippets
668            boolean success;
669            while (true) {
670                List<Unit> legit = ins.stream()
671                        .filter(Unit::isDefined)
672                        .collect(toList());
673                state.debug(DBG_GEN, "compileAndLoad ins = %s -- legit = %s\n",
674                        ins, legit);
675                if (legit.isEmpty()) {
676                    // no class files can be generated
677                    success = true;
678                } else {
679                    // re-wrap with legit imports
680                    legit.stream().forEach(u -> u.setWrap(ins, legit));
681
682                    // generate class files for those capable
683                    CompileTask ct = state.taskFactory.new CompileTask(outerWrapSet(legit));
684                    if (!ct.compile()) {
685                        // oy! compile failed because of recursive new unresolved
686                        if (legit.stream()
687                                .filter(u -> u.smashingErrorDiagnostics(ct))
688                                .count() > 0) {
689                            // try again, with the erroreous removed
690                            continue;
691                        } else {
692                            state.debug(DBG_GEN, "Should never happen error-less failure - %s\n",
693                                    legit);
694                        }
695                    }
696
697                    // load all new classes
698                    load(legit.stream()
699                            .flatMap(u -> u.classesToLoad(ct.classList(u.snippet().outerWrap())))
700                            .collect(toSet()));
701                    // attempt to redefine the remaining classes
702                    List<Unit> toReplace = legit.stream()
703                            .filter(u -> !u.doRedefines())
704                            .collect(toList());
705
706                    // prevent alternating redefine/replace cyclic dependency
707                    // loop by replacing all that have been replaced
708                    if (!toReplace.isEmpty()) {
709                        replaced.addAll(toReplace);
710                        replaced.stream().forEach(Unit::markForReplacement);
711                    }
712
713                    success = toReplace.isEmpty();
714                }
715                break;
716            }
717
718            // add any new dependencies to the working set
719            List<Unit> newDependencies = ins.stream()
720                    .flatMap(Unit::effectedDependents)
721                    .collect(toList());
722            state.debug(DBG_GEN, "compileAndLoad %s -- deps: %s  success: %s\n",
723                    ins, newDependencies, success);
724            if (!ins.addAll(newDependencies) && success) {
725                // all classes that could not be directly loaded (because they
726                // are new) have been redefined, and no new dependnencies were
727                // identified
728                ins.stream().forEach(Unit::finish);
729                return ins;
730            }
731        }
732    }
733
734    /**
735     * If there are classes to load, loads by calling the execution engine.
736     * @param classbytecodes names of the classes to load.
737     */
738    private void load(Collection<ClassBytecodes> classbytecodes) {
739        if (!classbytecodes.isEmpty()) {
740            ClassBytecodes[] cbcs = classbytecodes.toArray(new ClassBytecodes[classbytecodes.size()]);
741            try {
742                state.executionControl().load(cbcs);
743                state.classTracker.markLoaded(cbcs);
744            } catch (ClassInstallException ex) {
745                state.classTracker.markLoaded(cbcs, ex.installed());
746            } catch (NotImplementedException ex) {
747                state.debug(ex, "Seriously?!? load not implemented");
748                state.closeDown();
749            } catch (EngineTerminationException ex) {
750                state.closeDown();
751            }
752        }
753    }
754
755    private StackTraceElement[] translateExceptionStack(Exception ex) {
756        StackTraceElement[] raw = ex.getStackTrace();
757        int last = raw.length;
758        do {
759            if (last == 0) {
760                last = raw.length - 1;
761                break;
762            }
763        } while (!isWrap(raw[--last]));
764        StackTraceElement[] elems = new StackTraceElement[last + 1];
765        for (int i = 0; i <= last; ++i) {
766            StackTraceElement r = raw[i];
767            OuterSnippetsClassWrap outer = state.outerMap.getOuter(r.getClassName());
768            if (outer != null) {
769                String klass = expunge(r.getClassName());
770                String method = r.getMethodName().equals(DOIT_METHOD_NAME) ? "" : r.getMethodName();
771                int wln = r.getLineNumber() - 1;
772                int line = outer.wrapLineToSnippetLine(wln) + 1;
773                Snippet sn = outer.wrapLineToSnippet(wln);
774                String file = "#" + sn.id();
775                elems[i] = new StackTraceElement(klass, method, file, line);
776            } else if (r.getFileName().equals("<none>")) {
777                elems[i] = new StackTraceElement(r.getClassName(), r.getMethodName(), null, r.getLineNumber());
778            } else {
779                elems[i] = r;
780            }
781        }
782        return elems;
783    }
784
785    private boolean isWrap(StackTraceElement ste) {
786        return PREFIX_PATTERN.matcher(ste.getClassName()).find();
787    }
788
789    private DiagList modifierDiagnostics(ModifiersTree modtree,
790            final TreeDissector dis, boolean isAbstractProhibited) {
791
792        class ModifierDiagnostic extends Diag {
793
794            final boolean fatal;
795            final String message;
796
797            ModifierDiagnostic(List<Modifier> list, boolean fatal) {
798                this.fatal = fatal;
799                StringBuilder sb = new StringBuilder();
800                for (Modifier mod : list) {
801                    sb.append("'");
802                    sb.append(mod.toString());
803                    sb.append("' ");
804                }
805                String key = (list.size() > 1)
806                        ? fatal
807                            ? "jshell.diag.modifier.plural.fatal"
808                            : "jshell.diag.modifier.plural.ignore"
809                        : fatal
810                            ? "jshell.diag.modifier.single.fatal"
811                            : "jshell.diag.modifier.single.ignore";
812                this.message = state.messageFormat(key, sb.toString());
813            }
814
815            @Override
816            public boolean isError() {
817                return fatal;
818            }
819
820            @Override
821            public long getPosition() {
822                return dis.getStartPosition(modtree);
823            }
824
825            @Override
826            public long getStartPosition() {
827                return dis.getStartPosition(modtree);
828            }
829
830            @Override
831            public long getEndPosition() {
832                return dis.getEndPosition(modtree);
833            }
834
835            @Override
836            public String getCode() {
837                return fatal
838                        ? "jdk.eval.error.illegal.modifiers"
839                        : "jdk.eval.warn.illegal.modifiers";
840            }
841
842            @Override
843            public String getMessage(Locale locale) {
844                return message;
845            }
846        }
847
848        List<Modifier> list = new ArrayList<>();
849        boolean fatal = false;
850        for (Modifier mod : modtree.getFlags()) {
851            switch (mod) {
852                case SYNCHRONIZED:
853                case NATIVE:
854                    list.add(mod);
855                    fatal = true;
856                    break;
857                case ABSTRACT:
858                    if (isAbstractProhibited) {
859                        list.add(mod);
860                        fatal = true;
861                    }
862                    break;
863                case PUBLIC:
864                case PROTECTED:
865                case PRIVATE:
866                    // quietly ignore, user cannot see effects one way or the other
867                    break;
868                case STATIC:
869                case FINAL:
870                    list.add(mod);
871                    break;
872            }
873        }
874        return list.isEmpty()
875                ? new DiagList()
876                : new DiagList(new ModifierDiagnostic(list, fatal));
877    }
878
879}
880