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