diff --git a/pom.xml b/pom.xml index 8624b5e..2b14429 100644 --- a/pom.xml +++ b/pom.xml @@ -44,13 +44,14 @@ 1.60 2.11.0 3.12.0 + 1.9 31.1-jre 5.1.0 4.4.15 2.13.3 2.1.6 1.18 - 11 + 12 1.13.0 1.2.11 3.8.1 diff --git a/proteus-core/pom.xml b/proteus-core/pom.xml index 0ffaf51..9884744 100644 --- a/proteus-core/pom.xml +++ b/proteus-core/pom.xml @@ -9,6 +9,8 @@ 4.0.0 proteus-core Proteus Core + + jar @@ -153,6 +155,13 @@ Proteus Changelog. + + org.apache.commons + commons-text + ${commons-text.version} + + + org.slf4j slf4j-api diff --git a/proteus-core/src/main/java/io/sinistral/proteus/server/handlers/HandlerGenerator.java b/proteus-core/src/main/java/io/sinistral/proteus/server/handlers/HandlerGenerator.java index 8675edc..37a6b3b 100644 --- a/proteus-core/src/main/java/io/sinistral/proteus/server/handlers/HandlerGenerator.java +++ b/proteus-core/src/main/java/io/sinistral/proteus/server/handlers/HandlerGenerator.java @@ -5,7 +5,6 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.google.common.reflect.Invokable; -import com.google.common.reflect.MutableTypeToInstanceMap; import com.google.common.reflect.TypeToken; import com.google.inject.Inject; import com.google.inject.name.Named; @@ -25,6 +24,7 @@ import io.sinistral.proteus.server.ServerRequest; import io.sinistral.proteus.server.ServerResponse; import io.sinistral.proteus.server.endpoints.EndpointInfo; +import io.sinistral.proteus.utilities.ClassUtilities; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; @@ -289,7 +289,7 @@ protected void addClassMethodHandlers(TypeSpec.Builder typeBuilder, Class cla TypeHandler handler = TypeHandler.forType(t); return (handler.equals(TypeHandler.ModelType) || handler.equals(TypeHandler.OptionalModelType) || handler.equals(TypeHandler.NamedModelType) || handler.equals(TypeHandler.OptionalNamedModelType)); - }).collect(Collectors.toMap(java.util.function.Function.identity(), HandlerGenerator::typeReferenceNameForParameterizedType)); + }).collect(Collectors.toMap(java.util.function.Function.identity(), ClassUtilities::typeReferenceNameForParameterizedType)); java.util.regex.Pattern internalTypesPattern = java.util.regex.Pattern.compile("concurrent|<"); @@ -299,7 +299,7 @@ protected void addClassMethodHandlers(TypeSpec.Builder typeBuilder, Class cla .flatMap( m -> Invokable.from(m).getParameters().stream()) .filter(p -> internalTypesPattern.matcher(p.getType().getType().getTypeName()).find() ) - .distinct().collect(Collectors.toMap(p -> p.getType().toString(), com.google.common.reflect.Parameter::getType)); + .distinct().collect(Collectors.toMap(p -> p.getType().toString(), com.google.common.reflect.Parameter::getType, (p1, p2) -> p1 )); @@ -346,7 +346,7 @@ protected void addClassMethodHandlers(TypeSpec.Builder typeBuilder, Class cla || handler.equals(TypeHandler.OptionalBeanListValueOfType) || handler.equals(TypeHandler.OptionalBeanListFromStringType)) { - parameterizedLiteralsNameMap.put(p.getParameterizedType(), HandlerGenerator.typeReferenceNameForParameterizedType(p.getParameterizedType())); + parameterizedLiteralsNameMap.put(p.getParameterizedType(), ClassUtilities.typeReferenceNameForParameterizedType(p.getParameterizedType())); } } @@ -1334,120 +1334,6 @@ else if (matches > 2) return null; } - public static String typeReferenceNameForParameterizedType(Type type) - { - - log.info("creating name for reference: {}", type); - String typeName = type.getTypeName(); - - if (typeName.contains("Optional")) - { - log.warn("Type is for an optional named {}", typeName); - } - - Matcher matcher = TYPE_NAME_PATTERN.matcher(typeName); - - if (matcher.find()) - { - - int matches = matcher.groupCount(); - - if (matches == 2) - { - String genericInterface = matcher.group(1); - String erasedType = matcher.group(2).replaceAll("\\$", "."); - - String[] genericParts = genericInterface.split("\\."); - String[] erasedParts = erasedType.split("\\."); - - String genericTypeName = genericParts[genericParts.length - 1]; - String erasedTypeName; - - if (erasedParts.length > 1) - { - erasedTypeName = erasedParts[erasedParts.length - 2] + erasedParts[erasedParts.length - 1]; - } - else - { - erasedTypeName = erasedParts[0]; - } - - typeName = String.format("%s%s%s", Character.toLowerCase(erasedTypeName.charAt(0)), erasedTypeName.substring(1), genericTypeName); - - return typeName; - } - - } - - matcher = CONCURRENT_TYPE_NAME_PATTERN.matcher(typeName); - - if (matcher.find()) - { - - int matches = matcher.groupCount(); - - if (matches == 2) - { - String genericInterface = matcher.group(1); - String erasedType = matcher.group(2).replaceAll("\\$", "."); - - String[] genericParts = genericInterface.split("\\."); - String[] erasedParts = erasedType.split("\\."); - - String genericTypeName = genericParts[genericParts.length - 1]; - String erasedTypeName; - - if (erasedParts.length > 1) - { - erasedTypeName = erasedParts[erasedParts.length - 2] + erasedParts[erasedParts.length - 1]; - } - else - { - erasedTypeName = erasedParts[0]; - } - - typeName = String.format("%s%s%s", Character.toLowerCase(erasedTypeName.charAt(0)), erasedTypeName.substring(1), genericTypeName); - return typeName; - } - - } - - if (type.getTypeName().startsWith("sun")) - { - return typeName; - } - - if (type instanceof ParameterizedType) - { - ParameterizedType pType = (ParameterizedType) type; - log.debug("pType: {}", pType); - - Type actualTypeArgument0 = pType.getActualTypeArguments()[0]; - - if (actualTypeArgument0 instanceof Class) - { - Class genericType = (Class) pType.getActualTypeArguments()[0]; - Class rawType = (Class) pType.getRawType(); - Class erasedType = (Class) HandlerGenerator.extractErasedType(genericType); - - if (!(pType.getRawType() instanceof ParameterizedType)) - { - log.info("not a raw type that is parameterized {} {}", rawType, genericType); - return Character.toLowerCase(rawType.getSimpleName().charAt(0)) + rawType.getSimpleName().substring(1) + genericType.getSimpleName(); - } - } - else - { - log.error( - "failed to process {} ptype: {}", type, pType - ); - } - - } - - return typeName; - } - protected static String typeReferenceNameForType(Type type) { diff --git a/proteus-core/src/main/java/io/sinistral/proteus/utilities/ClassUtilities.java b/proteus-core/src/main/java/io/sinistral/proteus/utilities/ClassUtilities.java new file mode 100644 index 0000000..4f369c0 --- /dev/null +++ b/proteus-core/src/main/java/io/sinistral/proteus/utilities/ClassUtilities.java @@ -0,0 +1,273 @@ +package io.sinistral.proteus.utilities; + +import com.google.common.base.Joiner; +import com.google.common.reflect.Invokable; +import com.google.common.reflect.TypeParameter; +import com.google.common.reflect.TypeToken; +import io.sinistral.proteus.server.handlers.HandlerGenerator; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.text.WordUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.*; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ClassUtilities { + + static Map, List>> typeTokenMap = new LinkedHashMap<>(); + + private static final Logger logger = LoggerFactory.getLogger(ClassUtilities.class.getName()); + + + private static final Pattern TYPE_NAME_PATTERN = Pattern.compile("(java\\.util\\.[A-Za-z]+)<([^>]+)+", Pattern.DOTALL | Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + + private static final Pattern CONCURRENT_TYPE_NAME_PATTERN = Pattern.compile("(java\\.util\\.concurrent\\.[A-Za-z]+)<([^>]+)", Pattern.DOTALL | Pattern.UNIX_LINES); + + private static final Pattern CLASS_NAME_PATTERN = Pattern.compile("([^<>,\\s]+)+", Pattern.DOTALL | Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + + + public static String generateVariableName(TypeToken typeToken) throws Exception { + + Collection> typeTokenList = getGenericParameterTypeTokens(typeToken); + + visitTypes(typeTokenMap, typeToken); + + Deque> tokenStack = new ArrayDeque<>(); + + Map, String> typeTokenNameMap = new LinkedHashMap<>(); + + tokenStack.push(typeToken); + + for (TypeToken tt : typeTokenList) { + tokenStack.push(tt); + } + + while (!tokenStack.isEmpty()) { + TypeToken next = tokenStack.pop(); + + List> subTypes = getGenericParameterTypeTokens(next); + + if (!subTypes.isEmpty()) { + typeTokenMap.put(next, subTypes); + } else { + typeTokenMap.put(next, Collections.emptyList()); + } + + for (TypeToken subType : subTypes) { + getGenericParameterTypeTokens(subType); + } + } + + for (var entry : typeTokenMap.entrySet()) { + + var k = entry.getKey(); + var v = entry.getValue(); + + for (TypeToken subType : v) { + + List> parameters = typeTokenMap.getOrDefault(subType, new ArrayList<>()); + + if (!parameters.isEmpty()) { + + String subName = ClassUtilities.generateName(typeTokenNameMap, subType, parameters); + + typeTokenNameMap.put(subType, subName); + } else { + + String subName = ClassUtilities.generateName(typeTokenNameMap, subType, Collections.emptyList()); + + typeTokenNameMap.put(subType, subName); + + } + + } + + if (v.isEmpty()) { + String subName = ClassUtilities.generateName(typeTokenNameMap, k, Collections.emptyList()); + + typeTokenNameMap.put(k, subName); + + } else { + + String existing = ClassUtilities.generateName(typeTokenNameMap, k, v); + + typeTokenNameMap.put(k, existing); + } + } + + return typeTokenNameMap.get(typeToken); + + } + + static String typeToString(Type type) { + return (type instanceof Class) ? ((Class) type).getName() : type.toString(); + } + + private static List> getGenericParameterTypeTokens(TypeToken t) { + + Class rawType = t.getRawType(); + + TypeVariable[] pT = rawType.getTypeParameters(); + + List> pTT = new ArrayList<>(); + + for (TypeVariable typeVariable : pT) { + TypeToken token = t.resolveType(typeVariable); + pTT.add(token); + } + + return pTT; + } + + + public static String typeReferenceNameForParameterizedType(Type type) { + + logger.info("creating name for reference: {}", type); + String typeName = type.getTypeName(); + + if (typeName.contains("Optional")) { + logger.warn("Type is for an optional named {}", typeName); + } + + Matcher matcher = TYPE_NAME_PATTERN.matcher(typeName); + + if (matcher.find()) { + + int matches = matcher.groupCount(); + + if (matches == 2) { + String genericInterface = matcher.group(1); + String erasedType = matcher.group(2).replaceAll("\\$", "."); + + String[] genericParts = genericInterface.split("\\."); + String[] erasedParts = erasedType.split("\\."); + + String genericTypeName = genericParts[genericParts.length - 1]; + String erasedTypeName; + + if (erasedParts.length > 1) { + erasedTypeName = erasedParts[erasedParts.length - 2] + erasedParts[erasedParts.length - 1]; + } else { + erasedTypeName = erasedParts[0]; + } + + typeName = String.format("%s%s%s", Character.toLowerCase(erasedTypeName.charAt(0)), erasedTypeName.substring(1), genericTypeName); + + return typeName; + } + + } + + matcher = CONCURRENT_TYPE_NAME_PATTERN.matcher(typeName); + + if (matcher.find()) { + + int matches = matcher.groupCount(); + + if (matches == 2) { + String genericInterface = matcher.group(1); + String erasedType = matcher.group(2).replaceAll("\\$", "."); + + String[] genericParts = genericInterface.split("\\."); + String[] erasedParts = erasedType.split("\\."); + + String genericTypeName = genericParts[genericParts.length - 1]; + String erasedTypeName; + + if (erasedParts.length > 1) { + erasedTypeName = erasedParts[erasedParts.length - 2] + erasedParts[erasedParts.length - 1]; + } else { + erasedTypeName = erasedParts[0]; + } + + typeName = String.format("%s%s%s", Character.toLowerCase(erasedTypeName.charAt(0)), erasedTypeName.substring(1), genericTypeName); + return typeName; + } + + } + + if (type.getTypeName().startsWith("sun")) { + return typeName; + } + + if (type instanceof ParameterizedType) { + ParameterizedType pType = (ParameterizedType) type; + logger.info("pType: {}", pType); + + Type actualTypeArgument0 = pType.getActualTypeArguments()[0]; + + if (actualTypeArgument0 instanceof Class) { + Class genericType = (Class) pType.getActualTypeArguments()[0]; + Class rawType = (Class) pType.getRawType(); + Class erasedType = (Class) HandlerGenerator.extractErasedType(genericType); + + if (!(pType.getRawType() instanceof ParameterizedType)) { + logger.info("not a raw type that is parameterized {} {}", rawType, genericType); + return Character.toLowerCase(rawType.getSimpleName().charAt(0)) + rawType.getSimpleName().substring(1) + genericType.getSimpleName(); + } + } else { + logger.error( + "failed to process {} ptype: {}", type, pType + ); + } + + } + + return typeName; + } + + private static String generateName(Map, String> nameMap, TypeToken k, List> v) { + Class rawType = k.getRawType(); + + String parentName = rawType.getCanonicalName().replaceAll("[$]+", "."); + + List parts = new ArrayList<>(Arrays.asList(parentName.split("[.]+"))); + + List partNames = new ArrayList<>(); + + if (!v.isEmpty()) { + + for (TypeToken sub : v) { + String name = nameMap.get(sub); + + if (name != null) { + partNames.add(name); + } + } + + } + + for (String p : parts) { + + partNames.add(StringUtils.capitalize(p)); + } + + parentName = String.join("", partNames); + + return StringUtils.uncapitalize(parentName); + + } + + private static void visitTypes(Map, List>> map, TypeToken token) { + List> subTypes = getGenericParameterTypeTokens(token); + + for (TypeToken subType : subTypes) { + visitTypes(map, subType); + } + + if (!subTypes.isEmpty()) { + map.put(token, subTypes); + } else { + map.put(token, Collections.emptyList()); + } + + + } + +} + + + diff --git a/proteus-core/src/test/java/io/sinistral/proteus/test/controllers/GenericBeanTest.java b/proteus-core/src/test/java/io/sinistral/proteus/test/controllers/GenericBeanTest.java index 5f5157b..5c5eac3 100644 --- a/proteus-core/src/test/java/io/sinistral/proteus/test/controllers/GenericBeanTest.java +++ b/proteus-core/src/test/java/io/sinistral/proteus/test/controllers/GenericBeanTest.java @@ -6,6 +6,7 @@ import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeVariableName; import io.sinistral.proteus.server.handlers.HandlerGenerator; +import io.sinistral.proteus.utilities.ClassUtilities; import org.apache.commons.lang3.reflect.TypeUtils; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -179,7 +180,7 @@ void genericTypeHandler() throws Exception String s = ""; if (type instanceof ParameterizedType) { - s = HandlerGenerator.typeReferenceNameForParameterizedType((ParameterizedType) type); + s = ClassUtilities.typeReferenceNameForParameterizedType((ParameterizedType) type); diff --git a/proteus-core/src/test/java/io/sinistral/proteus/utilities/ClassUtilitiesTest.java b/proteus-core/src/test/java/io/sinistral/proteus/utilities/ClassUtilitiesTest.java new file mode 100644 index 0000000..a0cdd1b --- /dev/null +++ b/proteus-core/src/test/java/io/sinistral/proteus/utilities/ClassUtilitiesTest.java @@ -0,0 +1,35 @@ +package io.sinistral.proteus.utilities; + +import com.google.common.reflect.Invokable; +import com.google.common.reflect.TypeToken; +import org.junit.jupiter.api.Test; + +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ClassUtilitiesTest { + + + + @Test + void parseLonger() throws Exception + { + TypeToken> simpleToken = new TypeToken<>() {}; + + + var result = ClassUtilities.generateVariableName(simpleToken); + + assertEquals("javaLangStringJavaUtilSet",result); + + TypeToken>>> complexToken = new TypeToken<>() {}; + result = ClassUtilities.generateVariableName(complexToken); + + assertEquals("javaLangStringjavaUtilUUIDjavaLangLongJavaUtilSetJavaUtilMapJavaUtilMap",result); + + + } + +} \ No newline at end of file