Skip to content

Commit

Permalink
Merge pull request #442 from Ladicek/parse-type
Browse files Browse the repository at this point in the history
Add Type.parse()
  • Loading branch information
Ladicek authored Sep 18, 2024
2 parents 3cdfb83 + 77f0532 commit 1463fc7
Show file tree
Hide file tree
Showing 5 changed files with 377 additions and 6 deletions.
1 change: 1 addition & 0 deletions core/src/main/java/org/jboss/jandex/ClassInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,7 @@ public final List<MethodInfo> constructors() {
* order of record components corresponds to the declaration order.
*
* @return the canonical constructor of this record, or {@code null} if this class is not a record
* @since 3.2.2
*/
public MethodInfo canonicalRecordConstructor() {
if (!isRecord()) {
Expand Down
28 changes: 28 additions & 0 deletions core/src/main/java/org/jboss/jandex/Type.java
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,34 @@ public static Type createWithAnnotations(DotName name, Kind kind, AnnotationInst
return annotations == null ? type : type.copyType(annotations);
}

/**
* Creates a {@link Type} by parsing the given string according to the following grammar:
*
* <pre>
* Type -> VoidType | PrimitiveType | ReferenceType
* VoidType -> 'void'
* PrimitiveType -> 'boolean' | 'byte' | 'short' | 'int'
* | 'long' | 'float' | 'double' | 'char'
* ReferenceType -> PrimitiveType ('[' ']')+
* | ClassType ('<' TypeArgument (',' TypeArgument)* '>')? ('[' ']')*
* ClassType -> FULLY_QUALIFIED_NAME
* TypeArgument -> ReferenceType | WildcardType
* WildcardType -> '?' | '?' ('extends' | 'super') ReferenceType
* </pre>
*
* Notice that the resulting type never contains type variables, only "proper" types.
* Also notice that the grammar above does not support all kinds of nested types;
* it should be possible to add that later, if there's an actual need.
*
* @param type the string to parse; must not be {@code null}
* @return the parsed type
* @throws IllegalArgumentException if the string does not conform to the grammar given above
* @since 3.2.3
*/
public static Type parse(String type) {
return new TypeParser(type).parse();
}

/**
* Returns the name of this type (or its erasure in case of generic types) as a {@link DotName},
* using the {@link Class#getName()} format. Specifically:
Expand Down
169 changes: 169 additions & 0 deletions core/src/main/java/org/jboss/jandex/TypeParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package org.jboss.jandex;

import java.util.Objects;

// see Type.parse() for the grammar
class TypeParser {
private final String str;

private int pos = 0;

TypeParser(String str) {
this.str = Objects.requireNonNull(str);
}

Type parse() {
Type result;

String token = nextToken();
if (token.isEmpty()) {
throw unexpected(token);
} else if (token.equals("void")) {
result = VoidType.VOID;
} else if (isPrimitiveType(token) && peekToken().isEmpty()) {
result = PrimitiveType.decode(token);
} else {
result = parseReferenceType(token);
}

expect("");
return result;
}

private Type parseReferenceType(String token) {
if (isPrimitiveType(token)) {
PrimitiveType primitive = PrimitiveType.decode(token);
return parseArrayType(primitive);
} else if (isClassType(token)) {
Type result = ClassType.create(token);
if (peekToken().equals("<")) {
expect("<");
ParameterizedType.Builder builder = ParameterizedType.builder(result.name());
builder.addArgument(parseTypeArgument());
while (peekToken().equals(",")) {
expect(",");
builder.addArgument(parseTypeArgument());
}
expect(">");
result = builder.build();
}
if (peekToken().equals("[")) {
return parseArrayType(result);
}
return result;
} else {
throw unexpected(token);
}
}

private Type parseArrayType(Type elementType) {
expect("[");
expect("]");
int dimensions = 1;
while (peekToken().equals("[")) {
expect("[");
expect("]");
dimensions++;
}
return ArrayType.create(elementType, dimensions);
}

private Type parseTypeArgument() {
String token = nextToken();
if (token.equals("?")) {
if (peekToken().equals("extends")) {
expect("extends");
Type bound = parseReferenceType(nextToken());
return WildcardType.createUpperBound(bound);
} else if (peekToken().equals("super")) {
expect("super");
Type bound = parseReferenceType(nextToken());
return WildcardType.createLowerBound(bound);
} else {
return WildcardType.UNBOUNDED;
}
} else {
return parseReferenceType(token);
}
}

private boolean isPrimitiveType(String token) {
return token.equals("boolean")
|| token.equals("byte")
|| token.equals("short")
|| token.equals("int")
|| token.equals("long")
|| token.equals("float")
|| token.equals("double")
|| token.equals("char");
}

private boolean isClassType(String token) {
return !token.isEmpty() && Character.isJavaIdentifierStart(token.charAt(0));
}

// ---

private void expect(String expected) {
String token = nextToken();
if (!expected.equals(token)) {
throw unexpected(token);
}
}

private IllegalArgumentException unexpected(String token) {
if (token.isEmpty()) {
throw new IllegalArgumentException("Unexpected end of input: " + str);
}
return new IllegalArgumentException("Unexpected token '" + token + "' at position " + (pos - token.length())
+ ": " + str);
}

private String peekToken() {
// skip whitespace
while (pos < str.length() && Character.isWhitespace(str.charAt(pos))) {
pos++;
}

// end of input
if (pos == str.length()) {
return "";
}

int pos = this.pos;

// current char is a token on its own
if (isSpecial(str.charAt(pos))) {
return str.substring(pos, pos + 1);
}

// token is a keyword or fully qualified name
int begin = pos;
while (pos < str.length() && Character.isJavaIdentifierStart(str.charAt(pos))) {
do {
pos++;
} while (pos < str.length() && Character.isJavaIdentifierPart(str.charAt(pos)));

if (pos < str.length() && str.charAt(pos) == '.') {
pos++;
} else {
return str.substring(begin, pos);
}
}

if (pos == str.length()) {
throw new IllegalArgumentException("Unexpected end of input: " + str);
}
throw new IllegalArgumentException("Unexpected character '" + str.charAt(pos) + "' at position " + pos + ": " + str);
}

private String nextToken() {
String result = peekToken();
pos += result.length();
return result;
}

private boolean isSpecial(char c) {
return c == ',' || c == '?' || c == '<' || c == '>' || c == '[' || c == ']';
}
}
12 changes: 6 additions & 6 deletions core/src/main/java/org/jboss/jandex/WildcardType.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public class WildcardType extends Type {
* Creates a new wildcard type.
*
* @param bound the bound (lower or upper)
* @param isExtends true if the bound is an upper (extends) bound, false if lower (super)
* @param isExtends true if the bound is an upper ({@code extends}) bound, false if lower ({@code super})
* @return the new instance
*
* @since 2.1
Expand All @@ -50,7 +50,7 @@ public static WildcardType create(Type bound, boolean isExtends) {
}

/**
* Create a new wildcard type with an upper bound.
* Create a new wildcard type with an upper ({@code extends}) bound.
*
* @param upperBound the upper bound
* @return the new instance
Expand All @@ -61,7 +61,7 @@ public static WildcardType createUpperBound(Type upperBound) {
}

/**
* Create a new wildcard type with an upper bound.
* Create a new wildcard type with an upper ({@code extends}) bound.
*
* @param upperBound the upper bound
* @return the new instance
Expand All @@ -72,7 +72,7 @@ public static WildcardType createUpperBound(Class<?> upperBound) {
}

/**
* Create a new wildcard type with a lower bound.
* Create a new wildcard type with a lower ({@code super}) bound.
*
* @param lowerBound the lower bound
* @return the new instance
Expand All @@ -83,7 +83,7 @@ public static WildcardType createLowerBound(Type lowerBound) {
}

/**
* Create a new wildcard type with a lower bound.
* Create a new wildcard type with a lower ({@code super}) bound.
*
* @param lowerBound the lower bound
* @return the new instance
Expand Down Expand Up @@ -146,7 +146,7 @@ public Type extendsBound() {
* Returns {@code null} if this wildcard declares an upper bound
* ({@code ? extends SomeType}).
*
* @return the lower bound, or {@code null} if this wildcard has an uper bound
* @return the lower bound, or {@code null} if this wildcard has an upper bound
*/
public Type superBound() {
return isExtends ? null : bound;
Expand Down
Loading

0 comments on commit 1463fc7

Please sign in to comment.