├── .github └── workflows │ └── maven.yml ├── .gitignore ├── README.md ├── api ├── .gitignore ├── pom.xml └── src │ └── main │ └── java │ └── org │ └── soujava │ └── medatadata │ └── api │ ├── ClassMappings.java │ ├── Column.java │ ├── Constructor.java │ ├── Entity.java │ ├── EntityMetadata.java │ ├── FieldMetadata.java │ ├── Id.java │ ├── Mapper.java │ ├── MapperException.java │ └── Param.java ├── compile ├── .gitignore ├── pom.xml └── src │ ├── main │ ├── java │ │ └── org │ │ │ └── soujava │ │ │ └── metadata │ │ │ └── compiler │ │ │ ├── CompileMapper.java │ │ │ ├── CompilerAccessException.java │ │ │ ├── EntityMetadata.java │ │ │ ├── EntityMetadataFactory.java │ │ │ ├── FieldMetadata.java │ │ │ ├── FieldReader.java │ │ │ ├── FieldReaderFactory.java │ │ │ ├── FieldWriter.java │ │ │ ├── FieldWriterFactory.java │ │ │ ├── GeneratedJavaFileManager.java │ │ │ ├── InstanceSupplier.java │ │ │ ├── InstanceSupplierFactory.java │ │ │ ├── JavaCompiledStream.java │ │ │ ├── JavaCompilerClassLoader.java │ │ │ ├── JavaCompilerFacade.java │ │ │ ├── JavaFileObject.java │ │ │ ├── JavaSource.java │ │ │ ├── StringFormatter.java │ │ │ └── TemplateReader.java │ └── resources │ │ ├── FieldReader.tempalte │ │ ├── FieldWriter.template │ │ └── InstanceSupplier.template │ └── test │ └── java │ └── org │ └── soujava │ └── metadata │ └── compiler │ ├── Animal.java │ ├── CompilerMapperTest.java │ ├── CompilerTest.java │ ├── JavaCompilerBeanPropertyReaderFactory.java │ └── ReadFromGetterMethod.java ├── example ├── .gitignore ├── pom.xml └── src │ ├── main │ └── java │ │ └── org │ │ └── soujava │ │ └── metadata │ │ └── example │ │ ├── Animal.java │ │ ├── Car.java │ │ └── Person.java │ └── test │ └── java │ └── org │ └── soujava │ └── metadata │ └── example │ ├── AbstractMapperTest.java │ ├── CarTest.java │ ├── CompilerMapTest.java │ ├── PersonTest.java │ ├── ProcessorMapTest.java │ └── ReflectionMapTest.java ├── pom.xml ├── processor ├── .gitignore ├── pom.xml └── src │ ├── main │ ├── java │ │ └── org │ │ │ └── soujava │ │ │ └── metadata │ │ │ └── processor │ │ │ ├── BaseMappingModel.java │ │ │ ├── ClassAnalyzer.java │ │ │ ├── ClassMappingsModel.java │ │ │ ├── EntityModel.java │ │ │ ├── EntityProcessor.java │ │ │ ├── FieldAnalyzer.java │ │ │ ├── FieldModel.java │ │ │ └── ProcessorUtil.java │ └── resources │ │ ├── META-INF │ │ └── services │ │ │ └── javax.annotation.processing.Processor │ │ ├── ProcessorMap.java │ │ ├── classmappings.mustache │ │ ├── entitymetadata.mustache │ │ └── fieldmetadata.mustache │ └── test │ ├── java │ └── org │ │ └── soujava │ │ └── metadata │ │ └── processor │ │ └── CompilerTest.java │ └── resources │ ├── Person.java │ ├── Person2.java │ ├── Person3.java │ ├── Person4.java │ ├── Person5.java │ ├── Person6.java │ ├── Person7.java │ └── Person8.java └── reflection ├── .gitignore ├── pom.xml └── src ├── main └── java │ └── org │ └── soujava │ └── metadata │ └── reflection │ ├── EntitySupplier.java │ ├── FieldReader.java │ ├── FieldWriter.java │ └── ReflectionMapper.java └── test └── java └── org └── soujava └── metadata └── reflection ├── Animal.java ├── Person.java └── ReflectionMapperTest.java /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Java 21 CI with Maven 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Set up JDK 21 20 | uses: actions/setup-java@v3 21 | with: 22 | distribution: 'temurin' 23 | java-version: 21 24 | 25 | - name: Cache local Maven repository 26 | uses: actions/cache@v3 27 | with: 28 | path: ~/.m2/repository 29 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} 30 | restore-keys: | 31 | ${{ runner.os }}-maven 32 | - name: Build with Maven 33 | run: mvn -B package --file pom.xml 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | pom.xml.tag 3 | pom.xml.releaseBackup 4 | pom.xml.versionsBackup 5 | pom.xml.next 6 | test-output/ 7 | /doc 8 | *.iml 9 | *.idea 10 | *.log 11 | /.idea 12 | .checkstyle 13 | 14 | # Eclipse metadata 15 | .settings/ 16 | .project 17 | .factorypath 18 | .classpath 19 | -project 20 | /.resourceCache 21 | /.project 22 | 23 | # Annotation processor metadata 24 | .apt_generated/ 25 | .apt_generated_tests/ 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Modern Cloud-Native Jakarta EE Frameworks: tips, challenges, and trends 2 | 3 | This part demonstrates the generation of Java source code. 4 | 5 | The example requires Java 8 and Maven >= 3. 6 | 7 | Compile the Project: 8 | 9 | ``` 10 | mvn clean install 11 | ``` 12 | 13 | ## Articles 14 | 15 | * [Portuguese](https://www.infoq.com/br/articles/frameworks-java-na-era-cloud-native-java-Jakarta/) 16 | * [English](https://dzone.com/articles/modern-cloud-native-jakarta-ee-frameworks) 17 | * [French](https://www.infoq.com/fr/articles/java-frameworks-cloud-native/) 18 | -------------------------------------------------------------------------------- /api/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | pom.xml.tag 3 | pom.xml.releaseBackup 4 | pom.xml.versionsBackup 5 | pom.xml.next 6 | test-output/ 7 | /doc 8 | *.iml 9 | *.idea 10 | *.log 11 | /.idea 12 | .checkstyle 13 | 14 | # Eclipse metadata 15 | .settings/ 16 | .project 17 | .factorypath 18 | .classpath 19 | -project 20 | /.resourceCache 21 | /.project 22 | 23 | # Annotation processor metadata 24 | .apt_generated/ 25 | .apt_generated_tests/ 26 | -------------------------------------------------------------------------------- /api/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | 8 | 9 | org.soujava.metadata 10 | parent 11 | 1.0.0 12 | 13 | api 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /api/src/main/java/org/soujava/medatadata/api/ClassMappings.java: -------------------------------------------------------------------------------- 1 | package org.soujava.medatadata.api; 2 | 3 | import java.util.Optional; 4 | 5 | public interface ClassMappings { 6 | 7 | EntityMetadata get(Class classEntity); 8 | 9 | EntityMetadata findByName(String name); 10 | 11 | Optional findBySimpleName(String name); 12 | 13 | Optional findByClassName(String name); 14 | } 15 | -------------------------------------------------------------------------------- /api/src/main/java/org/soujava/medatadata/api/Column.java: -------------------------------------------------------------------------------- 1 | package org.soujava.medatadata.api; 2 | 3 | 4 | import java.lang.annotation.Documented; 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Documented 11 | @Target(ElementType.FIELD) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface Column { 14 | String value() default ""; 15 | } 16 | -------------------------------------------------------------------------------- /api/src/main/java/org/soujava/medatadata/api/Constructor.java: -------------------------------------------------------------------------------- 1 | package org.soujava.medatadata.api; 2 | 3 | 4 | import java.lang.annotation.Documented; 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Documented 11 | @Target(ElementType.CONSTRUCTOR) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface Constructor { 14 | } 15 | -------------------------------------------------------------------------------- /api/src/main/java/org/soujava/medatadata/api/Entity.java: -------------------------------------------------------------------------------- 1 | package org.soujava.medatadata.api; 2 | 3 | 4 | import java.lang.annotation.Documented; 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Documented 11 | @Target(ElementType.TYPE) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface Entity { 14 | String value() default ""; 15 | } 16 | -------------------------------------------------------------------------------- /api/src/main/java/org/soujava/medatadata/api/EntityMetadata.java: -------------------------------------------------------------------------------- 1 | package org.soujava.medatadata.api; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | import java.util.Optional; 6 | 7 | public interface EntityMetadata { 8 | 9 | String getName(); 10 | 11 | String getSimpleName(); 12 | 13 | String getClassName(); 14 | 15 | List getFieldsName(); 16 | 17 | Class getClassInstance(); 18 | 19 | List getFields(); 20 | 21 | T newInstance(); 22 | 23 | String getColumnField(String javaField); 24 | 25 | Optional getFieldMapping(String javaField); 26 | 27 | Map getFieldsGroupByName(); 28 | 29 | Optional getId(); 30 | } 31 | -------------------------------------------------------------------------------- /api/src/main/java/org/soujava/medatadata/api/FieldMetadata.java: -------------------------------------------------------------------------------- 1 | package org.soujava.medatadata.api; 2 | 3 | import java.util.Set; 4 | 5 | public interface FieldMetadata { 6 | 7 | boolean isId(); 8 | 9 | String getFieldName(); 10 | 11 | String getName(); 12 | 13 | void write(Object bean, Object value); 14 | 15 | Object read(Object bean); 16 | 17 | Class getType(); 18 | 19 | Set> getArguments(); 20 | } 21 | -------------------------------------------------------------------------------- /api/src/main/java/org/soujava/medatadata/api/Id.java: -------------------------------------------------------------------------------- 1 | package org.soujava.medatadata.api; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | @Documented 10 | @Target(ElementType.FIELD) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | public @interface Id { 13 | 14 | String value() default ""; 15 | } 16 | -------------------------------------------------------------------------------- /api/src/main/java/org/soujava/medatadata/api/Mapper.java: -------------------------------------------------------------------------------- 1 | package org.soujava.medatadata.api; 2 | 3 | import java.util.Map; 4 | 5 | public interface Mapper { 6 | 7 | T toEntity(Map map, Class type); 8 | 9 | Map toMap(T entity); 10 | } 11 | -------------------------------------------------------------------------------- /api/src/main/java/org/soujava/medatadata/api/MapperException.java: -------------------------------------------------------------------------------- 1 | package org.soujava.medatadata.api; 2 | 3 | public class MapperException extends RuntimeException { 4 | 5 | public MapperException(String message) { 6 | super(message); 7 | } 8 | 9 | public MapperException(String message, Throwable cause) { 10 | super(message, cause); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /api/src/main/java/org/soujava/medatadata/api/Param.java: -------------------------------------------------------------------------------- 1 | package org.soujava.medatadata.api; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.PARAMETER) 10 | public @interface Param { 11 | 12 | String value(); 13 | } -------------------------------------------------------------------------------- /compile/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | pom.xml.tag 3 | pom.xml.releaseBackup 4 | pom.xml.versionsBackup 5 | pom.xml.next 6 | test-output/ 7 | /doc 8 | *.iml 9 | *.idea 10 | *.log 11 | /.idea 12 | .checkstyle 13 | 14 | # Eclipse metadata 15 | .settings/ 16 | .project 17 | .factorypath 18 | .classpath 19 | -project 20 | /.resourceCache 21 | /.project 22 | 23 | # Annotation processor metadata 24 | .apt_generated/ 25 | .apt_generated_tests/ 26 | -------------------------------------------------------------------------------- /compile/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | 8 | 9 | org.soujava.metadata 10 | parent 11 | 1.0.0 12 | 13 | compile 14 | 15 | 16 | 17 | ${project.groupId} 18 | api 19 | ${project.version} 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /compile/src/main/java/org/soujava/metadata/compiler/CompileMapper.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.compiler; 2 | 3 | import org.soujava.medatadata.api.Mapper; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import java.util.Objects; 8 | import java.util.function.Function; 9 | import java.util.stream.Collectors; 10 | 11 | public class CompileMapper implements Mapper { 12 | 13 | private Map, EntityMetadata> mapper = new HashMap<>(); 14 | private final EntityMetadataFactory factory = new EntityMetadataFactory(); 15 | 16 | @Override 17 | public T toEntity(Map map, Class type) { 18 | Objects.requireNonNull(map, "Map is required"); 19 | Objects.requireNonNull(type, "type is required"); 20 | 21 | final EntityMetadata entityMetadata = getEntityMetadata(type); 22 | final T instance = entityMetadata.newInstance(); 23 | final Map fieldMap = entityMetadata.getFields().stream() 24 | .collect(Collectors.toMap(FieldMetadata::getName, Function.identity())); 25 | 26 | for (Map.Entry entry : map.entrySet()) { 27 | final FieldMetadata metadata = fieldMap.get(entry.getKey()); 28 | if (metadata != null) { 29 | metadata.write(instance, entry.getValue()); 30 | } 31 | } 32 | 33 | return instance; 34 | } 35 | 36 | @Override 37 | public Map toMap(T entity) { 38 | Objects.requireNonNull(entity, "entity is required"); 39 | final EntityMetadata entityMetadata = getEntityMetadata(entity.getClass()); 40 | Map map = new HashMap<>(); 41 | map.put("entity", entityMetadata.getName()); 42 | for (FieldMetadata field : entityMetadata.getFields()) { 43 | final Object value = field.getValue(entity); 44 | if (value != null) { 45 | map.put(field.getName(), value); 46 | } 47 | } 48 | return map; 49 | } 50 | 51 | private EntityMetadata getEntityMetadata(Class type) { 52 | final EntityMetadata entityMetadata = mapper.get(type); 53 | if (entityMetadata != null) { 54 | return entityMetadata; 55 | } 56 | synchronized (type) { 57 | final EntityMetadata entity = factory.apply(type); 58 | mapper.put(type, entity); 59 | return entity; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /compile/src/main/java/org/soujava/metadata/compiler/CompilerAccessException.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.compiler; 2 | 3 | public class CompilerAccessException extends RuntimeException{ 4 | 5 | public CompilerAccessException(String message, Throwable cause) { 6 | super(message, cause); 7 | } 8 | 9 | public CompilerAccessException(String message) { 10 | super(message); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /compile/src/main/java/org/soujava/metadata/compiler/EntityMetadata.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.compiler; 2 | 3 | import org.soujava.medatadata.api.Entity; 4 | 5 | import java.util.List; 6 | 7 | class EntityMetadata { 8 | 9 | private final Class type; 10 | 11 | private final InstanceSupplier supplier; 12 | 13 | private final List fields; 14 | 15 | EntityMetadata(Class type, InstanceSupplier supplier, List fields) { 16 | this.type = type; 17 | this.supplier = supplier; 18 | this.fields = fields; 19 | } 20 | 21 | 22 | public Class getType() { 23 | return type; 24 | } 25 | 26 | public T newInstance() { 27 | return (T) this.supplier.get(); 28 | } 29 | 30 | public List getFields() { 31 | return fields; 32 | } 33 | 34 | public String getName() { 35 | final Entity annotation = type.getAnnotation(Entity.class); 36 | final String value = annotation.value(); 37 | return value.isEmpty() ? type.getSimpleName() : value; 38 | } 39 | 40 | 41 | } 42 | -------------------------------------------------------------------------------- /compile/src/main/java/org/soujava/metadata/compiler/EntityMetadataFactory.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.compiler; 2 | 3 | import org.soujava.medatadata.api.Column; 4 | import org.soujava.medatadata.api.Id; 5 | 6 | import java.lang.reflect.Field; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.function.Function; 10 | 11 | class EntityMetadataFactory implements Function, EntityMetadata> { 12 | 13 | private final JavaCompilerFacade compilerFacade; 14 | private final FieldReaderFactory readerFactory; 15 | private final FieldWriterFactory writerFactory; 16 | private final InstanceSupplierFactory instanceFactory; 17 | 18 | public EntityMetadataFactory() { 19 | this.compilerFacade = new JavaCompilerFacade(EntityMetadataFactory.class.getClassLoader()); 20 | this.readerFactory = new FieldReaderFactory(compilerFacade); 21 | this.writerFactory = new FieldWriterFactory(compilerFacade); 22 | this.instanceFactory = new InstanceSupplierFactory(compilerFacade); 23 | } 24 | 25 | @Override 26 | public EntityMetadata apply(Class type) { 27 | 28 | List fields = new ArrayList<>(); 29 | for (Field field : type.getDeclaredFields()) { 30 | final Id id = field.getAnnotation(Id.class); 31 | final Column column = field.getAnnotation(Column.class); 32 | if (id != null || column != null) { 33 | final FieldReader reader = readerFactory.apply(field); 34 | final FieldWriter writer = writerFactory.apply(field); 35 | fields.add(new FieldMetadata(field, reader, writer)); 36 | } 37 | } 38 | final InstanceSupplier supplier = instanceFactory.apply(type.getConstructors()[0]); 39 | return new EntityMetadata(type, supplier, fields); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /compile/src/main/java/org/soujava/metadata/compiler/FieldMetadata.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.compiler; 2 | 3 | import org.soujava.medatadata.api.Column; 4 | import org.soujava.medatadata.api.Id; 5 | 6 | import java.lang.reflect.Field; 7 | 8 | class FieldMetadata { 9 | 10 | private final Field field; 11 | 12 | private final FieldReader reader; 13 | 14 | private final FieldWriter writer; 15 | 16 | FieldMetadata(Field field, FieldReader reader, FieldWriter writer) { 17 | this.field = field; 18 | this.reader = reader; 19 | this.writer = writer; 20 | } 21 | 22 | 23 | public String getName() { 24 | final Id id = field.getAnnotation(Id.class); 25 | final Column column = field.getAnnotation(Column.class); 26 | if (id != null) { 27 | return id.value().isEmpty() ? field.getName() : id.value(); 28 | } else if (column != null) { 29 | return column.value().isEmpty() ? field.getName() : column.value(); 30 | } 31 | return field.getName(); 32 | } 33 | 34 | public Object getValue(T entity) { 35 | return this.reader.read(entity); 36 | } 37 | 38 | public void write(T entity, Object value) { 39 | this.writer.write(entity, value); 40 | } 41 | 42 | @Override 43 | public String toString() { 44 | return "FieldMetadata{" + 45 | "field=" + field + 46 | ", reader=" + reader + 47 | ", writer=" + writer + 48 | '}'; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /compile/src/main/java/org/soujava/metadata/compiler/FieldReader.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.compiler; 2 | 3 | public interface FieldReader { 4 | 5 | Object read(Object bean); 6 | } 7 | -------------------------------------------------------------------------------- /compile/src/main/java/org/soujava/metadata/compiler/FieldReaderFactory.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.compiler; 2 | 3 | import java.beans.PropertyDescriptor; 4 | import java.lang.reflect.Field; 5 | import java.lang.reflect.Method; 6 | import java.lang.reflect.Modifier; 7 | import java.util.Optional; 8 | import java.util.function.Function; 9 | import java.util.logging.Level; 10 | import java.util.logging.Logger; 11 | 12 | 13 | class FieldReaderFactory { 14 | 15 | private static final Logger LOGGER = Logger.getLogger(FieldReaderFactory.class.getName()); 16 | 17 | private static final String TEMPLATE_FILE = "FieldReader.tempalte"; 18 | 19 | private static final String TEMPLATE = TemplateReader.INSTANCE.apply(TEMPLATE_FILE); 20 | 21 | private final JavaCompilerFacade compilerFacade; 22 | 23 | FieldReaderFactory(JavaCompilerFacade compilerFacade) { 24 | this.compilerFacade = compilerFacade; 25 | } 26 | 27 | 28 | public FieldReader apply(Field field) { 29 | 30 | Class declaringClass = field.getDeclaringClass(); 31 | Optional methodName = getMethodName(declaringClass, field); 32 | 33 | return methodName.map(compile(declaringClass)) 34 | .orElseThrow(() -> new RuntimeException("there is an issue to compile a FieldReader")); 35 | 36 | } 37 | 38 | private Function compile(Class declaringClass) { 39 | return method -> { 40 | String packageName = declaringClass.getPackage().getName(); 41 | 42 | String simpleName = declaringClass.getSimpleName() + "$" + method; 43 | String newInstance = declaringClass.getName(); 44 | String name = declaringClass.getName() + "$" + method; 45 | String javaSource = StringFormatter.INSTANCE.format(TEMPLATE, packageName, simpleName, newInstance, method); 46 | FieldReaderJavaSource source = new FieldReaderJavaSource(name, simpleName, javaSource); 47 | Optional> reader = compilerFacade.apply(source); 48 | return reader.map(this::newInstance).orElse(null); 49 | }; 50 | } 51 | 52 | private FieldReader newInstance(Class readerClass) { 53 | try { 54 | return (FieldReader) readerClass.getConstructors()[0].newInstance(); 55 | } catch (Exception e) { 56 | throw new RuntimeException("An issue to create a new instance class", e); 57 | } 58 | } 59 | 60 | private Optional getMethodName(Class declaringClass, Field field) { 61 | try { 62 | Method readMethod = new PropertyDescriptor(field.getName(), declaringClass).getReadMethod(); 63 | if (Modifier.isPublic(readMethod.getModifiers())) { 64 | return Optional.of(readMethod.getName()); 65 | } 66 | } catch (Exception e) { 67 | LOGGER.log(Level.FINE, "A getter method does not exist to the field: " 68 | + field.getName() + " within class " + declaringClass.getName() + " using the fallback with reflection", e); 69 | 70 | } 71 | return Optional.empty(); 72 | } 73 | 74 | private static final class FieldReaderJavaSource implements JavaSource { 75 | 76 | private final String name; 77 | 78 | private final String simpleName; 79 | 80 | private final String javaSource; 81 | 82 | 83 | FieldReaderJavaSource(String name, String simpleName, String javaSource) { 84 | this.name = name; 85 | this.simpleName = simpleName; 86 | this.javaSource = javaSource; 87 | } 88 | 89 | @Override 90 | public String getSimpleName() { 91 | return simpleName; 92 | } 93 | 94 | @Override 95 | public String getName() { 96 | return name; 97 | } 98 | 99 | @Override 100 | public String getJavaSource() { 101 | return javaSource; 102 | } 103 | 104 | @Override 105 | public Class getType() { 106 | return FieldReader.class; 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /compile/src/main/java/org/soujava/metadata/compiler/FieldWriter.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.compiler; 2 | 3 | public interface FieldWriter { 4 | 5 | void write(Object bean, Object value); 6 | } -------------------------------------------------------------------------------- /compile/src/main/java/org/soujava/metadata/compiler/FieldWriterFactory.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.compiler; 2 | 3 | import java.beans.PropertyDescriptor; 4 | import java.lang.reflect.Field; 5 | import java.lang.reflect.Method; 6 | import java.lang.reflect.Modifier; 7 | import java.util.Optional; 8 | import java.util.function.Function; 9 | import java.util.logging.Level; 10 | import java.util.logging.Logger; 11 | 12 | class FieldWriterFactory { 13 | 14 | private static final Logger LOGGER = Logger.getLogger(FieldWriterFactory.class.getName()); 15 | 16 | private static final String TEMPLATE_FILE = "FieldWriter.template"; 17 | 18 | private static final String TEMPLATE = TemplateReader.INSTANCE.apply(TEMPLATE_FILE); 19 | 20 | private final JavaCompilerFacade compilerFacade; 21 | 22 | FieldWriterFactory(JavaCompilerFacade compilerFacade) { 23 | this.compilerFacade = compilerFacade; 24 | } 25 | 26 | 27 | public FieldWriter apply(Field field) { 28 | 29 | Class declaringClass = field.getDeclaringClass(); 30 | Optional methodName = getMethodName(declaringClass, field); 31 | return methodName.map(compile(declaringClass, field.getType())) 32 | .orElseThrow(() -> new RuntimeException("there is an issue to compile a FieldReader")); 33 | } 34 | 35 | private Function compile(Class declaringClass, Class type) { 36 | return method -> { 37 | String packageName = declaringClass.getPackage().getName(); 38 | String simpleName = declaringClass.getSimpleName() + "$" + method; 39 | String newInstance = declaringClass.getName(); 40 | String name = declaringClass.getName() + "$" + method; 41 | String typeCast = type.getName(); 42 | String javaSource = StringFormatter.INSTANCE.format(TEMPLATE, packageName, simpleName, 43 | newInstance, method, typeCast); 44 | 45 | FieldWriterJavaSource source = new FieldWriterJavaSource(name, simpleName, javaSource); 46 | Optional> reader = compilerFacade.apply(source); 47 | return reader.map(this::newInstance).orElse(null); 48 | }; 49 | } 50 | 51 | private FieldWriter newInstance(Class writerClass) { 52 | try { 53 | return (FieldWriter) writerClass.getConstructors()[0].newInstance(); 54 | } catch (Exception e) { 55 | throw new RuntimeException("An issue to create a new instance class", e); 56 | } 57 | } 58 | 59 | private Optional getMethodName(Class declaringClass, Field field) { 60 | try { 61 | Method writeMethod = new PropertyDescriptor(field.getName(), declaringClass).getWriteMethod(); 62 | if (Modifier.isPublic(writeMethod.getModifiers())) { 63 | return Optional.of(writeMethod.getName()); 64 | } 65 | } catch (Exception e) { 66 | LOGGER.log(Level.FINE, "A setter method does not exist to the field: " 67 | + field.getName() + " within class " + declaringClass.getName() + " using the fallback with reflection", e); 68 | 69 | } 70 | return Optional.empty(); 71 | } 72 | 73 | private static final class FieldWriterJavaSource implements JavaSource { 74 | 75 | private final String name; 76 | 77 | private final String simpleName; 78 | 79 | private final String javaSource; 80 | 81 | 82 | FieldWriterJavaSource(String name, String simpleName, String javaSource) { 83 | this.name = name; 84 | this.simpleName = simpleName; 85 | this.javaSource = javaSource; 86 | } 87 | 88 | @Override 89 | public String getSimpleName() { 90 | return simpleName; 91 | } 92 | 93 | @Override 94 | public String getName() { 95 | return name; 96 | } 97 | 98 | @Override 99 | public String getJavaSource() { 100 | return javaSource; 101 | } 102 | 103 | @Override 104 | public Class getType() { 105 | return FieldWriter.class; 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /compile/src/main/java/org/soujava/metadata/compiler/GeneratedJavaFileManager.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.compiler; 2 | 3 | 4 | import javax.tools.FileObject; 5 | import javax.tools.ForwardingJavaFileManager; 6 | import javax.tools.JavaFileManager; 7 | import javax.tools.JavaFileObject; 8 | import javax.tools.JavaFileObject.Kind; 9 | 10 | final class GeneratedJavaFileManager extends ForwardingJavaFileManager { 11 | 12 | private final JavaCompilerClassLoader classLoader; 13 | 14 | public GeneratedJavaFileManager(JavaFileManager fileManager, JavaCompilerClassLoader classLoader) { 15 | super(fileManager); 16 | this.classLoader = classLoader; 17 | } 18 | 19 | @Override 20 | public JavaFileObject getJavaFileForOutput(Location location, String qualifiedName, Kind kind, FileObject sibling) { 21 | if (kind != Kind.CLASS) { 22 | throw new IllegalArgumentException("Unsupported kind (" + kind + ") for class (" + qualifiedName + ")."); 23 | } 24 | JavaCompiledStream fileObject = new JavaCompiledStream(qualifiedName); 25 | classLoader.addJavaFileObject(qualifiedName, fileObject); 26 | return fileObject; 27 | } 28 | 29 | @Override 30 | public ClassLoader getClassLoader(Location location) { 31 | return classLoader; 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /compile/src/main/java/org/soujava/metadata/compiler/InstanceSupplier.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.compiler; 2 | 3 | import java.util.function.Supplier; 4 | 5 | public interface InstanceSupplier extends Supplier { 6 | 7 | } -------------------------------------------------------------------------------- /compile/src/main/java/org/soujava/metadata/compiler/InstanceSupplierFactory.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.compiler; 2 | 3 | import java.lang.reflect.Constructor; 4 | import java.lang.reflect.Modifier; 5 | import java.util.Optional; 6 | import java.util.logging.Logger; 7 | 8 | public class InstanceSupplierFactory { 9 | 10 | private static final String TEMPLATE_FILE = "InstanceSupplier.template"; 11 | 12 | private static final String TEMPLATE = TemplateReader.INSTANCE.apply(TEMPLATE_FILE); 13 | 14 | private final JavaCompilerFacade compilerFacade; 15 | 16 | 17 | InstanceSupplierFactory(JavaCompilerFacade compilerFacade) { 18 | this.compilerFacade = compilerFacade; 19 | } 20 | 21 | public InstanceSupplier apply(Constructor constructor) { 22 | Class declaringClass = constructor.getDeclaringClass(); 23 | if (Modifier.isPublic(constructor.getModifiers())) { 24 | String packageName = declaringClass.getPackage().getName(); 25 | String simpleName = declaringClass.getSimpleName() + "$InstanceSupplier"; 26 | String newInstance = declaringClass.getName(); 27 | String name = declaringClass.getName() + "$InstanceSupplier"; 28 | String javaSource = StringFormatter.INSTANCE.format(TEMPLATE, packageName, simpleName, newInstance); 29 | InstanceJavaSource source = new InstanceJavaSource(name, simpleName, javaSource); 30 | Optional> supplier = compilerFacade.apply(source); 31 | Optional instanceSupplier = supplier.map(this::newInstance); 32 | return instanceSupplier.orElseThrow(() -> new RuntimeException("There is an issue to load the class")); 33 | 34 | } 35 | throw new RuntimeException(String.format("The constructor to the class %s is not public, using fallback with Reflectioin", 36 | declaringClass.getName())); 37 | } 38 | 39 | private InstanceSupplier newInstance(Class type) { 40 | try { 41 | return (InstanceSupplier) type.getConstructors()[0].newInstance(); 42 | } catch (Exception e) { 43 | throw new RuntimeException("An issue to create a new instance class", e); 44 | } 45 | } 46 | 47 | private static final class InstanceJavaSource implements JavaSource { 48 | 49 | private final String name; 50 | 51 | private final String simpleName; 52 | 53 | private final String javaSource; 54 | 55 | 56 | InstanceJavaSource(String name, String simpleName, String javaSource) { 57 | this.name = name; 58 | this.simpleName = simpleName; 59 | this.javaSource = javaSource; 60 | } 61 | 62 | @Override 63 | public String getSimpleName() { 64 | return simpleName; 65 | } 66 | 67 | @Override 68 | public String getName() { 69 | return name; 70 | } 71 | 72 | @Override 73 | public String getJavaSource() { 74 | return javaSource; 75 | } 76 | 77 | @Override 78 | public Class getType() { 79 | return InstanceSupplier.class; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /compile/src/main/java/org/soujava/metadata/compiler/JavaCompiledStream.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.compiler; 2 | 3 | 4 | import javax.tools.SimpleJavaFileObject; 5 | import java.io.ByteArrayInputStream; 6 | import java.io.ByteArrayOutputStream; 7 | import java.io.InputStream; 8 | import java.io.OutputStream; 9 | import java.net.URI; 10 | 11 | final class JavaCompiledStream extends SimpleJavaFileObject { 12 | 13 | private ByteArrayOutputStream classOutputStream; 14 | 15 | public JavaCompiledStream(String fullClassName) { 16 | super(URI.create("bytes:///" + fullClassName), Kind.CLASS); 17 | } 18 | 19 | @Override 20 | public InputStream openInputStream() { 21 | return new ByteArrayInputStream(getClassBytes()); 22 | } 23 | 24 | @Override 25 | public OutputStream openOutputStream() { 26 | classOutputStream = new ByteArrayOutputStream(); 27 | return classOutputStream; 28 | } 29 | 30 | public byte[] getClassBytes() { 31 | return classOutputStream.toByteArray(); 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /compile/src/main/java/org/soujava/metadata/compiler/JavaCompilerClassLoader.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.compiler; 2 | 3 | 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | final class JavaCompilerClassLoader extends ClassLoader { 8 | 9 | private final Map fileObjectMap = new HashMap<>(); 10 | 11 | public JavaCompilerClassLoader(ClassLoader parent) { 12 | super(parent); 13 | } 14 | 15 | @Override 16 | protected Class findClass(String fullClassName) throws ClassNotFoundException { 17 | JavaCompiledStream fileObject = fileObjectMap.get(fullClassName); 18 | if (fileObject != null) { 19 | byte[] classBytes = fileObject.getClassBytes(); 20 | return defineClass(fullClassName, classBytes, 0, classBytes.length); 21 | } 22 | return super.findClass(fullClassName); 23 | } 24 | 25 | public void addJavaFileObject(String qualifiedName, JavaCompiledStream fileObject) { 26 | fileObjectMap.put(qualifiedName, fileObject); 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /compile/src/main/java/org/soujava/metadata/compiler/JavaCompilerFacade.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.compiler; 2 | 3 | 4 | import javax.tools.DiagnosticCollector; 5 | import javax.tools.JavaCompiler; 6 | import javax.tools.JavaCompiler.CompilationTask; 7 | import javax.tools.JavaFileManager; 8 | import javax.tools.ToolProvider; 9 | import java.io.IOException; 10 | import java.security.PrivilegedAction; 11 | import java.util.Collections; 12 | import java.util.Optional; 13 | import java.util.logging.Level; 14 | import java.util.logging.Logger; 15 | import java.util.regex.Pattern; 16 | import java.util.stream.Collectors; 17 | 18 | import static java.security.AccessController.doPrivileged; 19 | 20 | /** 21 | * Class that converts a {@link JavaSource} to a compiled class 22 | */ 23 | final class JavaCompilerFacade { 24 | 25 | private static final Logger LOGGER = Logger.getLogger(JavaCompilerFacade.class.getName()); 26 | 27 | private static final Pattern BREAK_LINE = Pattern.compile("\n"); 28 | private final JavaCompilerClassLoader classLoader; 29 | private final JavaCompiler compiler; 30 | private final DiagnosticCollector diagnosticCollector; 31 | 32 | public JavaCompilerFacade(ClassLoader loader) { 33 | this.compiler = Optional.ofNullable(ToolProvider.getSystemJavaCompiler()) 34 | .orElseThrow(() -> new IllegalStateException("Cannot find the system Java compiler")); 35 | 36 | PrivilegedAction action = () -> new JavaCompilerClassLoader(loader); 37 | this.classLoader = doPrivileged(action); 38 | this.diagnosticCollector = new DiagnosticCollector<>(); 39 | } 40 | 41 | public Optional> apply(JavaSource source) { 42 | try { 43 | return Optional.of(compile(source)); 44 | } catch (CompilerAccessException exp) { 45 | if (LOGGER.isLoggable(Level.FINEST)) { 46 | LOGGER.log(Level.FINEST, "Error when tries to optimizes the accessor", exp); 47 | } 48 | return Optional.empty(); 49 | } 50 | } 51 | 52 | private synchronized Class compile(JavaSource source) { 53 | JavaFileObject fileObject = new JavaFileObject(source.getSimpleName(), source.getJavaSource()); 54 | 55 | JavaFileManager standardFileManager = compiler.getStandardFileManager(diagnosticCollector, null, null); 56 | 57 | try (GeneratedJavaFileManager javaFileManager = new GeneratedJavaFileManager(standardFileManager, classLoader)) { 58 | CompilationTask task = compiler.getTask(null, javaFileManager, diagnosticCollector, 59 | null, null, Collections.singletonList(fileObject)); 60 | 61 | if (!task.call()) { 62 | return createCompilerErrorMessage(source); 63 | } 64 | } catch (IOException e) { 65 | throw new CompilerAccessException("The generated class (" + source.getSimpleName() + ") failed to compile because the " 66 | + JavaFileManager.class.getSimpleName() + " didn't close.", e); 67 | } 68 | try { 69 | Class compiledClass = (Class) classLoader.loadClass(source.getName()); 70 | if (!source.getType().isAssignableFrom(compiledClass)) { 71 | throw new CompilerAccessException("The generated compiledClass (" + compiledClass 72 | + ") cannot be assigned to the superclass/interface (" + source.getType() + ")."); 73 | } 74 | return compiledClass; 75 | } catch (ClassNotFoundException e) { 76 | throw new CompilerAccessException("The generated class (" + source.getSimpleName() 77 | + ") compiled, but failed to load.", e); 78 | } 79 | 80 | } 81 | 82 | private Class createCompilerErrorMessage(JavaSource source) { 83 | String compilationMessages = diagnosticCollector.getDiagnostics().stream() 84 | .map(d -> d.getKind() + ":[" + d.getLineNumber() + "," + d.getColumnNumber() + "] " 85 | + d.getMessage(null) 86 | + "\n " + (d 87 | .getLineNumber() <= 0 ? "" : BREAK_LINE.splitAsStream(source.getJavaSource()) 88 | .skip(d.getLineNumber() - 1).findFirst().orElse(""))) 89 | .collect(Collectors.joining("\n")); 90 | throw new CompilerAccessException("The generated class (" + source.getSimpleName() + ") failed to compile.\n" 91 | + compilationMessages); 92 | } 93 | 94 | 95 | } -------------------------------------------------------------------------------- /compile/src/main/java/org/soujava/metadata/compiler/JavaFileObject.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.compiler; 2 | 3 | import javax.tools.SimpleJavaFileObject; 4 | import java.net.URI; 5 | 6 | final class JavaFileObject extends SimpleJavaFileObject { 7 | 8 | private final String javaSource; 9 | 10 | public JavaFileObject(String fullClassName, String javaSource) { 11 | super(URI.create("string:///" + fullClassName.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE); 12 | this.javaSource = javaSource; 13 | } 14 | 15 | @Override 16 | public String getCharContent(boolean ignoreEncodingErrors) { 17 | return javaSource; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /compile/src/main/java/org/soujava/metadata/compiler/JavaSource.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.compiler; 2 | 3 | interface JavaSource { 4 | 5 | String getSimpleName(); 6 | 7 | String getName(); 8 | 9 | String getJavaSource(); 10 | 11 | Class getType(); 12 | } -------------------------------------------------------------------------------- /compile/src/main/java/org/soujava/metadata/compiler/StringFormatter.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.compiler; 2 | 3 | import java.text.MessageFormat; 4 | 5 | enum StringFormatter { 6 | 7 | INSTANCE; 8 | 9 | public String format(String template, Object... params) { 10 | return MessageFormat.format(template, params); 11 | } 12 | 13 | } -------------------------------------------------------------------------------- /compile/src/main/java/org/soujava/metadata/compiler/TemplateReader.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.compiler; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.nio.charset.StandardCharsets; 7 | import java.util.function.Function; 8 | 9 | enum TemplateReader implements Function { 10 | 11 | INSTANCE; 12 | 13 | public static final int BUFFER = 1024; 14 | 15 | @Override 16 | public String apply(String file) { 17 | ClassLoader classLoader = TemplateReader.class.getClassLoader(); 18 | InputStream stream = classLoader.getResourceAsStream(file); 19 | ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 20 | int nRead; 21 | byte[] data = new byte[BUFFER]; 22 | try { 23 | while ((nRead = stream.read(data, 0, data.length)) != -1) { 24 | buffer.write(data, 0, nRead); 25 | } 26 | byte[] byteArray = buffer.toByteArray(); 27 | return new String(byteArray, StandardCharsets.UTF_8); 28 | } catch (IOException ex) { 29 | throw new TemplateReaderException("An error to load from the file: " + file, ex); 30 | } 31 | } 32 | 33 | static class TemplateReaderException extends RuntimeException { 34 | 35 | public TemplateReaderException(String message, IOException ex) { 36 | super(message, ex); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /compile/src/main/resources/FieldReader.tempalte: -------------------------------------------------------------------------------- 1 | package {0}; 2 | 3 | import org.soujava.metadata.compiler.FieldReader; 4 | import javax.annotation.processing.Generated; 5 | 6 | @Generated("Soujava Field Reader Generator") 7 | public class {1} implements FieldReader '{' 8 | @Override 9 | public Object read(Object bean) '{' 10 | return {2}.class.cast(bean).{3}(); 11 | '}' 12 | '}' -------------------------------------------------------------------------------- /compile/src/main/resources/FieldWriter.template: -------------------------------------------------------------------------------- 1 | package {0}; 2 | 3 | import org.soujava.metadata.compiler.FieldWriter; 4 | import javax.annotation.processing.Generated; 5 | 6 | @Generated("Soujava Writer Reader Generator") 7 | public class {1} implements FieldWriter '{' 8 | 9 | @Override 10 | public void write(Object bean, Object value) '{' 11 | {2}.class.cast(bean).{3}(({4}) value); 12 | 13 | '}' 14 | '}' 15 | -------------------------------------------------------------------------------- /compile/src/main/resources/InstanceSupplier.template: -------------------------------------------------------------------------------- 1 | package {0}; 2 | 3 | import org.soujava.metadata.compiler.InstanceSupplier; 4 | import javax.annotation.processing.Generated; 5 | 6 | @Generated("Soujava InstanceSupplier Generator") 7 | public class {1} implements InstanceSupplier '{' 8 | @Override 9 | public Object get() '{' 10 | return new {2}(); 11 | '}' 12 | } 13 | -------------------------------------------------------------------------------- /compile/src/test/java/org/soujava/metadata/compiler/Animal.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.compiler; 2 | 3 | import org.soujava.medatadata.api.Column; 4 | import org.soujava.medatadata.api.Entity; 5 | import org.soujava.medatadata.api.Id; 6 | 7 | @Entity("animal") 8 | public class Animal { 9 | 10 | @Id 11 | private String id; 12 | 13 | @Column 14 | private String name; 15 | 16 | public Animal(String id, String name) { 17 | this.id = id; 18 | this.name = name; 19 | } 20 | 21 | public Animal() { 22 | } 23 | 24 | public String getId() { 25 | return id; 26 | } 27 | 28 | public void setId(String id) { 29 | this.id = id; 30 | } 31 | 32 | public String getName() { 33 | return name; 34 | } 35 | 36 | public void setName(String name) { 37 | this.name = name; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /compile/src/test/java/org/soujava/metadata/compiler/CompilerMapperTest.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.compiler; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Test; 6 | import org.soujava.medatadata.api.Mapper; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | public class CompilerMapperTest { 12 | 13 | private Mapper mapper; 14 | 15 | @BeforeEach 16 | public void setUp() { 17 | this.mapper = new CompileMapper(); 18 | } 19 | 20 | @Test 21 | public void shouldCreateMap() { 22 | Animal animal = new Animal("id", "lion"); 23 | final Map map = mapper.toMap(animal); 24 | Assertions.assertEquals("animal", map.get("entity")); 25 | Assertions.assertEquals("id", map.get("id")); 26 | Assertions.assertEquals("lion", map.get("name")); 27 | } 28 | 29 | @Test 30 | public void shouldCreateEntity() { 31 | Map map = new HashMap<>(); 32 | map.put("id", "id"); 33 | map.put("name", "lion"); 34 | 35 | final Animal animal = mapper.toEntity(map, Animal.class); 36 | Assertions.assertEquals("id", animal.getId()); 37 | Assertions.assertEquals("lion", animal.getName()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /compile/src/test/java/org/soujava/metadata/compiler/CompilerTest.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.compiler; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | 6 | public class CompilerTest { 7 | 8 | @Test 9 | public void test() { 10 | JavaCompilerBeanPropertyReaderFactory factory = new JavaCompilerBeanPropertyReaderFactory(); 11 | final ReadFromGetterMethod name = factory.generate(Animal.class, "name"); 12 | Animal animal = new Animal("id", "lion"); 13 | final Object apply = name.apply(animal); 14 | Assertions.assertEquals("lion", apply); 15 | 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /compile/src/test/java/org/soujava/metadata/compiler/JavaCompilerBeanPropertyReaderFactory.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.compiler; 2 | 3 | 4 | class JavaCompilerBeanPropertyReaderFactory { 5 | 6 | private final JavaCompilerFacade compilerFacade = new JavaCompilerFacade( 7 | JavaCompilerBeanPropertyReaderFactory.class.getClassLoader()); 8 | 9 | public ReadFromGetterMethod generate(Class beanClass, String propertyName) { 10 | 11 | String getterName = "get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1); 12 | String packageName = JavaCompilerBeanPropertyReaderFactory.class.getPackage().getName() 13 | + ".generated." + beanClass.getPackage().getName(); 14 | String simpleClassName = beanClass.getSimpleName() + "$" + propertyName; 15 | String fullClassName = packageName + "." + simpleClassName; 16 | final String source = "package " + packageName + ";\n" 17 | + "public class " + simpleClassName + " implements " + ReadFromGetterMethod.class.getName() + " {\n" 18 | + " public Object apply(Object bean) {\n" 19 | + " return ((" + beanClass.getName() + ") bean)." + getterName + "();\n" 20 | + " }\n" 21 | + "}"; 22 | 23 | JavaSource javaSource = new JavaSource() { 24 | @Override 25 | public String getSimpleName() { 26 | return fullClassName; 27 | } 28 | 29 | @Override 30 | public String getName() { 31 | return fullClassName; 32 | } 33 | 34 | @Override 35 | public String getJavaSource() { 36 | return source; 37 | } 38 | 39 | @Override 40 | public Class getType() { 41 | return ReadFromGetterMethod.class; 42 | } 43 | }; 44 | Class compiledClass = compilerFacade.apply(javaSource).get(); 45 | try { 46 | return compiledClass.newInstance(); 47 | } catch (InstantiationException | IllegalAccessException e) { 48 | throw new IllegalStateException("The generated class (" + fullClassName + ") failed to instantiate.", e); 49 | } 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /compile/src/test/java/org/soujava/metadata/compiler/ReadFromGetterMethod.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.compiler; 2 | 3 | import java.util.function.Function; 4 | 5 | public interface ReadFromGetterMethod extends Function { 6 | 7 | } -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | pom.xml.tag 3 | pom.xml.releaseBackup 4 | pom.xml.versionsBackup 5 | pom.xml.next 6 | test-output/ 7 | /doc 8 | *.iml 9 | *.idea 10 | *.log 11 | /.idea 12 | .checkstyle 13 | 14 | # Eclipse metadata 15 | .settings/ 16 | .project 17 | .factorypath 18 | .classpath 19 | -project 20 | /.resourceCache 21 | /.project 22 | 23 | # Annotation processor metadata 24 | .apt_generated/ 25 | .apt_generated_tests/ 26 | -------------------------------------------------------------------------------- /example/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | 8 | 9 | org.soujava.metadata 10 | parent 11 | 1.0.0 12 | 13 | 14 | example 15 | 16 | 17 | 18 | ${project.groupId} 19 | reflection 20 | ${project.version} 21 | true 22 | 23 | 24 | ${project.groupId} 25 | compile 26 | ${project.version} 27 | true 28 | 29 | 30 | junit 31 | junit 32 | 4.13.1 33 | test 34 | 35 | 36 | 37 | 38 | 39 | 40 | maven-compiler-plugin 41 | 3.8.1 42 | 43 | 11 44 | 11 45 | 46 | 47 | ${project.groupId} 48 | processor 49 | ${project.version} 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /example/src/main/java/org/soujava/metadata/example/Animal.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.example; 2 | 3 | import org.soujava.medatadata.api.Column; 4 | import org.soujava.medatadata.api.Entity; 5 | import org.soujava.medatadata.api.Id; 6 | 7 | @Entity("kind") 8 | public class Animal { 9 | 10 | @Id 11 | private String name; 12 | 13 | @Column 14 | private String color; 15 | 16 | public String getName() { 17 | return name; 18 | } 19 | 20 | void setName(String name) { 21 | this.name = name; 22 | } 23 | 24 | public String getColor() { 25 | return color; 26 | } 27 | 28 | void setColor(String color) { 29 | this.color = color; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /example/src/main/java/org/soujava/metadata/example/Car.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.example; 2 | 3 | import org.soujava.medatadata.api.Column; 4 | import org.soujava.medatadata.api.Entity; 5 | import org.soujava.medatadata.api.Id; 6 | 7 | @Entity("car") 8 | public class Car { 9 | 10 | @Id 11 | private String name; 12 | 13 | @Column 14 | private String model; 15 | 16 | Car() { 17 | } 18 | 19 | 20 | String getName() { 21 | return name; 22 | } 23 | 24 | void setName(String name) { 25 | this.name = name; 26 | } 27 | 28 | protected String getModel() { 29 | return model; 30 | } 31 | 32 | protected void setModel(String model) { 33 | this.model = model; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /example/src/main/java/org/soujava/metadata/example/Person.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2017 Cloudogu GmbH 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package org.soujava.metadata.example; 26 | 27 | import java.util.List; 28 | import org.soujava.medatadata.api.Column; 29 | import org.soujava.medatadata.api.Entity; 30 | import org.soujava.medatadata.api.Id; 31 | 32 | @Entity 33 | public class Person { 34 | 35 | @Id 36 | private Long id; 37 | 38 | @Column("native") 39 | private String username; 40 | 41 | @Column 42 | private String email; 43 | 44 | @Column 45 | private List contacts; 46 | 47 | public Person() { 48 | } 49 | 50 | public Person(String username, String email) { 51 | this.username = username; 52 | this.email = email; 53 | } 54 | 55 | public String getUsername() { 56 | return username; 57 | } 58 | 59 | public String getEmail() { 60 | return email; 61 | } 62 | 63 | public void setUsername(String username) { 64 | this.username = username; 65 | } 66 | 67 | public void setEmail(String email) { 68 | this.email = email; 69 | } 70 | 71 | public Long getId() { 72 | return id; 73 | } 74 | 75 | public void setId(Long id) { 76 | this.id = id; 77 | } 78 | 79 | public List getContacts() { 80 | return contacts; 81 | } 82 | 83 | public void setContacts(List contacts) { 84 | this.contacts = contacts; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /example/src/test/java/org/soujava/metadata/example/AbstractMapperTest.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.example; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Test; 6 | import org.soujava.medatadata.api.Mapper; 7 | 8 | import java.util.Arrays; 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | public abstract class AbstractMapperTest { 13 | 14 | protected Mapper mapper; 15 | 16 | @BeforeEach 17 | public void setUp() { 18 | this.mapper = getMapper(); 19 | } 20 | 21 | protected abstract Mapper getMapper(); 22 | 23 | @Test 24 | public void shouldCreateMap() { 25 | Person person = new Person(); 26 | person.setId(10L); 27 | person.setEmail("otaviojava@java.net"); 28 | person.setUsername("otaviojava"); 29 | person.setContacts(Arrays.asList("Poliana", "Bruno Souza", "Yanaga", "Elder")); 30 | 31 | final Map map = mapper.toMap(person); 32 | Assertions.assertEquals(10L, map.get("id")); 33 | Assertions.assertEquals("Person", map.get("entity")); 34 | Assertions.assertEquals("otaviojava", map.get("native")); 35 | Assertions.assertEquals("otaviojava@java.net", map.get("email")); 36 | Assertions.assertEquals(person.getContacts(), map.get("contacts")); 37 | 38 | } 39 | 40 | @Test 41 | public void shouldCreateEntity() { 42 | Map map = new HashMap<>(); 43 | map.put("id", 10L); 44 | map.put("email","otaviojava@java.net"); 45 | map.put("native","otaviojava"); 46 | map.put("contacts",Arrays.asList("Poliana", "Bruno Souza", "Yanaga", "Elder")); 47 | 48 | final Person person = mapper.toEntity(map, Person.class); 49 | Assertions.assertEquals(10L, person.getId()); 50 | Assertions.assertEquals("otaviojava", person.getUsername()); 51 | Assertions.assertEquals("otaviojava@java.net", person.getEmail()); 52 | Assertions.assertEquals(person.getContacts(), person.getContacts()); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /example/src/test/java/org/soujava/metadata/example/CarTest.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.example; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | import org.junit.jupiter.api.Assertions; 6 | import org.soujava.medatadata.api.ClassMappings; 7 | import org.soujava.medatadata.api.EntityMetadata; 8 | import org.soujava.medatadata.api.FieldMetadata; 9 | 10 | import java.util.Map; 11 | 12 | public class CarTest { 13 | 14 | private ClassMappings mappings = new org.soujava.metadata.processor.ProcessorClassMappings(); 15 | 16 | @Test 17 | public void shouldCreate() { 18 | final EntityMetadata entityMetadata = mappings.get(Car.class); 19 | Car car = entityMetadata.newInstance(); 20 | Assert.assertNotNull(car); 21 | } 22 | 23 | @Test 24 | public void shouldGetter() { 25 | final EntityMetadata entityMetadata = mappings.get(Car.class); 26 | final Map fieldsGroupByName = entityMetadata.getFieldsGroupByName(); 27 | final FieldMetadata fieldMetadata = fieldsGroupByName.get("model"); 28 | Car car = entityMetadata.newInstance(); 29 | fieldMetadata.write(car, "native"); 30 | Assertions.assertEquals(car.getModel(), fieldMetadata.read(car)); 31 | } 32 | 33 | @Test 34 | public void shouldSetter() { 35 | final EntityMetadata entityMetadata = mappings.get(Car.class); 36 | final Map fieldsGroupByName = entityMetadata.getFieldsGroupByName(); 37 | final FieldMetadata fieldMetadata = fieldsGroupByName.get("name"); 38 | Car car = entityMetadata.newInstance(); 39 | fieldMetadata.write(car, "name"); 40 | Assertions.assertEquals(car.getName(), fieldMetadata.read(car)); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /example/src/test/java/org/soujava/metadata/example/CompilerMapTest.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.example; 2 | 3 | import org.soujava.medatadata.api.Mapper; 4 | import org.soujava.metadata.compiler.CompileMapper; 5 | import org.soujava.metadata.processor.ProcessorMap; 6 | 7 | public class CompilerMapTest extends AbstractMapperTest { 8 | 9 | @Override 10 | protected Mapper getMapper() { 11 | return new CompileMapper(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/src/test/java/org/soujava/metadata/example/PersonTest.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.example; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | import org.junit.jupiter.api.Assertions; 6 | import org.soujava.medatadata.api.ClassMappings; 7 | import org.soujava.medatadata.api.EntityMetadata; 8 | import org.soujava.medatadata.api.FieldMetadata; 9 | 10 | import java.util.Map; 11 | 12 | public class PersonTest { 13 | 14 | private ClassMappings mappings = new org.soujava.metadata.processor.ProcessorClassMappings(); 15 | 16 | @Test 17 | public void shouldCreate() { 18 | final EntityMetadata entityMetadata = mappings.get(Person.class); 19 | Person person = entityMetadata.newInstance(); 20 | Assert.assertNotNull(person); 21 | } 22 | 23 | @Test 24 | public void shouldGetter() { 25 | final EntityMetadata entityMetadata = mappings.get(Person.class); 26 | final Map fieldsGroupByName = entityMetadata.getFieldsGroupByName(); 27 | final FieldMetadata fieldMetadata = fieldsGroupByName.get("native"); 28 | Person person = entityMetadata.newInstance(); 29 | fieldMetadata.write(person, "native"); 30 | Assertions.assertEquals(person.getUsername(), fieldMetadata.read(person)); 31 | } 32 | 33 | @Test 34 | public void shouldSetter() { 35 | Person person = new org.soujava.metadata.example.PersonEntityMetaData().newInstance(); 36 | new org.soujava.metadata.example.PersonEmailFieldMetaData().write(person, "otavio"); 37 | Assertions.assertEquals("otavio", new org.soujava.metadata.example.PersonEmailFieldMetaData().read(person)); 38 | } 39 | } -------------------------------------------------------------------------------- /example/src/test/java/org/soujava/metadata/example/ProcessorMapTest.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.example; 2 | 3 | import org.soujava.medatadata.api.Mapper; 4 | import org.soujava.metadata.processor.ProcessorMap; 5 | 6 | public class ProcessorMapTest extends AbstractMapperTest { 7 | 8 | @Override 9 | protected Mapper getMapper() { 10 | return new ProcessorMap(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /example/src/test/java/org/soujava/metadata/example/ReflectionMapTest.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.example; 2 | 3 | import org.soujava.medatadata.api.Mapper; 4 | import org.soujava.metadata.reflection.ReflectionMapper; 5 | 6 | public class ReflectionMapTest extends AbstractMapperTest { 7 | 8 | @Override 9 | protected Mapper getMapper() { 10 | return new ReflectionMapper(); 11 | } 12 | 13 | 14 | } 15 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | 8 | org.soujava.metadata 9 | parent 10 | 1.0.0 11 | pom 12 | 13 | 14 | api 15 | processor 16 | reflection 17 | compile 18 | example 19 | 20 | 21 | 3.2.2 22 | 5.10.1 23 | 21 24 | 3.11.0 25 | 26 | 27 | 28 | 29 | org.junit.jupiter 30 | junit-jupiter-engine 31 | ${junit.version} 32 | test 33 | 34 | 35 | org.junit.jupiter 36 | junit-jupiter-params 37 | ${junit.version} 38 | test 39 | 40 | 41 | 42 | 43 | 44 | org.apache.maven.plugins 45 | maven-compiler-plugin 46 | ${maven.compiler.plugin} 47 | 48 | ${java.version} 49 | ${java.version} 50 | UTF-8 51 | true 52 | 53 | 54 | 55 | org.apache.maven.plugins 56 | maven-surefire-plugin 57 | ${maven.surefire.plugin.version} 58 | 59 | 60 | org.apache.maven.plugins 61 | maven-resources-plugin 62 | 3.3.1 63 | 64 | UTF-8 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /processor/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | pom.xml.tag 3 | pom.xml.releaseBackup 4 | pom.xml.versionsBackup 5 | pom.xml.next 6 | test-output/ 7 | /doc 8 | *.iml 9 | *.idea 10 | *.log 11 | /.idea 12 | .checkstyle 13 | 14 | # Eclipse metadata 15 | .settings/ 16 | .project 17 | .factorypath 18 | .classpath 19 | -project 20 | /.resourceCache 21 | /.project 22 | 23 | # Annotation processor metadata 24 | .apt_generated/ 25 | .apt_generated_tests/ 26 | -------------------------------------------------------------------------------- /processor/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | 8 | 9 | org.soujava.metadata 10 | parent 11 | 1.0.0 12 | 13 | processor 14 | 15 | 16 | 17 | ${project.groupId} 18 | api 19 | ${project.version} 20 | 21 | 22 | com.github.spullara.mustache.java 23 | compiler 24 | 0.9.6 25 | 26 | 27 | com.google.testing.compile 28 | compile-testing 29 | 0.18 30 | test 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | org.apache.maven.plugins 39 | maven-compiler-plugin 40 | 3.5.1 41 | 42 | -proc:none 43 | 11 44 | 11 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /processor/src/main/java/org/soujava/metadata/processor/BaseMappingModel.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.processor; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | abstract class BaseMappingModel { 6 | 7 | public LocalDateTime getNow() { 8 | return LocalDateTime.now(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /processor/src/main/java/org/soujava/metadata/processor/ClassAnalyzer.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.processor; 2 | 3 | import com.github.mustachejava.DefaultMustacheFactory; 4 | import com.github.mustachejava.Mustache; 5 | import com.github.mustachejava.MustacheFactory; 6 | import org.soujava.medatadata.api.Entity; 7 | import org.soujava.medatadata.api.MapperException; 8 | 9 | import javax.annotation.processing.Filer; 10 | import javax.annotation.processing.ProcessingEnvironment; 11 | import javax.lang.model.element.Element; 12 | import javax.lang.model.element.TypeElement; 13 | import javax.tools.Diagnostic; 14 | import javax.tools.JavaFileObject; 15 | import java.io.IOException; 16 | import java.io.Writer; 17 | import java.util.List; 18 | import java.util.function.Supplier; 19 | import java.util.logging.Logger; 20 | import java.util.stream.Collectors; 21 | 22 | public class ClassAnalyzer implements Supplier { 23 | 24 | private static Logger LOGGER = Logger.getLogger(ClassAnalyzer.class.getName()); 25 | private static final String NEW_INSTANCE = "entitymetadata.mustache"; 26 | 27 | private final Element entity; 28 | 29 | private final ProcessingEnvironment processingEnv; 30 | 31 | private final Mustache template; 32 | 33 | ClassAnalyzer(Element entity, ProcessingEnvironment processingEnv) { 34 | this.entity = entity; 35 | this.processingEnv = processingEnv; 36 | MustacheFactory factory = new DefaultMustacheFactory(); 37 | this.template = factory.compile(NEW_INSTANCE); 38 | } 39 | 40 | @Override 41 | public String get() { 42 | if (ProcessorUtil.isTypeElement(entity)) { 43 | TypeElement typeElement = (TypeElement) entity; 44 | LOGGER.info("Processing the class: " + typeElement); 45 | boolean hasValidConstructor = processingEnv.getElementUtils().getAllMembers(typeElement) 46 | .stream() 47 | .filter(EntityProcessor.IS_CONSTRUCTOR.and(EntityProcessor.HAS_ACCESS)) 48 | .anyMatch(EntityProcessor.IS_CONSTRUCTOR.and(EntityProcessor.HAS_ACCESS)); 49 | if (hasValidConstructor) { 50 | try { 51 | return analyze(typeElement); 52 | } catch (IOException exception) { 53 | error(exception); 54 | } 55 | } else { 56 | throw new MapperException("The class " + ProcessorUtil.getSimpleNameAsString(entity) + " must have at least an either public or default constructor"); 57 | } 58 | } 59 | 60 | return ""; 61 | } 62 | 63 | private String analyze(TypeElement typeElement) throws IOException { 64 | 65 | final List fields = processingEnv.getElementUtils() 66 | .getAllMembers(typeElement).stream() 67 | .filter(EntityProcessor.IS_FIELD.and(EntityProcessor.HAS_ANNOTATION)) 68 | .map(f -> new FieldAnalyzer(f, processingEnv, typeElement)) 69 | .map(FieldAnalyzer::get) 70 | .collect(Collectors.toList()); 71 | 72 | EntityModel metadata = getMetadata(typeElement, fields); 73 | createClass(entity, metadata); 74 | LOGGER.info("Found the fields: " + fields); 75 | return metadata.getQualified(); 76 | } 77 | 78 | private void createClass(Element entity, EntityModel metadata) throws IOException { 79 | Filer filer = processingEnv.getFiler(); 80 | JavaFileObject fileObject = filer.createSourceFile(metadata.getQualified(), entity); 81 | try (Writer writer = fileObject.openWriter()) { 82 | template.execute(writer, metadata); 83 | } 84 | } 85 | 86 | private EntityModel getMetadata(TypeElement element, List fields) { 87 | final Entity annotation = element.getAnnotation(Entity.class); 88 | String packageName = ProcessorUtil.getPackageName(element); 89 | String sourceClassName = ProcessorUtil.getSimpleNameAsString(element); 90 | String entityName = annotation.value().isBlank() ? sourceClassName : annotation.value(); 91 | return new EntityModel(packageName, sourceClassName, entityName, fields); 92 | } 93 | 94 | private void error(IOException exception) { 95 | processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "failed to write extension file: " 96 | + exception.getMessage()); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /processor/src/main/java/org/soujava/metadata/processor/ClassMappingsModel.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.processor; 2 | 3 | import java.util.List; 4 | 5 | public class ClassMappingsModel extends BaseMappingModel { 6 | 7 | private final List entities; 8 | 9 | public ClassMappingsModel(List entities) { 10 | this.entities = entities; 11 | } 12 | 13 | public List getEntities() { 14 | return entities; 15 | } 16 | 17 | public String getQualified() { 18 | return "org.soujava.metadata.processor.ProcessorClassMappings"; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /processor/src/main/java/org/soujava/metadata/processor/EntityModel.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.processor; 2 | 3 | import java.util.List; 4 | 5 | public class EntityModel extends BaseMappingModel { 6 | 7 | private final String packageName; 8 | 9 | private final String entity; 10 | 11 | private final String name; 12 | 13 | private final List fields; 14 | 15 | public EntityModel(String packageName, String entity, String name, List fields) { 16 | this.packageName = packageName; 17 | this.entity = entity; 18 | this.name = name; 19 | this.fields = fields; 20 | } 21 | 22 | public String getPackageName() { 23 | return packageName; 24 | } 25 | 26 | public String getEntity() { 27 | return entity; 28 | } 29 | 30 | public String getEntityQualified() { 31 | return packageName + '.' + entity; 32 | } 33 | 34 | public String getClassName() { 35 | return entity + "EntityMetaData"; 36 | } 37 | 38 | public String getQualified() { 39 | return packageName + "." + getClassName(); 40 | } 41 | 42 | public String getName() { 43 | return name; 44 | } 45 | 46 | public List getFields() { 47 | return fields; 48 | } 49 | 50 | @Override 51 | public String toString() { 52 | return "EntityMetadata{" + 53 | "packageName='" + packageName + '\'' + 54 | ", sourceClassName='" + entity + '\'' + 55 | ", entityName='" + name + '\'' + 56 | '}'; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /processor/src/main/java/org/soujava/metadata/processor/EntityProcessor.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.processor; 2 | 3 | import com.github.mustachejava.DefaultMustacheFactory; 4 | import com.github.mustachejava.Mustache; 5 | import com.github.mustachejava.MustacheFactory; 6 | import org.soujava.medatadata.api.Column; 7 | import org.soujava.medatadata.api.Id; 8 | 9 | import javax.annotation.processing.AbstractProcessor; 10 | import javax.annotation.processing.Filer; 11 | import javax.annotation.processing.RoundEnvironment; 12 | import javax.annotation.processing.SupportedAnnotationTypes; 13 | import javax.lang.model.element.Element; 14 | import javax.lang.model.element.ElementKind; 15 | import javax.lang.model.element.Modifier; 16 | import javax.lang.model.element.TypeElement; 17 | import javax.tools.Diagnostic; 18 | import javax.tools.JavaFileObject; 19 | import java.io.BufferedReader; 20 | import java.io.IOException; 21 | import java.io.InputStream; 22 | import java.io.InputStreamReader; 23 | import java.io.Writer; 24 | import java.nio.charset.StandardCharsets; 25 | import java.util.ArrayList; 26 | import java.util.EnumSet; 27 | import java.util.List; 28 | import java.util.Set; 29 | import java.util.function.Predicate; 30 | import java.util.stream.Collectors; 31 | 32 | import static javax.lang.model.element.Modifier.PROTECTED; 33 | import static javax.lang.model.element.Modifier.PUBLIC; 34 | 35 | @SupportedAnnotationTypes("org.soujava.medatadata.api.Entity") 36 | public class EntityProcessor extends AbstractProcessor { 37 | 38 | private static final EnumSet MODIFIERS = EnumSet.of(PUBLIC, PROTECTED); 39 | private static final String TEMPLATE = "classmappings.mustache"; 40 | static final Predicate IS_CONSTRUCTOR = el -> el.getKind() == ElementKind.CONSTRUCTOR; 41 | static final Predicate IS_BLANK = String::isBlank; 42 | static final Predicate IS_NOT_BLANK = IS_BLANK.negate(); 43 | static final Predicate PUBLIC_PRIVATE = el -> el.getModifiers().stream().anyMatch(m -> MODIFIERS.contains(m)); 44 | static final Predicate DEFAULT_MODIFIER = el -> el.getModifiers().isEmpty(); 45 | static final Predicate HAS_ACCESS = PUBLIC_PRIVATE.or(DEFAULT_MODIFIER); 46 | static final Predicate HAS_COLUMN_ANNOTATION = el -> el.getAnnotation(Column.class) != null; 47 | static final Predicate HAS_ID_ANNOTATION = el -> el.getAnnotation(Id.class) != null; 48 | static final Predicate HAS_ANNOTATION = HAS_COLUMN_ANNOTATION.or(HAS_ID_ANNOTATION); 49 | static final Predicate IS_FIELD = el -> el.getKind() == ElementKind.FIELD; 50 | 51 | private final Mustache template; 52 | 53 | public EntityProcessor() { 54 | this.template = createTemplate(); 55 | } 56 | 57 | @Override 58 | public boolean process(Set annotations, 59 | RoundEnvironment roundEnv) { 60 | 61 | final List entities = new ArrayList<>(); 62 | for (TypeElement annotation : annotations) { 63 | roundEnv.getElementsAnnotatedWith(annotation) 64 | .stream().map(e -> new ClassAnalyzer(e, processingEnv)) 65 | .map(ClassAnalyzer::get) 66 | .filter(IS_NOT_BLANK).forEach(entities::add); 67 | } 68 | 69 | try { 70 | if (!entities.isEmpty()) { 71 | createClassMapping(entities); 72 | createProcessorMap(); 73 | } 74 | } catch (IOException exception) { 75 | error(exception); 76 | } 77 | return false; 78 | } 79 | 80 | private void createClassMapping(List entities) throws IOException { 81 | ClassMappingsModel metadata = new ClassMappingsModel(entities); 82 | Filer filer = processingEnv.getFiler(); 83 | JavaFileObject fileObject = filer.createSourceFile(metadata.getQualified()); 84 | try (Writer writer = fileObject.openWriter()) { 85 | template.execute(writer, metadata); 86 | } 87 | } 88 | private void createProcessorMap() throws IOException { 89 | Filer filer = processingEnv.getFiler(); 90 | JavaFileObject fileObject = filer.createSourceFile("org.soujava.metadata.processor.ProcessorMap"); 91 | try (Writer writer = fileObject.openWriter()) { 92 | final InputStream stream = EntityProcessor.class 93 | .getClassLoader() 94 | .getResourceAsStream("ProcessorMap.java"); 95 | String source = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8)).lines() 96 | .collect(Collectors.joining("\n")); 97 | writer.append(source); 98 | } 99 | } 100 | 101 | private Mustache createTemplate() { 102 | MustacheFactory factory = new DefaultMustacheFactory(); 103 | return factory.compile(TEMPLATE); 104 | } 105 | 106 | private void error(IOException exception) { 107 | processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "failed to write extension file: " 108 | + exception.getMessage()); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /processor/src/main/java/org/soujava/metadata/processor/FieldAnalyzer.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.processor; 2 | 3 | import com.github.mustachejava.DefaultMustacheFactory; 4 | import com.github.mustachejava.Mustache; 5 | import com.github.mustachejava.MustacheFactory; 6 | import org.soujava.medatadata.api.Column; 7 | import org.soujava.medatadata.api.Id; 8 | import org.soujava.medatadata.api.MapperException; 9 | 10 | import javax.annotation.processing.Filer; 11 | import javax.annotation.processing.ProcessingEnvironment; 12 | import javax.lang.model.element.Element; 13 | import javax.lang.model.element.ElementKind; 14 | import javax.lang.model.element.TypeElement; 15 | import javax.lang.model.type.DeclaredType; 16 | import javax.lang.model.type.TypeMirror; 17 | import javax.tools.JavaFileObject; 18 | import java.io.IOException; 19 | import java.io.Writer; 20 | import java.util.Collections; 21 | import java.util.List; 22 | import java.util.function.Function; 23 | import java.util.function.Predicate; 24 | import java.util.function.Supplier; 25 | import java.util.stream.Collectors; 26 | 27 | import static org.soujava.metadata.processor.ProcessorUtil.capitalize; 28 | import static org.soujava.metadata.processor.ProcessorUtil.getPackageName; 29 | import static org.soujava.metadata.processor.ProcessorUtil.getSimpleNameAsString; 30 | 31 | public class FieldAnalyzer implements Supplier { 32 | 33 | private static final String TEMPLATE = "fieldmetadata.mustache"; 34 | private static final Predicate IS_METHOD = el -> el.getKind() == ElementKind.METHOD; 35 | public static final Function ELEMENT_TO_STRING = el -> el.getSimpleName().toString(); 36 | private final Element field; 37 | private final Mustache template; 38 | private final ProcessingEnvironment processingEnv; 39 | private final TypeElement entity; 40 | 41 | FieldAnalyzer(Element field, ProcessingEnvironment processingEnv, 42 | TypeElement entity) { 43 | this.field = field; 44 | this.processingEnv = processingEnv; 45 | this.entity = entity; 46 | this.template = createTemplate(); 47 | } 48 | 49 | @Override 50 | public String get() { 51 | FieldModel metadata = getMetaData(); 52 | Filer filer = processingEnv.getFiler(); 53 | JavaFileObject fileObject = getFileObject(metadata, filer); 54 | try (Writer writer = fileObject.openWriter()) { 55 | template.execute(writer, metadata); 56 | } catch (IOException exception) { 57 | throw new MapperException("An error to compile the class: " + 58 | metadata.getQualified(), exception); 59 | } 60 | return metadata.getQualified(); 61 | } 62 | 63 | private JavaFileObject getFileObject(FieldModel metadata, Filer filer) { 64 | try { 65 | return filer.createSourceFile(metadata.getQualified(), entity); 66 | } catch (IOException exception) { 67 | throw new MapperException("An error to create the class: " + 68 | metadata.getQualified(), exception); 69 | } 70 | 71 | } 72 | 73 | private FieldModel getMetaData() { 74 | final String fieldName = field.getSimpleName().toString(); 75 | final Predicate validName = el -> el.getSimpleName().toString() 76 | .contains(capitalize(fieldName)); 77 | final Predicate hasGetterName = el -> el.equals("get" + capitalize(fieldName)); 78 | final Predicate hasSetterName = el -> el.equals("set" + capitalize(fieldName)); 79 | final Predicate hasIsName = el -> el.equals("is" + capitalize(fieldName)); 80 | 81 | final List accessors = processingEnv.getElementUtils() 82 | .getAllMembers(entity).stream() 83 | .filter(validName.and(IS_METHOD).and(EntityProcessor.HAS_ACCESS)) 84 | .collect(Collectors.toList()); 85 | 86 | final TypeMirror typeMirror = field.asType(); 87 | String className; 88 | final List arguments; 89 | if (typeMirror instanceof DeclaredType) { 90 | DeclaredType declaredType = (DeclaredType) typeMirror; 91 | arguments = declaredType.getTypeArguments().stream() 92 | .map(TypeMirror::toString) 93 | .collect(Collectors.toList()); 94 | className = declaredType.asElement().toString(); 95 | 96 | } else { 97 | className = typeMirror.toString(); 98 | arguments = Collections.emptyList(); 99 | } 100 | 101 | Column column = field.getAnnotation(Column.class); 102 | Id id = field.getAnnotation(Id.class); 103 | final boolean isId = id != null; 104 | final String packageName = getPackageName(entity); 105 | final String entityName = getSimpleNameAsString(this.entity); 106 | final String name = getName(fieldName, column, id); 107 | 108 | final String getMethod = accessors.stream() 109 | .map(ELEMENT_TO_STRING) 110 | .filter(hasGetterName) 111 | .findFirst().orElseThrow(generateGetterError(fieldName, packageName, entityName, 112 | "There is not valid getter method to the field: ")); 113 | final String setMethod = accessors.stream() 114 | .map(ELEMENT_TO_STRING) 115 | .filter(hasSetterName.or(hasIsName)) 116 | .findFirst().orElseThrow(generateGetterError(fieldName, packageName, entityName, 117 | "There is not valid setter method to the field: ")); 118 | 119 | return FieldModel.builder() 120 | .withPackageName(packageName) 121 | .withName(name) 122 | .withType(className) 123 | .withEntity(entityName) 124 | .withReader(getMethod) 125 | .withWriter(setMethod) 126 | .withFieldName(fieldName) 127 | .withId(isId) 128 | .withArguments(arguments) 129 | .build(); 130 | } 131 | 132 | private String getName(String fieldName, Column column, Id id) { 133 | if (id == null) { 134 | return column.value().isBlank() ? fieldName : column.value(); 135 | } else { 136 | return id.value().isBlank() ? fieldName : id.value(); 137 | } 138 | } 139 | 140 | private Supplier generateGetterError(String fieldName, String packageName, String entity, String s) { 141 | return () -> new MapperException(s + fieldName + " in the class: " + packageName + "." + entity); 142 | } 143 | 144 | private Mustache createTemplate() { 145 | MustacheFactory factory = new DefaultMustacheFactory(); 146 | return factory.compile(TEMPLATE); 147 | } 148 | 149 | } 150 | -------------------------------------------------------------------------------- /processor/src/main/java/org/soujava/metadata/processor/FieldModel.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.processor; 2 | 3 | import java.util.List; 4 | 5 | public class FieldModel extends BaseMappingModel { 6 | 7 | private final String packageName; 8 | private final String name; 9 | private final String type; 10 | private final String entity; 11 | private final String reader; 12 | private final String writer; 13 | private final String fieldName; 14 | private final boolean id; 15 | private final List arguments; 16 | 17 | FieldModel(String packageName, String name, 18 | String type, String entity, 19 | String reader, String writer, String fieldName, 20 | boolean id, 21 | List arguments) { 22 | this.packageName = packageName; 23 | this.name = name; 24 | this.type = type; 25 | this.entity = entity; 26 | this.reader = reader; 27 | this.writer = writer; 28 | this.fieldName = fieldName; 29 | this.id = id; 30 | this.arguments = arguments; 31 | } 32 | 33 | public String getPackageName() { 34 | return packageName; 35 | } 36 | 37 | public String getName() { 38 | return name; 39 | } 40 | 41 | public String getType() { 42 | return type; 43 | } 44 | 45 | public String getEntity() { 46 | return entity; 47 | } 48 | 49 | public String getReader() { 50 | return reader; 51 | } 52 | 53 | public String getWriter() { 54 | return writer; 55 | } 56 | 57 | public String getQualified() { 58 | return packageName + "." + getClassName(); 59 | } 60 | 61 | public String getClassName() { 62 | return entity + ProcessorUtil.capitalize(fieldName) + "FieldMetaData"; 63 | } 64 | 65 | public String getFieldName() { 66 | return fieldName; 67 | } 68 | 69 | public boolean isId() { 70 | return id; 71 | } 72 | 73 | public List getArguments() { 74 | return arguments; 75 | } 76 | 77 | @Override 78 | public String toString() { 79 | return "FieldModel{" + 80 | "packageName='" + packageName + '\'' + 81 | ", name='" + name + '\'' + 82 | ", type='" + type + '\'' + 83 | ", entity='" + entity + '\'' + 84 | ", reader='" + reader + '\'' + 85 | ", writer='" + writer + '\'' + 86 | ", fieldName='" + fieldName + '\'' + 87 | ", id=" + id + 88 | '}'; 89 | } 90 | 91 | public static FieldMetaDataBuilder builder() { 92 | return new FieldMetaDataBuilder(); 93 | } 94 | 95 | 96 | public static class FieldMetaDataBuilder { 97 | private String packageName; 98 | private String name; 99 | private String type; 100 | private String entity; 101 | private String reader; 102 | private String writer; 103 | private String fieldName; 104 | private boolean id; 105 | List arguments; 106 | 107 | private FieldMetaDataBuilder() { 108 | } 109 | 110 | public FieldMetaDataBuilder withPackageName(String packageName) { 111 | this.packageName = packageName; 112 | return this; 113 | } 114 | 115 | public FieldMetaDataBuilder withName(String name) { 116 | this.name = name; 117 | return this; 118 | } 119 | 120 | public FieldMetaDataBuilder withType(String type) { 121 | this.type = type; 122 | return this; 123 | } 124 | 125 | public FieldMetaDataBuilder withEntity(String entity) { 126 | this.entity = entity; 127 | return this; 128 | } 129 | 130 | public FieldMetaDataBuilder withReader(String getName) { 131 | this.reader = getName; 132 | return this; 133 | } 134 | 135 | public FieldMetaDataBuilder withWriter(String setName) { 136 | this.writer = setName; 137 | return this; 138 | } 139 | 140 | public FieldMetaDataBuilder withFieldName(String fieldName) { 141 | this.fieldName = fieldName; 142 | return this; 143 | } 144 | 145 | public FieldMetaDataBuilder withId(boolean id) { 146 | this.id = id; 147 | return this; 148 | } 149 | 150 | public FieldMetaDataBuilder withArguments(List arguments) { 151 | this.arguments = arguments; 152 | return this; 153 | } 154 | 155 | public FieldModel build() { 156 | return new FieldModel(packageName, name, type, entity, reader, writer, fieldName, id, arguments); 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /processor/src/main/java/org/soujava/metadata/processor/ProcessorUtil.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.processor; 2 | 3 | import javax.lang.model.element.Element; 4 | import javax.lang.model.element.PackageElement; 5 | import javax.lang.model.element.TypeElement; 6 | 7 | import static java.util.Locale.ENGLISH; 8 | 9 | final class ProcessorUtil { 10 | 11 | private ProcessorUtil() { 12 | } 13 | 14 | static String getPackageName(TypeElement classElement) { 15 | return ((PackageElement) classElement.getEnclosingElement()).getQualifiedName().toString(); 16 | } 17 | 18 | static String getSimpleNameAsString(Element element) { 19 | return element.getSimpleName().toString(); 20 | } 21 | 22 | static String capitalize(String name) { 23 | return name.substring(0, 1).toUpperCase(ENGLISH) + name.substring(1); 24 | } 25 | 26 | static boolean isTypeElement(Element element) { 27 | return element instanceof TypeElement; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor: -------------------------------------------------------------------------------- 1 | org.soujava.metadata.processor.EntityProcessor -------------------------------------------------------------------------------- /processor/src/main/resources/ProcessorMap.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.processor; 2 | 3 | import org.soujava.medatadata.api.ClassMappings; 4 | import org.soujava.medatadata.api.EntityMetadata; 5 | import org.soujava.medatadata.api.FieldMetadata; 6 | import org.soujava.medatadata.api.Mapper; 7 | import org.soujava.medatadata.api.MapperException; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | import java.util.Objects; 12 | import java.util.Optional; 13 | 14 | import javax.annotation.processing.Generated; 15 | 16 | @Generated("Soujava Mapper Generator") 17 | public class ProcessorMap implements Mapper { 18 | 19 | private final ClassMappings mappings; 20 | 21 | public ProcessorMap() { 22 | this.mappings = new ProcessorClassMappings(); 23 | } 24 | 25 | @Override 26 | public T toEntity(Map map, Class type) { 27 | Objects.requireNonNull(map, "map is required"); 28 | Objects.requireNonNull(type, "type is required"); 29 | final EntityMetadata entityMetadata = Optional.ofNullable(mappings.get(type)) 30 | .orElseThrow(() -> new MapperException("The entity has not Entity annotation")); 31 | final Map groupByName = entityMetadata.getFieldsGroupByName(); 32 | T instance = entityMetadata.newInstance(); 33 | for (Map.Entry entry : map.entrySet()) { 34 | final FieldMetadata fieldMetadata = groupByName.get(entry.getKey()); 35 | if (fieldMetadata != null) { 36 | fieldMetadata.write(instance, entry.getValue()); 37 | } 38 | } 39 | return instance; 40 | } 41 | 42 | @Override 43 | public Map toMap(T entity) { 44 | Objects.requireNonNull(entity, "entity is required"); 45 | Map map = new HashMap<>(); 46 | final EntityMetadata entityMetadata = Optional.ofNullable(mappings.get(entity.getClass())) 47 | .orElseThrow(() -> new MapperException("The entity has not Entity annotation")); 48 | map.put("entity", entityMetadata.getName()); 49 | for (FieldMetadata field : entityMetadata.getFields()) { 50 | final Object value = field.read(entity); 51 | if (value != null) { 52 | map.put(field.getName(), value); 53 | } 54 | } 55 | return map; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /processor/src/main/resources/classmappings.mustache: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.processor; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Objects; 6 | import java.util.Optional; 7 | import org.soujava.medatadata.api.ClassMappings; 8 | import org.soujava.medatadata.api.EntityMetadata; 9 | 10 | import javax.annotation.processing.Generated; 11 | 12 | @Generated(value= "Soujava ClassMappings Generator", date = "{{now}}") 13 | public final class ProcessorClassMappings implements ClassMappings { 14 | 15 | private final List entities; 16 | 17 | public ProcessorClassMappings() { 18 | this.entities = new ArrayList<>(); 19 | {{#entities}} 20 | this.entities.add(new {{.}}()); 21 | {{/entities}} 22 | } 23 | 24 | @Override 25 | public EntityMetadata get(Class classEntity) { 26 | Objects.requireNonNull(classEntity, "classEntity is required"); 27 | return entities.stream() 28 | .filter(e -> classEntity.equals(e.getClassInstance())) 29 | .findFirst() 30 | .orElseThrow(() -> new RuntimeException("classEntity not found")); 31 | } 32 | 33 | @Override 34 | public EntityMetadata findByName(String name) { 35 | Objects.requireNonNull(name, "name is required"); 36 | return entities.stream() 37 | .filter(e -> name.equals(e.getName())) 38 | .findFirst() 39 | .orElseThrow(() -> new RuntimeException("classEntity not found")); 40 | } 41 | 42 | @Override 43 | public Optional findBySimpleName(String name) { 44 | Objects.requireNonNull(name, "name is required"); 45 | return entities.stream() 46 | .filter(e -> name.equals(e.getSimpleName())) 47 | .findFirst(); 48 | } 49 | 50 | @Override 51 | public Optional findByClassName(String name) { 52 | Objects.requireNonNull(name, "name is required"); 53 | return entities.stream() 54 | .filter(e -> name.equals(e.getClassName())) 55 | .findFirst(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /processor/src/main/resources/entitymetadata.mustache: -------------------------------------------------------------------------------- 1 | package {{packageName}}; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.Objects; 8 | import java.util.Optional; 9 | import org.soujava.medatadata.api.EntityMetadata; 10 | import org.soujava.medatadata.api.FieldMetadata; 11 | 12 | import static java.util.function.Function.identity; 13 | import static java.util.stream.Collectors.collectingAndThen; 14 | import static java.util.stream.Collectors.toList; 15 | import static java.util.stream.Collectors.toMap; 16 | 17 | import javax.annotation.processing.Generated; 18 | 19 | @Generated(value= "Soujava EntityMetadata Generator" , date = "{{now}}") 20 | public final class {{className}} implements EntityMetadata { 21 | 22 | private final List fields; 23 | 24 | public {{className}}() { 25 | this.fields = new ArrayList<>(); 26 | {{#fields}} 27 | this.fields.add(new {{.}}()); 28 | {{/fields}} 29 | } 30 | 31 | @Override 32 | public String getName() { 33 | return "{{name}}"; 34 | } 35 | 36 | @Override 37 | public String getClassName() { 38 | return "{{entityQualified}}"; 39 | } 40 | 41 | @Override 42 | public String getSimpleName() { 43 | return "{{entity}}"; 44 | } 45 | 46 | 47 | @Override 48 | public T newInstance() { 49 | return (T)new {{entity}}(); 50 | } 51 | 52 | @Override 53 | public Class getClassInstance() { 54 | return {{entity}}.class; 55 | } 56 | 57 | @Override 58 | public List getFields() { 59 | return Collections.unmodifiableList(fields); 60 | } 61 | 62 | @Override 63 | public List getFieldsName() { 64 | return getFields().stream() 65 | .map(FieldMetadata::getFieldName) 66 | .collect(collectingAndThen(toList(), Collections::unmodifiableList)); 67 | } 68 | 69 | @Override 70 | public String getColumnField(String javaField) { 71 | Objects.requireNonNull(javaField, "javaField is required"); 72 | return getFields().stream() 73 | .filter(f -> javaField.equals(f.getFieldName())) 74 | .map(FieldMetadata::getName) 75 | .findFirst().orElse(javaField); 76 | } 77 | 78 | @Override 79 | public Optional getFieldMapping(String javaField) { 80 | Objects.requireNonNull(javaField, "javaField is required"); 81 | return getFields().stream() 82 | .filter(f -> javaField.equals(f.getFieldName())) 83 | .findFirst(); 84 | } 85 | 86 | @Override 87 | public Map getFieldsGroupByName() { 88 | return getFields().stream() 89 | .collect(toMap(FieldMetadata::getName, identity())); 90 | } 91 | 92 | @Override 93 | public Optional getId() { 94 | return getFields().stream() 95 | .filter(FieldMetadata::isId) 96 | .findFirst(); 97 | } 98 | } -------------------------------------------------------------------------------- /processor/src/main/resources/fieldmetadata.mustache: -------------------------------------------------------------------------------- 1 | package {{packageName}}; 2 | 3 | import org.soujava.medatadata.api.FieldMetadata; 4 | import java.util.HashSet; 5 | import java.util.Set; 6 | import java.util.Collections; 7 | 8 | import javax.annotation.processing.Generated; 9 | 10 | @Generated(value= "Soujava FieldMetadata Generator", date = "{{now}}") 11 | public final class {{className}} implements FieldMetadata { 12 | 13 | private Set> arguments; 14 | 15 | public {{className}}() { 16 | this.arguments = new HashSet<>(); 17 | {{#arguments}} 18 | this.arguments.add({{.}}.class); 19 | {{/arguments}} 20 | } 21 | 22 | @Override 23 | public boolean isId() { 24 | return {{id}}; 25 | } 26 | 27 | @Override 28 | public String getName() { 29 | return "{{name}}"; 30 | } 31 | 32 | @Override 33 | public String getFieldName() { 34 | return "{{fieldName}}"; 35 | } 36 | 37 | @Override 38 | public void write(Object bean, Object value) { 39 | (({{entity}}) bean).{{writer}}(({{type}})value); 40 | } 41 | 42 | @Override 43 | public Object read(Object bean) { 44 | return (({{entity}}) bean).{{reader}}(); 45 | } 46 | 47 | @Override 48 | public Class getType() { 49 | return {{type}}.class; 50 | } 51 | 52 | @Override 53 | public Set> getArguments() { 54 | return Collections.unmodifiableSet(arguments); 55 | } 56 | } -------------------------------------------------------------------------------- /processor/src/test/java/org/soujava/metadata/processor/CompilerTest.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.processor; 2 | 3 | import com.google.testing.compile.Compilation; 4 | import com.google.testing.compile.JavaFileObjects; 5 | import org.junit.jupiter.api.Assertions; 6 | import org.junit.jupiter.api.Test; 7 | import org.soujava.medatadata.api.Entity; 8 | 9 | import javax.tools.JavaFileObject; 10 | import java.io.IOException; 11 | 12 | import static com.google.testing.compile.CompilationSubject.assertThat; 13 | import static com.google.testing.compile.Compiler.javac; 14 | 15 | public class CompilerTest { 16 | 17 | 18 | @Test 19 | public void shouldCompile() throws IOException { 20 | 21 | final JavaFileObject javaFileObject = JavaFileObjects.forResource("Person.java"); 22 | 23 | Compilation compilation = javac() 24 | .withClasspathFrom(Entity.class.getClassLoader()) 25 | .withOptions() 26 | .withProcessors(new EntityProcessor()) 27 | .compile(javaFileObject); 28 | assertThat(compilation).succeeded(); 29 | } 30 | 31 | @Test 32 | public void shouldReturnConstructorIssue() throws IOException { 33 | final JavaFileObject javaFileObject = JavaFileObjects.forResource("Person2.java"); 34 | Assertions.assertThrows(RuntimeException.class, () -> 35 | javac() 36 | .withClasspathFrom(Entity.class.getClassLoader()) 37 | .withOptions() 38 | .withProcessors(new EntityProcessor()) 39 | .compile(javaFileObject)); 40 | } 41 | 42 | @Test 43 | public void shouldCheckColumnCompile() { 44 | 45 | final JavaFileObject javaFileObject = JavaFileObjects.forResource("Person3.java"); 46 | 47 | Compilation compilation = javac() 48 | .withClasspathFrom(Entity.class.getClassLoader()) 49 | .withOptions() 50 | .withProcessors(new EntityProcessor()) 51 | .compile(javaFileObject); 52 | assertThat(compilation).succeeded(); 53 | } 54 | 55 | @Test 56 | public void shouldUseDefaultMethod() { 57 | 58 | final JavaFileObject javaFileObject = JavaFileObjects.forResource("Person5.java"); 59 | 60 | Compilation compilation = javac() 61 | .withClasspathFrom(Entity.class.getClassLoader()) 62 | .withOptions() 63 | .withProcessors(new EntityProcessor()) 64 | .compile(javaFileObject); 65 | assertThat(compilation).succeeded(); 66 | } 67 | 68 | @Test 69 | public void shouldGetTheGenericInformation() { 70 | 71 | final JavaFileObject javaFileObject = JavaFileObjects.forResource("Person6.java"); 72 | 73 | Compilation compilation = javac() 74 | .withClasspathFrom(Entity.class.getClassLoader()) 75 | .withOptions() 76 | .withProcessors(new EntityProcessor()) 77 | .compile(javaFileObject); 78 | assertThat(compilation).succeeded(); 79 | } 80 | 81 | 82 | @Test 83 | public void shouldReturnAccessorIssue() { 84 | final JavaFileObject javaFileObject = JavaFileObjects.forResource("Person4.java"); 85 | Assertions.assertThrows(RuntimeException.class, () -> 86 | javac() 87 | .withClasspathFrom(Entity.class.getClassLoader()) 88 | .withOptions() 89 | .withProcessors(new EntityProcessor()) 90 | .compile(javaFileObject)); 91 | } 92 | 93 | @Test 94 | public void shouldAddTwoClass() { 95 | final JavaFileObject javaFileObject = JavaFileObjects.forResource("Person.java"); 96 | final JavaFileObject javaFileObject2 = JavaFileObjects.forResource("Person5.java"); 97 | 98 | Compilation compilation = javac() 99 | .withClasspathFrom(Entity.class.getClassLoader()) 100 | .withOptions() 101 | .withProcessors(new EntityProcessor()) 102 | .compile(javaFileObject, javaFileObject2); 103 | assertThat(compilation).succeeded(); 104 | } 105 | 106 | @Test 107 | public void shouldCompileDefaultPackage() throws IOException { 108 | final JavaFileObject javaFileObject = JavaFileObjects.forResource("Person7.java"); 109 | Compilation compilation = javac() 110 | .withClasspathFrom(Entity.class.getClassLoader()) 111 | .withOptions() 112 | .withProcessors(new EntityProcessor()) 113 | .compile(javaFileObject); 114 | assertThat(compilation).succeeded(); 115 | } 116 | 117 | @Test 118 | public void shouldCompileProtected() throws IOException { 119 | final JavaFileObject javaFileObject = JavaFileObjects.forResource("Person8.java"); 120 | Compilation compilation = javac() 121 | .withClasspathFrom(Entity.class.getClassLoader()) 122 | .withOptions() 123 | .withProcessors(new EntityProcessor()) 124 | .compile(javaFileObject); 125 | assertThat(compilation).succeeded(); 126 | } 127 | 128 | 129 | } 130 | -------------------------------------------------------------------------------- /processor/src/test/resources/Person.java: -------------------------------------------------------------------------------- 1 | package org.soujava.example.model; 2 | 3 | 4 | import org.soujava.medatadata.api.Entity; 5 | 6 | @Entity("table") 7 | public class Person { 8 | 9 | private String name; 10 | 11 | public String getName() { 12 | return name; 13 | } 14 | 15 | public void setName(String name) { 16 | this.name = name; 17 | } 18 | 19 | @Override 20 | public String toString() { 21 | return "Person{" + 22 | "name='" + name + '\'' + 23 | '}'; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /processor/src/test/resources/Person2.java: -------------------------------------------------------------------------------- 1 | package org.soujava.example.model; 2 | 3 | 4 | import org.soujava.medatadata.api.Entity; 5 | import org.soujava.medatadata.api.Column; 6 | 7 | @Entity 8 | public class Person2 { 9 | 10 | @Column 11 | private String name; 12 | 13 | public String getName() { 14 | return name; 15 | } 16 | 17 | public void setName(String name) { 18 | this.name = name; 19 | } 20 | 21 | private Person2() {} 22 | 23 | @Override 24 | public String toString() { 25 | return "Person{" + 26 | "name='" + name + '\'' + 27 | '}'; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /processor/src/test/resources/Person3.java: -------------------------------------------------------------------------------- 1 | package org.soujava.example.model; 2 | 3 | 4 | import org.soujava.medatadata.api.Entity; 5 | import org.soujava.medatadata.api.Column; 6 | 7 | @Entity("table") 8 | public class Person3 { 9 | 10 | @Column 11 | private String name; 12 | 13 | public String getName() { 14 | return name; 15 | } 16 | 17 | public void setName(String name) { 18 | this.name = name; 19 | } 20 | 21 | @Override 22 | public String toString() { 23 | return "Person{" + 24 | "name='" + name + '\'' + 25 | '}'; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /processor/src/test/resources/Person4.java: -------------------------------------------------------------------------------- 1 | package org.soujava.example.model; 2 | 3 | import org.soujava.medatadata.api.Entity; 4 | import org.soujava.medatadata.api.Column; 5 | 6 | @Entity("table") 7 | public class Person4 { 8 | 9 | @Column("native") 10 | private String name; 11 | 12 | public String getName() { 13 | return name; 14 | } 15 | 16 | @Override 17 | public String toString() { 18 | return "Person{" + 19 | "name='" + name + '\'' + 20 | '}'; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /processor/src/test/resources/Person5.java: -------------------------------------------------------------------------------- 1 | package org.soujava.example.model; 2 | 3 | 4 | import org.soujava.medatadata.api.Entity; 5 | import org.soujava.medatadata.api.Column; 6 | 7 | @Entity("table") 8 | public class Person5 { 9 | 10 | @Column 11 | private String name; 12 | 13 | String getName() { 14 | return name; 15 | } 16 | 17 | void setName(String name) { 18 | this.name = name; 19 | } 20 | 21 | @Override 22 | public String toString() { 23 | return "Person{" + 24 | "name='" + name + '\'' + 25 | '}'; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /processor/src/test/resources/Person6.java: -------------------------------------------------------------------------------- 1 | package org.soujava.example.model; 2 | 3 | 4 | import org.soujava.medatadata.api.Entity; 5 | import org.soujava.medatadata.api.Column; 6 | 7 | import java.util.List; 8 | 9 | @Entity("table") 10 | public class Person6 { 11 | 12 | @Column("native") 13 | private String name; 14 | 15 | @Column 16 | private List names; 17 | 18 | public String getName() { 19 | return name; 20 | } 21 | 22 | public void setName(String name) { 23 | this.name = name; 24 | } 25 | 26 | public List getNames() { 27 | return names; 28 | } 29 | 30 | public void setNames(List names) { 31 | this.names = names; 32 | } 33 | 34 | @Override 35 | public String toString() { 36 | return "Person{" + 37 | "name='" + name + '\'' + 38 | '}'; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /processor/src/test/resources/Person7.java: -------------------------------------------------------------------------------- 1 | package org.soujava.example.model; 2 | 3 | 4 | import org.soujava.medatadata.api.Column; 5 | import org.soujava.medatadata.api.Entity; 6 | 7 | @Entity("table") 8 | public class Person7 { 9 | 10 | @Column 11 | private String name; 12 | 13 | String getName() { 14 | return name; 15 | } 16 | 17 | void setName(String name) { 18 | this.name = name; 19 | } 20 | 21 | @Override 22 | public String toString() { 23 | return "Person{" + 24 | "name='" + name + '\'' + 25 | '}'; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /processor/src/test/resources/Person8.java: -------------------------------------------------------------------------------- 1 | package org.soujava.example.model; 2 | 3 | 4 | import org.soujava.medatadata.api.Column; 5 | import org.soujava.medatadata.api.Entity; 6 | 7 | @Entity("table") 8 | public class Person8 { 9 | 10 | @Column 11 | private String name; 12 | 13 | protected String getName() { 14 | return name; 15 | } 16 | 17 | protected void setName(String name) { 18 | this.name = name; 19 | } 20 | 21 | @Override 22 | public String toString() { 23 | return "Person{" + 24 | "name='" + name + '\'' + 25 | '}'; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /reflection/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | pom.xml.tag 3 | pom.xml.releaseBackup 4 | pom.xml.versionsBackup 5 | pom.xml.next 6 | test-output/ 7 | /doc 8 | *.iml 9 | *.idea 10 | *.log 11 | /.idea 12 | .checkstyle 13 | 14 | # Eclipse metadata 15 | .settings/ 16 | .project 17 | .factorypath 18 | .classpath 19 | -project 20 | /.resourceCache 21 | /.project 22 | 23 | # Annotation processor metadata 24 | .apt_generated/ 25 | .apt_generated_tests/ 26 | -------------------------------------------------------------------------------- /reflection/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | 8 | 9 | org.soujava.metadata 10 | parent 11 | 1.0.0 12 | 13 | reflection 14 | 15 | 16 | 17 | ${project.groupId} 18 | api 19 | ${project.version} 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | org.apache.maven.plugins 28 | maven-compiler-plugin 29 | ${maven.compiler.plugin} 30 | 31 | -proc:none 32 | ${java.version} 33 | ${java.version} 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /reflection/src/main/java/org/soujava/metadata/reflection/EntitySupplier.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.reflection; 2 | 3 | import org.soujava.medatadata.api.Column; 4 | import org.soujava.medatadata.api.Id; 5 | import org.soujava.medatadata.api.MapperException; 6 | import org.soujava.medatadata.api.Param; 7 | 8 | import java.lang.reflect.Constructor; 9 | import java.lang.reflect.Field; 10 | import java.lang.reflect.InvocationTargetException; 11 | import java.lang.reflect.Parameter; 12 | import java.util.ArrayList; 13 | import java.util.Map; 14 | import java.util.Objects; 15 | import java.util.Optional; 16 | import java.util.function.Function; 17 | import java.util.stream.Collectors; 18 | import java.util.stream.Stream; 19 | 20 | interface EntitySupplier { 21 | 22 | T toEntity(Map map, Class type) throws InvocationTargetException, 23 | InstantiationException, IllegalAccessException; 24 | 25 | class FieldEntitySupplier implements EntitySupplier { 26 | 27 | private final Constructor constructor; 28 | 29 | FieldEntitySupplier(Constructor constructor) { 30 | this.constructor = constructor; 31 | } 32 | 33 | @Override 34 | public T toEntity(Map map, Class type) throws InvocationTargetException, 35 | InstantiationException, IllegalAccessException { 36 | final T instance = constructor.newInstance(); 37 | for (Field field : type.getDeclaredFields()) { 38 | FieldWriter writer = FieldWriter.of(field); 39 | writer.write(map, instance); 40 | } 41 | return instance; 42 | } 43 | } 44 | 45 | class ConstructorEntitySupplier implements EntitySupplier { 46 | 47 | private final Constructor constructor; 48 | 49 | ConstructorEntitySupplier(Constructor constructor) { 50 | this.constructor = constructor; 51 | } 52 | 53 | @Override 54 | public T toEntity(Map map, Class type) throws InvocationTargetException, 55 | InstantiationException, IllegalAccessException { 56 | 57 | Map fieldMap = Stream.of(type.getDeclaredFields()) 58 | .collect(Collectors.toMap(f -> f.getName(), Function.identity())); 59 | 60 | ArrayList parameters = new ArrayList<>(constructor.getParameters().length); 61 | for (Parameter parameter : constructor.getParameters()) { 62 | Param param = parameter.getAnnotation(Param.class); 63 | String paramName = Optional.ofNullable(param) 64 | .map(Param::value) 65 | .orElseThrow(() -> new MapperException("The param in the constructor should have the annotation " 66 | + Param.class)); 67 | Field field = fieldMap.get(paramName); 68 | if (Objects.nonNull(field.getAnnotation(Id.class))) { 69 | Id id = field.getAnnotation(Id.class); 70 | String name = Optional.of(id).map(Id::value) 71 | .filter(s -> !s.isBlank()) 72 | .orElse(field.getName()); 73 | parameters.add(map.get(name)); 74 | } else if (Objects.nonNull(field.getAnnotation(Column.class))) { 75 | Column column = field.getAnnotation(Column.class); 76 | String name = Optional.of(column).map(Column::value) 77 | .filter(s -> !s.isBlank()) 78 | .orElse(field.getName()); 79 | parameters.add(map.get(name)); 80 | } 81 | } 82 | return constructor.newInstance(parameters.toArray()); 83 | } 84 | } 85 | 86 | static EntitySupplier of(Constructor constructor) { 87 | if (constructor.getParameters().length == 0) { 88 | return new FieldEntitySupplier<>(constructor); 89 | } 90 | return new ConstructorEntitySupplier<>(constructor); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /reflection/src/main/java/org/soujava/metadata/reflection/FieldReader.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.reflection; 2 | 3 | import org.soujava.medatadata.api.Column; 4 | import org.soujava.medatadata.api.Id; 5 | 6 | import java.lang.reflect.Field; 7 | import java.util.Map; 8 | 9 | interface FieldReader { 10 | 11 | void read(T entity, Map map) throws IllegalAccessException; 12 | 13 | 14 | class IdFieldReader implements FieldReader { 15 | 16 | private final Field field; 17 | 18 | public IdFieldReader(Field field) { 19 | this.field = field; 20 | } 21 | 22 | @Override 23 | public void read(T entity, Map map) throws IllegalAccessException { 24 | final Id id = field.getAnnotation(Id.class); 25 | final String fieldName = field.getName(); 26 | String idName = id.value().isBlank() ? fieldName : id.value(); 27 | field.setAccessible(true); 28 | final Object value = field.get(entity); 29 | map.put(idName, value); 30 | } 31 | } 32 | 33 | class ColumnFieldReader implements FieldReader { 34 | 35 | private final Field field; 36 | 37 | public ColumnFieldReader(Field field) { 38 | this.field = field; 39 | } 40 | 41 | @Override 42 | public void read(T entity, Map map) throws IllegalAccessException { 43 | final Column column = field.getAnnotation(Column.class); 44 | final String fieldName = field.getName(); 45 | String columnName = column.value().isBlank() ? fieldName : column.value(); 46 | field.setAccessible(true); 47 | final Object value = field.get(entity); 48 | map.put(columnName, value); 49 | } 50 | } 51 | 52 | class NoopFieldReader implements FieldReader { 53 | 54 | @Override 55 | public void read(T entity, Map map) throws IllegalAccessException { 56 | 57 | } 58 | } 59 | 60 | static FieldReader of(Field field) { 61 | if (field.getAnnotation(Id.class) != null) { 62 | return new IdFieldReader(field); 63 | } else if (field.getAnnotation(Column.class) != null) { 64 | return new ColumnFieldReader(field); 65 | } else { 66 | return new NoopFieldReader(); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /reflection/src/main/java/org/soujava/metadata/reflection/FieldWriter.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.reflection; 2 | 3 | import org.soujava.medatadata.api.Column; 4 | import org.soujava.medatadata.api.Id; 5 | 6 | import java.lang.reflect.Field; 7 | import java.util.Map; 8 | import java.util.Objects; 9 | 10 | interface FieldWriter { 11 | 12 | void write(Map map, T instance) throws IllegalAccessException; 13 | 14 | class IdFieldWriter implements FieldWriter { 15 | 16 | private final Field field; 17 | 18 | public IdFieldWriter(Field field) { 19 | this.field = field; 20 | } 21 | 22 | @Override 23 | public void write(Map map, T instance) throws IllegalAccessException { 24 | final Id id = field.getAnnotation(Id.class); 25 | final String fieldName = field.getName(); 26 | String idName = id.value().isBlank() ? fieldName : id.value(); 27 | field.setAccessible(true); 28 | final Object value = map.get(idName); 29 | if (value != null) { 30 | field.set(instance, value); 31 | } 32 | } 33 | } 34 | 35 | class ColumnFieldWriter implements FieldWriter { 36 | 37 | private final Field field; 38 | 39 | public ColumnFieldWriter(Field field) { 40 | this.field = field; 41 | } 42 | 43 | @Override 44 | public void write(Map map, T instance) throws IllegalAccessException { 45 | final Column column = field.getAnnotation(Column.class); 46 | final String fieldName = field.getName(); 47 | String columnName = column.value().isBlank() ? fieldName : column.value(); 48 | field.setAccessible(true); 49 | final Object value = map.get(columnName); 50 | if (value != null) { 51 | field.set(instance, value); 52 | } 53 | } 54 | } 55 | 56 | class NooPFieldWriter implements FieldWriter { 57 | @Override 58 | public void write(Map map, T instance) throws IllegalAccessException { 59 | 60 | } 61 | } 62 | 63 | static FieldWriter of(Field field) { 64 | if (Objects.nonNull(field.getAnnotation(Id.class))) { 65 | return new IdFieldWriter(field); 66 | } else if (Objects.nonNull(field.getAnnotation(Column.class))) { 67 | return new ColumnFieldWriter(field); 68 | } else { 69 | return new NooPFieldWriter(); 70 | } 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /reflection/src/main/java/org/soujava/metadata/reflection/ReflectionMapper.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.reflection; 2 | 3 | import org.soujava.medatadata.api.Entity; 4 | import org.soujava.medatadata.api.Mapper; 5 | import org.soujava.medatadata.api.MapperException; 6 | 7 | import java.lang.reflect.Constructor; 8 | import java.lang.reflect.Field; 9 | import java.lang.reflect.InvocationTargetException; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | import java.util.Objects; 13 | import java.util.Optional; 14 | import java.util.function.Predicate; 15 | import java.util.stream.Stream; 16 | 17 | public class ReflectionMapper implements Mapper { 18 | 19 | private final static Predicate> HAS_CONSTRUCTOR_ANNOTATION = c -> c.getAnnotation( 20 | org.soujava.medatadata.api.Constructor.class) != null; 21 | private static final Predicate> HAS_DEFAULT_CONSTRUCTOR = c -> c.getParameters().length == 0; 22 | 23 | @Override 24 | public T toEntity(Map map, Class type) { 25 | Objects.requireNonNull(map, "Map is required"); 26 | Objects.requireNonNull(type, "type is required"); 27 | 28 | try { 29 | Constructor constructor = findConstructor(type); 30 | EntitySupplier supplier = EntitySupplier.of(constructor); 31 | return supplier.toEntity(map, type); 32 | } catch (InstantiationException | IllegalAccessException | InvocationTargetException exception) { 33 | throw new MapperException("An error to field the entity process", exception); 34 | } 35 | } 36 | 37 | 38 | @Override 39 | public Map toMap(T entity) { 40 | Objects.requireNonNull(entity, "entity is required"); 41 | Map map = new HashMap<>(); 42 | final Class type = entity.getClass(); 43 | final Entity annotation = Optional.ofNullable( 44 | type.getAnnotation(Entity.class)) 45 | .orElseThrow(() -> new RuntimeException("The class must have Entity annotation")); 46 | 47 | String name = annotation.value().isBlank() ? type.getSimpleName() : annotation.value(); 48 | map.put("entity", name); 49 | for (Field field : type.getDeclaredFields()) { 50 | try { 51 | FieldReader reader = FieldReader.of(field); 52 | reader.read(entity, map); 53 | } catch (IllegalAccessException exception) { 54 | throw new MapperException("An error to field the map process", exception); 55 | } 56 | } 57 | return map; 58 | } 59 | 60 | private Constructor findConstructor(Class type) { 61 | Constructor[] constructors = (Constructor[]) type.getConstructors(); 62 | if (constructors.length == 1) { 63 | return constructors[0]; 64 | } 65 | 66 | return Stream.of(constructors) 67 | .filter(HAS_CONSTRUCTOR_ANNOTATION.or(HAS_DEFAULT_CONSTRUCTOR)) 68 | .findFirst() 69 | .orElseThrow(() -> new MapperException("The constructor is required in the class: " + type)); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /reflection/src/test/java/org/soujava/metadata/reflection/Animal.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.reflection; 2 | 3 | 4 | import org.soujava.medatadata.api.Column; 5 | import org.soujava.medatadata.api.Entity; 6 | import org.soujava.medatadata.api.Id; 7 | 8 | @Entity("animal") 9 | public class Animal { 10 | 11 | @Id 12 | private String id; 13 | 14 | @Column("native_name") 15 | private String name; 16 | 17 | public Animal() { 18 | } 19 | 20 | public Animal(String id, String name) { 21 | this.id = id; 22 | this.name = name; 23 | } 24 | 25 | public String getId() { 26 | return id; 27 | } 28 | 29 | public void setId(String id) { 30 | this.id = id; 31 | } 32 | 33 | public String getName() { 34 | return name; 35 | } 36 | 37 | public void setName(String name) { 38 | this.name = name; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /reflection/src/test/java/org/soujava/metadata/reflection/Person.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.reflection; 2 | 3 | 4 | import org.soujava.medatadata.api.Column; 5 | import org.soujava.medatadata.api.Constructor; 6 | import org.soujava.medatadata.api.Entity; 7 | import org.soujava.medatadata.api.Id; 8 | import org.soujava.medatadata.api.Param; 9 | 10 | @Entity 11 | public class Person { 12 | 13 | @Id 14 | private final String id; 15 | 16 | @Column("native") 17 | private final String name; 18 | 19 | @Column 20 | private final String country; 21 | 22 | @Constructor 23 | public Person(@Param("id") String id, 24 | @Param("name") String name, 25 | @Param("country") String country) { 26 | this.id = id; 27 | this.name = name; 28 | this.country = country; 29 | } 30 | 31 | public String getId() { 32 | return id; 33 | } 34 | 35 | public String getName() { 36 | return name; 37 | } 38 | 39 | public String getCountry() { 40 | return country; 41 | } 42 | 43 | @Override 44 | public String toString() { 45 | return "Person{" + 46 | "id='" + id + '\'' + 47 | ", name='" + name + '\'' + 48 | ", country='" + country + '\'' + 49 | '}'; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /reflection/src/test/java/org/soujava/metadata/reflection/ReflectionMapperTest.java: -------------------------------------------------------------------------------- 1 | package org.soujava.metadata.reflection; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Test; 6 | import org.soujava.medatadata.api.Mapper; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | public class ReflectionMapperTest { 12 | 13 | private Mapper mapper; 14 | 15 | @BeforeEach 16 | public void setUp() { 17 | this.mapper = new ReflectionMapper(); 18 | } 19 | 20 | @Test 21 | public void shouldCreateMap() { 22 | Animal animal = new Animal("id", "lion"); 23 | final Map map = mapper.toMap(animal); 24 | Assertions.assertEquals("animal", map.get("entity")); 25 | Assertions.assertEquals("id", map.get("id")); 26 | Assertions.assertEquals("lion", map.get("native_name")); 27 | } 28 | 29 | @Test 30 | public void shouldCreateEntity() { 31 | Map map = new HashMap<>(); 32 | map.put("id", "id"); 33 | map.put("native_name", "lion"); 34 | 35 | final Animal animal = mapper.toEntity(map, Animal.class); 36 | Assertions.assertEquals("id", animal.getId()); 37 | Assertions.assertEquals("lion", animal.getName()); 38 | } 39 | 40 | @Test 41 | public void shouldCreateMapFromImmutable() { 42 | Person person = new Person("id", "Ada Lovelace", "England"); 43 | final Map map = mapper.toMap(person); 44 | Assertions.assertEquals("Person", map.get("entity")); 45 | Assertions.assertEquals("id", map.get("id")); 46 | Assertions.assertEquals("Ada Lovelace", map.get("native")); 47 | Assertions.assertEquals("England", map.get("country")); 48 | } 49 | 50 | @Test 51 | public void shouldCreateFromImmutableEntity() { 52 | Map map = new HashMap<>(); 53 | map.put("id", "id"); 54 | map.put("native", "Ada Lovelace"); 55 | map.put("country", "England"); 56 | 57 | final Person person = mapper.toEntity(map, Person.class); 58 | Assertions.assertEquals("id", person.getId()); 59 | Assertions.assertEquals("Ada Lovelace", person.getName()); 60 | Assertions.assertEquals("England", person.getCountry()); 61 | } 62 | } 63 | --------------------------------------------------------------------------------