diff --git a/libraries/.gitignore b/libraries/.gitignore new file mode 100644 index 0000000000..ac45fafa62 --- /dev/null +++ b/libraries/.gitignore @@ -0,0 +1,8 @@ +*.class + +# Folders # +/gensrc +/target + +# Packaged files # +*.jar diff --git a/libraries/pom.xml b/libraries/pom.xml index 663b9e68bb..e3a6656995 100644 --- a/libraries/pom.xml +++ b/libraries/pom.xml @@ -722,6 +722,17 @@ test + + com.squareup + javapoet + ${javapoet.version} + + + org.hamcrest + hamcrest-all + ${hamcrest-all.version} + test + @@ -939,6 +950,8 @@ 3.5.2 3.6 2.7.1 + 1.10.0 + 1.3 \ No newline at end of file diff --git a/libraries/src/main/java/com/baeldung/javapoet/PersonGenerator.java b/libraries/src/main/java/com/baeldung/javapoet/PersonGenerator.java new file mode 100644 index 0000000000..6dd41cc0bd --- /dev/null +++ b/libraries/src/main/java/com/baeldung/javapoet/PersonGenerator.java @@ -0,0 +1,183 @@ +package com.baeldung.javapoet; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; + +import javax.lang.model.element.Modifier; +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.stream.IntStream; + +public class PersonGenerator { + + private static final String FOUR_WHITESPACES = " "; + private static final String PERSON_PACKAGE_NAME = "com.baeldung.javapoet.test.person"; + + private File outputFile; + + public PersonGenerator() { + outputFile = new File(getOutputPath().toUri()); + } + + public static String getPersonPackageName() { + return PERSON_PACKAGE_NAME; + } + + public Path getOutputPath() { + return Paths.get(new File(".").getAbsolutePath() + "/gensrc"); + } + + public FieldSpec getDefaultNameField() { + return FieldSpec + .builder(String.class, "DEFAULT_NAME") + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .initializer("$S", "Alice") + .build(); + } + + public MethodSpec getSortByLengthMethod() { + return MethodSpec + .methodBuilder("sortByLength") + .addModifiers(Modifier.PUBLIC, Modifier.STATIC) + .addParameter(ParameterSpec + .builder(ParameterizedTypeName.get(ClassName.get(List.class), TypeName.get(String.class)), "strings") + .build()) + .addStatement("$T.sort($N, $L)", Collections.class, "strings", getComparatorAnonymousClass()) + .build(); + } + + public MethodSpec getPrintNameMultipleTimesMethod() { + return MethodSpec + .methodBuilder("printNameMultipleTimes") + .addModifiers(Modifier.PUBLIC) + .addCode(getPrintNameMultipleTimesLambdaImpl()) + .build(); + } + + public CodeBlock getPrintNameMultipleTimesImpl() { + return CodeBlock + .builder() + .beginControlFlow("for (int i = $L; i < $L; i++)") + .addStatement("System.out.println(name)") + .endControlFlow() + .build(); + } + + public CodeBlock getPrintNameMultipleTimesLambdaImpl() { + return CodeBlock + .builder() + .addStatement("$T<$T> names = new $T<>()", List.class, String.class, ArrayList.class) + .addStatement("$T.range($L, $L).forEach(i -> names.add(name))", IntStream.class, 0, 10) + .addStatement("names.forEach(System.out::println)") + .build(); + } + + public TypeSpec getGenderEnum() { + return TypeSpec + .enumBuilder("Gender") + .addModifiers(Modifier.PUBLIC) + .addEnumConstant("MALE") + .addEnumConstant("FEMALE") + .addEnumConstant("UNSPECIFIED") + .build(); + } + + public TypeSpec getPersonInterface() { + return TypeSpec + .interfaceBuilder("Person") + .addModifiers(Modifier.PUBLIC) + .addField(getDefaultNameField()) + .addMethod(MethodSpec + .methodBuilder("getName") + .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) + .returns(String.class) + .build()) + .addMethod(MethodSpec + .methodBuilder("getDefaultName") + .addModifiers(Modifier.PUBLIC, Modifier.DEFAULT) + .returns(String.class) + .addCode(CodeBlock + .builder() + .addStatement("return DEFAULT_NAME") + .build()) + .build()) + .build(); + } + + public TypeSpec getStudentClass() { + return TypeSpec + .classBuilder("Student") + .addSuperinterface(ClassName.get(PERSON_PACKAGE_NAME, "Person")) + .addModifiers(Modifier.PUBLIC) + .addField(FieldSpec + .builder(String.class, "name") + .addModifiers(Modifier.PRIVATE) + .build()) + .addMethod(MethodSpec + .methodBuilder("getName") + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC) + .returns(String.class) + .addStatement("return this.name") + .build()) + .addMethod(MethodSpec + .methodBuilder("setName") + .addParameter(String.class, "name") + .addModifiers(Modifier.PUBLIC) + .addStatement("this.name = name") + .build()) + .addMethod(getPrintNameMultipleTimesMethod()) + .addMethod(getSortByLengthMethod()) + .build(); + } + + public TypeSpec getComparatorAnonymousClass() { + return TypeSpec + .anonymousClassBuilder("") + .addSuperinterface(ParameterizedTypeName.get(Comparator.class, String.class)) + .addMethod(MethodSpec + .methodBuilder("compare") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .addParameter(String.class, "a") + .addParameter(String.class, "b") + .returns(int.class) + .addStatement("return a.length() - b.length()") + .build()) + .build(); + } + + public void generateGenderEnum() throws IOException { + writeToOutputFile(getPersonPackageName(), getGenderEnum()); + } + + public void generatePersonInterface() throws IOException { + writeToOutputFile(getPersonPackageName(), getPersonInterface()); + } + + public void generateStudentClass() throws IOException { + writeToOutputFile(getPersonPackageName(), getStudentClass()); + } + + private void writeToOutputFile(String packageName, TypeSpec typeSpec) throws IOException { + JavaFile javaFile = JavaFile + .builder(packageName, typeSpec) + .indent(FOUR_WHITESPACES) + .build(); + javaFile.writeTo(outputFile); + } + +} diff --git a/libraries/src/test/java/com/baeldung/javapoet/test/PersonGeneratorUnitTest.java b/libraries/src/test/java/com/baeldung/javapoet/test/PersonGeneratorUnitTest.java new file mode 100644 index 0000000000..61bf3ebc33 --- /dev/null +++ b/libraries/src/test/java/com/baeldung/javapoet/test/PersonGeneratorUnitTest.java @@ -0,0 +1,99 @@ +package com.baeldung.javapoet.test; + +import com.baeldung.javapoet.PersonGenerator; +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +@RunWith(JUnit4.class) +public class PersonGeneratorUnitTest { + + private PersonGenerator generator; + private Path generatedFolderPath; + private Path expectedFolderPath; + + @Before + public void setUp() { + String packagePath = this + .getClass() + .getPackage() + .getName() + .replace(".", "/") + "/person"; + generator = new PersonGenerator(); + generatedFolderPath = generator + .getOutputPath() + .resolve(packagePath); + expectedFolderPath = Paths.get(new File(".").getAbsolutePath() + "/src/test/java/" + packagePath); + } + + @After + public void tearDown() throws Exception { + FileUtils.deleteDirectory(new File(generator + .getOutputPath() + .toUri())); + } + + @Test + public void whenGenerateGenderEnum_thenGenerateGenderEnumAndWriteToFile() throws IOException { + generator.generateGenderEnum(); + String fileName = "Gender.java"; + assertThatFileIsGeneratedAsExpected(fileName); + deleteGeneratedFile(fileName); + } + + @Test + public void whenGeneratePersonInterface_thenGeneratePersonInterfaceAndWriteToFile() throws IOException { + generator.generatePersonInterface(); + String fileName = "Person.java"; + assertThatFileIsGeneratedAsExpected(fileName); + deleteGeneratedFile(fileName); + } + + @Test + public void whenGenerateStudentClass_thenGenerateStudentClassAndWriteToFile() throws IOException { + generator.generateStudentClass(); + String fileName = "Student.java"; + assertThatFileIsGeneratedAsExpected(fileName); + deleteGeneratedFile(fileName); + } + + private void assertThatFileIsGeneratedAsExpected(String fileName) throws IOException { + String generatedFileContent = extractFileContent(generatedFolderPath.resolve(fileName)); + String expectedFileContent = extractFileContent(expectedFolderPath.resolve(fileName)); + + assertThat("Generated file is identical to the file with the expected content", generatedFileContent, is(equalTo(expectedFileContent))); + + } + + private void deleteGeneratedFile(String fileName) throws IOException { + Path generatedFilePath = generatedFolderPath.resolve(fileName); + Files.delete(generatedFilePath); + } + + private String extractFileContent(Path filePath) throws IOException { + byte[] fileContentAsBytes = Files.readAllBytes(filePath); + String fileContentAsString = new String(fileContentAsBytes, StandardCharsets.UTF_8); + + if (!fileContentAsString.contains("\r\n")) { + // file is not in DOS format + // convert it first, so that the content comparison will be relevant + return fileContentAsString.replaceAll("\n", "\r\n"); + } + return fileContentAsString; + } + +} diff --git a/libraries/src/test/java/com/baeldung/javapoet/test/person/Gender.java b/libraries/src/test/java/com/baeldung/javapoet/test/person/Gender.java new file mode 100644 index 0000000000..3c5657fb9d --- /dev/null +++ b/libraries/src/test/java/com/baeldung/javapoet/test/person/Gender.java @@ -0,0 +1,9 @@ +package com.baeldung.javapoet.test.person; + +public enum Gender { + MALE, + + FEMALE, + + UNSPECIFIED +} diff --git a/libraries/src/test/java/com/baeldung/javapoet/test/person/Person.java b/libraries/src/test/java/com/baeldung/javapoet/test/person/Person.java new file mode 100644 index 0000000000..fae8b23075 --- /dev/null +++ b/libraries/src/test/java/com/baeldung/javapoet/test/person/Person.java @@ -0,0 +1,13 @@ +package com.baeldung.javapoet.test.person; + +import java.lang.String; + +public interface Person { + String DEFAULT_NAME = "Alice"; + + String getName(); + + default String getDefaultName() { + return DEFAULT_NAME; + } +} diff --git a/libraries/src/test/java/com/baeldung/javapoet/test/person/Student.java b/libraries/src/test/java/com/baeldung/javapoet/test/person/Student.java new file mode 100644 index 0000000000..1c7d5cc096 --- /dev/null +++ b/libraries/src/test/java/com/baeldung/javapoet/test/person/Student.java @@ -0,0 +1,37 @@ +package com.baeldung.javapoet.test.person; + +import java.lang.Override; +import java.lang.String; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.stream.IntStream; + +public class Student implements Person { + private String name; + + @Override + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public void printNameMultipleTimes() { + List names = new ArrayList<>(); + IntStream.range(0, 10).forEach(i -> names.add(name)); + names.forEach(System.out::println); + } + + public static void sortByLength(List strings) { + Collections.sort(strings, new Comparator() { + @Override + public int compare(String a, String b) { + return a.length() - b.length(); + } + }); + } +}