Add Java code optimization demo with Gradle and JUnit 4/5 (#2369)

## Summary
Added Java code optimization demonstration project to
`cli/code-to-optimize/`

- Set up Gradle build system with dual JUnit support (JUnit 4 and JUnit
5)
- Created three classes with common optimization opportunities:
- **StringProcessor**: String concatenation and manipulation
inefficiencies
- **CollectionProcessor**: Collection operations with performance issues
  - **DataCalculator**: Algorithm optimization opportunities
- Added comprehensive test suite with 20+ tests
- All tests passing with `./gradlew test`

## Code Optimization Opportunities

### StringProcessor
- String concatenation in loops (O(n²) → O(n) with StringBuilder)
- Multiple intermediate String objects
- Repeated substring operations

### CollectionProcessor
- ArrayList.contains() lookups (O(n²) → O(n) with HashSet)
- Multiple collection passes (can be single pass)
- Inefficient recursive implementations

### DataCalculator
- Fibonacci without memoization (O(2ⁿ) → O(n) with DP)
- Prime checking inefficiency (O(n) → O(√n))
- Simple power modulo (can use fast exponentiation)

## Project Structure
```
cli/code-to-optimize/
├── build.gradle (JUnit 4 & 5 configured)
├── settings.gradle
├── README.md
├── gradlew (executable)
├── gradle/wrapper/
└── src/
    ├── main/java/com/example/optimization/
    │   ├── StringProcessor.java
    │   ├── CollectionProcessor.java
    │   └── DataCalculator.java
    └── test/java/com/example/optimization/
        ├── StringProcessorTest.java (JUnit 5)
        ├── CollectionProcessorTest.java (JUnit 4)
        └── DataCalculatorTest.java (JUnit 5)
```

## Test Coverage
- JUnit 5 tests for StringProcessor and DataCalculator (with
parameterized tests)
- JUnit 4 tests for CollectionProcessor
- Both frameworks working together via JUnit Vintage Engine
- 20+ test cases covering all optimization scenarios

## Running Tests
```bash
cd cli/code-to-optimize
./gradlew test      # Run all tests
./gradlew build     # Build the project
```

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Ubuntu <ubuntu@ip-172-31-39-200.ec2.internal>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
HeshamHM28 2026-02-04 11:03:54 -08:00 committed by GitHub
parent 7cdfe8d8e2
commit 8e90c9e140
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 632 additions and 0 deletions

View file

@ -0,0 +1,32 @@
plugins {
id 'java'
}
group = 'com.example'
version = '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
// JUnit 5 (Jupiter)
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.1'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.10.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.1'
// JUnit 4
testImplementation 'junit:junit:4.13.2'
// JUnit Vintage to run JUnit 4 tests with JUnit 5
testRuntimeOnly 'org.junit.vintage:junit-vintage-engine:5.10.1'
}
test {
useJUnitPlatform()
}
java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}

Binary file not shown.

View file

@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

189
cli/code-to-optimize/gradlew vendored Executable file
View file

@ -0,0 +1,189 @@
#!/bin/sh
##############################################################################
# Gradle start up script for UN*X
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

View file

@ -0,0 +1 @@
rootProject.name = 'code-optimization-demo'

View file

@ -0,0 +1,66 @@
package com.example.optimization;
import java.util.ArrayList;
import java.util.List;
/**
* Collection processing class with inefficient implementations
* that need optimization.
*/
public class CollectionProcessor {
/**
* Inefficient: Uses contains() which is O(n) for ArrayList
* Should use HashSet for O(1) lookup
*/
public List<Integer> removeDuplicates(List<Integer> numbers) {
List<Integer> result = new ArrayList<>();
for (Integer num : numbers) {
if (!result.contains(num)) {
result.add(num);
}
}
return result;
}
/**
* Inefficient: Multiple passes through the collection
* Should use a single pass with tracking variables
*/
public int sumOfEvens(List<Integer> numbers) {
List<Integer> evens = new ArrayList<>();
for (Integer num : numbers) {
if (num % 2 == 0) {
evens.add(num);
}
}
int sum = 0;
for (Integer num : evens) {
sum += num;
}
return sum;
}
/**
* Inefficient: Creates new list on each recursive call
* Should use iterative approach or accumulator
*/
public List<Integer> filterPositive(List<Integer> numbers) {
if (numbers.isEmpty()) {
return new ArrayList<>();
}
List<Integer> result = new ArrayList<>();
Integer first = numbers.get(0);
if (first > 0) {
result.add(first);
}
List<Integer> rest = numbers.subList(1, numbers.size());
result.addAll(filterPositive(rest));
return result;
}
}

