Unit.java revision 3827:44bdefe64114
190075Sobrien/*
290075Sobrien * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
390075Sobrien * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
490075Sobrien *
590075Sobrien * This code is free software; you can redistribute it and/or modify it
690075Sobrien * under the terms of the GNU General Public License version 2 only, as
790075Sobrien * published by the Free Software Foundation.  Oracle designates this
890075Sobrien * particular file as subject to the "Classpath" exception as provided
990075Sobrien * by Oracle in the LICENSE file that accompanied this code.
1090075Sobrien *
1190075Sobrien * This code is distributed in the hope that it will be useful, but WITHOUT
1290075Sobrien * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
1390075Sobrien * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
1490075Sobrien * version 2 for more details (a copy is included in the LICENSE file that
1590075Sobrien * accompanied this code).
1690075Sobrien *
1790075Sobrien * You should have received a copy of the GNU General Public License version
1890075Sobrien * 2 along with this work; if not, write to the Free Software Foundation,
1990075Sobrien * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
2090075Sobrien *
2190075Sobrien * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2290075Sobrien * or visit www.oracle.com if you need additional information or have any
2390075Sobrien * questions.
2490075Sobrien */
2590075Sobrien
2690075Sobrienpackage jdk.jshell;
2790075Sobrien
2890075Sobrienimport java.util.ArrayList;
2990075Sobrienimport java.util.Collection;
3090075Sobrienimport java.util.Collections;
3190075Sobrienimport java.util.LinkedHashSet;
3290075Sobrienimport java.util.List;
3390075Sobrienimport java.util.Set;
3490075Sobrienimport java.util.stream.Stream;
3590075Sobrienimport jdk.jshell.ClassTracker.ClassInfo;
3690075Sobrienimport jdk.jshell.Snippet.Kind;
3790075Sobrienimport jdk.jshell.Snippet.Status;
3890075Sobrienimport jdk.jshell.Snippet.SubKind;
3990075Sobrienimport jdk.jshell.TaskFactory.AnalyzeTask;
4090075Sobrienimport jdk.jshell.TaskFactory.CompileTask;
4190075Sobrienimport jdk.jshell.spi.ExecutionControl.ClassBytecodes;
4290075Sobrienimport jdk.jshell.spi.ExecutionControl.ClassInstallException;
4390075Sobrienimport jdk.jshell.spi.ExecutionControl.EngineTerminationException;
4490075Sobrienimport jdk.jshell.spi.ExecutionControl.NotImplementedException;
4590075Sobrienimport static java.util.stream.Collectors.toList;
4690075Sobrienimport static java.util.stream.Collectors.toSet;
4790075Sobrienimport static jdk.internal.jshell.debug.InternalDebugControl.DBG_EVNT;
4890075Sobrienimport static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
4990075Sobrienimport static jdk.jshell.Snippet.Status.OVERWRITTEN;
5090075Sobrienimport static jdk.jshell.Snippet.Status.RECOVERABLE_DEFINED;
5190075Sobrienimport static jdk.jshell.Snippet.Status.RECOVERABLE_NOT_DEFINED;
5290075Sobrienimport static jdk.jshell.Snippet.Status.REJECTED;
5390075Sobrienimport static jdk.jshell.Snippet.Status.VALID;
5490075Sobrienimport static jdk.jshell.Util.PARSED_LOCALE;
5590075Sobrienimport static jdk.jshell.Util.expunge;
5690075Sobrien
5790075Sobrien/**
5890075Sobrien * Tracks the compilation and load of a new or updated snippet.
5990075Sobrien * @author Robert Field
6090075Sobrien */
6190075Sobrienfinal class Unit {
6296263Sobrien
6396263Sobrien    private final JShell state;
6496263Sobrien    private final Snippet si;
6590075Sobrien    private final Snippet siOld;
6690075Sobrien    private final boolean isDependency;
6790075Sobrien    private final boolean isNew;
6890075Sobrien    private final Snippet causalSnippet;
6990075Sobrien    private final DiagList generatedDiagnostics;
7090075Sobrien
7190075Sobrien    private int seq;
7290075Sobrien    private String classNameInitial;
7390075Sobrien    private Wrap activeGuts;
7490075Sobrien    private Status status;
7590075Sobrien    private Status prevStatus;
7690075Sobrien    private boolean signatureChanged;
7790075Sobrien    private DiagList compilationDiagnostics;
7890075Sobrien    private DiagList recompilationDiagnostics = null;
7990075Sobrien    private List<String> unresolved;
8090075Sobrien    private SnippetEvent replaceOldEvent;
8190075Sobrien    private List<SnippetEvent> secondaryEvents;
8290075Sobrien    private boolean isAttemptingCorral;
8390075Sobrien    private List<ClassInfo> toRedefine;
8490075Sobrien    private boolean dependenciesNeeded;
8590075Sobrien
8690075Sobrien    Unit(JShell state, Snippet si, Snippet causalSnippet,
8790075Sobrien            DiagList generatedDiagnostics) {
8890075Sobrien        this.state = state;
8990075Sobrien        this.si = si;
9090075Sobrien        this.isDependency = causalSnippet != null;
9190075Sobrien        this.siOld = isDependency
9290075Sobrien                ? si
9390075Sobrien                : state.maps.getSnippet(si.key());
9490075Sobrien        this.isNew = siOld == null;
9590075Sobrien        this.causalSnippet = causalSnippet;
9690075Sobrien        this.generatedDiagnostics = generatedDiagnostics;
9790075Sobrien
9890075Sobrien        this.seq = isNew? 0 : siOld.sequenceNumber();
9990075Sobrien        this.classNameInitial = isNew? "<none>" : siOld.className();
10090075Sobrien        this.prevStatus = (isNew || isDependency)
10190075Sobrien                ? si.status()
10290075Sobrien                : siOld.status();
10390075Sobrien        si.setSequenceNumber(seq);
10490075Sobrien    }
10590075Sobrien
10690075Sobrien    // Drop entry
10790075Sobrien    Unit(JShell state, Snippet si) {
10890075Sobrien        this.state = state;
10990075Sobrien        this.si = si;
11090075Sobrien        this.siOld = null;
11190075Sobrien        this.isDependency = false;
11290075Sobrien        this.isNew = false;
11390075Sobrien        this.causalSnippet = null;
11490075Sobrien        this.generatedDiagnostics = new DiagList();
11590075Sobrien        this.prevStatus = si.status();
11690075Sobrien        si.setDropped();
11790075Sobrien        this.status = si.status();
11890075Sobrien    }
11990075Sobrien
12090075Sobrien    @Override
12190075Sobrien    public int hashCode() {
12290075Sobrien        return si.hashCode();
12390075Sobrien    }
12490075Sobrien
12590075Sobrien    @Override
12690075Sobrien    public boolean equals(Object o) {
12790075Sobrien        return (o instanceof Unit)
12890075Sobrien                ? si.equals(((Unit) o).si)
12990075Sobrien                : false;
13090075Sobrien    }
13190075Sobrien
13290075Sobrien    Snippet snippet() {
13390075Sobrien        return si;
13490075Sobrien    }
13590075Sobrien
13690075Sobrien    boolean isDependency() {
13790075Sobrien        return isDependency;
13890075Sobrien    }
13990075Sobrien
14090075Sobrien    void initialize() {
14190075Sobrien        isAttemptingCorral = false;
14290075Sobrien        dependenciesNeeded = false;
14390075Sobrien        toRedefine = null; // assure NPE if classToLoad not called
14490075Sobrien        activeGuts = si.guts();
14590075Sobrien        markOldDeclarationOverwritten();
14690075Sobrien    }
14790075Sobrien
14896263Sobrien    // Set the outer wrap of our Snippet
14996263Sobrien    void setWrap(Collection<Unit> exceptUnit, Collection<Unit> plusUnfiltered) {
15096263Sobrien        if (isImport()) {
15196263Sobrien            si.setOuterWrap(state.outerMap.wrapImport(activeGuts, si));
15290075Sobrien        } else {
15390075Sobrien            // Collect Units for be wrapped together.  Just this except for overloaded methods
15490075Sobrien            List<Unit> units;
15590075Sobrien            if (snippet().kind() == Kind.METHOD) {
15690075Sobrien                String name = ((MethodSnippet) snippet()).name();
15790075Sobrien                units = plusUnfiltered.stream()
15890075Sobrien                        .filter(u -> u.snippet().kind() == Kind.METHOD &&
15990075Sobrien                                 ((MethodSnippet) u.snippet()).name().equals(name))
16090075Sobrien                        .collect(toList());
16190075Sobrien            } else {
16290075Sobrien                units = Collections.singletonList(this);
16390075Sobrien            }
16490075Sobrien            // Keys to exclude from imports
16590075Sobrien            Set<Key> except = exceptUnit.stream()
16690075Sobrien                    .map(u -> u.snippet().key())
16790075Sobrien                    .collect(toSet());
16890075Sobrien            // Snippets to add to imports
16990075Sobrien            Collection<Snippet> plus = plusUnfiltered.stream()
17090075Sobrien                    .filter(u -> !units.contains(u))
17190075Sobrien                    .map(Unit::snippet)
17290075Sobrien                    .collect(toList());
17390075Sobrien            // Snippets to wrap in an outer
17490075Sobrien            List<Snippet> snippets = units.stream()
17590075Sobrien                    .map(Unit::snippet)
17690075Sobrien                    .collect(toList());
17790075Sobrien            // Snippet wraps to wrap in an outer
17890075Sobrien            List<Wrap> wraps = units.stream()
17990075Sobrien                    .map(u -> u.activeGuts)
18090075Sobrien                    .collect(toList());
18190075Sobrien            // Set the outer wrap for this snippet
18290075Sobrien            si.setOuterWrap(state.outerMap.wrapInClass(except, plus, snippets, wraps));
18390075Sobrien        }
18490075Sobrien    }
18590075Sobrien
18690075Sobrien    void setDiagnostics(AnalyzeTask ct) {
18790075Sobrien        setDiagnostics(ct.getDiagnostics().ofUnit(this));
18890075Sobrien    }
18990075Sobrien
19090075Sobrien    void setDiagnostics(DiagList diags) {
19190075Sobrien        compilationDiagnostics = diags;
19290075Sobrien        UnresolvedExtractor ue = new UnresolvedExtractor(diags);
19390075Sobrien        unresolved = ue.unresolved();
19490075Sobrien        state.debug(DBG_GEN, "++setCompilationInfo() %s\n%s\n-- diags: %s\n",
19590075Sobrien                si, si.outerWrap().wrapped(), diags);
19690075Sobrien    }
19790075Sobrien
19890075Sobrien    private boolean isRecoverable() {
19990075Sobrien        // Unit failed, use corralling if it is defined on this Snippet,
20090075Sobrien        // and either all the errors are resolution errors or this is a
20190075Sobrien        // redeclare of an existing method
20290075Sobrien        return compilationDiagnostics.hasErrors()
20390075Sobrien                && si instanceof DeclarationSnippet
20490075Sobrien                && (isDependency()
20590075Sobrien                    || (si.subKind() != SubKind.VAR_DECLARATION_WITH_INITIALIZER_SUBKIND
20690075Sobrien                        && compilationDiagnostics.hasResolutionErrorsAndNoOthers()));
20790075Sobrien    }
20890075Sobrien
20990075Sobrien    /**
21090075Sobrien     * If it meets the conditions for corralling, install the corralled wrap
21190075Sobrien     * @return true is the corralled wrap was installed
21290075Sobrien     */
21390075Sobrien    boolean corralIfNeeded(Collection<Unit> working) {
21490075Sobrien        if (isRecoverable()
21590075Sobrien                && si.corralled() != null) {
21690075Sobrien            activeGuts = si.corralled();
21790075Sobrien            setWrap(working, working);
21890075Sobrien            return isAttemptingCorral = true;
21990075Sobrien        }
22090075Sobrien        return isAttemptingCorral = false;
22190075Sobrien    }
22290075Sobrien
22390075Sobrien    void setCorralledDiagnostics(AnalyzeTask cct) {
22490075Sobrien        // set corralled diagnostics, but don't reset unresolved
22590075Sobrien        recompilationDiagnostics = cct.getDiagnostics().ofUnit(this);
22690075Sobrien        state.debug(DBG_GEN, "++recomp %s\n%s\n-- diags: %s\n",
22790075Sobrien                si, si.outerWrap().wrapped(), recompilationDiagnostics);
22890075Sobrien    }
22990075Sobrien
23090075Sobrien    boolean smashingErrorDiagnostics(CompileTask ct) {
23190075Sobrien        if (isDefined()) {
23290075Sobrien            // set corralled diagnostics, but don't reset unresolved
23390075Sobrien            DiagList dl = ct.getDiagnostics().ofUnit(this);
23490075Sobrien            if (dl.hasErrors()) {
23590075Sobrien                setDiagnostics(dl);
23690075Sobrien                status = RECOVERABLE_NOT_DEFINED;
23790075Sobrien                // overwrite orginal bytes
23890075Sobrien                state.debug(DBG_GEN, "++smashingErrorDiagnostics %s\n%s\n-- diags: %s\n",
23990075Sobrien                        si, si.outerWrap().wrapped(), dl);
24090075Sobrien                return true;
24190075Sobrien            }
24290075Sobrien        }
24390075Sobrien        return false;
24490075Sobrien    }
24590075Sobrien
24690075Sobrien    void setStatus(AnalyzeTask at) {
24790075Sobrien        if (!compilationDiagnostics.hasErrors()) {
24890075Sobrien            status = VALID;
24990075Sobrien        } else if (isRecoverable()) {
25090075Sobrien            if (isAttemptingCorral && !recompilationDiagnostics.hasErrors()) {
25190075Sobrien                status = RECOVERABLE_DEFINED;
25290075Sobrien            } else {
25390075Sobrien                status = RECOVERABLE_NOT_DEFINED;
25490075Sobrien            }
25590075Sobrien        } else {
25690075Sobrien            status = REJECTED;
25790075Sobrien        }
25890075Sobrien        checkForOverwrite(at);
25990075Sobrien
26090075Sobrien        state.debug(DBG_GEN, "setStatus() %s - status: %s\n",
26190075Sobrien                si, status);
26290075Sobrien    }
26390075Sobrien
26490075Sobrien    boolean isDefined() {
26590075Sobrien        return status.isDefined();
26690075Sobrien    }
26790075Sobrien
26890075Sobrien    /**
26990075Sobrien     * Process the class information from the last compile. Requires loading of
27090075Sobrien     * returned list.
27190075Sobrien     *
27290075Sobrien     * @return the list of classes to load
27390075Sobrien     */
27490075Sobrien    Stream<ClassBytecodes> classesToLoad(List<String> classnames) {
27590075Sobrien        toRedefine = new ArrayList<>();
27690075Sobrien        List<ClassBytecodes> toLoad = new ArrayList<>();
27790075Sobrien        if (status.isDefined() && !isImport()) {
27890075Sobrien            // Classes should only be loaded/redefined if the compile left them
27990075Sobrien            // in a defined state.  Imports do not have code and are not loaded.
28090075Sobrien            for (String cn : classnames) {
28190075Sobrien                ClassInfo ci = state.classTracker.get(cn);
28290075Sobrien                if (ci.isLoaded()) {
28390075Sobrien                    if (ci.isCurrent()) {
28490075Sobrien                        // nothing to do
28590075Sobrien                    } else {
28690075Sobrien                        toRedefine.add(ci);
28790075Sobrien                    }
28890075Sobrien                } else {
28990075Sobrien                    // If not loaded, add to the list of classes to load.
29090075Sobrien                    toLoad.add(ci.toClassBytecodes());
29190075Sobrien                    dependenciesNeeded = true;
29290075Sobrien                }
29390075Sobrien            }
29490075Sobrien        }
29590075Sobrien        return toLoad.stream();
29690075Sobrien    }
29790075Sobrien
29890075Sobrien    /**
29990075Sobrien     * Redefine classes needing redefine. classesToLoad() must be called first.
30090075Sobrien     *
30190075Sobrien     * @return true if all redefines succeeded (can be vacuously true)
30290075Sobrien     */
30390075Sobrien    boolean doRedefines() {
30490075Sobrien        if (toRedefine.isEmpty()) {
30590075Sobrien            return true;
30690075Sobrien        }
30790075Sobrien        ClassBytecodes[] cbcs = toRedefine.stream()
30890075Sobrien                .map(ClassInfo::toClassBytecodes)
30990075Sobrien                .toArray(ClassBytecodes[]::new);
31090075Sobrien        try {
31190075Sobrien            state.executionControl().redefine(cbcs);
31290075Sobrien            state.classTracker.markLoaded(cbcs);
31390075Sobrien            return true;
31490075Sobrien        } catch (ClassInstallException ex) {
31590075Sobrien            state.classTracker.markLoaded(cbcs, ex.installed());
31690075Sobrien            return false;
31790075Sobrien        } catch (EngineTerminationException ex) {
31890075Sobrien            state.closeDown();
31990075Sobrien            return false;
32090075Sobrien        } catch (NotImplementedException ex) {
32190075Sobrien            return false;
32290075Sobrien        }
32390075Sobrien    }
32490075Sobrien
32590075Sobrien    void markForReplacement() {
32690075Sobrien        // increment for replace class wrapper
32796263Sobrien        si.setSequenceNumber(++seq);
32890075Sobrien    }
32990075Sobrien
33090075Sobrien    private boolean isImport() {
33190075Sobrien        return si.kind() == Kind.IMPORT;
33290075Sobrien    }
33390075Sobrien
33490075Sobrien    private boolean sigChanged() {
33590075Sobrien        return (status.isDefined() != prevStatus.isDefined())
33690075Sobrien                || (status.isDefined() && !si.className().equals(classNameInitial))
33790075Sobrien                || signatureChanged;
33890075Sobrien    }
33990075Sobrien
34090075Sobrien    Stream<Unit> effectedDependents() {
34190075Sobrien        //System.err.printf("effectedDependents sigChanged=%b  dependenciesNeeded=%b   status=%s\n",
34290075Sobrien        //       sigChanged(), dependenciesNeeded, status);
34390075Sobrien        return sigChanged() || dependenciesNeeded || status == RECOVERABLE_NOT_DEFINED
34490075Sobrien                ? dependents()
34590075Sobrien                : Stream.empty();
34690075Sobrien    }
34790075Sobrien
34890075Sobrien    Stream<Unit> dependents() {
34990075Sobrien        return state.maps.getDependents(si)
35090075Sobrien                    .stream()
35190075Sobrien                    .filter(xsi -> xsi != si && xsi.status().isActive())
35290075Sobrien                    .map(xsi -> new Unit(state, xsi, si, new DiagList()));
35390075Sobrien    }
35490075Sobrien
35590075Sobrien    void finish() {
35690075Sobrien        recordCompilation();
35790075Sobrien        state.maps.installSnippet(si);
35890075Sobrien    }
35990075Sobrien
36090075Sobrien    private void markOldDeclarationOverwritten() {
36190075Sobrien        if (si != siOld && siOld != null && siOld.status().isActive()) {
36290075Sobrien            // Mark the old declaraion as replaced
36390075Sobrien            replaceOldEvent = new SnippetEvent(siOld,
36490075Sobrien                    siOld.status(), OVERWRITTEN,
36590075Sobrien                    false, si, null, null);
36690075Sobrien            siOld.setOverwritten();
36790075Sobrien        }
36890075Sobrien    }
36990075Sobrien
37090075Sobrien    private DiagList computeDiagnostics() {
37190075Sobrien        DiagList diagnostics = new DiagList();
37290075Sobrien        DiagList diags = compilationDiagnostics;
37390075Sobrien        if (status == RECOVERABLE_DEFINED || status == RECOVERABLE_NOT_DEFINED) {
37490075Sobrien            UnresolvedExtractor ue = new UnresolvedExtractor(diags);
37590075Sobrien            diagnostics.addAll(ue.otherAll());
37690075Sobrien        } else {
37790075Sobrien            unresolved = Collections.emptyList();
37890075Sobrien            diagnostics.addAll(diags);
37990075Sobrien        }
38090075Sobrien        diagnostics.addAll(generatedDiagnostics);
38190075Sobrien        return diagnostics;
38290075Sobrien    }
38390075Sobrien
38490075Sobrien    private void recordCompilation() {
38590075Sobrien        state.maps.mapDependencies(si);
38690075Sobrien        DiagList diags = computeDiagnostics();
38790075Sobrien        si.setCompilationStatus(status, unresolved, diags);
38890075Sobrien        state.debug(DBG_GEN, "recordCompilation: %s -- status %s, unresolved %s\n",
38990075Sobrien                si, status, unresolved);
39090075Sobrien    }
39190075Sobrien
39290075Sobrien    private void checkForOverwrite(AnalyzeTask at) {
39390075Sobrien        secondaryEvents = new ArrayList<>();
39490075Sobrien        if (replaceOldEvent != null) secondaryEvents.add(replaceOldEvent);
39590075Sobrien
39690075Sobrien        // Defined methods can overwrite methods of other (equivalent) snippets
39790075Sobrien        if (isNew && si.kind() == Kind.METHOD && status.isDefined()) {
39890075Sobrien            MethodSnippet msi = (MethodSnippet)si;
39990075Sobrien            String oqpt = msi.qualifiedParameterTypes();
40090075Sobrien            String nqpt = computeQualifiedParameterTypes(at, msi);
40190075Sobrien            if (!nqpt.equals(oqpt)) {
40290075Sobrien                msi.setQualifiedParamaterTypes(nqpt);
40390075Sobrien                Status overwrittenStatus = overwriteMatchingMethod(msi);
40490075Sobrien                if (overwrittenStatus != null) {
405                    prevStatus = overwrittenStatus;
406                    signatureChanged = true;
407                }
408            }
409        }
410    }
411
412    // Check if there is a method whose user-declared parameter types are
413    // different (and thus has a different snippet) but whose compiled parameter
414    // types are the same. if so, consider it an overwrite replacement.
415    private Status overwriteMatchingMethod(MethodSnippet msi) {
416        String qpt = msi.qualifiedParameterTypes();
417        List<MethodSnippet> matching = state.methods()
418                .filter(sn ->
419                           sn != null
420                        && sn != msi
421                        && sn.status().isActive()
422                        && sn.name().equals(msi.name())
423                        && qpt.equals(sn.qualifiedParameterTypes()))
424                .collect(toList());
425
426        // Look through all methods for a method of the same name, with the
427        // same computed qualified parameter types
428        Status overwrittenStatus = null;
429        for (MethodSnippet sn : matching) {
430            overwrittenStatus = sn.status();
431            SnippetEvent se = new SnippetEvent(
432                    sn, overwrittenStatus, OVERWRITTEN,
433                    false, msi, null, null);
434            sn.setOverwritten();
435            secondaryEvents.add(se);
436            state.debug(DBG_EVNT,
437                    "Overwrite event #%d -- key: %s before: %s status: %s sig: %b cause: %s\n",
438                    secondaryEvents.size(), se.snippet(), se.previousStatus(),
439                    se.status(), se.isSignatureChange(), se.causeSnippet());
440        }
441        return overwrittenStatus;
442    }
443
444    private String computeQualifiedParameterTypes(AnalyzeTask at, MethodSnippet msi) {
445        String rawSig = TreeDissector.createBySnippet(at, msi).typeOfMethod(msi);
446        String signature = expunge(rawSig);
447        int paren = signature.lastIndexOf(')');
448
449        // Extract the parameter type string from the method signature,
450        // if method did not compile use the user-supplied parameter types
451        return paren >= 0
452                ? signature.substring(0, paren + 1)
453                : msi.parameterTypes();
454    }
455
456    SnippetEvent event(String value, JShellException exception) {
457        boolean wasSignatureChanged = sigChanged();
458        state.debug(DBG_EVNT, "Snippet: %s id: %s before: %s status: %s sig: %b cause: %s\n",
459                si, si.id(), prevStatus, si.status(), wasSignatureChanged, causalSnippet);
460        return new SnippetEvent(si, prevStatus, si.status(),
461                wasSignatureChanged, causalSnippet, value, exception);
462    }
463
464    List<SnippetEvent> secondaryEvents() {
465        return secondaryEvents==null
466                ? Collections.emptyList()
467                : secondaryEvents;
468    }
469
470    @Override
471    public String toString() {
472        return "Unit(" + si.name() + ")";
473    }
474
475    /**
476     * Separate out the unresolvedDependencies errors from both the other
477     * corralling errors and the overall errors.
478     */
479    private static class UnresolvedExtractor {
480
481        private static final String RESOLVE_ERROR_SYMBOL = "symbol:";
482        private static final String RESOLVE_ERROR_LOCATION = "location:";
483
484        //TODO extract from tree instead -- note: internationalization
485        private final Set<String> unresolved = new LinkedHashSet<>();
486        private final DiagList otherErrors = new DiagList();
487        private final DiagList otherAll = new DiagList();
488
489        UnresolvedExtractor(DiagList diags) {
490            for (Diag diag : diags) {
491                if (diag.isError()) {
492                    if (diag.isResolutionError()) {
493                        String m = diag.getMessage(PARSED_LOCALE);
494                        int symPos = m.indexOf(RESOLVE_ERROR_SYMBOL);
495                        if (symPos >= 0) {
496                            m = m.substring(symPos + RESOLVE_ERROR_SYMBOL.length());
497                            int symLoc = m.indexOf(RESOLVE_ERROR_LOCATION);
498                            if (symLoc >= 0) {
499                                m = m.substring(0, symLoc);
500                            }
501                            m = m.trim();
502                            unresolved.add(m);
503                            continue;
504                        }
505                    }
506                    otherErrors.add(diag);
507                }
508                otherAll.add(diag);
509            }
510        }
511
512        DiagList otherCorralledErrors() {
513            return otherErrors;
514        }
515
516        DiagList otherAll() {
517            return otherAll;
518        }
519
520        List<String> unresolved() {
521            return new ArrayList<>(unresolved);
522        }
523    }
524}
525