--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/jshell/Unit.java Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,527 @@
+/*
+ * Copyright (c) 2015, 2016, 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 java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Stream;
+import jdk.jshell.ClassTracker.ClassInfo;
+import jdk.jshell.Snippet.Kind;
+import jdk.jshell.Snippet.Status;
+import jdk.jshell.Snippet.SubKind;
+import jdk.jshell.TaskFactory.AnalyzeTask;
+import jdk.jshell.TaskFactory.CompileTask;
+import jdk.jshell.spi.ExecutionControl.ClassBytecodes;
+import jdk.jshell.spi.ExecutionControl.ClassInstallException;
+import jdk.jshell.spi.ExecutionControl.EngineTerminationException;
+import jdk.jshell.spi.ExecutionControl.NotImplementedException;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
+import static jdk.internal.jshell.debug.InternalDebugControl.DBG_EVNT;
+import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
+import static jdk.internal.jshell.debug.InternalDebugControl.DBG_WRAP;
+import static jdk.jshell.Snippet.Status.OVERWRITTEN;
+import static jdk.jshell.Snippet.Status.RECOVERABLE_DEFINED;
+import static jdk.jshell.Snippet.Status.RECOVERABLE_NOT_DEFINED;
+import static jdk.jshell.Snippet.Status.REJECTED;
+import static jdk.jshell.Snippet.Status.VALID;
+import static jdk.jshell.Util.PARSED_LOCALE;
+import static jdk.jshell.Util.expunge;
+
+/**
+ * Tracks the compilation and load of a new or updated snippet.
+ * @author Robert Field
+ */
+final class Unit {
+
+ private final JShell state;
+ private final Snippet si;
+ private final Snippet siOld;
+ private final boolean isDependency;
+ private final boolean isNew;
+ private final Snippet causalSnippet;
+ private final DiagList generatedDiagnostics;
+
+ private int seq;
+ private String classNameInitial;
+ private Wrap activeGuts;
+ private Status status;
+ private Status prevStatus;
+ private boolean signatureChanged;
+ private DiagList compilationDiagnostics;
+ private DiagList recompilationDiagnostics = null;
+ private List<String> unresolved;
+ private SnippetEvent replaceOldEvent;
+ private List<SnippetEvent> secondaryEvents;
+ private boolean isAttemptingCorral;
+ private List<ClassInfo> toRedefine;
+ private boolean dependenciesNeeded;
+
+ Unit(JShell state, Snippet si, Snippet causalSnippet,
+ DiagList generatedDiagnostics) {
+ this.state = state;
+ this.si = si;
+ this.isDependency = causalSnippet != null;
+ this.siOld = isDependency
+ ? si
+ : state.maps.getSnippet(si.key());
+ this.isNew = siOld == null;
+ this.causalSnippet = causalSnippet;
+ this.generatedDiagnostics = generatedDiagnostics;
+
+ this.seq = isNew? 0 : siOld.sequenceNumber();
+ this.classNameInitial = isNew? "<none>" : siOld.className();
+ this.prevStatus = (isNew || isDependency)
+ ? si.status()
+ : siOld.status();
+ si.setSequenceNumber(seq);
+ }
+
+ // Drop entry
+ Unit(JShell state, Snippet si) {
+ this.state = state;
+ this.si = si;
+ this.siOld = null;
+ this.isDependency = false;
+ this.isNew = false;
+ this.causalSnippet = null;
+ this.generatedDiagnostics = new DiagList();
+ this.prevStatus = si.status();
+ si.setDropped();
+ this.status = si.status();
+ }
+
+ @Override
+ public int hashCode() {
+ return si.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return (o instanceof Unit)
+ ? si.equals(((Unit) o).si)
+ : false;
+ }
+
+ Snippet snippet() {
+ return si;
+ }
+
+ boolean isDependency() {
+ return isDependency;
+ }
+
+ void initialize() {
+ isAttemptingCorral = false;
+ dependenciesNeeded = false;
+ toRedefine = null; // assure NPE if classToLoad not called
+ activeGuts = si.guts();
+ markOldDeclarationOverwritten();
+ }
+
+ // Set the outer wrap of our Snippet
+ void setWrap(Collection<Unit> exceptUnit, Collection<Unit> plusUnfiltered) {
+ if (isImport()) {
+ si.setOuterWrap(state.outerMap.wrapImport(activeGuts, si));
+ } else {
+ // Collect Units for be wrapped together. Just this except for overloaded methods
+ List<Unit> units;
+ if (snippet().kind() == Kind.METHOD) {
+ String name = ((MethodSnippet) snippet()).name();
+ units = plusUnfiltered.stream()
+ .filter(u -> u.snippet().kind() == Kind.METHOD &&
+ ((MethodSnippet) u.snippet()).name().equals(name))
+ .collect(toList());
+ } else {
+ units = Collections.singletonList(this);
+ }
+ // Keys to exclude from imports
+ Set<Key> except = exceptUnit.stream()
+ .map(u -> u.snippet().key())
+ .collect(toSet());
+ // Snippets to add to imports
+ Collection<Snippet> plus = plusUnfiltered.stream()
+ .filter(u -> !units.contains(u))
+ .map(Unit::snippet)
+ .collect(toList());
+ // Snippets to wrap in an outer
+ List<Snippet> snippets = units.stream()
+ .map(Unit::snippet)
+ .collect(toList());
+ // Snippet wraps to wrap in an outer
+ List<Wrap> wraps = units.stream()
+ .map(u -> u.activeGuts)
+ .collect(toList());
+ // Set the outer wrap for this snippet
+ si.setOuterWrap(state.outerMap.wrapInClass(except, plus, snippets, wraps));
+ state.debug(DBG_WRAP, "++setWrap() %s\n%s\n",
+ si, si.outerWrap().wrapped());
+ }
+ }
+
+ void setDiagnostics(AnalyzeTask ct) {
+ setDiagnostics(ct.getDiagnostics().ofUnit(this));
+ }
+
+ void setDiagnostics(DiagList diags) {
+ compilationDiagnostics = diags;
+ UnresolvedExtractor ue = new UnresolvedExtractor(diags);
+ unresolved = ue.unresolved();
+ state.debug(DBG_GEN, "++setCompilationInfo() %s\n%s\n-- diags: %s\n",
+ si, si.outerWrap().wrapped(), diags);
+ }
+
+ private boolean isRecoverable() {
+ // Unit failed, use corralling if it is defined on this Snippet,
+ // and either all the errors are resolution errors or this is a
+ // redeclare of an existing method
+ return compilationDiagnostics.hasErrors()
+ && si instanceof DeclarationSnippet
+ && (isDependency()
+ || (si.subKind() != SubKind.VAR_DECLARATION_WITH_INITIALIZER_SUBKIND
+ && compilationDiagnostics.hasResolutionErrorsAndNoOthers()));
+ }
+
+ /**
+ * If it meets the conditions for corralling, install the corralled wrap
+ * @return true is the corralled wrap was installed
+ */
+ boolean corralIfNeeded(Collection<Unit> working) {
+ if (isRecoverable()
+ && si.corralled() != null) {
+ activeGuts = si.corralled();
+ setWrap(working, working);
+ return isAttemptingCorral = true;
+ }
+ return isAttemptingCorral = false;
+ }
+
+ void setCorralledDiagnostics(AnalyzeTask cct) {
+ // set corralled diagnostics, but don't reset unresolved
+ recompilationDiagnostics = cct.getDiagnostics().ofUnit(this);
+ state.debug(DBG_GEN, "++recomp %s\n%s\n-- diags: %s\n",
+ si, si.outerWrap().wrapped(), recompilationDiagnostics);
+ }
+
+ boolean smashingErrorDiagnostics(CompileTask ct) {
+ if (isDefined()) {
+ // set corralled diagnostics, but don't reset unresolved
+ DiagList dl = ct.getDiagnostics().ofUnit(this);
+ if (dl.hasErrors()) {
+ setDiagnostics(dl);
+ status = RECOVERABLE_NOT_DEFINED;
+ // overwrite orginal bytes
+ state.debug(DBG_GEN, "++smashingErrorDiagnostics %s\n%s\n-- diags: %s\n",
+ si, si.outerWrap().wrapped(), dl);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void setStatus(AnalyzeTask at) {
+ if (!compilationDiagnostics.hasErrors()) {
+ status = VALID;
+ } else if (isRecoverable()) {
+ if (isAttemptingCorral && !recompilationDiagnostics.hasErrors()) {
+ status = RECOVERABLE_DEFINED;
+ } else {
+ status = RECOVERABLE_NOT_DEFINED;
+ }
+ } else {
+ status = REJECTED;
+ }
+ checkForOverwrite(at);
+
+ state.debug(DBG_GEN, "setStatus() %s - status: %s\n",
+ si, status);
+ }
+
+ boolean isDefined() {
+ return status.isDefined();
+ }
+
+ /**
+ * Process the class information from the last compile. Requires loading of
+ * returned list.
+ *
+ * @return the list of classes to load
+ */
+ Stream<ClassBytecodes> classesToLoad(List<String> classnames) {
+ toRedefine = new ArrayList<>();
+ List<ClassBytecodes> toLoad = new ArrayList<>();
+ if (status.isDefined() && !isImport()) {
+ // Classes should only be loaded/redefined if the compile left them
+ // in a defined state. Imports do not have code and are not loaded.
+ for (String cn : classnames) {
+ ClassInfo ci = state.classTracker.get(cn);
+ if (ci.isLoaded()) {
+ if (ci.isCurrent()) {
+ // nothing to do
+ } else {
+ toRedefine.add(ci);
+ }
+ } else {
+ // If not loaded, add to the list of classes to load.
+ toLoad.add(ci.toClassBytecodes());
+ dependenciesNeeded = true;
+ }
+ }
+ }
+ return toLoad.stream();
+ }
+
+ /**
+ * Redefine classes needing redefine. classesToLoad() must be called first.
+ *
+ * @return true if all redefines succeeded (can be vacuously true)
+ */
+ boolean doRedefines() {
+ if (toRedefine.isEmpty()) {
+ return true;
+ }
+ ClassBytecodes[] cbcs = toRedefine.stream()
+ .map(ClassInfo::toClassBytecodes)
+ .toArray(ClassBytecodes[]::new);
+ try {
+ state.executionControl().redefine(cbcs);
+ state.classTracker.markLoaded(cbcs);
+ return true;
+ } catch (ClassInstallException ex) {
+ state.classTracker.markLoaded(cbcs, ex.installed());
+ return false;
+ } catch (EngineTerminationException ex) {
+ state.closeDown();
+ return false;
+ } catch (NotImplementedException ex) {
+ return false;
+ }
+ }
+
+ void markForReplacement() {
+ // increment for replace class wrapper
+ si.setSequenceNumber(++seq);
+ }
+
+ private boolean isImport() {
+ return si.kind() == Kind.IMPORT;
+ }
+
+ private boolean sigChanged() {
+ return (status.isDefined() != prevStatus.isDefined())
+ || (status.isDefined() && !si.className().equals(classNameInitial))
+ || signatureChanged;
+ }
+
+ Stream<Unit> effectedDependents() {
+ //System.err.printf("effectedDependents sigChanged=%b dependenciesNeeded=%b status=%s\n",
+ // sigChanged(), dependenciesNeeded, status);
+ return sigChanged() || dependenciesNeeded || status == RECOVERABLE_NOT_DEFINED
+ ? dependents()
+ : Stream.empty();
+ }
+
+ Stream<Unit> dependents() {
+ return state.maps.getDependents(si)
+ .stream()
+ .filter(xsi -> xsi != si && xsi.status().isActive())
+ .map(xsi -> new Unit(state, xsi, si, new DiagList()));
+ }
+
+ void finish() {
+ recordCompilation();
+ state.maps.installSnippet(si);
+ }
+
+ private void markOldDeclarationOverwritten() {
+ if (si != siOld && siOld != null && siOld.status().isActive()) {
+ // Mark the old declaraion as replaced
+ replaceOldEvent = new SnippetEvent(siOld,
+ siOld.status(), OVERWRITTEN,
+ false, si, null, null);
+ siOld.setOverwritten();
+ }
+ }
+
+ private DiagList computeDiagnostics() {
+ DiagList diagnostics = new DiagList();
+ DiagList diags = compilationDiagnostics;
+ if (status == RECOVERABLE_DEFINED || status == RECOVERABLE_NOT_DEFINED) {
+ UnresolvedExtractor ue = new UnresolvedExtractor(diags);
+ diagnostics.addAll(ue.otherAll());
+ } else {
+ unresolved = Collections.emptyList();
+ diagnostics.addAll(diags);
+ }
+ diagnostics.addAll(generatedDiagnostics);
+ return diagnostics;
+ }
+
+ private void recordCompilation() {
+ state.maps.mapDependencies(si);
+ DiagList diags = computeDiagnostics();
+ si.setCompilationStatus(status, unresolved, diags);
+ state.debug(DBG_GEN, "recordCompilation: %s -- status %s, unresolved %s\n",
+ si, status, unresolved);
+ }
+
+ private void checkForOverwrite(AnalyzeTask at) {
+ secondaryEvents = new ArrayList<>();
+ if (replaceOldEvent != null) secondaryEvents.add(replaceOldEvent);
+
+ // Defined methods can overwrite methods of other (equivalent) snippets
+ if (isNew && si.kind() == Kind.METHOD && status.isDefined()) {
+ MethodSnippet msi = (MethodSnippet)si;
+ String oqpt = msi.qualifiedParameterTypes();
+ String nqpt = computeQualifiedParameterTypes(at, msi);
+ if (!nqpt.equals(oqpt)) {
+ msi.setQualifiedParamaterTypes(nqpt);
+ Status overwrittenStatus = overwriteMatchingMethod(msi);
+ if (overwrittenStatus != null) {
+ prevStatus = overwrittenStatus;
+ signatureChanged = true;
+ }
+ }
+ }
+ }
+
+ // Check if there is a method whose user-declared parameter types are
+ // different (and thus has a different snippet) but whose compiled parameter
+ // types are the same. if so, consider it an overwrite replacement.
+ private Status overwriteMatchingMethod(MethodSnippet msi) {
+ String qpt = msi.qualifiedParameterTypes();
+ List<MethodSnippet> matching = state.methods()
+ .filter(sn ->
+ sn != null
+ && sn != msi
+ && sn.status().isActive()
+ && sn.name().equals(msi.name())
+ && qpt.equals(sn.qualifiedParameterTypes()))
+ .collect(toList());
+
+ // Look through all methods for a method of the same name, with the
+ // same computed qualified parameter types
+ Status overwrittenStatus = null;
+ for (MethodSnippet sn : matching) {
+ overwrittenStatus = sn.status();
+ SnippetEvent se = new SnippetEvent(
+ sn, overwrittenStatus, OVERWRITTEN,
+ false, msi, null, null);
+ sn.setOverwritten();
+ secondaryEvents.add(se);
+ state.debug(DBG_EVNT,
+ "Overwrite event #%d -- key: %s before: %s status: %s sig: %b cause: %s\n",
+ secondaryEvents.size(), se.snippet(), se.previousStatus(),
+ se.status(), se.isSignatureChange(), se.causeSnippet());
+ }
+ return overwrittenStatus;
+ }
+
+ private String computeQualifiedParameterTypes(AnalyzeTask at, MethodSnippet msi) {
+ String rawSig = TreeDissector.createBySnippet(at, msi).typeOfMethod(msi);
+ String signature = expunge(rawSig);
+ int paren = signature.lastIndexOf(')');
+
+ // Extract the parameter type string from the method signature,
+ // if method did not compile use the user-supplied parameter types
+ return paren >= 0
+ ? signature.substring(0, paren + 1)
+ : msi.parameterTypes();
+ }
+
+ SnippetEvent event(String value, JShellException exception) {
+ boolean wasSignatureChanged = sigChanged();
+ state.debug(DBG_EVNT, "Snippet: %s id: %s before: %s status: %s sig: %b cause: %s\n",
+ si, si.id(), prevStatus, si.status(), wasSignatureChanged, causalSnippet);
+ return new SnippetEvent(si, prevStatus, si.status(),
+ wasSignatureChanged, causalSnippet, value, exception);
+ }
+
+ List<SnippetEvent> secondaryEvents() {
+ return secondaryEvents==null
+ ? Collections.emptyList()
+ : secondaryEvents;
+ }
+
+ @Override
+ public String toString() {
+ return "Unit(" + si.name() + ")";
+ }
+
+ /**
+ * Separate out the unresolvedDependencies errors from both the other
+ * corralling errors and the overall errors.
+ */
+ private static class UnresolvedExtractor {
+
+ private static final String RESOLVE_ERROR_SYMBOL = "symbol:";
+ private static final String RESOLVE_ERROR_LOCATION = "location:";
+
+ //TODO extract from tree instead -- note: internationalization
+ private final Set<String> unresolved = new LinkedHashSet<>();
+ private final DiagList otherErrors = new DiagList();
+ private final DiagList otherAll = new DiagList();
+
+ UnresolvedExtractor(DiagList diags) {
+ for (Diag diag : diags) {
+ if (diag.isError()) {
+ if (diag.isResolutionError()) {
+ String m = diag.getMessage(PARSED_LOCALE);
+ int symPos = m.indexOf(RESOLVE_ERROR_SYMBOL);
+ if (symPos >= 0) {
+ m = m.substring(symPos + RESOLVE_ERROR_SYMBOL.length());
+ int symLoc = m.indexOf(RESOLVE_ERROR_LOCATION);
+ if (symLoc >= 0) {
+ m = m.substring(0, symLoc);
+ }
+ m = m.trim();
+ unresolved.add(m);
+ continue;
+ }
+ }
+ otherErrors.add(diag);
+ }
+ otherAll.add(diag);
+ }
+ }
+
+ DiagList otherCorralledErrors() {
+ return otherErrors;
+ }
+
+ DiagList otherAll() {
+ return otherAll;
+ }
+
+ List<String> unresolved() {
+ return new ArrayList<>(unresolved);
+ }
+ }
+}