-
Notifications
You must be signed in to change notification settings - Fork 282
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Expose type parameters of JavaClass #115
Comments
This might not be as easy as it seems at the first spot. Due to the recursive nature of generic types (a generic type can be parameterized by a generic type – consider for example
public interface JavaType {
...
JavaType getParameter();
boolean isParametrizedType();
...
}
//com.tngtech.archunit.core.importer.JavaClassProcessor
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
...
if (signature != null) {
XXXSignatureVisitor visitor = new XXXSignatureVisitor();
SignatureReader reader = new SignatureReader(signature);
reader.acceptType(visitor);
// visitor.getParametrizedType();
}
...
} I will try to proceed with implementation using smaller steps:
Few additional questions arise:
|
Thanks for looking into that 😃 Nobody ever said this is easy 😉
The outline of your plan sounds good, would be my approach, too. |
BTW. It would be best if
|
@slq sorry that I'm answering so late, I got sick for a couple of days...
Your problem with To elaborate, consider
So the So suppose we return something like
If we don't want to introduce breaking changes, we could start to do something like
where in time I think this "solves" your concrete problem with the Does this make sense in some way? (I'm just rambling about my thoughts, I have no code in front of me, so maybe I'm missing the point 😉) |
Continuing on the discussion from #183, I wonder if there's a less invasive way to achieve 90% of the functionality? First fundamental assumption: we don't actually need to change the API of Second assumption: we don't need any advanced generics inspection as we're only interested in the point of the declaration, not the ultimate expansion at runtime. Peter gave this example in the other ticket: class Example<T extends Base<? super T>> {
List<? extends T> elements;
}
class Base<T> {}
class Foo extends Base<Foo> {}
class ExampleExtension extends Example<Foo> {} In this example we don't have to find out that for This bit of sample code collects all types depended on directly traversing generics declarations for non-raw types: public class Sample {
class Example<T extends Base<? super T>> {
List<? extends T> elements;
}
class Base<T> {}
class Foo extends Base<Foo> {}
class ExampleExtension extends Example<Foo> {}
public static void main(String[] args) throws Exception {
// prints [class ….Sample$Example, class ….Sample$Base]
System.out.println(getTypeDependencies(Example.class));
// prints [interface java.util.List, class ….Sample$Base]
System.out.println(getTypeDependencies(Example.class.getDeclaredField("elements").getGenericType()));
// prints [class….Sample$ExampleExtension, class ….Sample$Example]
System.out.println(getTypeDependencies(ExampleExtension.class));
}
public static List<Class<?>> getTypeDependencies(Type type) {
return getTypeParametersRecursively(type, Collections.singleton(Object.class)) //
.collect(Collectors.toList());
}
private static Stream<Class<?>> getTypeParametersRecursively(Type type, Collection<Type> alreadyFound) {
Stream<Type> result = Stream.empty();
Type head = null;
if (Class.class.isInstance(type)) {
// For classes, inspect type parameters
var clazz = (Class<?>) type;
head = clazz;
var superclass = Stream.of(clazz.getGenericSuperclass());
var interfaces = Arrays.stream(clazz.getGenericInterfaces());
var parameters = Arrays.stream(clazz.getTypeParameters());
result = Stream.concat(Stream.concat(parameters, interfaces), superclass); //
} else if (ParameterizedType.class.isInstance(type)) {
// For parameterized types, inspect arguments
var parameterized = (ParameterizedType) type;
var arguments = parameterized.getActualTypeArguments();
head = parameterized.getRawType();
result = Arrays.stream(arguments);
} else if (WildcardType.class.isInstance(type)) {
// For wildcards, inspect bounds
var wildcard = (WildcardType) type;
result = Stream.concat(Arrays.stream(wildcard.getLowerBounds()), //
Arrays.stream(wildcard.getUpperBounds())); //
} else if (TypeVariable.class.isInstance(type)) {
// For type variables, inspect bounds
var variable = (TypeVariable<?>) type;
result = Arrays.stream(variable.getBounds());
}
// All the nested types we found
var nested = result.collect(Collectors.toSet());
// Create new set of already known types
var newFound = new HashSet<>(alreadyFound);
newFound.addAll(nested);
// Only recursively consider the ones that we haven't seen before
var recursive = nested.stream() //
.filter(Predicate.not(alreadyFound::contains)) //
.filter(Predicate.not(Class.class::isInstance)) //
.distinct() //
.flatMap(it -> getTypeParametersRecursively(it, newFound));
// Return the currently resolved type plus recursively resolved ones
return Class.class.isInstance(head) //
? Stream.concat(Stream.of(Class.class.cast(head)), recursive) //
: recursive;
}
} With that in place, we could create additional In case you think this might be a way you'd like to investigate I'd happily spent some time on a PR. For now it looks like changes in |
Another quick glance, it looks like the main challenge is to turn the discovered types back into |
Thanks a lot for listing the cases of generic dependencies so nicely @odrotbohm 😃
This code would have the advantage, that any existing infrastructure for
Seems like for However, to get back to the clean solution, I did the renaming from |
This will solve a part of #115 and add support for generic types to `JavaClass`. It will still not be possible to query superclass or interface type parameters, but it will add support for arbitrarily nested type signatures of `JavaClass` itself, like ``` class Foo<T extends Serializable, U extends List<? super Map<? extends Bar, T[][]>>, V extends T> {} ``` Issue: #115
Hi, |
Unfortunately full generics support is quite a big issue. As a next step we'll roll out generic superclasses, then generic interfaces (which will probably be easy after superclasses). But then there are still fields, constructor and method parameters and method return types. So to cover the full generic support will still take some time (I'm open to anyone jumping in to speed up the process 😃 Otherwise this is my highest priority, but I can only grind down one part after the other...) |
This will solve a part of #115 and add support for generic types to `JavaClass`. It will still not be possible to query superclass or interface type parameters, but it will add support for arbitrarily nested type signatures of `JavaClass` itself, like ``` class Foo<T extends Serializable, U extends List<? super Map<? extends Bar, T[][]>>, V extends T> {} ``` Issue: #115
As last step to fully support Generics within ArchUnit this adds support for generic method/constructor parameter types. In particular * `JavaMethod.getParameterTypes()` now returns a `List` of `JavaParameterizedType` for parameterized generic method/constructor parameter types and the raw types otherwise * all type arguments of generic method parameter types are added to `JavaClass.directDependencies{From/To}Self` Example: ArchUnit would now detect `String` and `File` as `Dependency` of a method declaration like ``` class SomeClass { void someMethod(Set<List<? super String>> first, Map<?, ? extends File> second) { } } ``` Note that analogously to the Java Reflection API `JavaConstructor.getParameterTypes()` and `JavaConstructor.getRawParameterTypes()` do not behave exactly the same for inner classes. While `JavaConstructor.getRawParameterTypes()` contains the enclosing class as first parameter type, `JavaConstructor.getParameterTypes()` does not contain it if the constructor has generic parameters. On the other hand it just returns the same list of raw parameter types if the constructor is non-generic. This might surprise some users, but I decided to stick to the same behavior as the Reflection API, because this has always been the strategy and the solution can never truly satisfy all assumptions a user might have. Resolves: #115 Resolves: #144 Resolves: #440
Hi guys. ArchRuleDefinition
.methods()
.that().areDeclaredIn(Myclass:class.java)
.should()
.haveRawReturnType(describe("UUID or collection of UUID") {
it.isAssignableFrom(UUID::class.java)
.or(it.isAssignableFrom(Collection::class.java).and(it.typeParameters.contains(UUID::class.java)))
})
.check(ArchUnit.testClasses) typeParameters do not hold information about UUID type, I had to go with more generic function should() which provides JavaMethod parameter and check for return types there |
Not knowing too much about Kotlin, but I'd assume that |
Ah, sure, because it is already erased -_- |
It should be possible to obtain the type parameters for a JavaClass.
When given code like this:
I now want to obtain the type parameters of the interface. Same goes for fields, method return types and parameters, etc.
Something like this would be great:
The text was updated successfully, but these errors were encountered: