Compare commits
No commits in common. "05620d9823f1638fa4ad15eef4fd6caf1e171eb5" and "d583784ee870b4b577f31129c0235f86d1cda25d" have entirely different histories.
05620d9823
...
d583784ee8
7 changed files with 26 additions and 313 deletions
26
.github/workflows/maven.yml
vendored
Normal file
26
.github/workflows/maven.yml
vendored
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time
|
||||||
|
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven
|
||||||
|
|
||||||
|
name: Java CI with Maven
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "master" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "master" ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Set up JDK 11
|
||||||
|
uses: actions/setup-java@v3
|
||||||
|
with:
|
||||||
|
java-version: '17'
|
||||||
|
distribution: 'temurin'
|
||||||
|
cache: maven
|
||||||
|
- name: Build with Maven
|
||||||
|
run: mvn -B package --file pom.xml
|
|
@ -1,5 +0,0 @@
|
||||||
steps:
|
|
||||||
- name: test
|
|
||||||
image: maven:3.9.6-eclipse-temurin-17-alpine
|
|
||||||
commands:
|
|
||||||
- mvn -B verify
|
|
31
pom.xml
31
pom.xml
|
@ -1,31 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
|
||||||
http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<groupId>eu.ztsh.training</groupId>
|
|
||||||
<artifactId>hackerrank</artifactId>
|
|
||||||
<version>1.0-SNAPSHOT</version>
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<maven.compiler.source>17</maven.compiler.source>
|
|
||||||
<maven.compiler.target>17</maven.compiler.target>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.junit.jupiter</groupId>
|
|
||||||
<artifactId>junit-jupiter</artifactId>
|
|
||||||
<version>5.9.0</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.assertj</groupId>
|
|
||||||
<artifactId>assertj-core</artifactId>
|
|
||||||
<version>3.23.1</version>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
</project>
|
|
|
@ -1,126 +0,0 @@
|
||||||
package eu.ztsh.training.hackerrank;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Scanner;
|
|
||||||
|
|
||||||
import eu.ztsh.training.hackerrank.SolutionClassDescription.FieldModifier;
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
|
||||||
import org.junit.jupiter.api.DisplayName;
|
|
||||||
import org.junit.jupiter.api.Nested;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
|
|
||||||
public class EnvironmentTest {
|
|
||||||
|
|
||||||
abstract static class HackerRankEnvironmentTest extends HackerRankTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void passTwoLinesTest() {
|
|
||||||
var input = List.of(
|
|
||||||
"Line 1",
|
|
||||||
"Line 2"
|
|
||||||
);
|
|
||||||
var result = invoke(input);
|
|
||||||
assertThat(result).containsExactlyElementsOf(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void multiInvokeTest() {
|
|
||||||
var input = List.of(
|
|
||||||
"Line 1",
|
|
||||||
"Line 2"
|
|
||||||
);
|
|
||||||
invoke(input);
|
|
||||||
invoke(input);
|
|
||||||
var result = invoke(input);
|
|
||||||
assertThat(result).containsExactlyElementsOf(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void passTwoAnotherLinesTest() {
|
|
||||||
var input = List.of(
|
|
||||||
"Line 3",
|
|
||||||
"Line 4"
|
|
||||||
);
|
|
||||||
var result = invoke(input);
|
|
||||||
assertThat(result).containsExactlyElementsOf(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nested
|
|
||||||
@DisplayName("Test with Scanner created as private static field")
|
|
||||||
class EnvironmentPrivateStaticTest extends HackerRankEnvironmentTest {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected SolutionClassDescription getSolutionClassDescription() {
|
|
||||||
return new SolutionClassDescription(SampleSolutionWithPrivateStaticScanner.class,
|
|
||||||
"scan",
|
|
||||||
new FieldModifier[]{FieldModifier.PRIVATE, FieldModifier.STATIC});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Disabled("Disabled: run with --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED")
|
|
||||||
@Nested
|
|
||||||
@DisplayName("Test with Scanner created as private static final field")
|
|
||||||
class EnvironmentPrivateStaticFinalTest extends HackerRankEnvironmentTest {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected SolutionClassDescription getSolutionClassDescription() {
|
|
||||||
return new SolutionClassDescription(SampleSolutionWithPrivateStaticScanner.class,
|
|
||||||
"scan",
|
|
||||||
FieldModifier.values());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nested
|
|
||||||
@DisplayName("Test with Scanner created in main(String[]) method")
|
|
||||||
class EnvironmentInlineTest extends HackerRankEnvironmentTest {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected SolutionClassDescription getSolutionClassDescription() {
|
|
||||||
return new SolutionClassDescription(SampleSolutionWithInlineScanner.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class SampleSolutionWithPrivateStaticScanner {
|
|
||||||
|
|
||||||
public static void main(String... args) {
|
|
||||||
while (scan.hasNext()) {
|
|
||||||
System.out.println(scan.nextLine());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("FieldMayBeFinal")
|
|
||||||
private static Scanner scan = new Scanner(System.in);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class SampleSolutionWithPrivateStaticFinalScanner {
|
|
||||||
|
|
||||||
public static void main(String... args) {
|
|
||||||
while (scan.hasNext()) {
|
|
||||||
System.out.println(scan.nextLine());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Scanner scan = new Scanner(System.in);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class SampleSolutionWithInlineScanner {
|
|
||||||
|
|
||||||
public static void main(String... args) {
|
|
||||||
var scan = new Scanner(System.in);
|
|
||||||
while (scan.hasNext()) {
|
|
||||||
System.out.println(scan.nextLine());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,73 +0,0 @@
|
||||||
package eu.ztsh.training.hackerrank;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.PrintStream;
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.AfterAll;
|
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Common class with all the necessary logic
|
|
||||||
*/
|
|
||||||
public abstract class HackerRankTest {
|
|
||||||
|
|
||||||
@BeforeAll
|
|
||||||
public static void setUpStreams() {
|
|
||||||
// Redirect stdout & stderr to own streams
|
|
||||||
System.setOut(new PrintStream(outContent));
|
|
||||||
System.setErr(new PrintStream(errContent));
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterAll
|
|
||||||
public static void restoreStreams() {
|
|
||||||
System.setOut(originalOut);
|
|
||||||
System.setErr(originalErr);
|
|
||||||
System.setIn(originalIn);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected List<String> invoke(List<String> input) {
|
|
||||||
try {
|
|
||||||
// reset our stdout as check is based on it
|
|
||||||
outContent.reset();
|
|
||||||
// write content to args of Solution#main
|
|
||||||
writeLines(input);
|
|
||||||
// check if scanner has other name than "scanner"
|
|
||||||
if (getSolutionClassDescription().fieldName() != null) {
|
|
||||||
ReflectionHelper.reloadScanner(getSolutionClassDescription());
|
|
||||||
}
|
|
||||||
// run Solution#main
|
|
||||||
getSolutionClassDescription().targetClass()
|
|
||||||
.getMethod("main", String[].class)
|
|
||||||
.invoke(null, (Object) new String[]{});
|
|
||||||
// return intercepted output
|
|
||||||
return readLines();
|
|
||||||
} catch (final NoSuchMethodException | InvocationTargetException | IllegalAccessException |
|
|
||||||
NoSuchFieldException e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract SolutionClassDescription getSolutionClassDescription();
|
|
||||||
|
|
||||||
private void writeLines(List<String> lines) {
|
|
||||||
// https://stackoverflow.com/a/31635737
|
|
||||||
System.setIn(new ByteArrayInputStream((String.join(System.lineSeparator(), lines) + System.lineSeparator()).getBytes()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> readLines() {
|
|
||||||
// https://stackoverflow.com/a/1119559
|
|
||||||
return List.copyOf(Arrays.stream(outContent.toString().split(System.lineSeparator())).toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
private final static ByteArrayOutputStream outContent = new ByteArrayOutputStream();
|
|
||||||
private final static ByteArrayOutputStream errContent = new ByteArrayOutputStream();
|
|
||||||
private final static PrintStream originalOut = System.out;
|
|
||||||
private final static PrintStream originalErr = System.err;
|
|
||||||
private final static InputStream originalIn = System.in;
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
package eu.ztsh.training.hackerrank;
|
|
||||||
|
|
||||||
import java.lang.invoke.MethodHandles;
|
|
||||||
import java.lang.invoke.VarHandle;
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.lang.reflect.Modifier;
|
|
||||||
import java.util.Scanner;
|
|
||||||
|
|
||||||
import eu.ztsh.training.hackerrank.SolutionClassDescription.FieldModifier;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Util that helps with scanner declared outside Solution#main
|
|
||||||
*/
|
|
||||||
public class ReflectionHelper {
|
|
||||||
|
|
||||||
// https://stackoverflow.com/a/56043252
|
|
||||||
static {
|
|
||||||
try {
|
|
||||||
var lookup = MethodHandles.privateLookupIn(Field.class, MethodHandles.lookup());
|
|
||||||
MODIFIERS = lookup.findVarHandle(Field.class, "modifiers", int.class);
|
|
||||||
CAN_MODIFY_FINALS = true;
|
|
||||||
} catch (IllegalAccessException | NoSuchFieldException ex) {
|
|
||||||
CAN_MODIFY_FINALS = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Modify scanner field to substitute it with custom readable one
|
|
||||||
*
|
|
||||||
* @param description Solution class parameters
|
|
||||||
* @throws NoSuchFieldException when there description.fieldName points to incorrect name
|
|
||||||
* @throws IllegalAccessException on scanner substitution error
|
|
||||||
*/
|
|
||||||
static void reloadScanner(SolutionClassDescription description)
|
|
||||||
throws NoSuchFieldException, IllegalAccessException {
|
|
||||||
// https://stackoverflow.com/a/3301720
|
|
||||||
var scannerField = description.hasModifier(FieldModifier.STATIC)
|
|
||||||
? description.targetClass().getDeclaredField(description.fieldName())
|
|
||||||
: description.targetClass().getField(description.fieldName());
|
|
||||||
if (description.hasModifier(FieldModifier.PRIVATE)) {
|
|
||||||
scannerField.setAccessible(true);
|
|
||||||
}
|
|
||||||
if (description.hasModifier(FieldModifier.FINAL) && CAN_MODIFY_FINALS) {
|
|
||||||
MODIFIERS.set(scannerField, scannerField.getModifiers() & ~Modifier.FINAL);
|
|
||||||
}
|
|
||||||
scannerField.set(null, new Scanner(System.in));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static VarHandle MODIFIERS;
|
|
||||||
private static boolean CAN_MODIFY_FINALS;
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
package eu.ztsh.training.hackerrank;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Solution params definition
|
|
||||||
*
|
|
||||||
* @param targetClass Solution
|
|
||||||
* @param fieldName scanner field name
|
|
||||||
* @param modifiers scanner field modifiers
|
|
||||||
*/
|
|
||||||
public record SolutionClassDescription(Class<?> targetClass, String fieldName, FieldModifier[] modifiers) {
|
|
||||||
|
|
||||||
SolutionClassDescription(Class<?> targetClass) {
|
|
||||||
this(targetClass, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasModifier(FieldModifier modifier) {
|
|
||||||
return Arrays.asList(modifiers).contains(modifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum FieldModifier {
|
|
||||||
PRIVATE, FINAL, STATIC
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue