blob: 9eba7f5541045c12e3471aec9054f6dc7a5aa529 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.jshell.parsing;
import java.io.IOException;
import org.netbeans.modules.jshell.model.Rng;
import org.netbeans.modules.jshell.model.ConsoleSection;
import org.netbeans.modules.jshell.model.ConsoleModel;
import java.util.ArrayList;
import java.util.List;
import jdk.jshell.Snippet;
import org.netbeans.modules.jshell.model.ConsoleContents;
import org.netbeans.modules.jshell.model.SnippetHandle;
import org.netbeans.modules.jshell.support.ShellSession;
import org.netbeans.modules.parsing.api.Embedding;
import org.netbeans.modules.parsing.api.Snapshot;
import org.openide.filesystems.FileObject;
import org.openide.util.Exceptions;
/**
* Creates embeddings for the given session/model/snapshot.
*
* @author sdedic
*/
final class EmbeddingProcessor {
/**
* The result list of embeddings
*/
private final List<Embedding> embeddings = new ArrayList<>();
private final ConsoleModel model;
private final Snapshot snapshot;
private final ShellSession session;
private final ConsoleSection inputSection;
private final ConsoleSection modelInputSection;
private final ConsoleContents contents;
private StringBuilder precedingImports = new StringBuilder();
private ConsoleSection section;
private int snippetIndex;
public EmbeddingProcessor(ShellSession session, ConsoleContents contents, Snapshot snapshot, ConsoleSection snapshotInput) {
this.session = session;
this.contents = contents;
this.model = contents.getSectionModel();
this.snapshot = snapshot;
this.modelInputSection = model.getInputSection();
this.inputSection = snapshotInput != null ? snapshotInput : modelInputSection;
}
@SuppressWarnings("ReturnOfCollectionOrArrayField")
public List<Embedding> process() {
model.getSections().stream().filter(s -> s.getType().java).forEach(this::processSection);
return embeddings;
}
private void defineEmbedding(SnippetHandle info, Rng posInfo, boolean lastSnippet) {
String contents = info.getWrappedCode();
String source = info.getSource();
int s = 0;
int e = source.length();
if (e > s) {
e--;
} else {
e = s;
}
int ts = info.getWrappedPosition(s);
int te;
int index = source.length() - 1;
while (true) {
te = info.getWrappedPosition(e);
if (te > ts || index < 0) {
break;
}
char c = source.charAt(index--);
if (!(Character.isWhitespace(c) || c == ';')) {
break;
}
index--;
e--;
}
if (ts == -1 || te == -1) {
// fall back: tell that the snippet text itself is the embedding
embeddings.add(snapshot.create(posInfo.start, posInfo.len(), JAVA_MIME_TYPE));
return;
}
boolean lengthMismatch = (te - ts) != (e - s);
te = Math.min(contents.length(), te + 1);
// this will produce file with a stable content. For the purposes of parsing,
// class name will be replaced so compiler does not complain about duplicate classes.
FileObject snipFile;
try {
snipFile = info.getFile();
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
return;
}
/*
model.getInputSection() == section ? snippetIndex++ : -1);
*/
if (snipFile == null) {
return;
}
ConsoleSection activeInput = inputSection;
String prologText = contents.substring(0, ts);
if (info.getKind() != Snippet.Kind.IMPORT && precedingImports.length() > 0) {
int indexOfClass = prologText.indexOf(JSHELL_CLASS_DECLARATION);
if (indexOfClass > 0) {
prologText = prologText.substring(0, indexOfClass) +
precedingImports.toString() +
prologText.substring(indexOfClass);
}
}
// hack: this embedding processor never works for files. Replace junk classnames
// so they are unique (do not collide with indexed stuff).
prologText = prologText.replace(JSHELL_CLASS_DECLARATION, CHANGED_CLASS_DECLARATION);
Embedding prolog = snapshot.create(prologText, JAVA_MIME_TYPE);
Embedding epilog = snapshot.create(contents.substring(te), JAVA_MIME_TYPE);
List<Embedding> embs = new ArrayList<>();
embs.add(prolog);
lengthMismatch &= info.getKind() == Snippet.Kind.VAR;
// Position in the source, where the declaration ends and the wrapper lists
// a semicolon.
int endSourceDeclPos = e - s + 1;
String insertedText = null;
if (lengthMismatch) {
// variable with initializer is torn apart: variable is first declared, then
// the initializer is executed with a method to capture any possible exceptions
// compute the length of the first part, which is moved to the declarator - from the start to the
// ';' in the 'contents'.
//
int x = contents.indexOf(';', ts); // NOI18N
if (x != -1) {
endSourceDeclPos = x - ts;
int y = endSourceDeclPos;
while (y < source.length() && (info.getWrappedPosition(y) - ts) == y) {
y++;
}
if (y == source.length()) {
throw new IllegalStateException();
}
// y is now a source position, which is mapped to the assignment part
// of the wrapper.
int assignPos = info.getWrappedPosition(y);
int equal = contents.indexOf('=', assignPos); // NOI18N
if (equal == -1) {
// this should not happen, initialized variable has always an equal sign
throw new IllegalStateException();
} else {
// the text can be recreated as follows:
// 0 ... ts -- from the wrapper
// 0 ... endSourceDeclPos -- from the source
// insertedText -- semicolon and garbage from the wrapper
// endSourceDeclPos- -- from the source
insertedText = contents.substring(x, equal);
}
// int afterSemiPos = info.getWrappedPosition(endSourceDeclPos + 1);
// insertedText = contents.substring(x, afterSemiPos);
}
}
int sourcePos = 0;
int l = snapshot.getText().length();
Rng[] fragments = info.getFragments();
for (int i = 0; i < fragments.length; i++) {
Rng r = fragments[i];
// the document may have changed, and the console model has already
// accommodated the change.
if (r.start > l) {
continue;
}
int fragStart = r.start;
int fragLen = r.len();
if (activeInput != null && activeInput.getStart() == section.getStart() && lastSnippet && i == fragments.length - 1) {
fragLen = snapshot.getText().length() - fragStart;
} else if (r.end > l) {
// not last fragment of input section, but still beyond...
continue;
}
if (lengthMismatch && (sourcePos <= endSourceDeclPos && sourcePos + fragLen >= endSourceDeclPos)) {
int xl = (endSourceDeclPos - sourcePos);
embs.add(snapshot.create(fragStart, xl, JAVA_MIME_TYPE));
sourcePos += xl;
// add the text in between semiPos
embs.add(snapshot.create(insertedText, JAVA_MIME_TYPE));
if (fragLen > xl) {
embs.add(snapshot.create(fragStart + xl, fragLen - xl, JAVA_MIME_TYPE));
sourcePos += (fragLen - xl);
}
} else {
embs.add(snapshot.create(fragStart, fragLen, JAVA_MIME_TYPE));
sourcePos += fragLen;
}
}
embs.add(epilog);
Embedding emb = Embedding.create(embs);
embeddings.add(emb);
}
private static final String JAVA_MIME_TYPE = "text/x-java";
private static final String CHANGED_CLASS_DECLARATION = "class $JSHELL$";
private static final String JSHELL_CLASS_DECLARATION = "class $JShell$";
/**
* Processes one section for embeddings. Note that one section may have more
* snippets, each of which is wrapped SEPARATELY. E.g. there may be 2 methods or
* method-import-method-expression, each of which receives a separate wrapping
*
* @param section
*/
private void processSection(ConsoleSection section) {
this.precedingImports = new StringBuilder();
this.section = section;
this.snippetIndex = 0;
List<SnippetHandle> snippets = contents.getHandles(section);
Rng[] ranges = section.getAllSnippetBounds();
if (snippets == null) {
return;
}
int index = 0;
for (SnippetHandle s : snippets) {
if (s.getKind() == Snippet.Kind.IMPORT) {
// special case: must add imports from preceding snippets.
String text = s.getSource().trim();
precedingImports.append(text);
if (!text.endsWith(";")) {
precedingImports.append(";");
}
}
defineEmbedding(s, ranges[index++], index == snippets.size());
}
}
}