SourceCodeAnalysis.java revision 3738:6ef8a1453577
1/*
2 * Copyright (c) 2014, 2015, 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 */
25
26package jdk.jshell;
27
28import java.util.Collection;
29import java.util.List;
30
31/**
32 * Provides analysis utilities for source code input.
33 * Optional functionality that provides for a richer interactive experience.
34 * Includes completion analysis:
35 * Is the input a complete snippet of code?
36 * Do I need to prompt for more input?
37 * Would adding a semicolon make it complete?
38 * Is there more than one snippet?
39 * etc.
40 * Also includes completion suggestions, as might be used in tab-completion.
41 *
42 */
43public abstract class SourceCodeAnalysis {
44
45    /**
46     * Given an input string, find the first snippet of code (one statement,
47     * definition, import, or expression) and evaluate if it is complete.
48     * @param input the input source string
49     * @return a CompletionInfo instance with location and completeness info
50     */
51    public abstract CompletionInfo analyzeCompletion(String input);
52
53    /**
54     * Compute possible follow-ups for the given input.
55     * Uses information from the current {@code JShell} state, including
56     * type information, to filter the suggestions.
57     * @param input the user input, so far
58     * @param cursor the current position of the cursors in the given {@code input} text
59     * @param anchor outgoing parameter - when an option will be completed, the text between
60     *               the anchor and cursor will be deleted and replaced with the given option
61     * @return list of candidate continuations of the given input.
62     */
63    public abstract List<Suggestion> completionSuggestions(String input, int cursor, int[] anchor);
64
65    /**
66     * Compute documentation for the given user's input. Multiple {@code Documentation} objects may
67     * be returned when multiple elements match the user's input (like for overloaded methods).
68     * @param input the snippet the user wrote so far
69     * @param cursor the current position of the cursors in the given {@code input} text
70     * @param computeJavadoc true if the javadoc for the given input should be computed in
71     *                       addition to the signature
72     * @return the documentations for the given user's input, if multiple elements match the input,
73     *         multiple {@code Documentation} objects are returned.
74     */
75    public abstract List<Documentation> documentation(String input, int cursor, boolean computeJavadoc);
76
77    /**
78     * Infer the type of the given expression. The expression spans from the beginning of {@code code}
79     * to the given {@code cursor} position. Returns null if the type of the expression cannot
80     * be inferred.
81     *
82     * @param code the expression for which the type should be inferred
83     * @param cursor current cursor position in the given code
84     * @return the inferred type, or null if it cannot be inferred
85     */
86    public abstract String analyzeType(String code, int cursor);
87
88    /**
89     * List qualified names known for the simple name in the given code immediately
90     * to the left of the given cursor position. The qualified names are gathered by inspecting the
91     * classpath used by eval (see {@link JShell#addToClasspath(java.lang.String)}).
92     *
93     * @param code the expression for which the candidate qualified names should be computed
94     * @param cursor current cursor position in the given code
95     * @return the known qualified names
96     */
97    public abstract QualifiedNames listQualifiedNames(String code, int cursor);
98
99    /**
100     * Returns the wrapper information for the {@code Snippet}. The wrapper changes as
101     * the environment changes, so calls to this method at different times may
102     * yield different results.
103     *
104     * @param snippet the {@code Snippet} from which to retrieve the wrapper
105     * @return information on the wrapper
106     */
107    public abstract SnippetWrapper wrapper(Snippet snippet);
108
109    /**
110     * Returns the wrapper information for the snippet within the
111     * input source string.
112     * <p>
113     * Wrapper information for malformed and incomplete
114     * snippets also generate wrappers. The list is in snippet encounter
115     * order. The wrapper changes as the environment changes, so calls to this
116     * method at different times may yield different results.
117     * <p>
118     * The input should be
119     * exactly one complete snippet of source code, that is, one expression,
120     * statement, variable declaration, method declaration, class declaration,
121     * or import.
122     * To break arbitrary input into individual complete snippets, use
123     * {@link SourceCodeAnalysis#analyzeCompletion(String)}.
124     * <p>
125     * The wrapper may not match that returned by
126     * {@link SourceCodeAnalysis#wrapper(Snippet) wrapper(Snippet)},
127     * were the source converted to a {@code Snippet}.
128     *
129     * @param input the source input from which to generate wrappers
130     * @return a list of wrapper information
131     */
132    public abstract List<SnippetWrapper> wrappers(String input);
133
134    /**
135     * Returns a collection of {@code Snippet}s which might need updating if the
136     * given {@code Snippet} is updated. The returned collection is designed to
137     * be inclusive and may include many false positives.
138     *
139     * @param snippet the {@code Snippet} whose dependents are requested
140     * @return the collection of dependents
141     */
142    public abstract Collection<Snippet> dependents(Snippet snippet);
143
144    /**
145     * Internal only constructor
146     */
147    SourceCodeAnalysis() {}
148
149    /**
150     * The result of {@code analyzeCompletion(String input)}.
151     * Describes the completeness of the first snippet in the given input.
152     */
153    public interface CompletionInfo {
154
155        /**
156         * The analyzed completeness of the input.
157         *
158         * @return an enum describing the completeness of the input string.
159         */
160        Completeness completeness();
161
162        /**
163         * Input remaining after the complete part of the source.
164         *
165         * @return the portion of the input string that remains after the
166         * complete Snippet
167         */
168        String remaining();
169
170        /**
171         * Source code for the first Snippet of code input. For example, first
172         * statement, or first method declaration. Trailing semicolons will be
173         * added, as needed.
174         *
175         * @return the source of the first encountered Snippet
176         */
177        String source();
178    }
179
180    /**
181     * Describes the completeness of the given input.
182     */
183    public enum Completeness {
184        /**
185         * The input is a complete source snippet (declaration or statement) as is.
186         */
187        COMPLETE(true),
188
189        /**
190         * With this addition of a semicolon the input is a complete source snippet.
191         * This will only be returned when the end of input is encountered.
192         */
193        COMPLETE_WITH_SEMI(true),
194
195        /**
196         * There must be further source beyond the given input in order for it
197         * to be complete.  A semicolon would not complete it.
198         * This will only be returned when the end of input is encountered.
199         */
200        DEFINITELY_INCOMPLETE(false),
201
202        /**
203         * A statement with a trailing (non-terminated) empty statement.
204         * Though technically it would be a complete statement
205         * with the addition of a semicolon, it is rare
206         * that that assumption is the desired behavior.
207         * The input is considered incomplete.  Comments and white-space are
208         * still considered empty.
209         */
210        CONSIDERED_INCOMPLETE(false),
211
212
213        /**
214         * An empty input.
215         * The input is considered incomplete.  Comments and white-space are
216         * still considered empty.
217         */
218        EMPTY(false),
219
220        /**
221         * The completeness of the input could not be determined because it
222         * contains errors. Error detection is not a goal of completeness
223         * analysis, however errors interfered with determining its completeness.
224         * The input is considered complete because evaluating is the best
225         * mechanism to get error information.
226         */
227        UNKNOWN(true);
228
229        private final boolean isComplete;
230
231        Completeness(boolean isComplete) {
232            this.isComplete = isComplete;
233        }
234
235        /**
236         * Indicates whether the first snippet of source is complete.
237         * For example, "{@code x=}" is not
238         * complete, but "{@code x=2}" is complete, even though a subsequent line could
239         * make it "{@code x=2+2}". Already erroneous code is marked complete.
240         *
241         * @return {@code true} if the input is or begins a complete Snippet;
242         * otherwise {@code false}
243         */
244        public boolean isComplete() {
245            return isComplete;
246        }
247    }
248
249    /**
250     * A candidate for continuation of the given user's input.
251     */
252    public interface Suggestion {
253
254        /**
255         * The candidate continuation of the given user's input.
256         *
257         * @return the continuation string
258         */
259        String continuation();
260
261        /**
262         * Indicates whether input continuation matches the target type and is thus
263         * more likely to be the desired continuation. A matching continuation is
264         * preferred.
265         *
266         * @return {@code true} if this suggested continuation matches the
267         * target type; otherwise {@code false}
268         */
269        boolean matchesType();
270    }
271
272    /**
273     * A documentation for a candidate for continuation of the given user's input.
274     */
275    public interface Documentation {
276
277        /**
278         * The signature of the given element.
279         *
280         * @return the signature
281         */
282        String signature();
283
284        /**
285         * The javadoc of the given element.
286         *
287         * @return the javadoc, or null if not found or not requested
288         */
289        String javadoc();
290    }
291
292    /**
293     * List of possible qualified names.
294     */
295    public static final class QualifiedNames {
296
297        private final List<String> names;
298        private final int simpleNameLength;
299        private final boolean upToDate;
300        private final boolean resolvable;
301
302        QualifiedNames(List<String> names, int simpleNameLength, boolean upToDate, boolean resolvable) {
303            this.names = names;
304            this.simpleNameLength = simpleNameLength;
305            this.upToDate = upToDate;
306            this.resolvable = resolvable;
307        }
308
309        /**
310         * Known qualified names for the given simple name in the original code.
311         *
312         * @return known qualified names
313         */
314        public List<String> getNames() {
315            return names;
316        }
317
318        /**
319         * The length of the simple name in the original code for which the
320         * qualified names where gathered.
321         *
322         * @return the length of the simple name; -1 if there is no name immediately left to the cursor for
323         *         which the candidates could be computed
324         */
325        public int getSimpleNameLength() {
326            return simpleNameLength;
327        }
328
329        /**
330         * Indicates whether the result is based on up-to-date data. The
331         * {@link SourceCodeAnalysis#listQualifiedNames(java.lang.String, int) listQualifiedNames}
332         * method may return before the classpath is fully inspected, in which case this method will
333         * return {@code false}. If the result is based on a fully inspected classpath, this method
334         * will return {@code true}.
335         *
336         * @return {@code true} if the result is based on up-to-date data;
337         * otherwise {@code false}
338         */
339        public boolean isUpToDate() {
340            return upToDate;
341        }
342
343        /**
344         * Indicates whether the given simple name in the original code refers
345         * to a resolvable element.
346         *
347         * @return {@code true} if the given simple name in the original code
348         * refers to a resolvable element; otherwise {@code false}
349         */
350        public boolean isResolvable() {
351            return resolvable;
352        }
353
354    }
355
356    /**
357     * The wrapping of a snippet of Java source into valid top-level Java
358     * source. The wrapping will always either be an import or include a
359     * synthetic class at the top-level. If a synthetic class is generated, it
360     * will be proceeded by the package and import declarations, and may contain
361     * synthetic class members.
362     * <p>
363     * This interface, in addition to the mapped form, provides the context and
364     * position mapping information.
365     */
366    public interface SnippetWrapper {
367
368        /**
369         * Returns the input that is wrapped. For
370         * {@link SourceCodeAnalysis#wrappers(java.lang.String) wrappers(String)},
371         * this is the source of the snippet within the input. A variable
372         * declaration of {@code N} variables will map to {@code N} wrappers
373         * with the source separated.
374         * <p>
375         * For {@link SourceCodeAnalysis#wrapper(Snippet) wrapper(Snippet)},
376         * this is {@link Snippet#source() }.
377         *
378         * @return the input source corresponding to the wrapper.
379         */
380        String source();
381
382        /**
383         * Returns a Java class definition that wraps the
384         * {@link SnippetWrapper#source()} or, if an import, the import source.
385         * <p>
386         * If the input is not a valid Snippet, this will not be a valid
387         * class/import definition.
388         * <p>
389         * The source may be divided and mapped to different locations within
390         * the wrapped source.
391         *
392         * @return the source wrapped into top-level Java code
393         */
394        String wrapped();
395
396        /**
397         * Returns the fully qualified class name of the
398         * {@link SnippetWrapper#wrapped() } class.
399         * For erroneous input, a best guess is returned.
400         *
401         * @return the name of the synthetic wrapped class; if an import, the
402         * name is not defined
403         */
404        String fullClassName();
405
406        /**
407         * Returns the {@link Snippet.Kind} of the
408         * {@link SnippetWrapper#source()}.
409         *
410         * @return an enum representing the general kind of snippet.
411         */
412        Snippet.Kind kind();
413
414        /**
415         * Maps character position within the source to character position
416         * within the wrapped.
417         *
418         * @param pos the position in {@link SnippetWrapper#source()}
419         * @return the corresponding position in
420         * {@link SnippetWrapper#wrapped() }
421         */
422        int sourceToWrappedPosition(int pos);
423
424        /**
425         * Maps character position within the wrapped to character position
426         * within the source.
427         *
428         * @param pos the position in {@link SnippetWrapper#wrapped()}
429         * @return the corresponding position in
430         * {@link SnippetWrapper#source() }
431         */
432        int wrappedToSourcePosition(int pos);
433    }
434}
435