Compare commits

30 Commits

Author SHA1 Message Date
dba1a366d9 Merge pull request 'Add Mutable and Immutable Factories' (#1) from feat/mutable-factories into dev
All checks were successful
Build / Gradle-Build (push) Successful in 1m58s
Publish / Gradle-Publish (push) Successful in 31s
Test / Gradle-Test (push) Successful in 33s
Reviewed-on: #1
2025-05-04 11:32:27 +00:00
eaf109e74e Merge branch 'dev' into feat/mutable-factories
All checks were successful
Build / Gradle-Build (push) Successful in 35s
Test / Gradle-Test (push) Successful in 39s
2025-05-02 12:33:39 +00:00
a2bdb549f9 feat(factory): introduce Mutable and Immutable methodfactory and change parser pipeline
All checks were successful
Build / Gradle-Build (push) Successful in 2m12s
Test / Gradle-Test (push) Successful in 32s
2025-05-02 13:00:28 +02:00
ad2c4c00f6 refactor(bcmethod): move duplicate checking out of factory 2025-05-02 12:57:02 +02:00
4dd7b70f40 refactor(factory): move to own package and add factories getter 2025-04-30 12:56:48 +02:00
d76db0ba61 bump Minor version
All checks were successful
Publish / Gradle-Publish (push) Successful in 12s
Build / Gradle-Build (push) Successful in 9s
Test / Gradle-Test (push) Successful in 12s
2025-04-15 23:25:45 +02:00
8d24604707 feat(factory): add utility method which does the "unchecked" cast
All checks were successful
Build / Gradle-Build (push) Successful in 1m36s
Publish / Gradle-Publish (push) Successful in 10s
Test / Gradle-Test (push) Successful in 12s
2025-04-15 11:06:00 +02:00
28d991f17c fix(log): missing placeholder 2025-04-15 11:03:33 +02:00
4c800cfd89 fix: compile error
All checks were successful
Build / Gradle-Build (push) Successful in 9s
Publish / Gradle-Publish (push) Successful in 10s
Test / Gradle-Test (push) Successful in 11s
2025-04-14 19:04:09 +02:00
e425989106 feat(parser): MethodParser rework
Some checks failed
Build / Gradle-Build (push) Failing after 9s
Publish / Gradle-Publish (push) Failing after 8s
Test / Gradle-Test (push) Failing after 9s
2025-04-14 19:01:01 +02:00
eccad71837 feat(logging): add more logging
All checks were successful
Build / Gradle-Build (push) Successful in 13s
Publish / Gradle-Publish (push) Successful in 10s
Test / Gradle-Test (push) Successful in 11s
2025-04-14 18:34:46 +02:00
cad019e44a fix(container): NullPointerException when trying to query a key which is not in the container yet
All checks were successful
Build / Gradle-Build (push) Successful in 9s
Publish / Gradle-Publish (push) Successful in 9s
Test / Gradle-Test (push) Successful in 11s
2025-04-13 16:28:16 +02:00
79dcc25afc fix(method): add toString override
All checks were successful
Build / Gradle-Build (push) Successful in 1m38s
Publish / Gradle-Publish (push) Successful in 10s
Test / Gradle-Test (push) Successful in 11s
2025-04-13 15:09:03 +02:00
dd4e048ce5 fix(bcmethod): make bcmethod abstract
All checks were successful
Build / Gradle-Build (push) Successful in 1m38s
Publish / Gradle-Publish (push) Successful in 11s
Test / Gradle-Test (push) Successful in 13s
2025-04-13 00:20:28 +02:00
deabaaf22f fix(method): remove enforced context parameter 2025-04-12 23:34:13 +02:00
7f5c242173 fix typo 2025-04-12 23:16:31 +02:00
7ec05d4e8a fix(container): add factory getters
All checks were successful
Build / Gradle-Build (push) Successful in 11s
Publish / Gradle-Publish (push) Successful in 11s
Test / Gradle-Test (push) Successful in 14s
2025-04-12 00:01:07 +02:00
535a0dad58 fix(method): add temporary public identifier setter to BCMethod
All checks were successful
Build / Gradle-Build (push) Successful in 11s
Publish / Gradle-Publish (push) Successful in 11s
Test / Gradle-Test (push) Successful in 13s
2025-04-11 21:12:22 +02:00
2a6eba871d feat(core): add Context generic
All checks were successful
Build / Gradle-Build (push) Successful in 15s
Publish / Gradle-Publish (push) Successful in 13s
Test / Gradle-Test (push) Successful in 13s
2025-04-11 20:44:03 +02:00
c44b874b41 feat(method): add Factory to BestCandidateMethod
All checks were successful
Build / Gradle-Build (push) Successful in 1m43s
Publish / Gradle-Publish (push) Successful in 11s
Test / Gradle-Test (push) Successful in 13s
2025-04-11 13:44:10 +02:00
8842e9d8e1 refactor(method): move Method related classes to method package 2025-04-11 12:16:58 +02:00
1891037ed7 feat(factory): add Identifier Generic
All checks were successful
Build / Gradle-Build (push) Successful in 13s
Publish / Gradle-Publish (push) Successful in 12s
Test / Gradle-Test (push) Successful in 14s
2025-04-10 15:05:46 +02:00
c0da5ee75d feat(container): add chaining, single method add method for overriding, key getter and protected entries getter
All checks were successful
Build / Gradle-Build (push) Successful in 16s
Publish / Gradle-Publish (push) Successful in 12s
Test / Gradle-Test (push) Successful in 12s
2025-04-10 14:44:57 +02:00
3deee2fd5d bump version 2025-04-10 14:25:12 +02:00
2cd14d4a5d bump version
All checks were successful
Build / Gradle-Build (push) Successful in 13s
Publish / Gradle-Publish (push) Successful in 15s
Test / Gradle-Test (push) Successful in 17s
2025-04-10 12:56:19 +02:00
2f07ac1822 feat(test): add Tests for ReflectedMethod and BestCandidateMethod
All checks were successful
Build / Gradle-Build (push) Successful in 1m41s
Publish / Gradle-Publish (push) Successful in 10s
Test / Gradle-Test (push) Successful in 13s
2025-04-09 17:02:55 +02:00
25b2e49396 feat(bestcandidate): add BestCandidate Method to always find the correct method for the parameters 2025-04-09 17:02:29 +02:00
52a05dd9d7 feat(container): add MethodContainers 2025-04-09 17:01:58 +02:00
075f0e13c9 feat(core): add ReflectedMethod and Processors 2025-04-09 17:01:36 +02:00
acbe6b23ef feat(util): add ReflectionUtil 2025-04-09 17:00:41 +02:00
25 changed files with 657 additions and 117 deletions

View File

@@ -6,7 +6,7 @@ plugins {
allprojects { allprojects {
group = "net.tomatentum.cutin" group = "net.tomatentum.cutin"
version = "0.1.0-RC1" + (if (!project.hasProperty("release")) ("-" + getGitHash()) else "") version = "0.2.0" + (if (!project.hasProperty("release")) ("-" + getGitHash()) else "")
description = "A lightweight Reflection abstraction specifically but not exclusively made for tueem/Marinara." description = "A lightweight Reflection abstraction specifically but not exclusively made for tueem/Marinara."
plugins.withType<JavaPlugin> { plugins.withType<JavaPlugin> {
tasks.withType<Jar>().configureEach { tasks.withType<Jar>().configureEach {

View File

@@ -8,3 +8,4 @@ slf4j = "2.0.17"
[libraries] [libraries]
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" } junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" }
slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j"} slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j"}
slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j"}

View File

@@ -20,6 +20,8 @@ dependencies {
testImplementation(libs.junit.jupiter) testImplementation(libs.junit.jupiter)
testRuntimeOnly("org.junit.platform:junit-platform-launcher") testRuntimeOnly("org.junit.platform:junit-platform-launcher")
testImplementation(libs.slf4j.simple)
implementation(libs.slf4j) implementation(libs.slf4j)
} }

View File

@@ -1,7 +1,7 @@
package net.tomatentum.cutin; package net.tomatentum.cutin;
public interface MethodExecutor { public interface MethodExecutor<C extends Object> {
void handle(Object context); void handle(C context);
} }

View File

@@ -1,5 +1,7 @@
package net.tomatentum.cutin; package net.tomatentum.cutin;
import java.lang.reflect.Method;
public interface MethodParser { public interface MethodParser {
void parse(); Object parse(Method method, Object containingObject);
} }

View File

@@ -1,7 +1,9 @@
package net.tomatentum.cutin; package net.tomatentum.cutin;
public interface MethodProcessor { import net.tomatentum.cutin.container.MethodContainer;
void process(Object context); public interface MethodProcessor<I extends Object, C extends Object> {
void process(Object context, MethodContainer<I, C> methodContainer);
} }

View File

@@ -1,7 +1,11 @@
package net.tomatentum.cutin; package net.tomatentum.cutin;
public interface ProcessorContainer { import java.util.Collection;
ProcessorContainer addProcessor(MethodProcessor processor); public interface ProcessorContainer<I extends Object, C extends Object> {
ProcessorContainer<I, C> addProcessor(MethodProcessor<I, C> processor);
Collection<MethodProcessor<I, C>> processor();
} }

View File

@@ -1,31 +1,41 @@
package net.tomatentum.cutin; package net.tomatentum.cutin;
import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
public class ProcessorMethodExecutor implements MethodExecutor, ProcessorContainer { import net.tomatentum.cutin.container.MethodContainer;
public class ProcessorMethodExecutor<I extends Object, C extends Object> implements MethodExecutor<C>, ProcessorContainer<I, C> {
private Logger logger = LoggerFactory.getLogger(getClass()); private Logger logger = LoggerFactory.getLogger(getClass());
private Set<MethodProcessor> processors; private MethodContainer<I, C> methodContainer;
private Set<MethodProcessor<I, C>> processors;
public ProcessorMethodExecutor() { public ProcessorMethodExecutor(MethodContainer<I, C> methodContainer) {
this.methodContainer = methodContainer;
this.processors = new HashSet<>(); this.processors = new HashSet<>();
} }
@Override @Override
public ProcessorContainer addProcessor(MethodProcessor processor) { public ProcessorContainer<I, C> addProcessor(MethodProcessor<I, C> processor) {
processors.add(processor); processors.add(processor);
return this; return this;
} }
@Override
public Collection<MethodProcessor<I, C>> processor() {
return this.processors;
}
@Override @Override
public void handle(Object context) { public void handle(Object context) {
logger.debug("Received {} interaction ", context); logger.debug("Handling {} context ", context);
processors.forEach(x -> x.process(context)); processors.forEach(x -> x.process(context, methodContainer));
} }
} }

View File

@@ -1,18 +0,0 @@
package net.tomatentum.cutin;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Optional;
public interface ReflectedMethodFactory {
Optional<ReflectedMethod> produce(Method method, Object containingClass);
ReflectedMethodFactory addFactory(Factory factory);
public interface Factory {
Optional<ReflectedMethod> produce(Method method, Object containingObject);
void addParser(ReflectedMethod method, List<MethodParser> parser);
}
}

View File

@@ -1,59 +0,0 @@
package net.tomatentum.cutin;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.tomatentum.cutin.util.ReflectionUtil;
public class ReflectedMethodFactoryImpl implements ReflectedMethodFactory {
private Logger logger = LoggerFactory.getLogger(getClass());
private List<Factory> factories;
public ReflectedMethodFactoryImpl() {
this(new ArrayList<>());
}
public ReflectedMethodFactoryImpl(List<Factory> factories) {
this.factories = factories;
}
@Override
public Optional<ReflectedMethod> produce(Method method, Object containingClass) {
Optional<ReflectedMethod> imethod = this.factories.stream()
.map(f -> factoryProduce(f, method, containingClass))
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
if (imethod.isEmpty()) {
logger.debug("Could not produce a ReflectedMethod for Method {}", ReflectionUtil.getFullMethodName(method));
}
return imethod;
}
@Override
public ReflectedMethodFactory addFactory(Factory factory) {
this.factories.add(factory);
return this;
}
private Optional<ReflectedMethod> factoryProduce(Factory factory, Method method, Object containingClass) {
List<MethodParser> parser = new ArrayList<>();
Optional<ReflectedMethod> m = factory.produce(method, containingClass);
m.ifPresent(x -> {
factory.addParser(x, parser);
parser.forEach(MethodParser::parse);
});
return m;
}
}

View File

@@ -0,0 +1,74 @@
package net.tomatentum.cutin.container;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.tomatentum.cutin.factory.ReflectedMethodFactory;
import net.tomatentum.cutin.method.ReflectedMethod;
public class LoneMethodContainer<I extends Object, C extends Object> implements MethodContainer<I, C> {
private Logger logger = LoggerFactory.getLogger(getClass());
private Map<I, ReflectedMethod<I, C>> methodStore;
private ReflectedMethodFactory<I, C> factory;
public LoneMethodContainer(ReflectedMethodFactory<I, C> factory) {
this.methodStore = new HashMap<>();
this.factory = factory;
}
@Override
public MethodContainer<I, C> addMethod(ReflectedMethod<I, C> method) {
if (this.methodStore.keySet().contains(method.identifier())) {
logger.warn("Could not add {} to container because the same identifier was already present.", method);
return this;
}
this.methodStore.put(method.identifier(), method);
logger.debug("Added {} to container", method);
return this;
}
@Override
public MethodContainer<I, C> addMethods(Object containingObject, Method... methods) {
for (Method method : methods)
this.factory.produce(method, containingObject)
.ifPresent(this::addMethod);
return this;
}
@Override
public Set<I> identifiers() {
return methodStore.keySet();
}
@Override
public Collection<ReflectedMethod<I, C>> methods() {
return this.methodStore.values();
}
@Override
public Collection<ReflectedMethod<I, C>> findFor(I identifier) {
ReflectedMethod<I, C> result = this.methodStore.get(identifier);
return result != null ? Arrays.asList(result) : Collections.emptyList();
}
@Override
public Optional<ReflectedMethod<I, C>> findFirstFor(I identifier) {
return findFor(identifier).stream().findFirst();
}
public ReflectedMethodFactory<I, C> factory() {
return this.factory;
}
}

View File

@@ -0,0 +1,30 @@
package net.tomatentum.cutin.container;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Optional;
import java.util.Set;
import net.tomatentum.cutin.method.ReflectedMethod;
public interface MethodContainer<I extends Object, C extends Object> {
MethodContainer<I, C> addMethod(ReflectedMethod<I, C> method);
default MethodContainer<I, C> addMethods(ReflectedMethod<I, C>[] methods) {
for (ReflectedMethod<I, C> reflectedMethod : methods) {
this.addMethod(reflectedMethod);
}
return this;
}
MethodContainer<I, C> addMethods(Object containingObject, Method... methods);
default MethodContainer<I, C> addAllMethods(Object containingObject) {
this.addMethods(containingObject, containingObject.getClass().getDeclaredMethods());
return this;
}
Set<I> identifiers();
Collection<ReflectedMethod<I, C>> methods();
Collection<ReflectedMethod<I, C>> findFor(I identifier);
Optional<ReflectedMethod<I, C>> findFirstFor(I identifier);
}

View File

@@ -0,0 +1,121 @@
package net.tomatentum.cutin.container;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.tomatentum.cutin.factory.ReflectedMethodFactory;
import net.tomatentum.cutin.method.ReflectedMethod;
import net.tomatentum.cutin.util.ReflectionUtil;
public class MultiMethodContainer<I extends Object, C extends Object> implements MethodContainer<I, C> {
private Set<Entry<I, C>> entries;
private ReflectedMethodFactory<I, C> factory;
public MultiMethodContainer(ReflectedMethodFactory<I, C> factory) {
this.entries = new HashSet<>();
this.factory = factory;
}
@Override
public MethodContainer<I, C> addMethod(ReflectedMethod<I, C> method) {
Optional<Entry<I, C>> oentry = this.entries.stream()
.filter(e -> method.identifier().equals(e.identifier()))
.findFirst();
Entry<I, C> entry = oentry.orElse(new Entry<>(method.identifier())).addMethod(method);
if (oentry.isEmpty()) this.entries.add(entry);
return this;
}
@Override
public MethodContainer<I, C> addMethods(Object containingObject, Method... methods) {
for (Method method : methods)
this.factory.produce(method, containingObject)
.ifPresent(this::addMethod);
return this;
}
@Override
public Set<I> identifiers() {
return entries().stream()
.map(Entry::identifier)
.collect(Collectors.toSet());
}
@Override
public Collection<ReflectedMethod<I, C>> methods() {
return this.entries.stream()
.flatMap(e -> e.methods.stream())
.toList();
}
@Override
public Collection<ReflectedMethod<I, C>> findFor(I identifier) {
return this.entries.stream()
.filter(e -> e.identifier().equals(identifier))
.flatMap(e -> e.methods.stream())
.toList();
}
@Override
public Optional<ReflectedMethod<I, C>> findFirstFor(I identifier) {
return this.entries.stream()
.filter(e -> e.identifier().equals(identifier))
.flatMap(e -> e.methods.stream())
.findFirst();
}
protected Set<Entry<I, C>> entries() {
return this.entries;
}
public ReflectedMethodFactory<I, C> factory() {
return this.factory;
}
public static record Entry<I extends Object, C extends Object>(I identifier, Set<ReflectedMethod<I, C>> methods) {
public Entry(I identifier) {
this(identifier, new HashSet<>());
}
private static Logger logger = LoggerFactory.getLogger(Entry.class);
public Entry<I, C> addMethod(ReflectedMethod<I, C> method) {
I midentifier = method.identifier();
if (!this.identifier().equals(midentifier))
throw new IllegalArgumentException("Method's identifier did not equal the entry's identifier");
this.methods.add(method);
logger.debug("Added method {} to entry {}", ReflectionUtil.getFullMethodName(method.method()), this);
return this;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Entry))
return false;
Entry<?, ?> other = (Entry<?, ?>) obj;
return other.identifier().equals(identifier());
}
@Override
public int hashCode() {
return this.identifier().hashCode();
}
@Override
public String toString() {
return "Ident(%s)".formatted(identifier().toString());
}
}
}

View File

@@ -0,0 +1,20 @@
package net.tomatentum.cutin.factory;
import java.util.Set;
import net.tomatentum.cutin.MethodParser;
public abstract class ImmutableMethodFactory<I extends Object, C extends Object> implements ReflectedMethodFactory.Factory<I, C> {
protected Set<MethodParser> parser;
protected ImmutableMethodFactory(MethodParser... parser) {
this.parser = Set.of(parser);
}
@Override
public Set<MethodParser> parser() {
return this.parser;
}
}

View File

@@ -0,0 +1,17 @@
package net.tomatentum.cutin.factory;
import net.tomatentum.cutin.MethodParser;
public abstract class MutableMethodFactory<I extends Object, C extends Object> extends ImmutableMethodFactory<I, C> {
public MutableMethodFactory<I, C> addParser(MethodParser parser) {
super.parser.add(parser);
return this;
}
public MutableMethodFactory<I, C> removeParser(MethodParser parser) {
super.parser.remove(parser);
return this;
}
}

View File

@@ -0,0 +1,42 @@
package net.tomatentum.cutin.factory;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Optional;
import java.util.Set;
import net.tomatentum.cutin.MethodParser;
import net.tomatentum.cutin.method.ReflectedMethod;
public interface ReflectedMethodFactory<I extends Object, C extends Object> {
Optional<ReflectedMethod<I, C>> produce(Method method, Object containingClass);
ReflectedMethodFactory<I, C> addFactory(Factory<I, C> factory);
Set<ReflectedMethodFactory.Factory<I, C>> factories();
public interface Factory<I extends Object, C extends Object> {
Optional<ReflectedMethod<I, C>> produce(Method method, Object containingObject, ParserResults parserResults);
Set<MethodParser> parser();
}
public static class ParserResults extends HashMap<Class<? extends MethodParser>, Object> {
public static ParserResults create(Set<MethodParser> parser, Method method, Object containingObject) {
ParserResults results = new ParserResults();
for (MethodParser p : parser) {
results.put(p.getClass(), p.parse(method, containingObject));
}
return results;
}
private ParserResults() {}
@SuppressWarnings("unchecked")
public <T extends Object> T get(Class<? extends MethodParser> key) {
return (T) super.get(key);
}
}
}

View File

@@ -0,0 +1,63 @@
package net.tomatentum.cutin.factory;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.tomatentum.cutin.MethodParser;
import net.tomatentum.cutin.method.ReflectedMethod;
import net.tomatentum.cutin.util.ReflectionUtil;
public class ReflectedMethodFactoryImpl<I extends Object, C extends Object> implements ReflectedMethodFactory<I, C> {
private Logger logger = LoggerFactory.getLogger(getClass());
private Set<Factory<I, C>> factories;
public ReflectedMethodFactoryImpl() {
this(new HashSet<>());
}
public ReflectedMethodFactoryImpl(Set<Factory<I, C>> factories) {
this.factories = factories;
}
@Override
public Optional<ReflectedMethod<I, C>> produce(Method method, Object containingClass) {
Optional<ReflectedMethod<I, C>> rmethod = this.factories.stream()
.map(f -> factoryProduce(f, method, containingClass))
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
if (rmethod.isEmpty())
logger.warn("Could not produce a ReflectedMethod for Method {} in {}", ReflectionUtil.getFullMethodName(method), this);
else
logger.debug("Produced {} for Method {} in {}", rmethod.get(), ReflectionUtil.getFullMethodName(method), this);
return rmethod;
}
@Override
public ReflectedMethodFactory<I, C> addFactory(Factory<I, C> factory) {
this.factories.add(factory);
logger.trace("Added Factory {} to {}", factory, this);
return this;
}
@Override
public Set<Factory<I, C>> factories() {
return this.factories;
}
private Optional<ReflectedMethod<I, C>> factoryProduce(Factory<I, C> factory, Method method, Object containingClass) {
Set<MethodParser> parser = factory.parser();
ParserResults results = ParserResults.create(parser, method, containingClass);
return factory.produce(method, containingClass, results);
}
}

View File

@@ -0,0 +1,70 @@
package net.tomatentum.cutin.method;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.tomatentum.cutin.util.ReflectionUtil;
public abstract class BestCandidateMethod<I extends Object, C extends Object> extends ReflectedMethod<I, C> {
private Logger logger = LoggerFactory.getLogger(getClass());
private String methodName;
protected BestCandidateMethod(String methodName, Object containingObject) {
super(getMethod(containingObject, methodName), containingObject);
this.methodName = methodName;
}
@Override
public Object run(C context) {
Method[] methods = Arrays.stream(containingObject.getClass().getDeclaredMethods())
.filter(x -> x.getName().equals(methodName))
.filter(x -> !x.isBridge())
.toArray(Method[]::new);
Class<?>[] parameters = getCurrentParameterList(context).stream()
.map(Object::getClass)
.toArray(Class<?>[]::new);
super.method = ReflectionUtil.getMostSpecificMethod(methods, parameters);
logger.trace("Found {} for {}({}) in {}", ReflectionUtil.getFullMethodName(method()), this.methodName, String.join(", ", Arrays.stream(parameters).map(Object::toString).toList()), this);
return super.run(context);
}
private static Method getMethod(Object containingMethod, String methodName) {
return Arrays.stream(containingMethod.getClass().getDeclaredMethods())
.filter(m -> m.getName().equals(methodName))
.findFirst().orElse(null);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof BestCandidateMethod<?, ?> bcMethod) {
return this.containingObject().getClass().equals(bcMethod.containingObject().getClass()) &&
this.methodName.equals(bcMethod.methodName);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(containingObject.getClass(), methodName);
}
private List<Object> getCurrentParameterList(C context) {
List<Object> parameters = new ArrayList<>();
int c = 0;
Object last;
while ((last = getParameter(context, c)) != null) {
parameters.add(last);
c++;
}
return parameters;
}
}

View File

@@ -1,4 +1,4 @@
package net.tomatentum.cutin; package net.tomatentum.cutin.method;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@@ -12,28 +12,31 @@ import org.slf4j.LoggerFactory;
import net.tomatentum.cutin.util.ReflectionUtil; import net.tomatentum.cutin.util.ReflectionUtil;
public abstract class ReflectedMethod { public abstract class ReflectedMethod<I extends Object, C extends Object> {
private Logger logger = LoggerFactory.getLogger(getClass()); private Logger logger = LoggerFactory.getLogger(getClass());
private Method method; protected Method method;
private Object containingObject; protected Object containingObject;
public ReflectedMethod(Method method, Object containingObject) { protected ReflectedMethod(Method method, Object containingObject) {
if (!Arrays.asList(containingObject.getClass().getMethods()).contains(method)) if (!Arrays.asList(containingObject.getClass().getDeclaredMethods()).contains(method))
throw new InvalidParameterException("Method does not apply to specified handler"); throw new InvalidParameterException("Method does not apply to specified handler");
this.method = method; this.method = method;
this.containingObject = containingObject; this.containingObject = containingObject;
} }
public abstract Object getParameter(Object context, int index); public abstract Object getParameter(C context, int index);
public Object run(Object context) { public abstract I identifier();
public Object run(C context) {
method.setAccessible(true); method.setAccessible(true);
try { try {
logger.debug("Invoking method {} from {}", ReflectionUtil.getFullMethodName(method), this);
return method.invoke(containingObject, getParameters(context)); return method.invoke(containingObject, getParameters(context));
}catch (IllegalAccessException | InvocationTargetException ex) { }catch (IllegalAccessException | InvocationTargetException ex) {
logger.error("ReflectedMethod failed to run", ex); logger.error("ReflectedMethod %s failed to run".formatted(this), ex);
return null; return null;
} }
} }
@@ -46,18 +49,18 @@ public abstract class ReflectedMethod {
return this.containingObject; return this.containingObject;
} }
private Object[] getParameters(Object context) { @Override
public String toString() {
return "ReflectedMethod(%s)".formatted(identifier());
}
private Object[] getParameters(C context) {
int parameterCount = method.getParameterCount(); int parameterCount = method.getParameterCount();
List<Object> parameters = new ArrayList<>(); List<Object> parameters = new ArrayList<>();
for (int i = 0; i < parameterCount; i++) { for (int i = 0; i < parameterCount; i++) {
Object parameter; Object parameter = getParameter(context, i);
if (i == 0) { logger.trace("Found parameter {}={} for method {} in {}", parameter != null ? parameter.getClass().toString() : " ", parameter, ReflectionUtil.getFullMethodName(method), this);
parameter = context;
}else
parameter = getParameter(context, i-1);
logger.trace("Found parameter {}={} for method {}", parameter != null ? parameter.getClass().toString() : " ", parameter, ReflectionUtil.getFullMethodName(method));
parameters.add(parameter); parameters.add(parameter);
} }
return parameters.toArray(); return parameters.toArray();

View File

@@ -4,16 +4,34 @@ import java.lang.annotation.Annotation;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Stream;
public final class ReflectionUtil { public final class ReflectionUtil {
public static <T extends Annotation> boolean isAnnotationPresent(Method method, Class<T> annotationClass) { private ReflectionUtil() {}
if (method.isAnnotationPresent(annotationClass) || method.getDeclaringClass().isAnnotationPresent(annotationClass))
return true;
return false; private static final Map<Class<?>, Class<?>> PRIMITIVE_TO_BOXED = Map.ofEntries(
Map.entry(boolean.class, Boolean.class),
Map.entry(byte.class, Byte.class),
Map.entry(char.class, Character.class),
Map.entry(double.class, Double.class),
Map.entry(float.class, Float.class),
Map.entry(int.class, Integer.class),
Map.entry(long.class, Long.class),
Map.entry(short.class, Short.class),
Map.entry(void.class, Void.class)
);
public static Class<?> box(Class<?> type) {
return type.isPrimitive() ? PRIMITIVE_TO_BOXED.get(type) : type;
}
public static <T extends Annotation> boolean isAnnotationPresent(Method method, Class<T> annotationClass) {
return method.isAnnotationPresent(annotationClass) || method.getDeclaringClass().isAnnotationPresent(annotationClass);
} }
public static <T extends Annotation> T getAnnotation(Method method, Class<T> annotationClass) { public static <T extends Annotation> T getAnnotation(Method method, Class<T> annotationClass) {
@@ -22,6 +40,14 @@ public final class ReflectionUtil {
method.getDeclaringClass().getAnnotation(annotationClass); method.getDeclaringClass().getAnnotation(annotationClass);
} }
public static Stream<Object> getReturnAsStream(Object ret) {
if (ret instanceof Object[] array)
return Stream.of(array);
if (ret instanceof Collection<?> coll)
return coll.stream().map(o -> (Object)o);
return Stream.of(ret);
}
public static int getCastDepth(Class<?> child, Class<?> parent) { public static int getCastDepth(Class<?> child, Class<?> parent) {
if (parent.equals(Object.class)) if (parent.equals(Object.class))
@@ -57,19 +83,20 @@ public final class ReflectionUtil {
.filter(x -> isMethodCallable(x, parameters)) .filter(x -> isMethodCallable(x, parameters))
.toList(); .toList();
if (compatibleMethods.size() == 0) if (compatibleMethods.isEmpty())
throw new IllegalArgumentException("There are no compatible Methods provided"); throw new IllegalArgumentException("There are no compatible Methods provided");
for (int i = 0; i < parameters.length; i++) { for (int i = 0; i < parameters.length; i++) {
final int currI = i; final int currI = i;
Class<?>[] parameterTypes = compatibleMethods.stream() Class<?>[] parameterTypes = compatibleMethods.stream()
.map(x -> x.getParameterTypes()[currI]) .map(x -> getBoxedParameterTypes(x)[currI])
.toArray(x -> new Class[x]); .toArray(x -> new Class[x]);
Class<?> mostSpecific = getMostSpecificClass(parameterTypes, parameters[i]); Class<?> mostSpecific = getMostSpecificClass(parameterTypes, parameters[i]);
compatibleMethods = compatibleMethods.stream() compatibleMethods = compatibleMethods.stream()
.filter(x -> Objects.equals(x.getParameterTypes()[currI], mostSpecific)) .filter(x -> Objects.equals(getBoxedParameterTypes(x)[currI], mostSpecific))
.toList(); .toList();
} }
@@ -93,7 +120,7 @@ public final class ReflectionUtil {
if (!Objects.equals(method.getParameterCount(), parameters.length)) if (!Objects.equals(method.getParameterCount(), parameters.length))
return false; return false;
Class<?>[] methodParams = method.getParameterTypes(); Class<?>[] methodParams = getBoxedParameterTypes(method);
for (int i = 0; i < parameters.length; i++) { for (int i = 0; i < parameters.length; i++) {
if (!methodParams[i].isAssignableFrom(parameters[i])) if (!methodParams[i].isAssignableFrom(parameters[i]))
return false; return false;
@@ -101,7 +128,14 @@ public final class ReflectionUtil {
return true; return true;
} }
public static Class<?>[] getBoxedParameterTypes(Method method) {
return Arrays.stream(method.getParameterTypes())
.map(ReflectionUtil::box)
.toArray(Class<?>[]::new);
}
public static String getFullMethodName(Method method) { public static String getFullMethodName(Method method) {
return method.getDeclaringClass().getName() + "." + method.getName(); List<String> parameters = Arrays.stream(method.getParameterTypes()).map(Object::toString).toList();
return String.format("%s.%s(%s)", method.getDeclaringClass().getName(), method.getName(), String.join(", ", parameters));
} }
} }

View File

@@ -0,0 +1,28 @@
package net.tomatentum.cutin;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
import net.tomatentum.cutin.method.ReflectedMethod;
class ReflectedMethodTest {
@Test
void methodTest() {
ReflectedMethod<String, String> method = new TestReflectedMethod(new TestMethodClass());
Object result = method.run("testContext");
assertTrue((boolean)result);
System.out.println("Success");
}
@Test
void testBCMethod() {
ReflectedMethod<String, Double> method = new TestBestCandidateMethod(
"test",
new TestMethodClass());
Object result = method.run((double)4);
assertTrue((boolean)result);
System.out.println("Success");
}
}

View File

@@ -0,0 +1,28 @@
package net.tomatentum.cutin;
import net.tomatentum.cutin.method.BestCandidateMethod;
public class TestBestCandidateMethod extends BestCandidateMethod<String, Double> {
protected TestBestCandidateMethod(String methodName, Object containingObject) {
super(methodName, containingObject);
}
@Override
public Object getParameter(Double context, int index) {
switch (index) {
case 0:
return context;
case 1:
return "testString";
default:
return null;
}
}
@Override
public String identifier() {
return "ident";
}
}

View File

@@ -0,0 +1,18 @@
package net.tomatentum.cutin;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TestMethodClass {
boolean test(String context, int testNumber) {
assertEquals("testContext", context);
assertEquals(2, testNumber);
return true;
}
boolean test(double context, String testString) {
assertEquals(4, context);
assertEquals("testString", testString);
return true;
}
}

View File

@@ -0,0 +1,41 @@
package net.tomatentum.cutin;
import java.lang.reflect.Method;
import net.tomatentum.cutin.method.ReflectedMethod;
public class TestReflectedMethod extends ReflectedMethod<String, String> {
protected TestReflectedMethod(Object containingObject) {
super(getMethod(containingObject), containingObject);
}
@Override
public Object getParameter(String context, int index) {
switch (index) {
case 0:
return context;
case 1:
return 2;
default:
return null;
}
}
@Override
public String identifier() {
return method().getName();
}
private static Method getMethod(Object containingObject) {
try {
return containingObject.getClass().getDeclaredMethod("test", String.class, int.class);
} catch (NoSuchMethodException | SecurityException e) {
e.printStackTrace();
}
return null;
}
}

View File

@@ -0,0 +1,7 @@
# SLF4J's SimpleLogger configuration file
# Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err.
# Default logging detail level for all instances of SimpleLogger.
# Must be one of ("trace", "debug", "info", "warn", or "error").
# If not specified, defaults to "info".
org.slf4j.simpleLogger.defaultLogLevel=trace