Skip to content

Commit

Permalink
ROASTER-64: Improve the handling of generics
Browse files Browse the repository at this point in the history
  • Loading branch information
sotty authored and gastaldi committed May 4, 2015
1 parent 0372db1 commit 541472e
Show file tree
Hide file tree
Showing 17 changed files with 415 additions and 108 deletions.
24 changes: 24 additions & 0 deletions api/src/main/java/org/jboss/forge/roaster/model/Type.java
Expand Up @@ -18,10 +18,34 @@ public interface Type<O extends JavaType<O>> extends Origin<O>
{
List<Type<O>> getTypeArguments();

/**
* Returns the type's name after erasing any type parameters.
* Preserves array dimensions
* @return the type's name without type parameters
*/
String getName();

/**
* Returns the type's name, simplifying qualified names based on imports
* Preserves generic parameters, simplifying them recursively
* Preserves array dimensions
* @return the type's simple name
*/
String getSimpleName();

/**
* Returns the type's qualified name, expanding simple names according to imports
* @return the type's qualified name
*/
String getQualifiedName();

/**
* Returns the type's qualified name, preserving type parameters (which are also qualified)
* Preserves array dimensions.
* @return the type's qualified name, including type parameters
*/
String getQualifiedNameWithGenerics();

Type<O> getParentType();

boolean isArray();
Expand Down
Expand Up @@ -10,6 +10,7 @@
import java.util.List;

import org.jboss.forge.roaster.model.JavaType;
import org.jboss.forge.roaster.model.Type;

/**
* Defines the aspect of {@link JavaSource} that handles type imports.
Expand Down Expand Up @@ -101,6 +102,18 @@ public interface Importer<O extends JavaSource<O>>
*/
public <T extends JavaType<?>> Import addImport(T type);

/**
* Ensures the type passed as argument is included in the list of imports for this java source.
* The method will also recursively import parameter types. This method is idempotent: if a type has
* already been imported, no further action will be required.
* The method returns the name that can be used inside the code to reference the type. The name will be simple
* if no ambiguity exists with other types having the same (local) name, or fully qualified otherwise.
* @param type The {@link org.jboss.forge.roaster.model.Type} to be imported.
* @return The name (simple or fully qualified) that should be used to reference the imported type in the code
* @seeAlso org.jboss.forge.roaster.model.Type
*/
public Import addImport(Type<?> type);

/**
* Remove any {@link Import} for the given fully-qualified class name, if it exists; otherwise, do nothing;
*/
Expand Down
Expand Up @@ -10,6 +10,7 @@
import java.util.List;

import org.jboss.forge.roaster.model.JavaType;
import org.jboss.forge.roaster.model.Type;

/**
* Represents a Java type in source form.
Expand All @@ -25,6 +26,9 @@ public interface JavaSource<T extends JavaSource<T>> extends JavaType<T>,
JavaDocCapableSource<T>
{
@Override
/**
* Returns the {@link JavaSource} enclosing this type
*/
public JavaSource<?> getEnclosingType();

/**
Expand Down
250 changes: 184 additions & 66 deletions api/src/main/java/org/jboss/forge/roaster/model/util/Types.java
Expand Up @@ -7,9 +7,14 @@

package org.jboss.forge.roaster.model.util;

import org.jboss.forge.roaster.model.JavaType;
import org.jboss.forge.roaster.model.Type;
import org.jboss.forge.roaster.model.source.JavaSource;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand All @@ -30,11 +35,14 @@ public class Types
// [S=short,
// [Z=boolean
private static final Pattern CLASS_ARRAY_PATTERN = Pattern.compile("\\[+(B|F|C|D|I|J|S|Z|L)([0-9a-zA-Z\\.\\$]*);?");
private static final Pattern SIMPLE_ARRAY_PATTERN = Pattern
.compile("((?:[0-9a-zA-Z\\$]+)(?:\\<[^\\.^\\[]+)?(?:\\.(?:[0-9a-zA-Z\\$]+)(?:\\<[^\\.^\\[]+)?)*)(\\[\\])+");
private static final Pattern GENERIC_PATTERN = Pattern.compile(".*<.*>$");

private static final Pattern SIMPLE_ARRAY_PATTERN = Pattern
.compile("^((.)+)(\\[\\])+$");
private static final Pattern IDENTIFIER_PATTERN = Pattern
.compile( "(\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*\\.)*\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*" );
// private static final Pattern GENERIC_PATTERN = Pattern.compile(".*<.*>(\\[\\])*$");
private static final Pattern WILDCARD_AWARE_TYPE_PATTERN = Pattern
.compile("^\\s*(\\?\\s+(?:extends|super)\\s+)?([A-Za-z$_]\\S*)\\s*$");
.compile("\\?|^\\s*(\\?\\s+(?:extends|super)\\s+)?([A-Za-z$_]\\S*)\\s*$");

private static final List<String> LANG_TYPES = Arrays.asList(
// Interfaces
Expand Down Expand Up @@ -177,7 +185,7 @@ public static String toSimpleName(final String type)
}
String result = type;

if (isGeneric(type))
if (isGeneric(stripArray(type)))
{
result = stripGenerics(result);
}
Expand Down Expand Up @@ -270,14 +278,56 @@ public static boolean isBasicType(String idType)
idType);
}

public static boolean isGeneric(final String type)
public static boolean isGeneric(String type)
{
return (type != null) && GENERIC_PATTERN.matcher(type).matches();
if (type == null)
{
return false;
}
int genericStart = type.indexOf('<');
if (genericStart < 0)
{
return false;
}
type = stripArray(type);
if (!validateName(type.substring(0,genericStart)))
{
return false;
}
String typeArgs = type.substring(genericStart+1,type.lastIndexOf('>'));
StringTokenizer tok = new StringTokenizer(typeArgs,", ");
while (tok.hasMoreTokens())
{
if (!validateNameWithGenerics(tok.nextToken()))
{
return false;
}
}
return true;
}

public static String stripGenerics(final String type)
{
return fixArray(type, true);
private static boolean validateNameWithGenerics(String name) {
return isGeneric(name) || validateName(name) || WILDCARD_AWARE_TYPE_PATTERN.matcher(name).matches();
}

private static boolean validateName(String name) {
return IDENTIFIER_PATTERN.matcher(name).matches();
}

public static String stripGenerics(String type)
{
if (isClassArray(type))
{
type = fixClassArray(type);
}
if (isGeneric(type))
{
return type.substring(0,type.indexOf('<')) + type.substring(type.lastIndexOf('>')+1);
}
else
{
return type;
}
}

public static String fixArray(final String type, boolean stripGenerics)
Expand Down Expand Up @@ -323,74 +373,112 @@ public static String getGenericsTypeParameter(final String type)
{
if (isGeneric(type))
{
return type.replaceFirst("^[^<]*<(.*?)>$", "$1");
return stripArray(type).replaceFirst( "^[^<]*<(.*?)>$", "$1" );
}
return "";
}

// [Bjava.lang.Boolean;
// [Ljava.util.Vector;
public static boolean isArray(final String type)
{
return (type != null)
&& (SIMPLE_ARRAY_PATTERN.matcher(type).matches() || CLASS_ARRAY_PATTERN.matcher(type).matches());
public static boolean isArray(final String type) {
if ( type == null ) {
return false;
}
if ( CLASS_ARRAY_PATTERN.matcher( type ).matches() )
{
return true;
}
Matcher matcher = SIMPLE_ARRAY_PATTERN.matcher(type);
if (matcher.find())
{
String candidateType = matcher.group(1);
return validateNameWithGenerics(candidateType);
} else
{
return false;
}
}

public static String stripArray(final String type)
{
String result = type;
if (isArray(type))
{
Matcher matcher;
matcher = SIMPLE_ARRAY_PATTERN.matcher(type);
if (matcher.find())
{
result = matcher.group(1);
}
else
{
matcher = CLASS_ARRAY_PATTERN.matcher(type);
public static String stripArray(final String type)
{
String result = type;
if (isClassArray(type))
{
result = fixClassArray(type);
}

if (isArray(result))
{
Matcher matcher;
matcher = SIMPLE_ARRAY_PATTERN.matcher(result);
if (matcher.find())
{
switch (matcher.group(1).charAt(0))
{
case 'B':
result = "byte";
break;
case 'F':
result = "float";
break;
case 'C':
result = "char";
break;
case 'D':
result = "double";
break;
case 'I':
result = "int";
break;
case 'J':
result = "long";
break;
case 'S':
result = "short";
break;
case 'Z':
result = "boolean";
break;
case 'L':
result = matcher.group(2);
break;
default:
throw new IllegalArgumentException("Invalid array format " + type);
}
int idx = result.length()-2;
while (idx>1 && result.charAt(idx-2)=='[')
{
idx-=2;
}
result = result.substring(0,idx);
}
}
}
return result;
}
else
{
return result;
}
}
return result;
}

public static boolean isPrimitive(final String result)
private static boolean isClassArray( String type ) {
Matcher matcher = CLASS_ARRAY_PATTERN.matcher(type);
return matcher.find();
}

private static String fixClassArray(String type) {
Matcher matcher = CLASS_ARRAY_PATTERN.matcher(type);
String result = type;
if (matcher.find())
{
int dim = getBasicArrayDimension(type);
switch (matcher.group(1).charAt(0)) {
case 'B':
result = "byte";
break;
case 'F':
result = "float";
break;
case 'C':
result = "char";
break;
case 'D':
result = "double";
break;
case 'I':
result = "int";
break;
case 'J':
result = "long";
break;
case 'S':
result = "short";
break;
case 'Z':
result = "boolean";
break;
case 'L':
result = matcher.group( 2 );
break;
default:
throw new IllegalArgumentException( "Invalid array format " + type );
}
for (int j=0;j<dim;j++)
{
result += "[]";
}
}
return result;
}

public static boolean isPrimitive(final String result)
{
return Arrays.asList("byte", "short", "int", "long", "float", "double", "boolean", "char").contains(result);
}
Expand All @@ -408,6 +496,10 @@ public static int getArrayDimension(String name)
int count = 0;
if (name != null)
{
if (isGeneric(name))
{
name = stripGenerics(name);
}
for (char c : name.toCharArray())
{
if (c == '[')
Expand All @@ -419,4 +511,30 @@ public static int getArrayDimension(String name)
return count;
}

public static int getBasicArrayDimension(String name)
{
int count = 0;
if (name != null)
{
for (char c : name.toCharArray())
{
if (c == '[')
{
count++;
}
}
}
return count;
}

public static <O extends JavaType<O>> String rebuildGenericNameWithArrays(String resolvedTypeName,Type<O> type)
{
StringBuilder resolvedType = new StringBuilder(stripArray(resolvedTypeName));
resolvedType.append(getGenerics(type.toString()));
for (int j=0; j<getArrayDimension(type.getName()); j++)
{
resolvedType.append("[]");
}
return resolvedType.toString();
}
}

1 comment on commit 541472e

@lincolnthree
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Thank you!

Please sign in to comment.