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();
+ }
+ });
+ }
+}