Add interfaces
This commit is contained in:
parent
628f1631e8
commit
892ba5fff0
33
resources/bytecode/javFiles/Interfaces.jav
Normal file
33
resources/bytecode/javFiles/Interfaces.jav
Normal file
@ -0,0 +1,33 @@
|
||||
import java.lang.Integer;
|
||||
|
||||
interface A {
|
||||
void method1();
|
||||
default method2() {
|
||||
}
|
||||
}
|
||||
|
||||
interface B {
|
||||
void method3();
|
||||
}
|
||||
|
||||
interface C {
|
||||
Integer myInt();
|
||||
}
|
||||
|
||||
class ClassX implements A {
|
||||
}
|
||||
|
||||
record ClassY(Integer myInt) implements C {}
|
||||
|
||||
public class Interfaces implements A, B {
|
||||
public void method1() {
|
||||
}
|
||||
public void method3() {
|
||||
var intf = new Interfaces();
|
||||
intf = new ClassX();
|
||||
intf.method1();
|
||||
|
||||
C c = new ClassY(10);
|
||||
c.myInt();
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ import de.dhbwstuttgart.target.tree.type.*;
|
||||
import org.objectweb.asm.*;
|
||||
|
||||
import java.lang.invoke.*;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.*;
|
||||
|
||||
import static org.objectweb.asm.Opcodes.*;
|
||||
@ -1345,45 +1346,61 @@ public class Codegen {
|
||||
}
|
||||
|
||||
private void generateMethod(TargetMethod method) {
|
||||
var access = method.access() | ACC_PUBLIC;
|
||||
if (method.block() == null)
|
||||
access |= ACC_ABSTRACT;
|
||||
|
||||
// TODO The older codegen has set ACC_PUBLIC for all methods, good for testing but bad for everything else
|
||||
MethodVisitor mv = cw.visitMethod(method.access() | ACC_PUBLIC, method.name(), method.getDescriptor(), method.getSignature(), null);
|
||||
MethodVisitor mv = cw.visitMethod(access, method.name(), method.getDescriptor(), method.getSignature(), null);
|
||||
if (method.txSignature() != null) {
|
||||
mv.visitAttribute(new JavaTXSignatureAttribute(method.getTXSignature()));
|
||||
}
|
||||
|
||||
mv.visitCode();
|
||||
var state = new State(method.signature().returnType(), mv, method.isStatic() ? 0 : 1);
|
||||
for (var param : method.signature().parameters()) {
|
||||
bindLocalVariables(state, param.pattern(), 1, 0);
|
||||
if (method.block() != null) {
|
||||
mv.visitCode();
|
||||
var state = new State(method.signature().returnType(), mv, method.isStatic() ? 0 : 1);
|
||||
for (var param : method.signature().parameters()) {
|
||||
bindLocalVariables(state, param.pattern(), 1, 0);
|
||||
}
|
||||
generate(state, method.block());
|
||||
if (method.signature().returnType() == null)
|
||||
mv.visitInsn(RETURN);
|
||||
mv.visitMaxs(0, 0);
|
||||
}
|
||||
generate(state, method.block());
|
||||
if (method.signature().returnType() == null)
|
||||
mv.visitInsn(RETURN);
|
||||
mv.visitMaxs(0, 0);
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
private static String generateSignature(TargetStructure clazz, Set<TargetGeneric> generics) {
|
||||
String ret = "";
|
||||
if (generics.size() > 0) {
|
||||
if (!generics.isEmpty()) {
|
||||
ret += "<";
|
||||
for (var generic : generics) {
|
||||
ret += generic.name() + ":" + generic.bound().toDescriptor();
|
||||
}
|
||||
ret += ">";
|
||||
}
|
||||
ret += clazz.superType().toDescriptor();
|
||||
if (clazz.superType() != null)
|
||||
ret += clazz.superType().toDescriptor();
|
||||
for (var intf : clazz.implementingInterfaces()) {
|
||||
ret += intf.toSignature();
|
||||
}
|
||||
|
||||
return ret;
|
||||
return ret.isEmpty() ? null : ret;
|
||||
}
|
||||
|
||||
public byte[] generate() {
|
||||
var access = clazz.modifiers();
|
||||
if ((access & ACC_PRIVATE) == 0 && (access & ACC_PROTECTED) == 0) // TODO Implement access modifiers properly
|
||||
access |= ACC_PUBLIC;
|
||||
if (!(clazz instanceof TargetInterface))
|
||||
access |= ACC_SUPER;
|
||||
|
||||
cw.visit(V1_8, access | ACC_SUPER, clazz.qualifiedName().toString().replaceAll("\\.", "/"), generateSignature(clazz, clazz.generics()), clazz.superType() != null ? clazz.superType().getInternalName() : "java/lang/Object", clazz.implementingInterfaces().stream().map(TargetType::toSignature).toArray(String[]::new));
|
||||
if (clazz.txGenerics() != null)
|
||||
var signature = generateSignature(clazz, clazz.generics());
|
||||
var interfaces = clazz.implementingInterfaces().stream().map(TargetType::getInternalName).toArray(String[]::new);
|
||||
var superType = clazz.superType() != null ? clazz.superType().getInternalName() : "java/lang/Object";
|
||||
|
||||
cw.visit(V1_8, access, clazz.qualifiedName().toString().replaceAll("\\.", "/"), signature, superType, interfaces);
|
||||
if (clazz.txGenerics() != null && signature != null)
|
||||
cw.visitAttribute(new JavaTXSignatureAttribute(generateSignature(clazz, clazz.txGenerics())));
|
||||
|
||||
clazz.fields().forEach(this::generateField);
|
||||
|
@ -40,8 +40,9 @@ public class ClassOrInterface extends SyntaxTreeNode implements TypeScope {
|
||||
|
||||
public ClassOrInterface(int modifiers, JavaClassName name, List<Field> fielddecl, Optional<Constructor> fieldInitializations, List<Method> methods, List<Constructor> constructors, GenericDeclarationList genericClassParameters, RefType superClass, Boolean isInterface, List<RefType> implementedInterfaces, List<RefType> permittedSubtypes, Token offset) {
|
||||
super(offset);
|
||||
if (isInterface && !Modifier.isInterface(modifiers))
|
||||
modifiers += Modifier.INTERFACE;
|
||||
if (isInterface) {
|
||||
modifiers |= Modifier.INTERFACE | Modifier.ABSTRACT;
|
||||
}
|
||||
this.modifiers = modifiers;
|
||||
this.name = name;
|
||||
this.fields = fielddecl;
|
||||
@ -72,6 +73,10 @@ public class ClassOrInterface extends SyntaxTreeNode implements TypeScope {
|
||||
this.constructors = new ArrayList<>(cl.constructors);
|
||||
}
|
||||
|
||||
public boolean isInterface() {
|
||||
return (Modifier.INTERFACE & this.getModifiers()) != 0;
|
||||
}
|
||||
|
||||
// Gets if it is added
|
||||
public Boolean areMethodsAdded() {
|
||||
return methodAdded;
|
||||
|
@ -113,7 +113,7 @@ public class OutputGenerator implements ASTVisitor {
|
||||
|
||||
@Override
|
||||
public void visit(ClassOrInterface classOrInterface) {
|
||||
if ((Modifier.INTERFACE & classOrInterface.getModifiers()) == 1) {
|
||||
if (classOrInterface.isInterface()) {
|
||||
out.append("interface ");
|
||||
} else {
|
||||
out.append("class ");
|
||||
|
@ -146,6 +146,8 @@ public class ASTToTargetAST {
|
||||
|
||||
if (input instanceof Record)
|
||||
return new TargetRecord(input.getModifiers(), input.getClassName(), javaGenerics, txGenerics, superInterfaces, constructors, fields, methods);
|
||||
else if (input.isInterface())
|
||||
return new TargetInterface(input.getModifiers(), input.getClassName(), javaGenerics, txGenerics, methods, superInterfaces);
|
||||
else return new TargetClass(input.getModifiers(), input.getClassName(), convert(input.getSuperClass(), generics.javaGenerics), javaGenerics, txGenerics, superInterfaces, constructors, fields, methods);
|
||||
}
|
||||
|
||||
@ -356,6 +358,7 @@ public class ASTToTargetAST {
|
||||
}
|
||||
|
||||
protected TargetBlock convert(Block block) {
|
||||
if (block == null) return null;
|
||||
return new TargetBlock(block.statements.stream().map(this::convert).toList());
|
||||
}
|
||||
|
||||
|
@ -262,153 +262,154 @@ public abstract class GenerateGenerics {
|
||||
}
|
||||
}
|
||||
|
||||
method.block.accept(new TracingStatementVisitor() {
|
||||
if (method.block != null)
|
||||
method.block.accept(new TracingStatementVisitor() {
|
||||
|
||||
private RefTypeOrTPHOrWildcardOrGeneric superType = new de.dhbwstuttgart.syntaxtree.type.Void(new NullToken());
|
||||
private RefTypeOrTPHOrWildcardOrGeneric superType = new de.dhbwstuttgart.syntaxtree.type.Void(new NullToken());
|
||||
|
||||
@Override
|
||||
public void visit(MethodCall methodCall) {
|
||||
//Anfang es werden Paare von TPHs gespeichert, die bei den Generated Generics ueber die Methodengrenzen hinweg
|
||||
//betrachtet werden muessen
|
||||
//Definition 7.2 (Family of generated generics). T1 <. R1 <.^∗ R2 <. T2
|
||||
Set<TPH> T1s =
|
||||
methodCall.getArgumentList()
|
||||
.getArguments()
|
||||
.stream()
|
||||
.map(TypableStatement::getType)
|
||||
.collect(Collectors.toCollection(HashSet::new))
|
||||
.stream().filter(TypePlaceholder.class::isInstance)
|
||||
.map(TypePlaceholder.class::cast)
|
||||
.map(TPH::new)
|
||||
.collect(Collectors.toCollection(HashSet::new));
|
||||
Set<TPH> T2s = new HashSet<>();
|
||||
findTphs(superType, T2s);
|
||||
@Override
|
||||
public void visit(MethodCall methodCall) {
|
||||
//Anfang es werden Paare von TPHs gespeichert, die bei den Generated Generics ueber die Methodengrenzen hinweg
|
||||
//betrachtet werden muessen
|
||||
//Definition 7.2 (Family of generated generics). T1 <. R1 <.^∗ R2 <. T2
|
||||
Set<TPH> T1s =
|
||||
methodCall.getArgumentList()
|
||||
.getArguments()
|
||||
.stream()
|
||||
.map(TypableStatement::getType)
|
||||
.collect(Collectors.toCollection(HashSet::new))
|
||||
.stream().filter(TypePlaceholder.class::isInstance)
|
||||
.map(TypePlaceholder.class::cast)
|
||||
.map(TPH::new)
|
||||
.collect(Collectors.toCollection(HashSet::new));
|
||||
Set<TPH> T2s = new HashSet<>();
|
||||
findTphs(superType, T2s);
|
||||
|
||||
System.out.println("T1s: " + T1s + " T2s: " + T2s);
|
||||
//Ende
|
||||
System.out.println("T1s: " + T1s + " T2s: " + T2s);
|
||||
//Ende
|
||||
|
||||
superType = methodCall.receiverType;
|
||||
methodCall.receiver.accept(this);
|
||||
for (int i = 0; i < methodCall.arglist.getArguments().size(); i++) {
|
||||
superType = methodCall.arglist.getArguments().get(i).getType();
|
||||
methodCall.arglist.getArguments().get(i).accept(this);
|
||||
}
|
||||
superType = methodCall.receiverType;
|
||||
methodCall.receiver.accept(this);
|
||||
for (int i = 0; i < methodCall.arglist.getArguments().size(); i++) {
|
||||
superType = methodCall.arglist.getArguments().get(i).getType();
|
||||
methodCall.arglist.getArguments().get(i).accept(this);
|
||||
}
|
||||
|
||||
if (methodCall.receiver instanceof ExpressionReceiver expressionReceiver) {
|
||||
if (expressionReceiver.expr instanceof This) {
|
||||
var optMethod = astToTargetAST.findMethod(owner, methodCall.name, methodCall.getArgumentList());
|
||||
if (optMethod.isEmpty()) return;
|
||||
var method2 = optMethod.get();
|
||||
System.out.println("In: " + method.getName() + " Method: " + method2.getName());
|
||||
var generics = family(owner, method2);
|
||||
if (methodCall.receiver instanceof ExpressionReceiver expressionReceiver) {
|
||||
if (expressionReceiver.expr instanceof This) {
|
||||
var optMethod = astToTargetAST.findMethod(owner, methodCall.name, methodCall.getArgumentList());
|
||||
if (optMethod.isEmpty()) return;
|
||||
var method2 = optMethod.get();
|
||||
System.out.println("In: " + method.getName() + " Method: " + method2.getName());
|
||||
var generics = family(owner, method2);
|
||||
|
||||
// transitive and
|
||||
var all = transitiveClosure(generics);
|
||||
// reflexive
|
||||
var toAdd = new HashSet<Pair>();
|
||||
for (var generic : all) {
|
||||
toAdd.add(new PairLT(generic.left, generic.left));
|
||||
}
|
||||
all.addAll(toAdd);
|
||||
// transitive and
|
||||
var all = transitiveClosure(generics);
|
||||
// reflexive
|
||||
var toAdd = new HashSet<Pair>();
|
||||
for (var generic : all) {
|
||||
toAdd.add(new PairLT(generic.left, generic.left));
|
||||
}
|
||||
all.addAll(toAdd);
|
||||
|
||||
HashSet<PairLT> newPairs = new HashSet<>();
|
||||
HashSet<PairLT> newPairs = new HashSet<>();
|
||||
|
||||
// Loop from hell
|
||||
outer:
|
||||
for (var R1 : typeVariables) {
|
||||
if (typeVariablesOfClass.contains(R1)) continue;
|
||||
for (var generic : all)
|
||||
if (generic instanceof PairLT ptph) {
|
||||
for (var pair : simplifiedConstraints) {
|
||||
if (!(pair.left.equals(R1) && pair.right.equals(ptph.left)))
|
||||
continue;
|
||||
// Loop from hell
|
||||
outer:
|
||||
for (var R1 : typeVariables) {
|
||||
if (typeVariablesOfClass.contains(R1)) continue;
|
||||
for (var generic : all)
|
||||
if (generic instanceof PairLT ptph) {
|
||||
for (var pair : simplifiedConstraints) {
|
||||
if (!(pair.left.equals(R1) && pair.right.equals(ptph.left)))
|
||||
continue;
|
||||
|
||||
for (var R2 : typeVariables) {
|
||||
for (var pair2 : simplifiedConstraints) {
|
||||
for (var R2 : typeVariables) {
|
||||
for (var pair2 : simplifiedConstraints) {
|
||||
|
||||
if (!(pair2.right.equals(R2) && pair2.left.equals(ptph.right)))
|
||||
continue;
|
||||
if (R1.equals(R2)) continue;
|
||||
if (!T1s.contains(R1) || !T2s.contains(R2)) continue;
|
||||
if (!(pair2.right.equals(R2) && pair2.left.equals(ptph.right)))
|
||||
continue;
|
||||
if (R1.equals(R2)) continue;
|
||||
if (!T1s.contains(R1) || !T2s.contains(R2)) continue;
|
||||
|
||||
var newPair = new PairLT(R1, R2);
|
||||
System.out.println("New pair: " + newPair);
|
||||
newPairs.add(newPair);
|
||||
var newPair = new PairLT(R1, R2);
|
||||
System.out.println("New pair: " + newPair);
|
||||
newPairs.add(newPair);
|
||||
|
||||
if (!containsRelation(result, newPair))
|
||||
addToPairs(result, newPair);
|
||||
continue outer;
|
||||
if (!containsRelation(result, newPair))
|
||||
addToPairs(result, newPair);
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
simplifiedConstraints.addAll(newPairs);
|
||||
}
|
||||
simplifiedConstraints.addAll(newPairs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(LambdaExpression lambdaExpression) {
|
||||
superType = new Void(new NullToken());
|
||||
lambdaExpression.methodBody.accept(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(Assign assign) {
|
||||
superType = assign.rightSide.getType();
|
||||
assign.rightSide.accept(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(BinaryExpr binary) {
|
||||
superType = new Void(new NullToken());
|
||||
binary.lexpr.accept(this);
|
||||
superType = new Void(new NullToken());
|
||||
binary.rexpr.accept(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(Block block) {
|
||||
for (var expr : block.statements) {
|
||||
superType = new de.dhbwstuttgart.syntaxtree.type.Void(new NullToken());
|
||||
expr.accept(this);
|
||||
@Override
|
||||
public void visit(LambdaExpression lambdaExpression) {
|
||||
superType = new Void(new NullToken());
|
||||
lambdaExpression.methodBody.accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(IfStmt ifStmt) {
|
||||
superType = new Void(new NullToken());
|
||||
ifStmt.expr.accept(this);
|
||||
superType = new Void(new NullToken());
|
||||
ifStmt.then_block.accept(this);
|
||||
superType = new Void(new NullToken());
|
||||
if (ifStmt.else_block != null)
|
||||
ifStmt.else_block.accept(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(Return aReturn) {
|
||||
superType = aReturn.getType();
|
||||
aReturn.retexpr.accept(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(WhileStmt whileStmt) {
|
||||
superType = new Void(new NullToken());
|
||||
whileStmt.expr.accept(this);
|
||||
superType = new Void(new NullToken());
|
||||
whileStmt.loopBlock.accept(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(ArgumentList arglist) {
|
||||
for (int i = 0; i < arglist.getArguments().size(); i++) {
|
||||
superType = arglist.getArguments().get(i).getType();
|
||||
arglist.getArguments().get(i).accept(this);
|
||||
@Override
|
||||
public void visit(Assign assign) {
|
||||
superType = assign.rightSide.getType();
|
||||
assign.rightSide.accept(this);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@Override
|
||||
public void visit(BinaryExpr binary) {
|
||||
superType = new Void(new NullToken());
|
||||
binary.lexpr.accept(this);
|
||||
superType = new Void(new NullToken());
|
||||
binary.rexpr.accept(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(Block block) {
|
||||
for (var expr : block.statements) {
|
||||
superType = new de.dhbwstuttgart.syntaxtree.type.Void(new NullToken());
|
||||
expr.accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(IfStmt ifStmt) {
|
||||
superType = new Void(new NullToken());
|
||||
ifStmt.expr.accept(this);
|
||||
superType = new Void(new NullToken());
|
||||
ifStmt.then_block.accept(this);
|
||||
superType = new Void(new NullToken());
|
||||
if (ifStmt.else_block != null)
|
||||
ifStmt.else_block.accept(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(Return aReturn) {
|
||||
superType = aReturn.getType();
|
||||
aReturn.retexpr.accept(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(WhileStmt whileStmt) {
|
||||
superType = new Void(new NullToken());
|
||||
whileStmt.expr.accept(this);
|
||||
superType = new Void(new NullToken());
|
||||
whileStmt.loopBlock.accept(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(ArgumentList arglist) {
|
||||
for (int i = 0; i < arglist.getArguments().size(); i++) {
|
||||
superType = arglist.getArguments().get(i).getType();
|
||||
arglist.getArguments().get(i).accept(this);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var closure = transitiveClosure(simplifiedConstraints);
|
||||
// Type variables with bounds that are also type variables of the class
|
||||
@ -493,18 +494,19 @@ public abstract class GenerateGenerics {
|
||||
typeVariables.addAll(findTypeVariables(arg.getType()));
|
||||
}
|
||||
|
||||
method.block.accept(new TracingStatementVisitor() {
|
||||
@Override
|
||||
public void visit(LocalVarDecl localVarDecl) {
|
||||
typeVariables.addAll(findTypeVariables(localVarDecl.getType()));
|
||||
}
|
||||
if (method.block != null)
|
||||
method.block.accept(new TracingStatementVisitor() {
|
||||
@Override
|
||||
public void visit(LocalVarDecl localVarDecl) {
|
||||
typeVariables.addAll(findTypeVariables(localVarDecl.getType()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(MethodCall methodCall) {
|
||||
super.visit(methodCall);
|
||||
typeVariables.addAll(findTypeVariables(methodCall.getType()));
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public void visit(MethodCall methodCall) {
|
||||
super.visit(methodCall);
|
||||
typeVariables.addAll(findTypeVariables(methodCall.getType()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
abstract void generics(ClassOrInterface owner, Method method, Set<Pair> result, Set<TPH> javaTypeVariablesOfClass);
|
||||
|
@ -1,6 +1,7 @@
|
||||
package de.dhbwstuttgart.target.tree;
|
||||
|
||||
import de.dhbwstuttgart.parser.scope.JavaClassName;
|
||||
import de.dhbwstuttgart.target.tree.expression.TargetBlock;
|
||||
import de.dhbwstuttgart.target.tree.type.TargetRefType;
|
||||
import de.dhbwstuttgart.target.tree.type.TargetType;
|
||||
|
||||
|
@ -1,6 +1,24 @@
|
||||
package de.dhbwstuttgart.target.tree;
|
||||
|
||||
import java.util.List;
|
||||
import de.dhbwstuttgart.parser.scope.JavaClassName;
|
||||
import de.dhbwstuttgart.target.tree.type.TargetType;
|
||||
|
||||
public record TargetInterface(String name, List<TargetMethod> methods, List<TargetInterface> extendedInterfaces) {
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public record TargetInterface(int modifiers, JavaClassName qualifiedName, Set<TargetGeneric> generics, Set<TargetGeneric> txGenerics, List<TargetMethod> methods, List<TargetType> implementingInterfaces) implements TargetStructure {
|
||||
@Override
|
||||
public TargetType superType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TargetConstructor> constructors() {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TargetField> fields() {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
@ -10,8 +10,6 @@ import java.util.Set;
|
||||
public record TargetRecord(int modifiers, JavaClassName qualifiedName, Set<TargetGeneric> generics, Set<TargetGeneric> txGenerics, List<TargetType> implementingInterfaces, List<TargetConstructor> constructors, List<TargetField> fields, List<TargetMethod> methods) implements TargetStructure {
|
||||
|
||||
public static final TargetType RECORD = new TargetRefType("java.lang.Record");
|
||||
|
||||
@Override
|
||||
public TargetType superType() {
|
||||
return RECORD;
|
||||
}
|
||||
|
@ -713,4 +713,12 @@ public class TestComplete {
|
||||
assertEquals(m1.invoke(instance, pt), 30);
|
||||
assertEquals(m2.invoke(instance, 10), 10);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInterfaces() throws Exception {
|
||||
var classFiles = generateClassFiles(new ByteArrayClassLoader(), "Interfaces.jav");
|
||||
var clazz = classFiles.get("Interfaces");
|
||||
var instance = clazz.getDeclaredConstructor().newInstance();
|
||||
System.out.println(Arrays.toString(clazz.getInterfaces()));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user