From 68bdbf72f93cd49090aadc036434a10e0dd462af Mon Sep 17 00:00:00 2001 From: tejaswarake Date: Sun, 25 Jan 2026 22:50:31 +0530 Subject: [PATCH] Initial implementation of Java Preprocessing Library (Maven) --- README.md | 134 +++++- example-usage/pom.xml | 59 +++ .../src/main/preprocess/com/example/App.java | 19 + .../preprocessed/com/example/App.java | 19 + .../compile/default-compile/createdFiles.lst | 1 + .../compile/default-compile/inputFiles.lst | 1 + pom.xml | 31 ++ preprocessor-core/pom.xml | 20 + .../core/ExpressionEvaluator.java | 231 +++++++++ .../javapreprocessor/core/Preprocessor.java | 126 +++++ .../core/PreprocessorTest.java | 111 +++++ .../target/maven-archiver/pom.properties | 5 + .../compile/default-compile/createdFiles.lst | 0 .../compile/default-compile/inputFiles.lst | 2 + .../default-testCompile/createdFiles.lst | 0 .../default-testCompile/inputFiles.lst | 1 + ...javapreprocessor.core.PreprocessorTest.xml | 66 +++ ...javapreprocessor.core.PreprocessorTest.txt | 4 + preprocessor-maven-plugin/pom.xml | 65 +++ .../maven/PreprocessMojo.java | 74 +++ .../preprocessor-maven-plugin/plugin-help.xml | 56 +++ .../target/classes/META-INF/maven/plugin.xml | 76 +++ .../javapreprocessor/maven/HelpMojo.java | 442 +++++++++++++++++ .../preprocessor_maven_plugin/HelpMojo.java | 446 ++++++++++++++++++ .../target/maven-archiver/pom.properties | 5 + .../target/maven-plugin-help.properties | 4 + .../compile/default-compile/createdFiles.lst | 0 .../compile/default-compile/inputFiles.lst | 1 + .../target/plugin-enhanced.xml | 76 +++ 29 files changed, 2073 insertions(+), 2 deletions(-) create mode 100644 example-usage/pom.xml create mode 100644 example-usage/src/main/preprocess/com/example/App.java create mode 100644 example-usage/target/generated-sources/preprocessed/com/example/App.java create mode 100644 example-usage/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst create mode 100644 example-usage/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst create mode 100644 pom.xml create mode 100644 preprocessor-core/pom.xml create mode 100644 preprocessor-core/src/main/java/com/github/javapreprocessor/core/ExpressionEvaluator.java create mode 100644 preprocessor-core/src/main/java/com/github/javapreprocessor/core/Preprocessor.java create mode 100644 preprocessor-core/src/test/java/com/github/javapreprocessor/core/PreprocessorTest.java create mode 100644 preprocessor-core/target/maven-archiver/pom.properties create mode 100644 preprocessor-core/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst create mode 100644 preprocessor-core/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst create mode 100644 preprocessor-core/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst create mode 100644 preprocessor-core/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst create mode 100644 preprocessor-core/target/surefire-reports/TEST-com.github.javapreprocessor.core.PreprocessorTest.xml create mode 100644 preprocessor-core/target/surefire-reports/com.github.javapreprocessor.core.PreprocessorTest.txt create mode 100644 preprocessor-maven-plugin/pom.xml create mode 100644 preprocessor-maven-plugin/src/main/java/com/github/javapreprocessor/maven/PreprocessMojo.java create mode 100644 preprocessor-maven-plugin/target/classes/META-INF/maven/com.github.javapreprocessor/preprocessor-maven-plugin/plugin-help.xml create mode 100644 preprocessor-maven-plugin/target/classes/META-INF/maven/plugin.xml create mode 100644 preprocessor-maven-plugin/target/generated-sources/plugin/com/github/javapreprocessor/maven/HelpMojo.java create mode 100644 preprocessor-maven-plugin/target/generated-sources/plugin/com/github/javapreprocessor/preprocessor_maven_plugin/HelpMojo.java create mode 100644 preprocessor-maven-plugin/target/maven-archiver/pom.properties create mode 100644 preprocessor-maven-plugin/target/maven-plugin-help.properties create mode 100644 preprocessor-maven-plugin/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst create mode 100644 preprocessor-maven-plugin/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst create mode 100644 preprocessor-maven-plugin/target/plugin-enhanced.xml diff --git a/README.md b/README.md index 096d5a8..2355621 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,132 @@ -# java-preprocessing-lib -Trying to create a library to somehow preprocess the java code using directives. +# Java Preprocessing Library + +A lightweight Java library and Maven plugin that brings C++ style preprocessor directives (`#if`, `#ifdef`, etc.) to Java. This allows for conditional compilation based on build-time symbols, enabling feature toggles, multi-version builds, and platform-specific code generation. + +## Features + +* **Standard Directives**: Support for `#if`, `#ifdef`, `#ifndef`, `#else`, `#endif`. +* **Expression Evaluation**: Support for complex boolean expressions in `#if` directives (e.g., `#if DEBUG && VERSION > 1`). +* **Maven Plugin**: Seamless integration into the Maven build lifecycle (`generate-sources`). +* **Zero Runtime Dependencies**: The preprocessor runs at build time; the generated code is pure Java with no extra runtime requirements. + +## Installation + +Currently, you need to build and install the library locally: + +```bash +git clone https://github.com/your-repo/java-preprocessing-lib.git +cd java-preprocessing-lib +mvn install +``` + +## Usage + +### Maven Configuration + +Add the `preprocessor-maven-plugin` to your `pom.xml`. It should be configured to run during the `generate-sources` phase. + +**Note**: To use the preprocessor effectively, it is recommended to keep your source files with directives in a separate directory (e.g., `src/main/preprocess`) to prevent the standard Java compiler from trying to compile the unprocessed files. + +```xml + + + + com.github.javapreprocessor + preprocessor-maven-plugin + 1.0.0-SNAPSHOT + + + preprocess-sources + generate-sources + + process + + + + ${project.basedir}/src/main/preprocess + + + + true + 2 + false + + + + + + + +``` + +### Supported Directives + +#### `#ifdef` / `#ifndef` +Checks if a symbol is defined (or not defined). + +```java +#ifdef DEBUG + System.out.println("Debug logging enabled"); +#endif + +#ifndef PRODUCTION + // Test helper code +#endif +``` + +#### `#if` / `#else` / `#endif` +Evaluates a boolean expression. Supports `&&`, `||`, `!`, `>`, `<`, `>=`, `<=`, `==`, `!=`, and `defined()`. + +```java +#if VERSION > 1 && defined(FEATURE_NEW_UI) + renderNewUI(); +#else + renderClassicUI(); +#endif +``` + +#### `#else` +Alternative branch for `#if`, `#ifdef`, or `#ifndef`. + +## Example + +**Input (`src/main/preprocess/com/example/App.java`):** +```java +package com.example; + +public class App { + public static void main(String[] args) { + #if DEBUG + System.out.println("Debugging..."); + #endif + + System.out.println("Running App"); + } +} +``` + +**Configuration (pom.xml):** +```xml + + true + +``` + +**Generated Output (`target/generated-sources/preprocessed/com/example/App.java`):** +```java +package com.example; + +public class App { + public static void main(String[] args) { + + System.out.println("Debugging..."); + + + System.out.println("Running App"); + } +} +``` + +## License + +[MIT License](LICENSE) diff --git a/example-usage/pom.xml b/example-usage/pom.xml new file mode 100644 index 0000000..680e564 --- /dev/null +++ b/example-usage/pom.xml @@ -0,0 +1,59 @@ + + 4.0.0 + com.example + example-usage + 1.0-SNAPSHOT + + + + + com.github.javapreprocessor + preprocessor-maven-plugin + 1.0.0-SNAPSHOT + + + preprocess-sources + generate-sources + + process + + + ${project.basedir}/src/main/preprocess + + true + 2 + + + + + + + + + + + + + 1.8 + 1.8 + + diff --git a/example-usage/src/main/preprocess/com/example/App.java b/example-usage/src/main/preprocess/com/example/App.java new file mode 100644 index 0000000..d463971 --- /dev/null +++ b/example-usage/src/main/preprocess/com/example/App.java @@ -0,0 +1,19 @@ +package com.example; + +public class App { + public static void main(String[] args) { + System.out.println("Hello World!"); + + #if DEBUG + System.out.println("Debug mode enabled"); + #endif + + #if VERSION > 1 + System.out.println("Version is greater than 1"); + #endif + + #if VERSION < 1 + System.out.println("Version is less than 1"); + #endif + } +} diff --git a/example-usage/target/generated-sources/preprocessed/com/example/App.java b/example-usage/target/generated-sources/preprocessed/com/example/App.java new file mode 100644 index 0000000..bf2d85b --- /dev/null +++ b/example-usage/target/generated-sources/preprocessed/com/example/App.java @@ -0,0 +1,19 @@ +package com.example; + +public class App { + public static void main(String[] args) { + System.out.println("Hello World!"); + + + System.out.println("Debug mode enabled"); + + + + System.out.println("Version is greater than 1"); + + + + + + } +} diff --git a/example-usage/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/example-usage/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..03a7c71 --- /dev/null +++ b/example-usage/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -0,0 +1 @@ +com/example/App.class diff --git a/example-usage/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/example-usage/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..5f42618 --- /dev/null +++ b/example-usage/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1 @@ +/home/tejas-warake/Desktop/open-source/java-preprocessing-lib/example-usage/target/generated-sources/preprocessed/com/example/App.java diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..ff3f3ee --- /dev/null +++ b/pom.xml @@ -0,0 +1,31 @@ + + 4.0.0 + com.github.javapreprocessor + java-preprocessing-lib-parent + pom + 1.0.0-SNAPSHOT + Java Preprocessing Library Parent + + + preprocessor-core + preprocessor-maven-plugin + + + + UTF-8 + 1.8 + 1.8 + + + + + + junit + junit + 4.13.2 + test + + + + diff --git a/preprocessor-core/pom.xml b/preprocessor-core/pom.xml new file mode 100644 index 0000000..8a26f43 --- /dev/null +++ b/preprocessor-core/pom.xml @@ -0,0 +1,20 @@ + + 4.0.0 + + com.github.javapreprocessor + java-preprocessing-lib-parent + 1.0.0-SNAPSHOT + + preprocessor-core + jar + Preprocessor Core + + + + junit + junit + test + + + diff --git a/preprocessor-core/src/main/java/com/github/javapreprocessor/core/ExpressionEvaluator.java b/preprocessor-core/src/main/java/com/github/javapreprocessor/core/ExpressionEvaluator.java new file mode 100644 index 0000000..d5ac6bb --- /dev/null +++ b/preprocessor-core/src/main/java/com/github/javapreprocessor/core/ExpressionEvaluator.java @@ -0,0 +1,231 @@ +package com.github.javapreprocessor.core; + +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ExpressionEvaluator { + + private final Map definedSymbols; + private String expression; + private int pos; + private int ch; + + public ExpressionEvaluator(Map definedSymbols) { + this.definedSymbols = definedSymbols; + } + + public boolean evaluate(String expression) { + this.expression = expression; + this.pos = -1; + nextChar(); + boolean result = parseExpression(); + if (ch != -1) + throw new RuntimeException("Unexpected character: " + (char) ch); + return result; + } + + private void nextChar() { + ch = (++pos < expression.length()) ? expression.charAt(pos) : -1; + } + + private boolean eat(int charToEat) { + while (ch == ' ') + nextChar(); + if (ch == charToEat) { + nextChar(); + return true; + } + return false; + } + + // Expression grammar: + // expression = orTerm + // orTerm = andTerm { "||" andTerm } + // andTerm = equalityTerm { "&&" equalityTerm } + // equalityTerm = relationalTerm { ("==" | "!=") relationalTerm } + // relationalTerm = unaryTerm { (">" | "<" | ">=" | "<=") unaryTerm } + // unaryTerm = "!" unaryTerm | "(" expression ")" | factor + // factor = NUMBER | SYMBOL | "defined(" SYMBOL ")" + + private boolean parseExpression() { + return parseOr(); + } + + private boolean parseOr() { + boolean x = parseAnd(); + while (true) { + if (eat('|')) { + if (eat('|')) { + boolean y = parseAnd(); + x = x || y; + } else { + throw new RuntimeException("Expected ||"); + } + } else { + return x; + } + } + } + + private boolean parseAnd() { + boolean x = parseEquality(); + while (true) { + if (eat('&')) { + if (eat('&')) { + boolean y = parseEquality(); + x = x && y; + } else { + throw new RuntimeException("Expected &&"); + } + } else { + return x; + } + } + } + + private boolean parseEquality() { + // Since we are mixing boolean and integer types, we need to be careful. + // For simplicity now, let's treat everything as Object and do runtime checks? + // Or just parse values, and then compare. + // Let's change the return type of lower levels to Object. + + // Actually, parseEquality should return boolean. + // But its operands can be integers. + Object a = parseRelational(); + while (true) { + if (eat('=')) { + if (eat('=')) { + Object b = parseRelational(); + a = compare(a, b) == 0; + } else { + throw new RuntimeException("Expected =="); + } + } else if (eat('!')) { + if (eat('=')) { + Object b = parseRelational(); + a = compare(a, b) != 0; + } else { + throw new RuntimeException("Expected !="); + } + } else { + if (a instanceof Boolean) + return (Boolean) a; + // If checking just "NUMBER" in conditional context, nonzero is true. + if (a instanceof Integer) + return ((Integer) a) != 0; + throw new RuntimeException("Invalid type in boolean context: " + a); + } + } + } + + private Object parseRelational() { + Object a = parseUnary(); + while (true) { + if (eat('>')) { + if (eat('=')) { + Object b = parseUnary(); + a = compare(a, b) >= 0; + } else { + Object b = parseUnary(); + a = compare(a, b) > 0; + } + } else if (eat('<')) { + if (eat('=')) { + Object b = parseUnary(); + a = compare(a, b) <= 0; + } else { + Object b = parseUnary(); + a = compare(a, b) < 0; + } + } else { + return a; + } + } + } + + private Object parseUnary() { + if (eat('!')) { + Object v = parseUnary(); + return !toBoolean(v); + } + if (eat('(')) { + boolean v = parseExpression(); // Should this return object? The recursive structure is a bit messy with + // mixed types. + // Let's assume parens wrap boolean expressions mostly. + // Actually, (1+1) is not supported yet (addition). + if (!eat(')')) + throw new RuntimeException("Missing ')'"); + return v; + } + return parseFactor(); + } + + private Object parseFactor() { + while (ch == ' ') + nextChar(); + + if (ch >= '0' && ch <= '9') { // Number + int startPos = pos; + while ((ch >= '0' && ch <= '9')) + nextChar(); + return Integer.parseInt(expression.substring(startPos, pos)); + } else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_') { // Identifier + int startPos = pos; + while ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_' || (ch >= '0' && ch <= '9')) + nextChar(); + String func = expression.substring(startPos, pos); + if ("true".equals(func)) + return true; + if ("false".equals(func)) + return false; + if ("defined".equals(func)) { + // expect ( SYMBOL ) + while (ch == ' ') + nextChar(); + if (eat('(')) { + while (ch == ' ') + nextChar(); + int symStart = pos; + while ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_' + || (ch >= '0' && ch <= '9')) + nextChar(); + String sym = expression.substring(symStart, pos); + while (ch == ' ') + nextChar(); + if (!eat(')')) + throw new RuntimeException("Expected ) after defined"); + return definedSymbols.containsKey(sym); + } + } + // Variable lookup + if (definedSymbols.containsKey(func)) { + String val = definedSymbols.get(func); + try { + return Integer.parseInt(val); + } catch (NumberFormatException e) { + return Boolean.parseBoolean(val) || "true".equalsIgnoreCase(val); // loose boolean parsing? + // Or perhaps treat as string? + } + } + return 0; // Undefined symbol evaluates to 0 in C-style + } + throw new RuntimeException("Unexpected: " + (char) ch); + } + + private int compare(Object a, Object b) { + if (a instanceof Integer && b instanceof Integer) { + return ((Integer) a).compareTo((Integer) b); + } + // Maybe support string equality? + throw new RuntimeException("Incompatible types for comparison: " + a + ", " + b); + } + + private boolean toBoolean(Object a) { + if (a instanceof Boolean) + return (Boolean) a; + if (a instanceof Integer) + return ((Integer) a) != 0; + return false; + } +} diff --git a/preprocessor-core/src/main/java/com/github/javapreprocessor/core/Preprocessor.java b/preprocessor-core/src/main/java/com/github/javapreprocessor/core/Preprocessor.java new file mode 100644 index 0000000..f85e060 --- /dev/null +++ b/preprocessor-core/src/main/java/com/github/javapreprocessor/core/Preprocessor.java @@ -0,0 +1,126 @@ +package com.github.javapreprocessor.core; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Preprocessor { + + private final Map definedSymbols; + + public Preprocessor(Map definedSymbols) { + this.definedSymbols = definedSymbols; + } + + public void process(File inputFile, File outputFile) throws IOException { + try (BufferedReader reader = new BufferedReader(new FileReader(inputFile)); + BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile))) { + + String line; + + Deque stack = new ArrayDeque<>(); + stack.push(new IfState(true, true)); // Root scope is always active + + while ((line = reader.readLine()) != null) { + String trimmed = line.trim(); + + if (trimmed.startsWith("#")) { + handleDirective(trimmed, stack); + // Write blank lines to preserve line numbers for debugging. + writer.write(""); + writer.newLine(); + } else { + if (stack.peek().isActive()) { + writer.write(line); + writer.newLine(); + } else { + // Write blank line to preserve line numbers + writer.write(""); + writer.newLine(); + } + } + } + + if (stack.size() > 1) { + throw new IOException("Unclosed directive in file: " + inputFile.getAbsolutePath()); + } + } + } + + private void handleDirective(String line, Deque stack) { + Pattern pattern = Pattern.compile("^#\\s*(\\w+)(?:\\s+(.*))?$"); + Matcher matcher = pattern.matcher(line); + if (matcher.matches()) { + String directive = matcher.group(1); + String argument = matcher.group(2); + + switch (directive) { + case "if": + case "ifdef": + case "ifndef": + boolean condition = evaluateCondition(directive, argument); + boolean parentActive = stack.peek().isActive(); + boolean shouldBeActive = parentActive && condition; + stack.push(new IfState(shouldBeActive, condition)); + break; + case "else": + if (stack.size() <= 1) + throw new RuntimeException("Unexpected #else"); + IfState currentState = stack.pop(); + boolean parentOfElse = stack.peek().isActive(); + + boolean hasTakenBranch = currentState.hasTakenBranch; + boolean elseActive = parentOfElse && !hasTakenBranch; + + stack.push(new IfState(elseActive, true)); + break; + + case "endif": + if (stack.size() <= 1) + throw new RuntimeException("Unexpected #endif"); + stack.pop(); + break; + default: + // Treat unknown directives as comments + break; + } + } + } + + private boolean evaluateCondition(String directive, String argument) { + if (argument == null) + return false; + argument = argument.trim(); + + if ("ifdef".equals(directive)) { + return definedSymbols.containsKey(argument); + } else if ("ifndef".equals(directive)) { + return !definedSymbols.containsKey(argument); + } else if ("if".equals(directive)) { + return new ExpressionEvaluator(definedSymbols).evaluate(argument); + } + return false; + } + + private static class IfState { + private final boolean active; + private final boolean hasTakenBranch; + + public IfState(boolean active, boolean hasTakenBranch) { + this.active = active; + this.hasTakenBranch = hasTakenBranch; + } + + public boolean isActive() { + return active; + } + } +} diff --git a/preprocessor-core/src/test/java/com/github/javapreprocessor/core/PreprocessorTest.java b/preprocessor-core/src/test/java/com/github/javapreprocessor/core/PreprocessorTest.java new file mode 100644 index 0000000..fd22506 --- /dev/null +++ b/preprocessor-core/src/test/java/com/github/javapreprocessor/core/PreprocessorTest.java @@ -0,0 +1,111 @@ +package com.github.javapreprocessor.core; + +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class PreprocessorTest { + + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + + @Test + public void testIfDef() throws IOException { + File input = folder.newFile("input.java"); + File output = folder.newFile("output.java"); + + List lines = Arrays.asList( + "public class Test {", + "#ifdef DEBUG", + " System.out.println(\"Debug mode\");", + "#endif", + " System.out.println(\"Normal mode\");", + "}"); + Files.write(input.toPath(), lines); + + Map symbols = new HashMap<>(); + symbols.put("DEBUG", "true"); + Preprocessor preprocessor = new Preprocessor(symbols); + preprocessor.process(input, output); + + List outputLines = Files.readAllLines(output.toPath()); + Assert.assertEquals("public class Test {", outputLines.get(0)); + Assert.assertEquals(" System.out.println(\"Debug mode\");", outputLines.get(2)); + } + + @Test + public void testComplexExpression() throws IOException { + File input = folder.newFile("input_complex.java"); + File output = folder.newFile("output_complex.java"); + + List lines = Arrays.asList( + "#if DEBUG && VERSION > 1", + "New Feature", + "#endif"); + Files.write(input.toPath(), lines); + + Map symbols = new HashMap<>(); + symbols.put("DEBUG", "true"); + symbols.put("VERSION", "2"); + + Preprocessor preprocessor = new Preprocessor(symbols); + preprocessor.process(input, output); + + List outputLines = Files.readAllLines(output.toPath()); + Assert.assertEquals("New Feature", outputLines.get(1)); + } + + @Test + public void testComplexExpressionFalse() throws IOException { + File input = folder.newFile("input_complex_false.java"); + File output = folder.newFile("output_complex_false.java"); + + List lines = Arrays.asList( + "#if DEBUG && VERSION > 2", + "Future Feature", + "#endif"); + Files.write(input.toPath(), lines); + + Map symbols = new HashMap<>(); + symbols.put("DEBUG", "true"); + symbols.put("VERSION", "2"); + + Preprocessor preprocessor = new Preprocessor(symbols); + preprocessor.process(input, output); + + List outputLines = Files.readAllLines(output.toPath()); + Assert.assertEquals("", outputLines.get(1)); + } + + @Test + public void testDefinedFunc() throws IOException { + File input = folder.newFile("input_defined.java"); + File output = folder.newFile("output_defined.java"); + + List lines = Arrays.asList( + "#if defined(ABC)", + "ABC", + "#endif"); + Files.write(input.toPath(), lines); + + Map symbols = new HashMap<>(); + symbols.put("ABC", "1"); + + Preprocessor preprocessor = new Preprocessor(symbols); + preprocessor.process(input, output); + + List outputLines = Files.readAllLines(output.toPath()); + Assert.assertEquals("ABC", outputLines.get(1)); + } +} diff --git a/preprocessor-core/target/maven-archiver/pom.properties b/preprocessor-core/target/maven-archiver/pom.properties new file mode 100644 index 0000000..5f9fc4b --- /dev/null +++ b/preprocessor-core/target/maven-archiver/pom.properties @@ -0,0 +1,5 @@ +#Generated by Maven +#Sun Jan 25 22:42:40 IST 2026 +groupId=com.github.javapreprocessor +artifactId=preprocessor-core +version=1.0.0-SNAPSHOT diff --git a/preprocessor-core/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/preprocessor-core/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..e69de29 diff --git a/preprocessor-core/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/preprocessor-core/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..403b4db --- /dev/null +++ b/preprocessor-core/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,2 @@ +/home/tejas-warake/Desktop/open-source/java-preprocessing-lib/preprocessor-core/src/main/java/com/github/javapreprocessor/core/ExpressionEvaluator.java +/home/tejas-warake/Desktop/open-source/java-preprocessing-lib/preprocessor-core/src/main/java/com/github/javapreprocessor/core/Preprocessor.java diff --git a/preprocessor-core/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst b/preprocessor-core/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst new file mode 100644 index 0000000..e69de29 diff --git a/preprocessor-core/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst b/preprocessor-core/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst new file mode 100644 index 0000000..8244778 --- /dev/null +++ b/preprocessor-core/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst @@ -0,0 +1 @@ +/home/tejas-warake/Desktop/open-source/java-preprocessing-lib/preprocessor-core/src/test/java/com/github/javapreprocessor/core/PreprocessorTest.java diff --git a/preprocessor-core/target/surefire-reports/TEST-com.github.javapreprocessor.core.PreprocessorTest.xml b/preprocessor-core/target/surefire-reports/TEST-com.github.javapreprocessor.core.PreprocessorTest.xml new file mode 100644 index 0000000..2463be2 --- /dev/null +++ b/preprocessor-core/target/surefire-reports/TEST-com.github.javapreprocessor.core.PreprocessorTest.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/preprocessor-core/target/surefire-reports/com.github.javapreprocessor.core.PreprocessorTest.txt b/preprocessor-core/target/surefire-reports/com.github.javapreprocessor.core.PreprocessorTest.txt new file mode 100644 index 0000000..6aa2e37 --- /dev/null +++ b/preprocessor-core/target/surefire-reports/com.github.javapreprocessor.core.PreprocessorTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.github.javapreprocessor.core.PreprocessorTest +------------------------------------------------------------------------------- +Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.094 sec diff --git a/preprocessor-maven-plugin/pom.xml b/preprocessor-maven-plugin/pom.xml new file mode 100644 index 0000000..844f522 --- /dev/null +++ b/preprocessor-maven-plugin/pom.xml @@ -0,0 +1,65 @@ + + 4.0.0 + + com.github.javapreprocessor + java-preprocessing-lib-parent + 1.0.0-SNAPSHOT + + preprocessor-maven-plugin + maven-plugin + Preprocessor Maven Plugin + + + + com.github.javapreprocessor + preprocessor-core + ${project.version} + + + org.apache.maven + maven-plugin-api + 3.8.1 + provided + + + org.apache.maven + maven-core + 3.8.1 + provided + + + org.apache.maven.plugin-tools + maven-plugin-annotations + 3.6.1 + provided + + + junit + junit + test + + + + + + + org.apache.maven.plugins + maven-plugin-plugin + 3.8.1 + + true + + + + mojo-descriptor + + descriptor + + + + + + + + diff --git a/preprocessor-maven-plugin/src/main/java/com/github/javapreprocessor/maven/PreprocessMojo.java b/preprocessor-maven-plugin/src/main/java/com/github/javapreprocessor/maven/PreprocessMojo.java new file mode 100644 index 0000000..f92bbb9 --- /dev/null +++ b/preprocessor-maven-plugin/src/main/java/com/github/javapreprocessor/maven/PreprocessMojo.java @@ -0,0 +1,74 @@ +package com.github.javapreprocessor.maven; + +import com.github.javapreprocessor.core.Preprocessor; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.MavenProject; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.HashMap; +import java.util.Map; + +@Mojo(name = "process", defaultPhase = LifecyclePhase.GENERATE_SOURCES) +public class PreprocessMojo extends AbstractMojo { + + @Parameter(defaultValue = "${project}", readonly = true, required = true) + private MavenProject project; + + @Parameter(defaultValue = "${project.basedir}/src/main/java", property = "sourceDirectory") + private File sourceDirectory; + + @Parameter(defaultValue = "${project.build.directory}/generated-sources/preprocessed", property = "outputDirectory") + private File outputDirectory; + + @Parameter + private Map symbols; + + @Override + public void execute() throws MojoExecutionException, MojoFailureException { + if (!sourceDirectory.exists()) { + getLog().warn("Source directory does not exist: " + sourceDirectory); + return; + } + + if (symbols == null) { + symbols = new HashMap<>(); + } + + getLog().info("Preprocessing sources from " + sourceDirectory + " to " + outputDirectory); + getLog().info("Defined symbols: " + symbols); + + Preprocessor preprocessor = new Preprocessor(symbols); + + try { + Files.walkFileTree(sourceDirectory.toPath(), new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (file.toString().endsWith(".java")) { + Path relative = sourceDirectory.toPath().relativize(file); + Path targetPath = outputDirectory.toPath().resolve(relative); + + Files.createDirectories(targetPath.getParent()); + + preprocessor.process(file.toFile(), targetPath.toFile()); + } + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException e) { + throw new MojoExecutionException("Error processing files", e); + } + + project.addCompileSourceRoot(outputDirectory.getAbsolutePath()); + } +} diff --git a/preprocessor-maven-plugin/target/classes/META-INF/maven/com.github.javapreprocessor/preprocessor-maven-plugin/plugin-help.xml b/preprocessor-maven-plugin/target/classes/META-INF/maven/com.github.javapreprocessor/preprocessor-maven-plugin/plugin-help.xml new file mode 100644 index 0000000..9ff9dff --- /dev/null +++ b/preprocessor-maven-plugin/target/classes/META-INF/maven/com.github.javapreprocessor/preprocessor-maven-plugin/plugin-help.xml @@ -0,0 +1,56 @@ + + + + + + Preprocessor Maven Plugin + + com.github.javapreprocessor + preprocessor-maven-plugin + 1.0.0-SNAPSHOT + preprocessor + + + process + false + true + false + false + false + true + generate-sources + com.github.javapreprocessor.maven.PreprocessMojo + java + per-lookup + once-per-session + false + + + outputDirectory + java.io.File + false + true + + + + sourceDirectory + java.io.File + false + true + + + + symbols + java.util.Map<java.lang.String, java.lang.String> + false + true + + + + + ${outputDirectory} + ${sourceDirectory} + + + + \ No newline at end of file diff --git a/preprocessor-maven-plugin/target/classes/META-INF/maven/plugin.xml b/preprocessor-maven-plugin/target/classes/META-INF/maven/plugin.xml new file mode 100644 index 0000000..22472b8 --- /dev/null +++ b/preprocessor-maven-plugin/target/classes/META-INF/maven/plugin.xml @@ -0,0 +1,76 @@ + + + + + + Preprocessor Maven Plugin + + com.github.javapreprocessor + preprocessor-maven-plugin + 1.0.0-SNAPSHOT + preprocessor + false + true + 1.8 + 3.8.1 + + + process + false + true + false + false + false + true + generate-sources + com.github.javapreprocessor.maven.PreprocessMojo + java + per-lookup + once-per-session + false + + + outputDirectory + java.io.File + false + true + + + + project + org.apache.maven.project.MavenProject + true + false + + + + sourceDirectory + java.io.File + false + true + + + + symbols + java.util.Map + false + true + + + + + ${outputDirectory} + + ${sourceDirectory} + + + + + + com.github.javapreprocessor + preprocessor-core + jar + 1.0.0-SNAPSHOT + + + \ No newline at end of file diff --git a/preprocessor-maven-plugin/target/generated-sources/plugin/com/github/javapreprocessor/maven/HelpMojo.java b/preprocessor-maven-plugin/target/generated-sources/plugin/com/github/javapreprocessor/maven/HelpMojo.java new file mode 100644 index 0000000..736af51 --- /dev/null +++ b/preprocessor-maven-plugin/target/generated-sources/plugin/com/github/javapreprocessor/maven/HelpMojo.java @@ -0,0 +1,442 @@ +package com.github.javapreprocessor.maven; + +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +/** + * Display help information on preprocessor-maven-plugin.
+ * Call mvn preprocessor:help -Ddetail=true -Dgoal=<goal-name> to display parameter details. + * @author maven-plugin-tools + */ +@Mojo( name = "help", requiresProject = false, threadSafe = true ) +public class HelpMojo + extends AbstractMojo +{ + /** + * If true, display all settable properties for each goal. + * + */ + @Parameter( property = "detail", defaultValue = "false" ) + private boolean detail; + + /** + * The name of the goal for which to show help. If unspecified, all goals will be displayed. + * + */ + @Parameter( property = "goal" ) + private java.lang.String goal; + + /** + * The maximum length of a display line, should be positive. + * + */ + @Parameter( property = "lineLength", defaultValue = "80" ) + private int lineLength; + + /** + * The number of spaces per indentation level, should be positive. + * + */ + @Parameter( property = "indentSize", defaultValue = "2" ) + private int indentSize; + + // /META-INF/maven///plugin-help.xml + private static final String PLUGIN_HELP_PATH = + "/META-INF/maven/com.github.javapreprocessor/preprocessor-maven-plugin/plugin-help.xml"; + + private static final int DEFAULT_LINE_LENGTH = 80; + + private Document build() + throws MojoExecutionException + { + getLog().debug( "load plugin-help.xml: " + PLUGIN_HELP_PATH ); + try ( InputStream is = getClass().getResourceAsStream( PLUGIN_HELP_PATH ) ) + { + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); + return dBuilder.parse( is ); + } + catch ( IOException e ) + { + throw new MojoExecutionException( e.getMessage(), e ); + } + catch ( ParserConfigurationException e ) + { + throw new MojoExecutionException( e.getMessage(), e ); + } + catch ( SAXException e ) + { + throw new MojoExecutionException( e.getMessage(), e ); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void execute() + throws MojoExecutionException + { + if ( lineLength <= 0 ) + { + getLog().warn( "The parameter 'lineLength' should be positive, using '80' as default." ); + lineLength = DEFAULT_LINE_LENGTH; + } + if ( indentSize <= 0 ) + { + getLog().warn( "The parameter 'indentSize' should be positive, using '2' as default." ); + indentSize = 2; + } + + Document doc = build(); + + StringBuilder sb = new StringBuilder(); + Node plugin = getSingleChild( doc, "plugin" ); + + + String name = getValue( plugin, "name" ); + String version = getValue( plugin, "version" ); + String id = getValue( plugin, "groupId" ) + ":" + getValue( plugin, "artifactId" ) + ":" + version; + if ( isNotEmpty( name ) && !name.contains( id ) ) + { + append( sb, name + " " + version, 0 ); + } + else + { + if ( isNotEmpty( name ) ) + { + append( sb, name, 0 ); + } + else + { + append( sb, id, 0 ); + } + } + append( sb, getValue( plugin, "description" ), 1 ); + append( sb, "", 0 ); + + //plugin + String goalPrefix = getValue( plugin, "goalPrefix" ); + + Node mojos1 = getSingleChild( plugin, "mojos" ); + + List mojos = findNamedChild( mojos1, "mojo" ); + + if ( goal == null || goal.length() <= 0 ) + { + append( sb, "This plugin has " + mojos.size() + ( mojos.size() > 1 ? " goals:" : " goal:" ), 0 ); + append( sb, "", 0 ); + } + + for ( Node mojo : mojos ) + { + writeGoal( sb, goalPrefix, (Element) mojo ); + } + + if ( getLog().isInfoEnabled() ) + { + getLog().info( sb.toString() ); + } + } + + + private static boolean isNotEmpty( String string ) + { + return string != null && string.length() > 0; + } + + private static String getValue( Node node, String elementName ) + throws MojoExecutionException + { + return getSingleChild( node, elementName ).getTextContent(); + } + + private static Node getSingleChild( Node node, String elementName ) + throws MojoExecutionException + { + List namedChild = findNamedChild( node, elementName ); + if ( namedChild.isEmpty() ) + { + throw new MojoExecutionException( "Could not find " + elementName + " in plugin-help.xml" ); + } + if ( namedChild.size() > 1 ) + { + throw new MojoExecutionException( "Multiple " + elementName + " in plugin-help.xml" ); + } + return namedChild.get( 0 ); + } + + private static List findNamedChild( Node node, String elementName ) + { + List result = new ArrayList(); + NodeList childNodes = node.getChildNodes(); + for ( int i = 0; i < childNodes.getLength(); i++ ) + { + Node item = childNodes.item( i ); + if ( elementName.equals( item.getNodeName() ) ) + { + result.add( item ); + } + } + return result; + } + + private static Node findSingleChild( Node node, String elementName ) + throws MojoExecutionException + { + List elementsByTagName = findNamedChild( node, elementName ); + if ( elementsByTagName.isEmpty() ) + { + return null; + } + if ( elementsByTagName.size() > 1 ) + { + throw new MojoExecutionException( "Multiple " + elementName + "in plugin-help.xml" ); + } + return elementsByTagName.get( 0 ); + } + + private void writeGoal( StringBuilder sb, String goalPrefix, Element mojo ) + throws MojoExecutionException + { + String mojoGoal = getValue( mojo, "goal" ); + Node configurationElement = findSingleChild( mojo, "configuration" ); + Node description = findSingleChild( mojo, "description" ); + if ( goal == null || goal.length() <= 0 || mojoGoal.equals( goal ) ) + { + append( sb, goalPrefix + ":" + mojoGoal, 0 ); + Node deprecated = findSingleChild( mojo, "deprecated" ); + if ( ( deprecated != null ) && isNotEmpty( deprecated.getTextContent() ) ) + { + append( sb, "Deprecated. " + deprecated.getTextContent(), 1 ); + if ( detail && description != null ) + { + append( sb, "", 0 ); + append( sb, description.getTextContent(), 1 ); + } + } + else if ( description != null ) + { + append( sb, description.getTextContent(), 1 ); + } + append( sb, "", 0 ); + + if ( detail ) + { + Node parametersNode = getSingleChild( mojo, "parameters" ); + List parameters = findNamedChild( parametersNode, "parameter" ); + append( sb, "Available parameters:", 1 ); + append( sb, "", 0 ); + + for ( Node parameter : parameters ) + { + writeParameter( sb, parameter, configurationElement ); + } + } + } + } + + private void writeParameter( StringBuilder sb, Node parameter, Node configurationElement ) + throws MojoExecutionException + { + String parameterName = getValue( parameter, "name" ); + String parameterDescription = getValue( parameter, "description" ); + + Element fieldConfigurationElement = null; + if ( configurationElement != null ) + { + fieldConfigurationElement = (Element) findSingleChild( configurationElement, parameterName ); + } + + String parameterDefaultValue = ""; + if ( fieldConfigurationElement != null && fieldConfigurationElement.hasAttribute( "default-value" ) ) + { + parameterDefaultValue = " (Default: " + fieldConfigurationElement.getAttribute( "default-value" ) + ")"; + } + append( sb, parameterName + parameterDefaultValue, 2 ); + Node deprecated = findSingleChild( parameter, "deprecated" ); + if ( ( deprecated != null ) && isNotEmpty( deprecated.getTextContent() ) ) + { + append( sb, "Deprecated. " + deprecated.getTextContent(), 3 ); + append( sb, "", 0 ); + } + append( sb, parameterDescription, 3 ); + if ( "true".equals( getValue( parameter, "required" ) ) ) + { + append( sb, "Required: Yes", 3 ); + } + if ( ( fieldConfigurationElement != null ) && isNotEmpty( fieldConfigurationElement.getTextContent() ) ) + { + String property = getPropertyFromExpression( fieldConfigurationElement.getTextContent() ); + append( sb, "User property: " + property, 3 ); + } + + append( sb, "", 0 ); + } + + /** + *

Repeat a String n times to form a new string.

+ * + * @param str String to repeat + * @param repeat number of times to repeat str + * @return String with repeated String + * @throws NegativeArraySizeException if repeat < 0 + * @throws NullPointerException if str is null + */ + private static String repeat( String str, int repeat ) + { + StringBuilder buffer = new StringBuilder( repeat * str.length() ); + + for ( int i = 0; i < repeat; i++ ) + { + buffer.append( str ); + } + + return buffer.toString(); + } + + /** + * Append a description to the buffer by respecting the indentSize and lineLength parameters. + * Note: The last character is always a new line. + * + * @param sb The buffer to append the description, not null. + * @param description The description, not null. + * @param indent The base indentation level of each line, must not be negative. + */ + private void append( StringBuilder sb, String description, int indent ) + { + for ( String line : toLines( description, indent, indentSize, lineLength ) ) + { + sb.append( line ).append( '\n' ); + } + } + + /** + * Splits the specified text into lines of convenient display length. + * + * @param text The text to split into lines, must not be null. + * @param indent The base indentation level of each line, must not be negative. + * @param indentSize The size of each indentation, must not be negative. + * @param lineLength The length of the line, must not be negative. + * @return The sequence of display lines, never null. + * @throws NegativeArraySizeException if indent < 0 + */ + private static List toLines( String text, int indent, int indentSize, int lineLength ) + { + List lines = new ArrayList(); + + String ind = repeat( "\t", indent ); + + String[] plainLines = text.split( "(\r\n)|(\r)|(\n)" ); + + for ( String plainLine : plainLines ) + { + toLines( lines, ind + plainLine, indentSize, lineLength ); + } + + return lines; + } + + /** + * Adds the specified line to the output sequence, performing line wrapping if necessary. + * + * @param lines The sequence of display lines, must not be null. + * @param line The line to add, must not be null. + * @param indentSize The size of each indentation, must not be negative. + * @param lineLength The length of the line, must not be negative. + */ + private static void toLines( List lines, String line, int indentSize, int lineLength ) + { + int lineIndent = getIndentLevel( line ); + StringBuilder buf = new StringBuilder( 256 ); + + String[] tokens = line.split( " +" ); + + for ( String token : tokens ) + { + if ( buf.length() > 0 ) + { + if ( buf.length() + token.length() >= lineLength ) + { + lines.add( buf.toString() ); + buf.setLength( 0 ); + buf.append( repeat( " ", lineIndent * indentSize ) ); + } + else + { + buf.append( ' ' ); + } + } + + for ( int j = 0; j < token.length(); j++ ) + { + char c = token.charAt( j ); + if ( c == '\t' ) + { + buf.append( repeat( " ", indentSize - buf.length() % indentSize ) ); + } + else if ( c == '\u00A0' ) + { + buf.append( ' ' ); + } + else + { + buf.append( c ); + } + } + } + lines.add( buf.toString() ); + } + + /** + * Gets the indentation level of the specified line. + * + * @param line The line whose indentation level should be retrieved, must not be null. + * @return The indentation level of the line. + */ + private static int getIndentLevel( String line ) + { + int level = 0; + for ( int i = 0; i < line.length() && line.charAt( i ) == '\t'; i++ ) + { + level++; + } + for ( int i = level + 1; i <= level + 4 && i < line.length(); i++ ) + { + if ( line.charAt( i ) == '\t' ) + { + level++; + break; + } + } + return level; + } + + private static String getPropertyFromExpression( String expression ) + { + if ( expression != null && expression.startsWith( "${" ) && expression.endsWith( "}" ) + && !expression.substring( 2 ).contains( "${" ) ) + { + // expression="${xxx}" -> property="xxx" + return expression.substring( 2, expression.length() - 1 ); + } + // no property can be extracted + return null; + } +} diff --git a/preprocessor-maven-plugin/target/generated-sources/plugin/com/github/javapreprocessor/preprocessor_maven_plugin/HelpMojo.java b/preprocessor-maven-plugin/target/generated-sources/plugin/com/github/javapreprocessor/preprocessor_maven_plugin/HelpMojo.java new file mode 100644 index 0000000..43ecc7f --- /dev/null +++ b/preprocessor-maven-plugin/target/generated-sources/plugin/com/github/javapreprocessor/preprocessor_maven_plugin/HelpMojo.java @@ -0,0 +1,446 @@ +package com.github.javapreprocessor.preprocessor_maven_plugin; + +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +/** + * Display help information on preprocessor-maven-plugin.
+ * Call mvn preprocessor:help -Ddetail=true -Dgoal=<goal-name> to display parameter details. + * @author maven-plugin-tools + */ +@Mojo( name = "help", requiresProject = false, threadSafe = true ) +public class HelpMojo + extends AbstractMojo +{ + /** + * If true, display all settable properties for each goal. + * + */ + @Parameter( property = "detail", defaultValue = "false" ) + private boolean detail; + + /** + * The name of the goal for which to show help. If unspecified, all goals will be displayed. + * + */ + @Parameter( property = "goal" ) + private java.lang.String goal; + + /** + * The maximum length of a display line, should be positive. + * + */ + @Parameter( property = "lineLength", defaultValue = "80" ) + private int lineLength; + + /** + * The number of spaces per indentation level, should be positive. + * + */ + @Parameter( property = "indentSize", defaultValue = "2" ) + private int indentSize; + + // /META-INF/maven///plugin-help.xml + private static final String PLUGIN_HELP_PATH = + "/META-INF/maven/com.github.javapreprocessor/preprocessor-maven-plugin/plugin-help.xml"; + + private static final int DEFAULT_LINE_LENGTH = 80; + + private Document build() + throws MojoExecutionException + { + getLog().debug( "load plugin-help.xml: " + PLUGIN_HELP_PATH ); + try ( InputStream is = getClass().getResourceAsStream( PLUGIN_HELP_PATH ) ) + { + if ( is == null ) + { + throw new MojoExecutionException( "Could not find plugin descriptor at " + PLUGIN_HELP_PATH ); + } + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); + return dBuilder.parse( is ); + } + catch ( IOException e ) + { + throw new MojoExecutionException( e.getMessage(), e ); + } + catch ( ParserConfigurationException e ) + { + throw new MojoExecutionException( e.getMessage(), e ); + } + catch ( SAXException e ) + { + throw new MojoExecutionException( e.getMessage(), e ); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void execute() + throws MojoExecutionException + { + if ( lineLength <= 0 ) + { + getLog().warn( "The parameter 'lineLength' should be positive, using '80' as default." ); + lineLength = DEFAULT_LINE_LENGTH; + } + if ( indentSize <= 0 ) + { + getLog().warn( "The parameter 'indentSize' should be positive, using '2' as default." ); + indentSize = 2; + } + + Document doc = build(); + + StringBuilder sb = new StringBuilder(); + Node plugin = getSingleChild( doc, "plugin" ); + + + String name = getValue( plugin, "name" ); + String version = getValue( plugin, "version" ); + String id = getValue( plugin, "groupId" ) + ":" + getValue( plugin, "artifactId" ) + ":" + version; + if ( isNotEmpty( name ) && !name.contains( id ) ) + { + append( sb, name + " " + version, 0 ); + } + else + { + if ( isNotEmpty( name ) ) + { + append( sb, name, 0 ); + } + else + { + append( sb, id, 0 ); + } + } + append( sb, getValue( plugin, "description" ), 1 ); + append( sb, "", 0 ); + + //plugin + String goalPrefix = getValue( plugin, "goalPrefix" ); + + Node mojos1 = getSingleChild( plugin, "mojos" ); + + List mojos = findNamedChild( mojos1, "mojo" ); + + if ( goal == null || goal.length() <= 0 ) + { + append( sb, "This plugin has " + mojos.size() + ( mojos.size() > 1 ? " goals:" : " goal:" ), 0 ); + append( sb, "", 0 ); + } + + for ( Node mojo : mojos ) + { + writeGoal( sb, goalPrefix, (Element) mojo ); + } + + if ( getLog().isInfoEnabled() ) + { + getLog().info( sb.toString() ); + } + } + + + private static boolean isNotEmpty( String string ) + { + return string != null && string.length() > 0; + } + + private static String getValue( Node node, String elementName ) + throws MojoExecutionException + { + return getSingleChild( node, elementName ).getTextContent(); + } + + private static Node getSingleChild( Node node, String elementName ) + throws MojoExecutionException + { + List namedChild = findNamedChild( node, elementName ); + if ( namedChild.isEmpty() ) + { + throw new MojoExecutionException( "Could not find " + elementName + " in plugin-help.xml" ); + } + if ( namedChild.size() > 1 ) + { + throw new MojoExecutionException( "Multiple " + elementName + " in plugin-help.xml" ); + } + return namedChild.get( 0 ); + } + + private static List findNamedChild( Node node, String elementName ) + { + List result = new ArrayList(); + NodeList childNodes = node.getChildNodes(); + for ( int i = 0; i < childNodes.getLength(); i++ ) + { + Node item = childNodes.item( i ); + if ( elementName.equals( item.getNodeName() ) ) + { + result.add( item ); + } + } + return result; + } + + private static Node findSingleChild( Node node, String elementName ) + throws MojoExecutionException + { + List elementsByTagName = findNamedChild( node, elementName ); + if ( elementsByTagName.isEmpty() ) + { + return null; + } + if ( elementsByTagName.size() > 1 ) + { + throw new MojoExecutionException( "Multiple " + elementName + "in plugin-help.xml" ); + } + return elementsByTagName.get( 0 ); + } + + private void writeGoal( StringBuilder sb, String goalPrefix, Element mojo ) + throws MojoExecutionException + { + String mojoGoal = getValue( mojo, "goal" ); + Node configurationElement = findSingleChild( mojo, "configuration" ); + Node description = findSingleChild( mojo, "description" ); + if ( goal == null || goal.length() <= 0 || mojoGoal.equals( goal ) ) + { + append( sb, goalPrefix + ":" + mojoGoal, 0 ); + Node deprecated = findSingleChild( mojo, "deprecated" ); + if ( ( deprecated != null ) && isNotEmpty( deprecated.getTextContent() ) ) + { + append( sb, "Deprecated. " + deprecated.getTextContent(), 1 ); + if ( detail && description != null ) + { + append( sb, "", 0 ); + append( sb, description.getTextContent(), 1 ); + } + } + else if ( description != null ) + { + append( sb, description.getTextContent(), 1 ); + } + append( sb, "", 0 ); + + if ( detail ) + { + Node parametersNode = getSingleChild( mojo, "parameters" ); + List parameters = findNamedChild( parametersNode, "parameter" ); + append( sb, "Available parameters:", 1 ); + append( sb, "", 0 ); + + for ( Node parameter : parameters ) + { + writeParameter( sb, parameter, configurationElement ); + } + } + } + } + + private void writeParameter( StringBuilder sb, Node parameter, Node configurationElement ) + throws MojoExecutionException + { + String parameterName = getValue( parameter, "name" ); + String parameterDescription = getValue( parameter, "description" ); + + Element fieldConfigurationElement = null; + if ( configurationElement != null ) + { + fieldConfigurationElement = (Element) findSingleChild( configurationElement, parameterName ); + } + + String parameterDefaultValue = ""; + if ( fieldConfigurationElement != null && fieldConfigurationElement.hasAttribute( "default-value" ) ) + { + parameterDefaultValue = " (Default: " + fieldConfigurationElement.getAttribute( "default-value" ) + ")"; + } + append( sb, parameterName + parameterDefaultValue, 2 ); + Node deprecated = findSingleChild( parameter, "deprecated" ); + if ( ( deprecated != null ) && isNotEmpty( deprecated.getTextContent() ) ) + { + append( sb, "Deprecated. " + deprecated.getTextContent(), 3 ); + append( sb, "", 0 ); + } + append( sb, parameterDescription, 3 ); + if ( "true".equals( getValue( parameter, "required" ) ) ) + { + append( sb, "Required: Yes", 3 ); + } + if ( ( fieldConfigurationElement != null ) && isNotEmpty( fieldConfigurationElement.getTextContent() ) ) + { + String property = getPropertyFromExpression( fieldConfigurationElement.getTextContent() ); + append( sb, "User property: " + property, 3 ); + } + + append( sb, "", 0 ); + } + + /** + *

Repeat a String n times to form a new string.

+ * + * @param str String to repeat + * @param repeat number of times to repeat str + * @return String with repeated String + * @throws NegativeArraySizeException if repeat < 0 + * @throws NullPointerException if str is null + */ + private static String repeat( String str, int repeat ) + { + StringBuilder buffer = new StringBuilder( repeat * str.length() ); + + for ( int i = 0; i < repeat; i++ ) + { + buffer.append( str ); + } + + return buffer.toString(); + } + + /** + * Append a description to the buffer by respecting the indentSize and lineLength parameters. + * Note: The last character is always a new line. + * + * @param sb The buffer to append the description, not null. + * @param description The description, not null. + * @param indent The base indentation level of each line, must not be negative. + */ + private void append( StringBuilder sb, String description, int indent ) + { + for ( String line : toLines( description, indent, indentSize, lineLength ) ) + { + sb.append( line ).append( '\n' ); + } + } + + /** + * Splits the specified text into lines of convenient display length. + * + * @param text The text to split into lines, must not be null. + * @param indent The base indentation level of each line, must not be negative. + * @param indentSize The size of each indentation, must not be negative. + * @param lineLength The length of the line, must not be negative. + * @return The sequence of display lines, never null. + * @throws NegativeArraySizeException if indent < 0 + */ + private static List toLines( String text, int indent, int indentSize, int lineLength ) + { + List lines = new ArrayList(); + + String ind = repeat( "\t", indent ); + + String[] plainLines = text.split( "(\r\n)|(\r)|(\n)" ); + + for ( String plainLine : plainLines ) + { + toLines( lines, ind + plainLine, indentSize, lineLength ); + } + + return lines; + } + + /** + * Adds the specified line to the output sequence, performing line wrapping if necessary. + * + * @param lines The sequence of display lines, must not be null. + * @param line The line to add, must not be null. + * @param indentSize The size of each indentation, must not be negative. + * @param lineLength The length of the line, must not be negative. + */ + private static void toLines( List lines, String line, int indentSize, int lineLength ) + { + int lineIndent = getIndentLevel( line ); + StringBuilder buf = new StringBuilder( 256 ); + + String[] tokens = line.split( " +" ); + + for ( String token : tokens ) + { + if ( buf.length() > 0 ) + { + if ( buf.length() + token.length() >= lineLength ) + { + lines.add( buf.toString() ); + buf.setLength( 0 ); + buf.append( repeat( " ", lineIndent * indentSize ) ); + } + else + { + buf.append( ' ' ); + } + } + + for ( int j = 0; j < token.length(); j++ ) + { + char c = token.charAt( j ); + if ( c == '\t' ) + { + buf.append( repeat( " ", indentSize - buf.length() % indentSize ) ); + } + else if ( c == '\u00A0' ) + { + buf.append( ' ' ); + } + else + { + buf.append( c ); + } + } + } + lines.add( buf.toString() ); + } + + /** + * Gets the indentation level of the specified line. + * + * @param line The line whose indentation level should be retrieved, must not be null. + * @return The indentation level of the line. + */ + private static int getIndentLevel( String line ) + { + int level = 0; + for ( int i = 0; i < line.length() && line.charAt( i ) == '\t'; i++ ) + { + level++; + } + for ( int i = level + 1; i <= level + 4 && i < line.length(); i++ ) + { + if ( line.charAt( i ) == '\t' ) + { + level++; + break; + } + } + return level; + } + + private static String getPropertyFromExpression( String expression ) + { + if ( expression != null && expression.startsWith( "${" ) && expression.endsWith( "}" ) + && !expression.substring( 2 ).contains( "${" ) ) + { + // expression="${xxx}" -> property="xxx" + return expression.substring( 2, expression.length() - 1 ); + } + // no property can be extracted + return null; + } +} diff --git a/preprocessor-maven-plugin/target/maven-archiver/pom.properties b/preprocessor-maven-plugin/target/maven-archiver/pom.properties new file mode 100644 index 0000000..db42f26 --- /dev/null +++ b/preprocessor-maven-plugin/target/maven-archiver/pom.properties @@ -0,0 +1,5 @@ +#Generated by Maven +#Sun Jan 25 22:43:35 IST 2026 +groupId=com.github.javapreprocessor +artifactId=preprocessor-maven-plugin +version=1.0.0-SNAPSHOT diff --git a/preprocessor-maven-plugin/target/maven-plugin-help.properties b/preprocessor-maven-plugin/target/maven-plugin-help.properties new file mode 100644 index 0000000..c664e7f --- /dev/null +++ b/preprocessor-maven-plugin/target/maven-plugin-help.properties @@ -0,0 +1,4 @@ +#maven plugin help mojo generation informations +#Sun Jan 25 22:42:46 IST 2026 +destinationDirectory=/home/tejas-warake/Desktop/open-source/java-preprocessing-lib/preprocessor-maven-plugin/target/generated-sources/plugin +helpPackageName=com.github.javapreprocessor.maven diff --git a/preprocessor-maven-plugin/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/preprocessor-maven-plugin/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..e69de29 diff --git a/preprocessor-maven-plugin/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/preprocessor-maven-plugin/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..901a3a5 --- /dev/null +++ b/preprocessor-maven-plugin/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1 @@ +/home/tejas-warake/Desktop/open-source/java-preprocessing-lib/preprocessor-maven-plugin/src/main/java/com/github/javapreprocessor/maven/PreprocessMojo.java diff --git a/preprocessor-maven-plugin/target/plugin-enhanced.xml b/preprocessor-maven-plugin/target/plugin-enhanced.xml new file mode 100644 index 0000000..b58ac25 --- /dev/null +++ b/preprocessor-maven-plugin/target/plugin-enhanced.xml @@ -0,0 +1,76 @@ + + + + + + Preprocessor Maven Plugin + + com.github.javapreprocessor + preprocessor-maven-plugin + 1.0.0-SNAPSHOT + preprocessor + false + true + 1.8 + 3.8.1 + + + process + false + true + false + false + false + true + generate-sources + com.github.javapreprocessor.maven.PreprocessMojo + java + per-lookup + once-per-session + false + + + outputDirectory + java.io.File + false + true + + + + project + org.apache.maven.project.MavenProject + true + false + + + + sourceDirectory + java.io.File + false + true + + + + symbols + java.util.Map<java.lang.String, java.lang.String> + false + true + + + + + ${outputDirectory} + + ${sourceDirectory} + + + + + + com.github.javapreprocessor + preprocessor-core + jar + 1.0.0-SNAPSHOT + + + \ No newline at end of file