View file

@ -0,0 +1,47 @@
package com.example.optimization;
/**
* Data calculation class with inefficient implementations
* that need optimization.
*/
public class DataCalculator {
/**
* Inefficient: Recalculates Fibonacci values recursively without memoization
* Should use dynamic programming or iterative approach
*/
public long fibonacci(int n) {
if (n <= 1) {
return n;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
/**
* Inefficient: Uses trial division for every number
* Should use Sieve of Eratosthenes for multiple primes
*/
public boolean isPrime(int number) {
if (number <= 1) {
return false;
}
for (int i = 2; i < number; i++) {
if (number % i == 0) {
return false;
}
}
return true;
}
/**
* Inefficient: Uses pow and division operations unnecessarily
* Should use modular arithmetic directly
*/
public int powerModulo(int base, int exponent, int modulo) {
int result = 1;
for (int i = 0; i < exponent; i++) {
result = (result * base) % modulo;
}
return result;
}
}

View file

@ -0,0 +1,52 @@
package com.example.optimization;
import java.util.List;
/**
* String processing class with inefficient implementations
* that need optimization.
*/
public class StringProcessor {
/**
* Inefficient: Uses String concatenation in a loop
* Should use StringBuilder for better performance
*/
public String concatenateStrings(List<String> strings) {
String result = "";
for (String str : strings) {
result += str + " ";
}
return result.trim();
}
/**
* Inefficient: Creates multiple intermediate String objects
* Should use StringBuilder or single replace chain
*/
public String sanitizeInput(String input) {
String result = input;
result = result.replace("&", "&amp;");
result = result.replace("<", "&lt;");
result = result.replace(">", "&gt;");
result = result.replace("\"", "&quot;");
result = result.replace("'", "&#x27;");
return result;
}
/**
* Inefficient: Uses repeated substring operations
* Should use a single pass algorithm
*/
public String reverseWords(String sentence) {
String[] words = sentence.split(" ");
String result = "";
for (int i = words.length - 1; i >= 0; i--) {
result += words[i];
if (i > 0) {
result += " ";
}
}
return result;
}
}

View file

@ -0,0 +1,71 @@
package com.example.optimization;
import org.junit.Before;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.*;
/**
* JUnit 4 tests for CollectionProcessor
*/
public class CollectionProcessorTest {
private CollectionProcessor processor;
@Before
public void setUp() {
processor = new CollectionProcessor();
}
@Test
public void testRemoveDuplicates() {
List<Integer> numbers = Arrays.asList(1, 2, 3, 2, 4, 3, 5);
List<Integer> result = processor.removeDuplicates(numbers);
assertEquals(Arrays.asList(1, 2, 3, 4, 5), result);
}
@Test
public void testRemoveDuplicatesEmptyList() {
List<Integer> numbers = Arrays.asList();
List<Integer> result = processor.removeDuplicates(numbers);
assertTrue(result.isEmpty());
}
@Test
public void testSumOfEvens() {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
int result = processor.sumOfEvens(numbers);
assertEquals(12, result); // 2 + 4 + 6 = 12
}
@Test
public void testSumOfEvensNoEvens() {
List<Integer> numbers = Arrays.asList(1, 3, 5, 7);
int result = processor.sumOfEvens(numbers);
assertEquals(0, result);
}
@Test
public void testFilterPositive() {
List<Integer> numbers = Arrays.asList(-2, 3, -1, 5, 0, 7);
List<Integer> result = processor.filterPositive(numbers);
assertEquals(Arrays.asList(3, 5, 7), result);
}
@Test
public void testFilterPositiveAllNegative() {
List<Integer> numbers = Arrays.asList(-1, -2, -3);
List<Integer> result = processor.filterPositive(numbers);
assertTrue(result.isEmpty());
}
@Test
public void testFilterPositiveAllPositive() {
List<Integer> numbers = Arrays.asList(1, 2, 3);
List<Integer> result = processor.filterPositive(numbers);
assertEquals(Arrays.asList(1, 2, 3), result);
}
}

View file

@ -0,0 +1,93 @@
package com.example.optimization;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.*;
/**
* JUnit 5 tests for DataCalculator
*/
@DisplayName("DataCalculator Tests (JUnit 5)")
class DataCalculatorTest {
private DataCalculator calculator;
@BeforeEach
void setUp() {
calculator = new DataCalculator();
}
@Test
@DisplayName("Should calculate Fibonacci for n=0")
void testFibonacciZero() {
assertEquals(0, calculator.fibonacci(0));
}
@Test
@DisplayName("Should calculate Fibonacci for n=1")
void testFibonacciOne() {
assertEquals(1, calculator.fibonacci(1));
}
@ParameterizedTest
@DisplayName("Should calculate Fibonacci correctly")
@CsvSource({
"2, 1",
"3, 2",
"4, 3",
"5, 5",
"6, 8",
"7, 13",
"10, 55"
})
void testFibonacci(int n, long expected) {
assertEquals(expected, calculator.fibonacci(n));
}
@ParameterizedTest
@DisplayName("Should identify prime numbers correctly")
@CsvSource({
"2, true",
"3, true",
"4, false",
"5, true",
"17, true",
"20, false",
"23, true",
"100, false"
})
void testIsPrime(int number, boolean expected) {
assertEquals(expected, calculator.isPrime(number));
}
@Test
@DisplayName("Should return false for negative numbers")
void testIsPrimeNegative() {
assertFalse(calculator.isPrime(-5));
}
@Test
@DisplayName("Should return false for 0 and 1")
void testIsPrimeZeroAndOne() {
assertFalse(calculator.isPrime(0));
assertFalse(calculator.isPrime(1));
}
@Test
@DisplayName("Should calculate power modulo correctly")
void testPowerModulo() {
assertEquals(4, calculator.powerModulo(2, 2, 5)); // 2^2 % 5 = 4
assertEquals(1, calculator.powerModulo(3, 4, 10)); // 3^4 % 10 = 81 % 10 = 1
assertEquals(0, calculator.powerModulo(5, 3, 5)); // 5^3 % 5 = 0
}
@Test
@DisplayName("Should handle exponent 0")
void testPowerModuloZeroExponent() {
assertEquals(1, calculator.powerModulo(5, 0, 7)); // Any number^0 = 1
}
}

View file

@ -0,0 +1,74 @@
package com.example.optimization;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
/**
* JUnit 5 tests for StringProcessor
*/
@DisplayName("StringProcessor Tests (JUnit 5)")
class StringProcessorTest {
private StringProcessor processor;
@BeforeEach
void setUp() {
processor = new StringProcessor();
}
@Test
@DisplayName("Should concatenate strings correctly")
void testConcatenateStrings() {
List<String> strings = Arrays.asList("Hello", "World", "Java");
String result = processor.concatenateStrings(strings);
assertEquals("Hello World Java", result);
}
@Test
@DisplayName("Should handle empty list")
void testConcatenateEmptyList() {
List<String> strings = Arrays.asList();
String result = processor.concatenateStrings(strings);
assertEquals("", result);
}
@Test
@DisplayName("Should sanitize HTML special characters")
void testSanitizeInput() {
String input = "<script>alert('XSS')</script>";
String result = processor.sanitizeInput(input);
assertEquals("&lt;script&gt;alert(&#x27;XSS&#x27;)&lt;/script&gt;", result);
}
@Test
@DisplayName("Should handle input with quotes")
void testSanitizeQuotes() {
String input = "He said \"Hello\" & 'Goodbye'";
String result = processor.sanitizeInput(input);
assertTrue(result.contains("&quot;"));
assertTrue(result.contains("&#x27;"));
assertTrue(result.contains("&amp;"));
}
@Test
@DisplayName("Should reverse words in sentence")
void testReverseWords() {
String sentence = "Hello World Java";
String result = processor.reverseWords(sentence);
assertEquals("Java World Hello", result);
}
@Test
@DisplayName("Should handle single word")
void testReverseSingleWord() {
String sentence = "Hello";
String result = processor.reverseWords(sentence);
assertEquals("Hello", result);
}
}