package bluej.parser;
import java.util.*;
import threadchecker.OnThread;
import threadchecker.Tag;
import bluej.debugger.gentype.FieldReflective;
import bluej.debugger.gentype.GenTypeArrayClass;
import bluej.debugger.gentype.GenTypeClass;
import bluej.debugger.gentype.GenTypeParameter;
import bluej.debugger.gentype.JavaPrimitiveType;
import bluej.debugger.gentype.JavaType;
import bluej.debugger.gentype.MethodReflective;
import bluej.debugger.gentype.Reflective;
import bluej.parser.entity.EntityResolver;
import bluej.parser.entity.ImportedEntity;
import bluej.parser.entity.JavaEntity;
import bluej.parser.entity.ParsedArrayReflective;
import bluej.parser.entity.SolidTargEntity;
import bluej.parser.entity.TypeArgumentEntity;
import bluej.parser.entity.TypeEntity;
import bluej.parser.entity.UnboundedWildcardEntity;
import bluej.parser.entity.UnresolvedArray;
import bluej.parser.entity.UnresolvedEntity;
import bluej.parser.entity.WildcardExtendsEntity;
import bluej.parser.entity.WildcardSuperEntity;
import bluej.parser.lexer.JavaTokenTypes;
import bluej.parser.lexer.LocatableToken;
import bluej.pkgmgr.JavadocResolver;
import bluej.utility.JavaReflective;
import bluej.utility.JavaUtils;
| Utilities for parsers.
|
| @author Davin McCall
|
public class ParseUtils
{
private static class DepthRef
{
int depth = 0;
}
| Interface for receiving assist content
|
public interface AssistContentConsumer
{
public void consume(AssistContent ac, boolean overridden);
}
| Get the possible code completions, based on the provided suggestions context.
| If there are can be no valid completions in the given context, returns null.
|
@OnThread(Tag.FXPlatform)
public static AssistContent[] getPossibleCompletions(ExpressionTypeInfo suggests,
JavadocResolver javadocResolver, AssistContentConsumer consumer)
{
GenTypeClass exprType = initGetPossibleCompletions(suggests);
if (exprType != null){
List<AssistContent> completions = getCompletionsForTarget(exprType, suggests, javadocResolver, consumer);
return completions.toArray(new AssistContent[completions.size()]);
}
return null;
}
| Determine the target type for which members can be suggested (for code completion).
| This utility method wraps primitives arrays as a suitable class type.
|
| @param suggests The code completion data
| @param javadocResolver A javadoc resolver (not used)
| @return A suitable GenTypeClass representing the target type for completion
| purposes, or null if there is no such suitable type.
|
public static GenTypeClass initGetPossibleCompletions(ExpressionTypeInfo suggests)
{
if (suggests != null) {
GenTypeClass exprType = suggests.getSuggestionType().asClass();
if (exprType == null) {
final JavaType arrayComponent = suggests.getSuggestionType().getArrayComponent();
if (arrayComponent != null && arrayComponent.isPrimitive()) {
exprType = new GenTypeArrayClass(new ParsedArrayReflective(new JavaReflective(Object.class),"Object")
{
@Override
@OnThread(Tag.Any)
public String getSimpleName()
{
return arrayComponent.toString() + "[]";
}
}, arrayComponent);
}
else {
return null;
}
}
return exprType;
}
return null;
}
| Gets the available completions for a given target: methods and fields (not constructors)
| @param exprType The target type from which to get completions. This class and all super-types are scanned.
| @param suggests Information about the code suggestions
| @param javadocResolver Resolver for fetching Javadoc
| @param consumer The consumer to be called with each AssistContent, if non-null (may be null)
| @return The list of found completions.
|
@OnThread(Tag.FXPlatform)
private static List getCompletionsForTarget(GenTypeClass exprType, ExpressionTypeInfo suggests,
JavadocResolver javadocResolver, AssistContentConsumer consumer)
{
GenTypeClass accessType = suggests.getAccessType();
Reflective accessReflective = (accessType != null) ? accessType.getReflective() : null;
Set<String> contentSigs = new HashSet<String>();
Set<String> typesDone = new HashSet<String>();
List<AssistContent> completions = new ArrayList<AssistContent>();
LinkedList<GenTypeClass> typeQueue = new LinkedList<GenTypeClass>();
typeQueue.add(exprType);
GenTypeClass origExprType = exprType;
while (!typeQueue.isEmpty()){
exprType = typeQueue.removeFirst();
if (!typesDone.add(exprType.getReflective().getName())) {
continue;
}
Map<String, Set<MethodReflective>> methods = exprType.getReflective().getDeclaredMethods();
Map<String, GenTypeParameter> typeArgs = exprType.getMap();
for (String name : methods.keySet()) {
Set<MethodReflective> mset = new HashSet<>(methods.get(name));
mset.removeIf(method -> accessReflective != null
&& !JavaUtils.checkMemberAccess(method.getDeclaringType(),
origExprType,
suggests.getAccessType().getReflective(),
method.getModifiers(), suggests.isStatic()));
completions.addAll(discoverElements(exprType, javadocResolver, contentSigs,
typeArgs, mset, consumer));
}
Map<String, FieldReflective> fields = exprType.getReflective().getDeclaredFields();
for (String name : fields.keySet()) {
FieldReflective field = fields.get(name);
if (accessReflective != null &&
! JavaUtils.checkMemberAccess(field.getDeclaringType(),
origExprType,
suggests.getAccessType().getReflective(),
field.getModifiers(), suggests.isStatic())) {
continue;
}
Map<String,GenTypeParameter> declMap =
exprType.mapToSuper(field.getDeclaringType().getName()).getMap();
GenTypeParameter fieldType = field.getType().mapTparsToTypes(declMap).getUpperBound();
FieldCompletion completion = new FieldCompletion(fieldType.toString(true), field.getName(),
field.getModifiers(), field.getDeclaringType().getName());
completions.add(completion);
if (consumer != null) {
consumer.consume(completion, false);
}
}
for (GenTypeClass stype : exprType.getReflective().getSuperTypes()) {
if (typeArgs != null) {
typeQueue.add(stype.mapTparsToTypes(typeArgs));
}
else {
typeQueue.add(stype.getErasedType());
}
}
Collections.sort(completions, (o1, o2) -> o1.getName().compareTo(o2.getName()) );
Reflective outer = exprType.getReflective().getOuterClass();
if (outer != null) {
typeQueue.add(new GenTypeClass(outer));
}
}
return completions;
}
| Check whether the given methods should be added to the set of possible code completions (i.e. if they
| have a unique signature), and do so if necessary. Returns a collection of AssistContent objects
| representing any methods that were added (methods which were not added because they were already
| present are not returned).
|
| @param gclass The declaring class for the methods.
| @param javadocResolver The Javadoc resolver used to look up Javadoc for the method.
| @param contentSigs The set of existing method signatures. The newly-found method will be
| added if and only if it is not already in the set.
| @param typeArgs The relevant type arguments (used for generic methods)
| @param methods The methods to be scanned. Must all come from the same declaring class.
| @param consumer If non-null, it will be passed the completion (regardless of whether the completion
| was already in the set, but the overridden flag will be passed accordingly to the
| consumer)
| @return If the method was added to the set (and was not already there), it is returned.
| If the method was already in the set, null will be returned.
|
@OnThread(Tag.FXPlatform)
private static Collection discoverElements(GenTypeClass gclass,
JavadocResolver javadocResolver, Set<String> contentSigs,
Map<String, GenTypeParameter> typeArgs,
Collection<MethodReflective> methods, AssistContentConsumer consumer)
{
boolean resolveJavadoc = false;
Set<MethodCompletion> completions = new HashSet<>();
for (MethodReflective method : methods)
{
completions.add(new MethodCompletion(method, typeArgs, javadocResolver));
resolveJavadoc |= (method.getJavaDoc() == null);
}
if (resolveJavadoc)
{
javadocResolver.getJavadoc(gclass.getReflective(), methods);
}
List<AssistContent> allNewMethods = new ArrayList<>();
for (MethodCompletion completion : completions) {
String sig = completion.getSignature();
if (contentSigs.add(sig)) {
if (consumer != null) {
consumer.consume(completion, false
| not overridden
|
;
}
allNewMethods.add(completion);
}
else
{
if (consumer != null) {
consumer.consume(completion, true
| overridden
|
;
}
}
}
return allNewMethods;
}
| Get an entity for an imported type specifier. This is different from a non-imported type
| because in that it must be qualified.
|
| @param resolver Entity resolver which will (eventually) resolve the entity
| @param tokens The tokens making up the specification
|
@OnThread(Tag.FXPlatform)
public static JavaEntity getImportEntity(EntityResolver resolver,
Reflective querySource, List<LocatableToken> tokens)
{
if (tokens.isEmpty()) {
return null;
}
Iterator<LocatableToken> i = tokens.iterator();
LocatableToken tok = i.next();
if (tok.getType() != JavaTokenTypes.IDENT) {
return null;
}
List<String> names = new LinkedList<String>();
names.add(tok.getText());
while (i.hasNext()){
tok = i.next();
if (tok.getType() != JavaTokenTypes.DOT || ! i.hasNext()) {
return null;
}
tok = i.next();
if (tok.getType() != JavaTokenTypes.IDENT) {
return null;
}
names.add(tok.getText());
}
return new ImportedEntity(resolver, names, querySource);
}
| Get an entity for a type specification (specified by a list of tokens). The
| returned entity might be unresolved.
|
| @param resolver Entity resolver which will (eventually) resolve the entity
| @param querySource The source of the query - a fully qualified class name
| @param tokens The tokens specifying the type
|
@OnThread(Tag.FXPlatform)
public static JavaEntity getTypeEntity(EntityResolver resolver,
Reflective querySource, List<LocatableToken> tokens)
{
DepthRef dr = new DepthRef();
return getTypeEntity(resolver, querySource, tokens.listIterator(), dr);
}
| Get an entity for a type specification. The returned entity may be unresolved.
| Returns null if the type specification appears to be invalid.
|
@OnThread(Tag.FXPlatform)
private static JavaEntity getTypeEntity(EntityResolver resolver, Reflective querySource,
ListIterator<LocatableToken> i, DepthRef depthRef)
{
LocatableToken token = i.next();
if (JavaParser.isPrimitiveType(token)) {
JavaType type = null;
switch (token.getType()) {
case JavaTokenTypes.LITERAL_int:
type = JavaPrimitiveType.getInt();
break;
case JavaTokenTypes.LITERAL_short:
type = JavaPrimitiveType.getShort();
break;
case JavaTokenTypes.LITERAL_long:
type = JavaPrimitiveType.getLong();
break;
case JavaTokenTypes.LITERAL_char:
type = JavaPrimitiveType.getChar();
break;
case JavaTokenTypes.LITERAL_byte:
type = JavaPrimitiveType.getByte();
break;
case JavaTokenTypes.LITERAL_boolean:
type = JavaPrimitiveType.getBoolean();
break;
case JavaTokenTypes.LITERAL_double:
type = JavaPrimitiveType.getDouble();
break;
case JavaTokenTypes.LITERAL_float:
type = JavaPrimitiveType.getFloat();
break;
case JavaTokenTypes.LITERAL_void:
type = JavaPrimitiveType.getVoid();
}
while (i.hasNext()){
token = i.next();
if (token.getType() == JavaTokenTypes.LBRACK) {
type = type.getArray();
i.next();
}
else {
return null;
}
}
return new TypeEntity(type);
}
String text = token.getText();
JavaEntity poc = UnresolvedEntity.getEntity(resolver, text, querySource);
while (poc != null && i.hasNext()){
token = i.next();
if (token.getType() == JavaTokenTypes.LT) {
poc = processTypeArgs(resolver, querySource, poc, i, depthRef);
if (poc == null) {
return null;
}
if (! i.hasNext()) {
return poc;
}
token = i.next();
}
if (token.getType() != JavaTokenTypes.DOT) {
while (token.getType() == JavaTokenTypes.LBRACK){
poc = new UnresolvedArray(poc);
if (i.hasNext()) {
token = i.next();
}
if (! i.hasNext()) {
return poc;
}
token = i.next();
}
i.previous();
return poc;
}
token = i.next();
if (token.getType() != JavaTokenTypes.IDENT) {
break;
}
poc = poc.getSubentity(token.getText(), querySource);
}
return poc;
}
| Process tokens as type arguments
| @param base The base type, i.e. the type to which the arguments are applied
| @param i A ListIterator to iterate through the tokens
| @param depthRef The current argument depth; will be adjusted on return
| @return A JavaEntity representing the type with type arguments applied (or null)
|
@OnThread(Tag.FXPlatform)
private static JavaEntity processTypeArgs(EntityResolver resolver, Reflective querySource,
JavaEntity base, ListIterator<LocatableToken> i, DepthRef depthRef)
{
int startDepth = depthRef.depth;
List<TypeArgumentEntity> taList = new LinkedList<TypeArgumentEntity>();
depthRef.depth++;
mainLoop:
while (i.hasNext() && depthRef.depth > startDepth){
LocatableToken token = i.next();
if (token.getType() == JavaTokenTypes.QUESTION) {
if (! i.hasNext()) {
return null;
}
token = i.next();
if (token.getType() == JavaTokenTypes.LITERAL_super) {
JavaEntity taEnt = getTypeEntity(resolver, querySource, i, depthRef);
if (taEnt == null) {
return null;
}
taList.add(new WildcardSuperEntity(taEnt));
}
else if (token.getType() == JavaTokenTypes.LITERAL_extends) {
JavaEntity taEnt = getTypeEntity(resolver, querySource, i, depthRef);
if (taEnt == null) {
return null;
}
taList.add(new WildcardExtendsEntity(taEnt));
}
else {
taList.add(new UnboundedWildcardEntity(resolver));
i.previous();
}
}
else {
i.previous();
JavaEntity taEnt = getTypeEntity(resolver, querySource, i, depthRef);
if (taEnt == null) {
return null;
}
taList.add(new SolidTargEntity(taEnt));
}
if (depthRef.depth <= startDepth) {
break;
}
if (! i.hasNext()) {
return null;
}
token = i.next();
int ttype = token.getType();
while (ttype == JavaTokenTypes.GT || ttype == JavaTokenTypes.SR || ttype == JavaTokenTypes.BSR){
switch (ttype) {
case JavaTokenTypes.BSR:
depthRef.depth--;
case JavaTokenTypes.SR:
depthRef.depth--;
default:
depthRef.depth--;
}
if (! i.hasNext()) {
break mainLoop;
}
token = i.next();
ttype = token.getType();
}
if (ttype != JavaTokenTypes.COMMA) {
i.previous();
break;
}
}
return base.setTypeArgs(taList);
}
}
top,
use,
map,
class ParseUtils
top,
use,
map,
class ParseUtils . DepthRef
top,
use,
map,
interface ParseUtils . DepthRef . AssistContentConsumer
. consume
. getPossibleCompletions
. initGetPossibleCompletions
. getSimpleName
. getCompletionsForTarget
. discoverElements
. getImportEntity
. getTypeEntity
. getTypeEntity
. processTypeArgs
576 neLoCode
+ 50 LoComm