diff --git a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/base/LeetcodeInvoker.java b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/base/LeetcodeInvoker.java index 8a4e36a..70efdcd 100644 --- a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/base/LeetcodeInvoker.java +++ b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/base/LeetcodeInvoker.java @@ -85,4 +85,37 @@ public interface LeetcodeInvoker { * in the invoking process. */ Object invoke(Object object, Object... args) throws Throwable; + + /** + * Returns a string describing this {@code Constructor}, + * including type parameters. The string is formatted as the + * constructor access modifiers, if any, followed by an + * angle-bracketed comma separated list of the constructor's type + * parameters, if any, followed by the fully-qualified name of the + * declaring class, followed by a parenthesized, comma-separated + * list of the constructor's generic formal parameter types. + *

+ * If this constructor was declared to take a variable number of + * arguments, instead of denoting the last parameter as + * "Type[]", it is denoted as + * "Type...". + *

+ * A space is used to separate access modifiers from one another + * and from the type parameters or return type. If there are no + * type parameters, the type parameter list is elided; if the type + * parameter list is present, a space separates the list from the + * class name. If the constructor is declared to throw + * exceptions, the parameter list is followed by a space, followed + * by the word "{@code throws}" followed by a + * comma-separated list of the thrown exception types. + * + *

The only possible modifiers for constructors are the access + * modifiers {@code public}, {@code protected} or + * {@code private}. Only one of these may appear, or none if the + * constructor has default (package) access. + * + * @return a string describing this {@code Constructor}, + * include type parameters + */ + String toGenericString(); } diff --git a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/LeetcodeJavaDebugEnhanceProcessor.java b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/LeetcodeJavaDebugEnhanceProcessor.java index b8595b4..8f4f57b 100644 --- a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/LeetcodeJavaDebugEnhanceProcessor.java +++ b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/LeetcodeJavaDebugEnhanceProcessor.java @@ -16,23 +16,18 @@ package io.github.jidcoo.opto.lcdb.enhancer.core; -import io.github.jidcoo.opto.lcdb.enhancer.core.parser.InputParserProcessor; +import io.github.jidcoo.opto.lcdb.enhancer.LeetcodeJavaDebugEnhancer; +import io.github.jidcoo.opto.lcdb.enhancer.core.pipeline.LeetcodeJavaDebugEnhancerPipelineProcessor; import io.github.jidcoo.opto.lcdb.enhancer.utils.EnhancerLogUtil; import io.github.jidcoo.opto.lcdb.enhancer.utils.ReflectUtil; -import io.github.jidcoo.opto.lcdb.enhancer.LeetcodeJavaDebugEnhancer; -import io.github.jidcoo.opto.lcdb.enhancer.base.InputProvider; -import io.github.jidcoo.opto.lcdb.enhancer.base.OutputConsumer; -import io.github.jidcoo.opto.lcdb.enhancer.core.executor.LeetcodeExecutorFactory; -import io.github.jidcoo.opto.lcdb.enhancer.core.executor.LeetcodeExecutorProcessor; -import io.github.jidcoo.opto.lcdb.enhancer.core.io.IOFactory; -import io.github.jidcoo.opto.lcdb.enhancer.core.parser.InputParserFactory; -import io.github.jidcoo.opto.lcdb.enhancer.core.printer.OutputPrinterFactory; -import io.github.jidcoo.opto.lcdb.enhancer.core.printer.OutputPrinterProcessor; /** *

LeetcodeJavaDebugEnhanceProcessor is a primary enhancer.

- *

All features of the {@link LeetcodeJavaDebugEnhancer} will - * be controlled and dominated by this processor.

+ * + *

In version 1.0.1 and later, the control and support for + * all features of {@link LeetcodeJavaDebugEnhancer} have been + * moved from {@link LeetcodeJavaDebugEnhanceProcessor} to + * {@link LeetcodeJavaDebugEnhancerPipelineProcessor}.

* *

By the way, today is a so bad day for me. Because I * still need coding on my day off. Can you give me @@ -42,7 +37,7 @@ * @author Jidcoo * @since 1.0 */ -public class LeetcodeJavaDebugEnhanceProcessor { +public final class LeetcodeJavaDebugEnhanceProcessor { /** *

Do leetcode debugging enhance process with an AT class.

@@ -56,46 +51,15 @@ public static void process(Class AT) throws // Setup EnhancerLog log level. EnhancerLogUtil.setLogLevel(enhancer.getEnhancerLogLevel()); - EnhancerLogUtil.logI("Starting do debugging enhance at (AT) class: %s", AT.getSimpleName()); - // Create a LeetcodeExecutor from the enhancer. - Object leetcodeExecutor = LeetcodeExecutorFactory.getLeetcodeExecutor(enhancer); - // Create an OutputPrinter from the enhancer. - Object outputPrinter = OutputPrinterFactory.getOutputPrinter(enhancer); - // Create a InputParser from the enhancer. - Object inputParser = InputParserFactory.getInputParser(enhancer); + EnhancerLogUtil.logI("Start leetcode debugging enhancer at (AT) class: %s", AT.getSimpleName()); - try ( - // Get or create a InputProvider from the enhancer. - InputProvider inputProvider = IOFactory.getInputProvider(enhancer); - // Get or create OutputConsumer from the enhancer. - OutputConsumer outputConsumer = IOFactory.getOutputConsumer(enhancer) - - ) { - // Now we can happily run the io loop to perform leetcode debugging enhancements. - EnhancerLogUtil.logI("Running leetcode debugging enhancer at (AT) class: %s", AT.getSimpleName()); - while (true) { - // Provide the next string input from the InputProvider. - String input = inputProvider.provideNextInput(); - // We need to break this loop when the input indicates end. - if (inputProvider.isEnd(input)) { - break; - } - // Parse the string input to input object. - Object inputObject = InputParserProcessor.process(inputParser, leetcodeExecutor, input); - // Execute leetcode target and get the output object. - Object outputObject = LeetcodeExecutorProcessor.process(leetcodeExecutor, inputObject); - // Print the output object. - String output = OutputPrinterProcessor.process(outputPrinter, leetcodeExecutor, outputObject); - // Consume the next output string to the OutputConsumer. - outputConsumer.consumeNextOutput(output); - } - } + // Do process debugging enhancement pipeline with AT instance. (Since 1.0.1) + LeetcodeJavaDebugEnhancerPipelineProcessor.process(enhancer); // Now, the enhancement work has ended here. It's time to say goodbye. // Wishing all programmers around the world the true joy of life in Leetcode. // May you be so powerful that you don't need debugging and have no bugs. // Good luck!!!. - EnhancerLogUtil.logI("Stopped leetcode debugging enhancer at (AT) class: %s", AT.getSimpleName()); + EnhancerLogUtil.logI("Stop leetcode debugging enhancer at (AT) class: %s", AT.getSimpleName()); } - } diff --git a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/executor/ConstructorLeetcodeInvoker.java b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/executor/ConstructorLeetcodeInvoker.java index 7c75500..6d5167e 100644 --- a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/executor/ConstructorLeetcodeInvoker.java +++ b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/executor/ConstructorLeetcodeInvoker.java @@ -21,6 +21,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Parameter; +import java.util.Arrays; /** *

ConstructorLeetcodeInvoker is an invoker @@ -42,6 +43,11 @@ final class ConstructorLeetcodeInvoker implements LeetcodeInvoker { */ private final Integer id; + /** + * Friendly matching mode flag. + */ + private boolean matchingFriendly; + /** * Create a ConstructorLeetcodeInvoker instance. * @@ -54,6 +60,8 @@ final class ConstructorLeetcodeInvoker implements LeetcodeInvoker { // Make accessible. this.constructor.setAccessible(true); this.id = id; + // Friendly-Matching-Mode is closed by default. + this.matchingFriendly = false; } /** @@ -73,7 +81,11 @@ public Integer getId() { */ @Override public int getParameterCount() { - return this.constructor.getParameterCount(); + int parameterCount = this.constructor.getParameterCount(); + if (parameterCount > 0 && matchingFriendly) { + return parameterCount - 1; + } + return parameterCount; } /** @@ -83,7 +95,11 @@ public int getParameterCount() { */ @Override public String getInvokerName() { - return this.constructor.getName(); + String name = this.constructor.getName(); + if (name.contains("$") && matchingFriendly) { + return name.substring(name.lastIndexOf('$') + 1); + } + return name; } /** @@ -93,7 +109,11 @@ public String getInvokerName() { */ @Override public Class[] getParameterTypes() { - return this.constructor.getParameterTypes(); + Class[] parameterTypes = this.constructor.getParameterTypes(); + if (parameterTypes.length > 0 && matchingFriendly) { + return Arrays.stream(parameterTypes).skip(1).toArray(Class[]::new); + } + return parameterTypes; } /** @@ -103,7 +123,11 @@ public Class[] getParameterTypes() { */ @Override public Parameter[] getParameters() { - return this.constructor.getParameters(); + Parameter[] parameters = this.constructor.getParameters(); + if (parameters.length > 0 && matchingFriendly) { + return Arrays.stream(parameters).skip(1).toArray(Parameter[]::new); + } + return parameters; } /** @@ -127,6 +151,45 @@ public Class getReturnType() { */ @Override public Object invoke(Object object, Object... args) throws Throwable { - return this.constructor.newInstance(object, args); + Object[] initArgsArray = new Object[args.length + 1]; + initArgsArray[0] = object; + System.arraycopy(args, 0, initArgsArray, 1, args.length); + return this.constructor.newInstance(initArgsArray); + } + + /** + * Returns a string describing this {@code Constructor}, + * including type parameters. The string is formatted as the + * constructor access modifiers, if any, followed by an + * angle-bracketed comma separated list of the constructor's type + * parameters, if any, followed by the fully-qualified name of the + * declaring class, followed by a parenthesized, comma-separated + * list of the constructor's generic formal parameter types. + *

+ * If this constructor was declared to take a variable number of + * arguments, instead of denoting the last parameter as + * "Type[]", it is denoted as + * "Type...". + *

+ * A space is used to separate access modifiers from one another + * and from the type parameters or return type. If there are no + * type parameters, the type parameter list is elided; if the type + * parameter list is present, a space separates the list from the + * class name. If the constructor is declared to throw + * exceptions, the parameter list is followed by a space, followed + * by the word "{@code throws}" followed by a + * comma-separated list of the thrown exception types. + * + *

The only possible modifiers for constructors are the access + * modifiers {@code public}, {@code protected} or + * {@code private}. Only one of these may appear, or none if the + * constructor has default (package) access. + * + * @return a string describing this {@code Constructor}, + * include type parameters + */ + @Override + public String toGenericString() { + return this.constructor.toGenericString(); } } diff --git a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/executor/LeetcodeExecutor.java b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/executor/LeetcodeExecutor.java index 4490a54..2cd7d08 100644 --- a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/executor/LeetcodeExecutor.java +++ b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/executor/LeetcodeExecutor.java @@ -17,10 +17,13 @@ package io.github.jidcoo.opto.lcdb.enhancer.core.executor; import io.github.jidcoo.opto.lcdb.enhancer.base.EnhancerException; +import io.github.jidcoo.opto.lcdb.enhancer.base.LeetcodeInvoker; import io.github.jidcoo.opto.lcdb.enhancer.utils.AssertUtil; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; /** *

LeetcodeExecutor is an executor used to @@ -45,30 +48,38 @@ final class LeetcodeExecutor { /** * The leetcode executor. + * + * @since 1.0.1 */ - private final Method executor; + private LeetcodeInvoker executor; /** - * The leetcode invoker. + * The candidate leetcode invokers. + * + * @since 1.0.1 */ - private Method invoker; + private final List candidateInvokers; /** * The leetcode invoker response type. */ - private Class invokerResponseType; + private Class invokerResponseType; /** * Create a LeetcodeExecutor instance. * * @param instance the leetcode instance. * @param executor the leetcode executor. + * @since 1.0.1 */ - LeetcodeExecutor(Object instance, Method executor) { + LeetcodeExecutor(Object instance, LeetcodeInvoker executor) { this.instance = instance; this.executor = executor; - // Set executor as default leetcode invoker. - this.invoker = executor; + this.candidateInvokers = new ArrayList<>(); + if (Objects.nonNull(executor)) { + // Add executor to candidate leetcode invokers list. + this.candidateInvokers.add(executor); + } } /** @@ -81,10 +92,9 @@ final class LeetcodeExecutor { * or invoker error. */ Object execute(Object input) { - AssertUtil.nonNull(invoker, "The leetcode execute invoker cannot be null."); + AssertUtil.nonNull(executor, "The leetcode executor cannot be null."); try { - invoker.setAccessible(true); - return invoker.invoke(instance, (Object[]) input); + return executor.invoke(instance, (Object[]) input); } catch (InvocationTargetException exception) { throw new EnhancerException(exception.getCause()); } catch (Throwable exception) { @@ -106,16 +116,27 @@ Object getInstance() { * * @return the leetcode executor. */ - Method getExecutor() { + LeetcodeInvoker getExecutor() { return executor; } /** - * Get the leetcode invoker. + * Set the leetcode executor. + * + * @param executor the leetcode executor. + * @since 1.0.1 + */ + void setExecutor(LeetcodeInvoker executor) { + this.executor = executor; + } + + /** + * Get the candidate leetcode invokers list. * - * @return the leetcode invoker. + * @return the candidate leetcode invokers list. + * @since 1.0.1 */ - Method getInvoker() { - return invoker; + List getCandidateInvokers() { + return this.candidateInvokers; } } \ No newline at end of file diff --git a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/executor/LeetcodeExecutorFactory.java b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/executor/LeetcodeExecutorFactory.java index 9264e14..67fa06e 100644 --- a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/executor/LeetcodeExecutorFactory.java +++ b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/executor/LeetcodeExecutorFactory.java @@ -16,11 +16,9 @@ package io.github.jidcoo.opto.lcdb.enhancer.core.executor; -import io.github.jidcoo.opto.lcdb.enhancer.LeetcodeJavaDebugEnhancer; +import io.github.jidcoo.opto.lcdb.enhancer.base.LeetcodeInvoker; import io.github.jidcoo.opto.lcdb.enhancer.utils.AssertUtil; -import io.github.jidcoo.opto.lcdb.enhancer.utils.ReflectUtil; -import java.lang.reflect.Method; import java.util.Objects; /** @@ -34,22 +32,24 @@ public final class LeetcodeExecutorFactory { /** - * Product a LeetcodeExecutor instance by - * {@link LeetcodeJavaDebugEnhancer} instance. + * Product a LeetcodeExecutor instance by leetcode instance. * - * @param enhancer the LeetcodeJavaDebugEnhancer instance. + * @param instance the leetcode instance. + * @param leetcodeInvokers the leetcode invokers. * @return the LeetcodeExecutor instance. + * @since 1.0.1 */ - public static LeetcodeExecutor getLeetcodeExecutor(LeetcodeJavaDebugEnhancer enhancer) { - // Hate the npe. - AssertUtil.nonNull(enhancer, "The enhancer cannot be null."); - Method enhancementPoint = enhancer.getEnhancementPoint(); - Object target = enhancer; - // Try to look for the inner class Solution in AT if the enhancementPoint is null. - if (Objects.isNull(enhancementPoint)) { - target = ReflectUtil.resolveSolutionInstance(enhancer); - AssertUtil.nonNull(target, "Cannot resolve the inner class Solution from the AT enhancer instance."); + public static LeetcodeExecutor getLeetcodeExecutor(Object instance, LeetcodeInvoker... leetcodeInvokers) { + AssertUtil.nonNull(instance, "The instance cannot be null."); + LeetcodeInvoker primaryInvoker = leetcodeInvokers.length > 0 ? leetcodeInvokers[0] : null; + LeetcodeExecutor executor = new LeetcodeExecutor(instance, primaryInvoker); + for (int i = 1; i < leetcodeInvokers.length; i++) { + LeetcodeInvoker leetcodeInvoker = leetcodeInvokers[i]; + if (Objects.nonNull(leetcodeInvoker)) { + // Add non-null leetcode invoker. + executor.getCandidateInvokers().add(leetcodeInvoker); + } } - return new LeetcodeExecutor(target, enhancementPoint); + return executor; } } diff --git a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/executor/MethodLeetcodeInvoker.java b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/executor/MethodLeetcodeInvoker.java index ba083dd..628aab3 100644 --- a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/executor/MethodLeetcodeInvoker.java +++ b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/executor/MethodLeetcodeInvoker.java @@ -129,4 +129,40 @@ public Class getReturnType() { public Object invoke(Object object, Object... args) throws Throwable { return this.method.invoke(object, args); } + + /** + * Returns a string describing this {@code Constructor}, + * including type parameters. The string is formatted as the + * constructor access modifiers, if any, followed by an + * angle-bracketed comma separated list of the constructor's type + * parameters, if any, followed by the fully-qualified name of the + * declaring class, followed by a parenthesized, comma-separated + * list of the constructor's generic formal parameter types. + *

+ * If this constructor was declared to take a variable number of + * arguments, instead of denoting the last parameter as + * "Type[]", it is denoted as + * "Type...". + *

+ * A space is used to separate access modifiers from one another + * and from the type parameters or return type. If there are no + * type parameters, the type parameter list is elided; if the type + * parameter list is present, a space separates the list from the + * class name. If the constructor is declared to throw + * exceptions, the parameter list is followed by a space, followed + * by the word "{@code throws}" followed by a + * comma-separated list of the thrown exception types. + * + *

The only possible modifiers for constructors are the access + * modifiers {@code public}, {@code protected} or + * {@code private}. Only one of these may appear, or none if the + * constructor has default (package) access. + * + * @return a string describing this {@code Constructor}, + * include type parameters + */ + @Override + public String toGenericString() { + return this.method.toGenericString(); + } } diff --git a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/io/builtin/ConsoleOutputConsumer.java b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/io/builtin/ConsoleOutputConsumer.java index 08246aa..ed7d3eb 100644 --- a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/io/builtin/ConsoleOutputConsumer.java +++ b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/io/builtin/ConsoleOutputConsumer.java @@ -18,6 +18,8 @@ import io.github.jidcoo.opto.lcdb.enhancer.base.OutputConsumer; +import java.io.Closeable; + /** *

ConsoleOutputConsumer is a {@link OutputConsumer} and * extends on {@link BaseBufferWriterOutputConsumer}.

@@ -41,4 +43,57 @@ public ConsoleOutputConsumer() { // Use the stdout as output source. super(System.out); } + + /** + * Closes this resource, relinquishing any underlying resources. + * This method is invoked automatically on objects managed by the + * {@code try}-with-resources statement. + * + *

While this interface method is declared to throw {@code + * Exception}, implementers are strongly encouraged to + * declare concrete implementations of the {@code close} method to + * throw more specific exceptions, or to throw no exception at all + * if the close operation cannot fail. + * + *

Cases where the close operation may fail require careful + * attention by implementers. It is strongly advised to relinquish + * the underlying resources and to internally mark the + * resource as closed, prior to throwing the exception. The {@code + * close} method is unlikely to be invoked more than once and so + * this ensures that the resources are released in a timely manner. + * Furthermore it reduces problems that could arise when the resource + * wraps, or is wrapped, by another resource. + * + *

Implementers of this interface are also strongly advised + * to not have the {@code close} method throw {@link + * InterruptedException}. + *

+ * This exception interacts with a thread's interrupted status, + * and runtime misbehavior is likely to occur if an {@code + * InterruptedException} is {@linkplain Throwable#addSuppressed + * suppressed}. + *

+ * More generally, if it would cause problems for an + * exception to be suppressed, the {@code AutoCloseable.close} + * method should not throw it. + * + *

Note that unlike the {@link Closeable#close close} + * method of {@link Closeable}, this {@code close} method + * is not required to be idempotent. In other words, + * calling this {@code close} method more than once may have some + * visible side effect, unlike {@code Closeable.close} which is + * required to have no effect if called more than once. + *

+ * However, implementers of this interface are strongly encouraged + * to make their {@code close} methods idempotent. + * + * @throws Exception if this resource cannot be closed + * @since 1.0.1 + */ + @Override + public void close() throws Exception { + // Override close() method and do nothing here + // to prevent the console output stream from + // being closed. + } } diff --git a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/io/builtin/FileInputProvider.java b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/io/builtin/FileInputProvider.java index 35be675..b9ead63 100644 --- a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/io/builtin/FileInputProvider.java +++ b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/io/builtin/FileInputProvider.java @@ -18,7 +18,10 @@ import io.github.jidcoo.opto.lcdb.enhancer.base.InputProvider; -import java.io.*; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; /** *

FileInputProvider is a {@link InputProvider} and diff --git a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/io/builtin/FileOutputConsumer.java b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/io/builtin/FileOutputConsumer.java index 4e25e02..babf747 100644 --- a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/io/builtin/FileOutputConsumer.java +++ b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/io/builtin/FileOutputConsumer.java @@ -18,7 +18,10 @@ import io.github.jidcoo.opto.lcdb.enhancer.base.OutputConsumer; -import java.io.*; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.OutputStream; /** *

FileOutputConsumer is a {@link OutputConsumer} and diff --git a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/parser/IRMatchInputParserNode.java b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/parser/IRMatchInputParserNode.java index 2b5aefe..08ddb99 100644 --- a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/parser/IRMatchInputParserNode.java +++ b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/parser/IRMatchInputParserNode.java @@ -16,14 +16,17 @@ package io.github.jidcoo.opto.lcdb.enhancer.core.parser; +import io.github.jidcoo.opto.lcdb.enhancer.base.LeetcodeInvoker; +import io.github.jidcoo.opto.lcdb.enhancer.core.executor.LeetcodeInvokerFactory; +import io.github.jidcoo.opto.lcdb.enhancer.utils.ContainerCheckUtil; import io.github.jidcoo.opto.lcdb.enhancer.utils.EnhancerLogUtil; import io.github.jidcoo.opto.lcdb.enhancer.utils.GsonUtil; import javax.annotation.Resource; import java.io.*; -import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.*; +import java.util.stream.Collectors; /** *

IRMatchInputParserNode is an input parser node.

@@ -77,22 +80,18 @@ public int getOrder() { @Override Object parse(InputParserContext context) { // Define the boss invoker. - Method bossInvoker = null; + LeetcodeInvoker bossInvoker = null; // Define the boss input. List bossInput = new ArrayList<>(); // Peek last input. List input = (List) context.peekInput(); // Fetch all possible leetcode invokers. - Method[] leetcodeInvokers = fetchLeetcodeInvokers(context, input.size()); + List leetcodeInvokers = fetchLeetcodeInvokers(context, input.size()); // Create an invoker-matching tracer map to record the invoker matching detail. - Map>> matchTracer = new HashMap<>(); - if (Objects.nonNull(leetcodeInvokers) && leetcodeInvokers.length > 0) { + Map>> matchTracer = new HashMap<>(); + if (!ContainerCheckUtil.isListEmpty(leetcodeInvokers)) { // Try to match all possible leetcode invokers. - for (Method leetcodeInvoker : leetcodeInvokers) { - // Filter out non-public leetcodeInvoker. - if (leetcodeInvoker.isDefault()) { - continue; - } + for (LeetcodeInvoker leetcodeInvoker : leetcodeInvokers) { // Filter out leetcodeInvoker with different numbers of parameters than the input parameters. if (leetcodeInvoker.getParameterCount() != input.size()) { continue; @@ -135,7 +134,7 @@ Object parse(InputParserContext context) { // Log parameter accepting tracer detail after IR-Matching if the bossInvoker is null. logDetailAfterIRMatching(bossInvoker, leetcodeInvokers, input, matchTracer); - context.setTargetMethod(bossInvoker); + context.setTargetInvoker(bossInvoker); return (bossInput.stream().toArray(Object[]::new)); } @@ -143,22 +142,24 @@ Object parse(InputParserContext context) { * Log tracer detail after IR-Matching if the bossInvoker is null. * * @param bossInvoker the bossInvoker. - * @param invokers the invokers array. + * @param invokers the invokers list. * @param input the input object. * @param matchTracer the match tracer. + * @since 1.0.1 */ - private void logDetailAfterIRMatching(Method bossInvoker, Method[] invokers, List input, Map>> matchTracer) { + private void logDetailAfterIRMatching(LeetcodeInvoker bossInvoker, List invokers, + List input, Map>> matchTracer) { if (Objects.isNull(bossInvoker)) { - if (invokers.length == 0) { - throw new RuntimeException("Cannot find any possible leetcode invoker."); + if (ContainerCheckUtil.isListEmpty(invokers)) { + throw new RuntimeException("Cannot find any possible leetcode invoker. Because the candidate leetcode invokers is empty."); } StringBuilder logBuffer = new StringBuilder(); logBuffer.append("[IR-Matching Tracer Error Report Detail Start]\n"); - logBuffer.append("LeetcodeInvokers: " + invokers.length + ", IRInputs: " + input.size()); + logBuffer.append("LeetcodeInvokers: " + invokers.size() + ", IRInputs: " + input.size()); logBuffer.append("\n\n"); int invokerIdx = 0; - for (Map.Entry>> methodListEntry : + for (Map.Entry>> methodListEntry : matchTracer.entrySet()) { logBuffer.append("LeetcodeInvoker-" + (invokerIdx++)); logBuffer.append(": "); @@ -178,10 +179,13 @@ private void logDetailAfterIRMatching(Method bossInvoker, Method[] invokers, Lis logBuffer.append(tracer.toString()); } } - if (invokerIdx < invokers.length) logBuffer.append("\n"); + if (invokerIdx < invokers.size()) { + logBuffer.append("\n"); + } } logBuffer.append("[IR-Matching Tracer Error Report Detail END]\n"); - EnhancerLogUtil.logE("Cannot match any leetcode invoker for IR-Input: %s\n\n%s", input, logBuffer.toString()); + EnhancerLogUtil.logE("Cannot match any leetcode invoker for IR-Input: %s\n\n%s", input, + logBuffer.toString()); throw new RuntimeException("Cannot match any leetcode invoker for IR-Input."); } } @@ -230,17 +234,31 @@ private Object deepCopy(Object object) { * * @param context the input parser context. * @param invokerParameterSize the limit invoker parameter size - * @return the invokers array. + * @return the invokers list. + * @since 1.0.1 */ - private Method[] fetchLeetcodeInvokers(InputParserContext context, int invokerParameterSize) { - // Return the target method only if target method is not null. - if (Objects.nonNull(context.getTargetMethod())) { - return new Method[]{context.getTargetMethod()}; + private List fetchLeetcodeInvokers(InputParserContext context, int invokerParameterSize) { + // Here are the new features for version 1.0.0 and later. + List leetcodeInvokers = new ArrayList<>(); + // At first, we fetch the candidate invokers list from the context. + if (!ContainerCheckUtil.isListEmpty(context.getCandidateInvokers())) { + leetcodeInvokers.addAll(context.getCandidateInvokers()); + } + // Then, aware target class from context's target instance. + Class targetClass = Objects.nonNull(context.getTargetInstance()) ? + ((context.getTargetInstance() instanceof Class) ? (Class) context.getTargetInstance() : + context.getTargetInstance().getClass()) : null; + if (Objects.nonNull(targetClass)) { + // Get all public methods from the targetClass and convert each method to leetcode invoker. + List publicLeetcodeInvokers = Arrays.stream(targetClass.getDeclaredMethods()) + .filter(m -> Modifier.isPublic(m.getModifiers())) + .filter(m -> !m.isDefault()) + .filter(m -> m.getParameterCount() == invokerParameterSize) + .map(LeetcodeInvokerFactory::getLeetcodeInvoker) + .collect(Collectors.toList()); + // Add public leetcode invokers list to leetcodeInvokers. + leetcodeInvokers.addAll(publicLeetcodeInvokers); } - // Get all public methods as invokers and return it. - return Arrays.stream(context.getTargetInstance().getClass().getDeclaredMethods()) - .filter(m -> Modifier.isPublic(m.getModifiers())) - .filter(m -> m.getParameterCount() == invokerParameterSize) - .toArray(Method[]::new); + return leetcodeInvokers; } } diff --git a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/parser/InputParseTask.java b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/parser/InputParseTask.java index 9b16f85..5f09a57 100644 --- a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/parser/InputParseTask.java +++ b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/parser/InputParseTask.java @@ -16,7 +16,9 @@ package io.github.jidcoo.opto.lcdb.enhancer.core.parser; -import java.lang.reflect.Method; +import io.github.jidcoo.opto.lcdb.enhancer.base.LeetcodeInvoker; + +import java.util.List; /** *

InputParseTask is an input parse task holder.

@@ -36,9 +38,18 @@ final class InputParseTask { private final Object targetInstance; /** - * The target method used for debug. + * The target invoker used for debug. + * + * @since 1.0.1 + */ + private LeetcodeInvoker targetInvoker; + + /** + * The candidate leetcode invokers list for input parsing. + * + * @since 1.0.1 */ - private Method targetMethod; + private List candidateInvokers; /** * The string input used for debug. @@ -48,13 +59,15 @@ final class InputParseTask { /** * Create an InputParseTask instance. * - * @param targetInstance the target instance used for debug. - * @param targetMethod the target method used for debug. - * @param input the string input used for debug. + * @param targetInstance the target instance used for debug. + * @param candidateInvokers the candidate leetcode invokers list for input + * parsing. + * @param input the string input used for debug. + * @since 1.0.1 */ - InputParseTask(Object targetInstance, Method targetMethod, String input) { + InputParseTask(Object targetInstance, List candidateInvokers, String input) { this.targetInstance = targetInstance; - this.targetMethod = targetMethod; + this.candidateInvokers = candidateInvokers; this.input = input; } @@ -68,21 +81,33 @@ Object getTargetInstance() { } /** - * Get the target method used for debug. + * Get the target invoker used for debug. + * + * @return the target invoker. + * @since 1.0.1 + */ + LeetcodeInvoker getTargetInvoker() { + return targetInvoker; + } + + /** + * Set the target invoker used for debug. * - * @return the target method. + * @param targetInvoker the target invoker. + * @since 1.0.1 */ - Method getTargetMethod() { - return targetMethod; + void setTargetInvoker(LeetcodeInvoker targetInvoker) { + this.targetInvoker = targetInvoker; } /** - * Set the target method. + * Get the candidate leetcode invokers list for input parsing. * - * @param targetMethod the target method. + * @return the candidate invokers list. + * @since 1.0.1 */ - public void setTargetMethod(Method targetMethod) { - this.targetMethod = targetMethod; + List getCandidateInvokers() { + return candidateInvokers; } /** diff --git a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/parser/InputParser.java b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/parser/InputParser.java index c19d517..8c69c6d 100644 --- a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/parser/InputParser.java +++ b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/parser/InputParser.java @@ -16,11 +16,11 @@ package io.github.jidcoo.opto.lcdb.enhancer.core.parser; +import io.github.jidcoo.opto.lcdb.enhancer.base.Order; import io.github.jidcoo.opto.lcdb.enhancer.utils.AssertUtil; import io.github.jidcoo.opto.lcdb.enhancer.utils.ContainerCheckUtil; import io.github.jidcoo.opto.lcdb.enhancer.utils.PackageUtil; import io.github.jidcoo.opto.lcdb.enhancer.utils.ReflectUtil; -import io.github.jidcoo.opto.lcdb.enhancer.base.Order; import javax.annotation.Resource; import java.util.ArrayList; @@ -94,7 +94,7 @@ Object parse(InputParseTask inputParseTask) { // Execute parsing chain and return the chain output. Object inputObject = parserChain.parse(inputParserContext); // Set the final leetcode invoker from inputParserContext to the inputParseTask. - inputParseTask.setTargetMethod(inputParserContext.getTargetMethod()); + inputParseTask.setTargetInvoker(inputParserContext.getTargetInvoker()); return inputObject; } @@ -108,8 +108,7 @@ private InputParserContext createParserContext(InputParseTask inputParseTask) { AssertUtil.nonNull(inputParseTask, "The inputParseTask cannot be null"); AssertUtil.nonNull(inputParseTask.getTargetInstance(), "The target cannot be null"); AssertUtil.nonNull(inputParseTask.getInput(), "The input cannot be null"); - AssertUtil.isTrue(!inputParseTask.getInput().isEmpty(), "The input cannot be empty."); return new InputParserContext(inputParseTask.getTargetInstance(), inputParseTask.getInput(), - inputParseTask.getTargetMethod()); + inputParseTask.getCandidateInvokers()); } } diff --git a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/parser/InputParserContext.java b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/parser/InputParserContext.java index eee1dfe..e97f351 100644 --- a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/parser/InputParserContext.java +++ b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/parser/InputParserContext.java @@ -16,8 +16,10 @@ package io.github.jidcoo.opto.lcdb.enhancer.core.parser; -import java.lang.reflect.Method; +import io.github.jidcoo.opto.lcdb.enhancer.base.LeetcodeInvoker; + import java.util.EmptyStackException; +import java.util.List; import java.util.Stack; /** @@ -37,31 +39,41 @@ final class InputParserContext { private final Object targetInstance; /** - * The input stack used for debug. + * The target invoker used for debug. + * + * @since 1.0.1 */ - private final Stack inputStack; + private LeetcodeInvoker targetInvoker; /** - * The target method used for debug. + * The candidate leetcode invokers list for input parsing. + * + * @since 1.0.1 */ - private Method targetMethod; + private List candidateInvokers; + + /** + * The input stack used for debug. + */ + private final Stack inputStack; /** * Create a InputParserContext instance. * - * @param targetInstance the target instance used for debug. - * @param input the string input used for debug. - * @param targetMethod the target method used for debug. + * @param targetInstance the target instance used for debug. + * @param input the string input used for debug. + * @param candidateInvokers the candidate leetcode invokers list for input + * parsing. + * @since 1.0.1 */ - InputParserContext(Object targetInstance, String input, Method targetMethod) { + InputParserContext(Object targetInstance, String input, List candidateInvokers) { this.targetInstance = targetInstance; this.inputStack = new Stack<>(); // Add the first input to the inputStack this.inputStack.push(input); - this.targetMethod = targetMethod; + this.candidateInvokers = candidateInvokers; } - /** * Get the target instance used for debug. * @@ -94,7 +106,6 @@ Object peekInput() { return this.inputStack.peek(); } - /** * Remove the input at the top of the input stack * from this context and return it. @@ -116,20 +127,32 @@ int getInputStackSize() { } /** - * Get the target method used for debug. + * Get the target invoker used for debug. + * + * @return the target invoker. + * @since 1.0.1 + */ + LeetcodeInvoker getTargetInvoker() { + return targetInvoker; + } + + /** + * Set the target invoker used for debug. * - * @return the target method used for debug. + * @param targetInvoker the target invoker. + * @since 1.0.1 */ - Method getTargetMethod() { - return targetMethod; + void setTargetInvoker(LeetcodeInvoker targetInvoker) { + this.targetInvoker = targetInvoker; } /** - * Set the target method used for debug. + * Get the candidate leetcode invokers list for input parsing. * - * @param targetMethod the target method used for debug. + * @return the candidate invokers list. + * @since 1.0.1 */ - void setTargetMethod(Method targetMethod) { - this.targetMethod = targetMethod; + List getCandidateInvokers() { + return candidateInvokers; } } diff --git a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/parser/InputParserProcessor.java b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/parser/InputParserProcessor.java index a44dcfa..4e5f090 100644 --- a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/parser/InputParserProcessor.java +++ b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/parser/InputParserProcessor.java @@ -16,11 +16,11 @@ package io.github.jidcoo.opto.lcdb.enhancer.core.parser; +import io.github.jidcoo.opto.lcdb.enhancer.base.LeetcodeInvoker; import io.github.jidcoo.opto.lcdb.enhancer.utils.AssertUtil; import io.github.jidcoo.opto.lcdb.enhancer.utils.ReflectUtil; -import io.github.jidcoo.opto.lcdb.enhancer.utils.StringUtil; -import java.lang.reflect.Method; +import java.util.List; /** *

InputParserProcessor is a publicly available @@ -48,20 +48,24 @@ public final class InputParserProcessor { public static Object process(Object parser, Object executor, String input) { AssertUtil.nonNull(parser, "The parser cannot be null."); AssertUtil.nonNull(executor, "The parser cannot be null."); - AssertUtil.isTrue(!StringUtil.isBlank(input), "The input cannot be blank."); + // In version 1.0.1 and later, the empty string "" represent no parameters + // instead of no input. So here we only verify whether the input is null + // rather than whether it is blank. + AssertUtil.nonNull(input, "The input cannot be null."); AssertUtil.isTrue((parser instanceof InputParser), "The parser is not a InputParser."); + // Get the leetcode target instance and target method from LeetcodeExecutor. Object targetInstance = ReflectUtil.getFieldValue("instance", Object.class, executor); - Method targetExecutor = ReflectUtil.getFieldValue("executor", Method.class, executor); + List candidateInvokers = ReflectUtil.getFieldValue("candidateInvokers", List.class, executor); // Create an InputParseTask instance. - InputParseTask inputParseTask = new InputParseTask(targetInstance, targetExecutor, input); + InputParseTask inputParseTask = new InputParseTask(targetInstance, candidateInvokers, input); // Do real parse logic and return the parser output. Object output = ((InputParser) (parser)).parse(inputParseTask); // Set the final leetcode invoker from inputParseTask to the LeetcodeExecutor. - ReflectUtil.setFieldValue("invoker", Method.class, inputParseTask.getTargetMethod(), executor); + ReflectUtil.setFieldValue("executor", LeetcodeInvoker.class, inputParseTask.getTargetInvoker(), executor); // Set the leetcode invoker response type to the LeetcodeExecutor. ReflectUtil.setFieldValue("invokerResponseType", Class.class, - inputParseTask.getTargetMethod().getReturnType(), executor); + inputParseTask.getTargetInvoker().getReturnType(), executor); return output; } } diff --git a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/parser/ParameterAcceptor.java b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/parser/ParameterAcceptor.java index d3f15e3..34454f5 100644 --- a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/parser/ParameterAcceptor.java +++ b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/parser/ParameterAcceptor.java @@ -16,13 +16,13 @@ package io.github.jidcoo.opto.lcdb.enhancer.core.parser; -import io.github.jidcoo.opto.lcdb.enhancer.utils.BeanUtil; -import io.github.jidcoo.opto.lcdb.enhancer.utils.ReflectUtil; import io.github.jidcoo.opto.lcdb.enhancer.base.BaseParameterAcceptStrategy; import io.github.jidcoo.opto.lcdb.enhancer.base.Order; import io.github.jidcoo.opto.lcdb.enhancer.base.Strategizable; import io.github.jidcoo.opto.lcdb.enhancer.utils.AssertUtil; +import io.github.jidcoo.opto.lcdb.enhancer.utils.BeanUtil; import io.github.jidcoo.opto.lcdb.enhancer.utils.ContainerCheckUtil; +import io.github.jidcoo.opto.lcdb.enhancer.utils.ReflectUtil; import javax.annotation.Resource; import java.lang.reflect.Modifier; diff --git a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/pipeline/LeetcodeJavaDebugEnhancerPipeline.java b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/pipeline/LeetcodeJavaDebugEnhancerPipeline.java new file mode 100644 index 0000000..39eb939 --- /dev/null +++ b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/pipeline/LeetcodeJavaDebugEnhancerPipeline.java @@ -0,0 +1,305 @@ +/* + * Copyright (C) 2024-2026 Jidcoo(https://github.com/jidcoo). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.github.jidcoo.opto.lcdb.enhancer.core.pipeline; + +import io.github.jidcoo.opto.lcdb.enhancer.LeetcodeJavaDebugEnhancer; +import io.github.jidcoo.opto.lcdb.enhancer.base.InputProvider; +import io.github.jidcoo.opto.lcdb.enhancer.base.LeetcodeInvoker; +import io.github.jidcoo.opto.lcdb.enhancer.base.Order; +import io.github.jidcoo.opto.lcdb.enhancer.base.OutputConsumer; +import io.github.jidcoo.opto.lcdb.enhancer.core.executor.LeetcodeExecutorFactory; +import io.github.jidcoo.opto.lcdb.enhancer.core.executor.LeetcodeExecutorProcessor; +import io.github.jidcoo.opto.lcdb.enhancer.core.executor.LeetcodeInvokerFactory; +import io.github.jidcoo.opto.lcdb.enhancer.core.parser.InputParserProcessor; +import io.github.jidcoo.opto.lcdb.enhancer.core.printer.OutputPrinterProcessor; +import io.github.jidcoo.opto.lcdb.enhancer.utils.AssertUtil; +import io.github.jidcoo.opto.lcdb.enhancer.utils.BeanUtil; +import io.github.jidcoo.opto.lcdb.enhancer.utils.ReflectUtil; + +import javax.annotation.Resource; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.stream.Collectors; + +/** + *

LeetcodeJavaDebugEnhancerPipeline is a pipeline + * used to run enhanced pipeline processes.

+ *

In order to adapt to complex scenarios, + * LeetcodeJavaDebugEnhancerPipeline takes on + * more proactive enhancement work here.

+ * + * @author Jidcoo + * @since 1.0.1 + */ +final class LeetcodeJavaDebugEnhancerPipeline extends PipelineRunner { + + /** + * The enhancer instance of this pipeline. + */ + private final LeetcodeJavaDebugEnhancer leetcodeJavaDebugEnhancer; + + /** + * The input provider instance of this pipeline. + */ + private final InputProvider inputProvider; + + /** + * The output consumer instance of this pipeline. + */ + private final OutputConsumer outputConsumer; + + /** + * The output printer instance of this pipeline. + */ + private final Object outputPrinter; + + /** + * The leetcode executor instance of this pipeline. + */ + private final Object leetcodeExecutor; + + /** + * The input parser instance of this pipeline. + */ + private final Object inputParser; + + /** + * The builtin pipeline runner map. + */ + private final Map pipelineRunnerMap; + + /** + * The pipeline runner invokers from the builtin pipeline runner. + */ + private final List pipelineRunnerInvokers; + + /** + * Built-in pipeline runner set package location. + */ + private static final String BUILT_IN_PIPELINE_RUNNER_PACKAGE = "io.github.jidcoo.opto.lcdb.enhancer.core.pipeline"; + + /** + * Create a LeetcodeJavaDebugEnhancerPipeline instance. + * + * @param leetcodeJavaDebugEnhancer the enhancer instance. + * @param inputProvider the input provider. + * @param outputConsumer the output consumer. + * @param outputPrinter the output printer instance. + * @param leetcodeExecutor the bootstrap leetcode executor instance. + * @param inputParser the input parser instance. + */ + LeetcodeJavaDebugEnhancerPipeline(LeetcodeJavaDebugEnhancer leetcodeJavaDebugEnhancer, + InputProvider inputProvider, OutputConsumer outputConsumer, + Object outputPrinter, Object leetcodeExecutor, Object inputParser) { + AssertUtil.nonNull(leetcodeJavaDebugEnhancer, "The enhancer cannot be null."); + this.leetcodeJavaDebugEnhancer = leetcodeJavaDebugEnhancer; + AssertUtil.nonNull(inputProvider, "The input provider cannot be null."); + this.inputProvider = inputProvider; + AssertUtil.nonNull(outputConsumer, "The output consumer cannot be null."); + this.outputConsumer = outputConsumer; + AssertUtil.nonNull(outputPrinter, "The output printer cannot be null."); + this.outputPrinter = outputPrinter; + AssertUtil.nonNull(leetcodeExecutor, "The leetcode executor cannot be null."); + this.leetcodeExecutor = leetcodeExecutor; + AssertUtil.nonNull(inputParser, "The input parser cannot be null."); + this.inputParser = inputParser; + + // Build up runner map and invokers list. + pipelineRunnerMap = new HashMap<>(); + pipelineRunnerInvokers = new ArrayList<>(); + // Collect all builtin pipeline runners instance and sort it. + List builtinPipelineRunners = BeanUtil.collectBeans(PipelineRunner.class, + BUILT_IN_PIPELINE_RUNNER_PACKAGE, + klass -> klass.isAnnotationPresent(Resource.class) && ReflectUtil.isExtendsClass(klass, + PipelineRunner.class) && !Modifier.isAbstract(klass.getModifiers()), + klass -> ReflectUtil.createInstance(klass)) + .stream().filter(Objects::nonNull).sorted(Comparator.comparingInt(Order::getOrder).reversed()) + .collect(Collectors.toList()); + // Dispatch each builtinPipelineRunner into the pipelineRunnerMap. + for (PipelineRunner builtinPipelineRunner : builtinPipelineRunners) { + // Get all candidate methods from the builtinPipelineRunner. + for (Method candidateMethod : builtinPipelineRunner.getClass().getDeclaredMethods()) { + // Filter out methods without @Resource annotation. + if (!candidateMethod.isAnnotationPresent(Resource.class)) { + continue; + } + // Create a LeetcodeInvoker instance by the candidateMethod. + LeetcodeInvoker leetcodeInvoker = LeetcodeInvokerFactory.getLeetcodeInvoker(candidateMethod); + // Add leetcodeInvoker to pipelineRunnerInvokers list. + pipelineRunnerInvokers.add(leetcodeInvoker); + // Map leetcodeInvoker's id -> pipelineRunner owner. + pipelineRunnerMap.put(leetcodeInvoker.getId(), builtinPipelineRunner); + } + } + } + + /** + * Run this pipeline. + */ + void run() { + // Now we can happily run the io loop to perform leetcode debugging enhancements. + while (true) { + // Provide the next string input from the InputProvider. + String input = inputProvider.provideNextInput(); + // We need to break this loop when the input indicates end. + if (inputProvider.isEnd(input)) { + break; + } + Object bossLeetcodeExecutor = leetcodeExecutor; + // Do enhance before input parsing. + bossLeetcodeExecutor = doEnhanceBeforeInputParseProcess(bossLeetcodeExecutor); + // Parse the string input to input object. + Object inputObject = InputParserProcessor.process(inputParser, bossLeetcodeExecutor, input); + // Do enhance after input parsing. + bossLeetcodeExecutor = doEnhanceAfterInputParseProcess(bossLeetcodeExecutor); + // Execute leetcode target and get the output object. + Object outputObject = LeetcodeExecutorProcessor.process(bossLeetcodeExecutor, inputObject); + // Print the output object. + String output = OutputPrinterProcessor.process(outputPrinter, bossLeetcodeExecutor, outputObject); + // Consume the next output string to the OutputConsumer. + outputConsumer.consumeNextOutput(output); + } + } + + /** + * Do enhance before input parse process. + * In this function, we will add all pipeline runner + * invokers as additional candidate invokers to + * leetcodeExecutor. + * + * @param bossLeetcodeExecutor the leetcode executor instance. + * @return the final leetcode executor. + */ + private Object doEnhanceBeforeInputParseProcess(Object bossLeetcodeExecutor) { + // Aware candidate leetcode invoker list from the bossLeetcodeExecutor. + List candidateInvokers = ReflectUtil.getFieldValue("candidateInvokers", List.class, + bossLeetcodeExecutor); + // Add all builtin pipeline runner invokers as additional candidate invokers. + if (Objects.nonNull(candidateInvokers)) { + candidateInvokers.addAll(pipelineRunnerInvokers); + } + // Just return the origin bossLeetcodeExecutor. + return bossLeetcodeExecutor; + } + + /** + * Do enhance after input parse process. + * In this function, we will make a decision on + * a suitable leetcode executor as the final + * leetcode executor. + * + * @param bossLeetcodeExecutor the leetcode executor instance. + * @return the final leetcode executor. + */ + private Object doEnhanceAfterInputParseProcess(Object bossLeetcodeExecutor) { + // Aware leetcode invoker from the bossLeetcodeExecutor. + LeetcodeInvoker leetcodeInvoker = ReflectUtil.getFieldValue("executor", LeetcodeInvoker.class, + bossLeetcodeExecutor); + if (!pipelineRunnerMap.containsKey(leetcodeInvoker.getId())) { + // If this current leetcodeInvoker is not a pipeline runner invoker, + // then we consider the bossLeetcodeExecutor to be the final leetcode executor. + // So just return it. + return bossLeetcodeExecutor; + } + + // Get pipeline runner instance by invoker id. + PipelineRunner pipelineRunner = pipelineRunnerMap.get(leetcodeInvoker.getId()); + // Set up the pipelineRunner. + setupPipelineRunner(pipelineRunner); + // Create a new leetcode executor as the final leetcode executor. + Object finalLeetcodeExecutor = LeetcodeExecutorFactory.getLeetcodeExecutor(pipelineRunner, leetcodeInvoker); + // Set the bossLeetcodeExecutor's invoker to null. + ReflectUtil.setFieldValue("executor", LeetcodeInvoker.class, null, bossLeetcodeExecutor); + // Return the final leetcode executor. + return finalLeetcodeExecutor; + } + + /** + * Set up pipeline runner with this current pipeline instance. + * + * @param pipelineRunner the pipeline runner. + */ + private void setupPipelineRunner(PipelineRunner pipelineRunner) { + // Set cur pipeline instance to the pipelineRunner. + ReflectUtil.setFieldValue("baseRunner", PipelineRunner.class, this, pipelineRunner); + } + + /** + * Get the order of the object. + * + * @return the int order of the object. + */ + @Override + public int getOrder() { + return 0; + } + + /** + * Get enhancer of this pipeline. + * + * @return enhancer. + */ + protected LeetcodeJavaDebugEnhancer getEnhancer() { + return leetcodeJavaDebugEnhancer; + } + + /** + * Get input provider of this pipeline. + * + * @return input provider. + */ + protected InputProvider getInputProvider() { + return inputProvider; + } + + /** + * Get output consumer of this pipeline. + * + * @return output consumer. + */ + protected OutputConsumer getOutputConsumer() { + return outputConsumer; + } + + /** + * Get output printer of this pipeline. + * + * @return output printer instance. + */ + protected Object getOutputPrinter() { + return outputPrinter; + } + + /** + * Get leetcode executor of this pipeline. + * + * @return leetcode executor instance. + */ + protected Object getLeetcodeExecutor() { + return leetcodeExecutor; + } + + /** + * Get input parser of this pipeline runner. + * + * @return input parser instance. + */ + protected Object getInputParser() { + return inputParser; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/pipeline/LeetcodeJavaDebugEnhancerPipelineProcessor.java b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/pipeline/LeetcodeJavaDebugEnhancerPipelineProcessor.java new file mode 100644 index 0000000..b3922b3 --- /dev/null +++ b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/pipeline/LeetcodeJavaDebugEnhancerPipelineProcessor.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2024-2026 Jidcoo(https://github.com/jidcoo). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.github.jidcoo.opto.lcdb.enhancer.core.pipeline; + +import io.github.jidcoo.opto.lcdb.enhancer.LeetcodeJavaDebugEnhancer; +import io.github.jidcoo.opto.lcdb.enhancer.base.InputProvider; +import io.github.jidcoo.opto.lcdb.enhancer.base.OutputConsumer; +import io.github.jidcoo.opto.lcdb.enhancer.core.executor.LeetcodeExecutorFactory; +import io.github.jidcoo.opto.lcdb.enhancer.core.executor.LeetcodeInvokerFactory; +import io.github.jidcoo.opto.lcdb.enhancer.core.io.IOFactory; +import io.github.jidcoo.opto.lcdb.enhancer.core.parser.InputParserFactory; +import io.github.jidcoo.opto.lcdb.enhancer.core.printer.OutputPrinterFactory; +import io.github.jidcoo.opto.lcdb.enhancer.utils.AssertUtil; +import io.github.jidcoo.opto.lcdb.enhancer.utils.ReflectUtil; + +import java.util.Objects; + +/** + *

LeetcodeJavaDebugEnhancerPipelineProcessor is a publicly + * available LeetcodeJavaDebugEnhancerPipeline processor. + * It has used a proxy to access {@link LeetcodeJavaDebugEnhancerPipeline}. + *

+ * + *

The main {@link #process(LeetcodeJavaDebugEnhancer)} method + * of LeetcodeJavaDebugEnhancerPipelineProcessor is to proxy + * external callers to execute {@link LeetcodeJavaDebugEnhancerPipeline}. + *

+ * + *

All features of the {@link LeetcodeJavaDebugEnhancer} will + * be controlled and dominated by this processor in version + * 1.0.1 and later.

+ * + * @author Jidcoo + * @since 1.0.1 + */ +public final class LeetcodeJavaDebugEnhancerPipelineProcessor { + + /** + * Do starting real enhancement pipeline with + * LeetcodeJavaDebugEnhancer instance. + * + * @param enhancer the LeetcodeJavaDebugEnhancer instance. + */ + public static void process(LeetcodeJavaDebugEnhancer enhancer) throws Exception, Error { + // Check NPE. + AssertUtil.nonNull(enhancer, "The enhancer cannot be null."); + // Create a LeetcodeExecutor from the enhancer. + Object leetcodeExecutor = createBootstrapLeetcodeExecutor(enhancer); + // Create an OutputPrinter from the enhancer. + Object outputPrinter = OutputPrinterFactory.getOutputPrinter(enhancer); + // Create a InputParser from the enhancer. + Object inputParser = InputParserFactory.getInputParser(enhancer); + + try ( + // Get or create a InputProvider from the enhancer. + InputProvider inputProvider = IOFactory.getInputProvider(enhancer); + // Get or create OutputConsumer from the enhancer. + OutputConsumer outputConsumer = IOFactory.getOutputConsumer(enhancer) + + ) { + // Create a pipeline instance by enhancer's components. + LeetcodeJavaDebugEnhancerPipeline pipeline = new LeetcodeJavaDebugEnhancerPipeline(enhancer, + inputProvider, outputConsumer, outputPrinter, leetcodeExecutor, inputParser); + // Run pipeline. + pipeline.run(); + } + + } + + /** + * Create a bootstrap leetcode executor instance by enhancer. + * + * @param enhancer the LeetcodeJavaDebugEnhancer instance. + * @return the leetcode executor instance. + */ + private static Object createBootstrapLeetcodeExecutor(LeetcodeJavaDebugEnhancer enhancer) { + // At first, if the enhancement point from the enhancer is not null, + // we will prioritize using it. + if (Objects.nonNull(enhancer.getEnhancementPoint())) { + return LeetcodeExecutorFactory.getLeetcodeExecutor(enhancer, + LeetcodeInvokerFactory.getLeetcodeInvoker(enhancer.getEnhancementPoint())); + } + + // Then, we will try to resolve all first level INNER-CLASS in AT. + Class[] innerClasses = ReflectUtil.resolveInnerClasses(enhancer.getClass()); + // The innerClasses length must be greater than zero. + AssertUtil.isTrue(innerClasses.length > 0, "Cannot resolve any inner class from the AT instance."); + // Unfortunately, we are currently unable to handle situations where + // there are multiple INNER-CLASS in AT. + AssertUtil.isTrue(innerClasses.length < 2, "Multiple inner classes were found in AT instance. There can only be one inner class in AT instance."); + // Use this only inner class as an instance of leetcode executor. + Class bossInnerClassInstance = innerClasses[0]; + // If the name of bossInnerClassInstance is "Solution", + // then we only need to instantiate the class. + if ("Solution".equals(bossInnerClassInstance.getSimpleName())) { + return LeetcodeExecutorFactory.getLeetcodeExecutor(ReflectUtil.createInstance(bossInnerClassInstance, + new Class[]{enhancer.getClass()}, enhancer)); + } + // Create a leetcode executor by bossInnerClassInstance and return it. + return LeetcodeExecutorFactory.getLeetcodeExecutor(bossInnerClassInstance); + } +} \ No newline at end of file diff --git a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/pipeline/PipelineRunner.java b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/pipeline/PipelineRunner.java new file mode 100644 index 0000000..a6b41fc --- /dev/null +++ b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/pipeline/PipelineRunner.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2024-2026 Jidcoo(https://github.com/jidcoo). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.github.jidcoo.opto.lcdb.enhancer.core.pipeline; + +import io.github.jidcoo.opto.lcdb.enhancer.LeetcodeJavaDebugEnhancer; +import io.github.jidcoo.opto.lcdb.enhancer.base.InputProvider; +import io.github.jidcoo.opto.lcdb.enhancer.base.Order; +import io.github.jidcoo.opto.lcdb.enhancer.base.OutputConsumer; + +/** + *

PipelineRunner provides important components + * in the enhancer runtime pipeline.

+ * + *

Note: PipelineRunner implements + * the {@link Order}, please do not forget to + * implement the {@link #getOrder()} method. + * Because it will directly reflect the priority of + * this pipeline runner object!!! + *

+ * + * @author Jidcoo + * @since 1.0.1 + */ +abstract class PipelineRunner implements Order { + + /** + * The base pipeline runner. + */ + private PipelineRunner baseRunner; + + /** + * Get enhancer of this pipeline runner. + * + * @return enhancer. + */ + protected LeetcodeJavaDebugEnhancer getEnhancer() { + return baseRunner.getEnhancer(); + } + + /** + * Get input provider of this pipeline runner. + * + * @return input provider. + */ + protected InputProvider getInputProvider() { + return baseRunner.getInputProvider(); + } + + /** + * Get output consumer of this pipeline runner. + * + * @return output consumer. + */ + protected OutputConsumer getOutputConsumer() { + return baseRunner.getOutputConsumer(); + } + + /** + * Get output printer of this pipeline runner. + * + * @return output printer instance. + */ + protected Object getOutputPrinter() { + return baseRunner.getOutputPrinter(); + } + + /** + * Get leetcode executor of this pipeline runner. + * + * @return leetcode executor instance. + */ + protected Object getLeetcodeExecutor() { + return baseRunner.getLeetcodeExecutor(); + } + + /** + * Get input parser of this pipeline runner. + * + * @return input parser instance. + */ + protected Object getInputParser() { + return baseRunner.getInputParser(); + } +} diff --git a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/printer/OutputPrinter.java b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/printer/OutputPrinter.java index 2557961..29cd72a 100644 --- a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/printer/OutputPrinter.java +++ b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/core/printer/OutputPrinter.java @@ -17,10 +17,10 @@ package io.github.jidcoo.opto.lcdb.enhancer.core.printer; import io.github.jidcoo.opto.lcdb.enhancer.base.BasePrintingStrategy; +import io.github.jidcoo.opto.lcdb.enhancer.base.Order; import io.github.jidcoo.opto.lcdb.enhancer.base.Strategizable; import io.github.jidcoo.opto.lcdb.enhancer.utils.AssertUtil; import io.github.jidcoo.opto.lcdb.enhancer.utils.GsonUtil; -import io.github.jidcoo.opto.lcdb.enhancer.base.Order; import javax.lang.model.type.NullType; import java.util.*; diff --git a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/utils/ReflectUtil.java b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/utils/ReflectUtil.java index aa45812..a860f5d 100644 --- a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/utils/ReflectUtil.java +++ b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/utils/ReflectUtil.java @@ -16,8 +16,6 @@ package io.github.jidcoo.opto.lcdb.enhancer.utils; -import io.github.jidcoo.opto.lcdb.enhancer.LeetcodeJavaDebugEnhancer; - import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -33,50 +31,50 @@ public class ReflectUtil { /** - * Create an instance from the class by default class constructor. + * Create an instance from the class by custom class constructor. * - * @param clazz clazz object. - * @return the instance created by the input class. + * @param clazz clazz object. + * @param constructorParameterTypes the parameter type array used to find constructor. + * @param constructorParameters the parameters used to initialize object. + * @return the instance created by the class. + * @since 1.0.1 */ - public static T createInstance(Class clazz) { + public static T createInstance(Class clazz, Class[] constructorParameterTypes, + Object... constructorParameters) { AssertUtil.nonNull(clazz, "The class cannot be null."); + if (Objects.isNull(constructorParameterTypes)) { + constructorParameterTypes = new Class[0]; + } try { - Constructor declaredConstructor = clazz.getDeclaredConstructor(); + Constructor declaredConstructor = clazz.getDeclaredConstructor(constructorParameterTypes); declaredConstructor.setAccessible(true); - return declaredConstructor.newInstance(); + return declaredConstructor.newInstance(constructorParameters); } catch (Exception e) { throw new RuntimeException(e); } } /** - * Resolve the inner class Solution and instantiate - * it from the AT object. + * Create an instance from the class by default class constructor. * - * @param object the AT object. - * @return the Solution instance. + * @param clazz clazz object. + * @return the instance created by the class. + * @since 1.0.1 */ - public static Object resolveSolutionInstance(Object object) { - AssertUtil.nonNull(object, "The object cannot be null."); - AssertUtil.isTrue((object instanceof LeetcodeJavaDebugEnhancer), "The object is not an inherited object from " - + "LeetcodeJavaDebugEnhancer"); - Class __AT__class = object.getClass(); - Class[] declaredClasses = __AT__class.getDeclaredClasses(); - for (Class declaredClass : declaredClasses) { - // Match the simple name of the class. - if ("Solution".equals(declaredClass.getSimpleName())) { - try { - Constructor declaredConstructor = declaredClass.getDeclaredConstructor(__AT__class); - declaredConstructor.setAccessible(true); - return declaredConstructor.newInstance(object); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - } + public static T createInstance(Class clazz) { + return createInstance(clazz, null); + } - // Sorry, we cannot find any inner class named "Solution". - throw new RuntimeException("No inner class Solution found in class: " + object.getClass()); + /** + * Resolve all inner-class from the given class. + * + * @param clazz the given class object. + * @return the inner-class array in the given class. + * @since 1.0.1 + */ + public static Class[] resolveInnerClasses(Class clazz) { + AssertUtil.nonNull(clazz, "The clazz cannot be null."); + return clazz.getDeclaredClasses(); } /** @@ -112,19 +110,25 @@ public static boolean isExtendsClass(Class clazz, Class superClass) { * @param fieldType the field type. * @param obj the object. * @return the field + * @since 1.0.1 */ public static Field getField(String fieldName, Class fieldType, Object obj) { AssertUtil.isTrue(!StringUtil.isBlank(fieldName), "The fieldName cannot be blank."); AssertUtil.nonNull(fieldType, "The fieldType cannot be null."); AssertUtil.nonNull(obj, "The obj cannot be null."); - try { - Field declaredField = obj.getClass().getDeclaredField(fieldName); - AssertUtil.isTrue(Objects.equals(declaredField.getType(), fieldType), - "The type of the field " + fieldName + " in object " + obj + " is " + declaredField.getType().getSimpleName() + ", not " + fieldType.getSimpleName() + "."); - return declaredField; - } catch (Exception | Error e) { - throw new RuntimeException(e); + Field field = null; + Class classFinder = obj.getClass(); + while (Objects.isNull(field) && Objects.nonNull(classFinder)) { + try { + field = classFinder.getDeclaredField(fieldName); + } catch (Exception | Error e) { + classFinder = classFinder.getSuperclass(); + } } + AssertUtil.nonNull(field, "Cannot match any field by field name [" + fieldName + "] in object: " + obj); + AssertUtil.isTrue(Objects.equals(field.getType(), fieldType), + "The type of the field " + fieldName + " in " + "object " + obj + " is " + field.getType().getSimpleName() + ", not " + fieldType.getSimpleName() + "."); + return field; } /** diff --git a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/utils/StringUtil.java b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/utils/StringUtil.java index 750a868..afccf65 100644 --- a/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/utils/StringUtil.java +++ b/src/main/java/io/github/jidcoo/opto/lcdb/enhancer/utils/StringUtil.java @@ -26,7 +26,6 @@ */ public class StringUtil { - /** * Check if CharSequence is empty. *