| /* |
| * 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()); |
| } |
| } |
| } |