Skip to content

Commit

Permalink
Merge pull request #31 from endrealm/better-annotations
Browse files Browse the repository at this point in the history
Add better annotation support
  • Loading branch information
Gerolmed authored Oct 5, 2019
2 parents 0a8cdde + 398fcc9 commit 31169ce
Show file tree
Hide file tree
Showing 16 changed files with 389 additions and 50 deletions.
65 changes: 65 additions & 0 deletions docs/annotations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Using the annotations API
First thing for anything to work is register the affected classes.
```java
//service is the implementation of DriveService
ConversionHandler conversion = service.getConversionHandler();
conversion.registerClasses(GreatEntity.class);
```
If you forget doing this errors will occur.
## Simple field saving
Now that you have your service you will probably want to start mapping your first classes. As an example we will map the class `GreatEntity` here.
```java
@SaveAll
public class GreatEntity {

//No-Args constructor is required, when reading values
public GreatEntity() {
this.feet = 2;
}

public GreatEntity(String entityName, int age, String[] addresses, int feet) {
this.entityName = entityName;
this.age = age;
this.addresses = addresses;
this.feet = feet;
}

private String entityName;
private int age;
private String[] addresses;

private int feet;
}
```

## Advanced annotation handling

`@SaveVar(properties)` | scope = Field
properties:
* name: String | default "" || name field will be saved under and loaded from. Leave empty for fieldName-
* aliases: String[] | default {} || A list of aliases this fields value might be saved under. Used when name not found
* optional: Boolean | default true || Is the field optional. If not class cannot be loaded without the value being asigned
> Fields marked with this will be saved and loaded under this value

`@SaveTable(properties)` | scope = Class
properties:
* value: String | default "" || table name to save under, if query does not force other
> Should be used to save the entity under a specific table. This will pregenerate a table depending on the backend type
`@SaveAll(properties)` | scope = Class
properties:
* ignore: String[] | default {} || field names to ignore
> This will tell the program to save all fields. Note: @SaveVar and @IgnoreVar will overwrite this behaviour

`@IgnoreVar()` | scope = Field
> Fields marked with this will be ignored when saving and loading
`@WriteOnly()` | scope = Field
> Fields marked with this will only be saved, but not loaded.
`@ReadOnly()` | scope = Field
> Fields marked with this will only be loaded, but not saved.
> **Note:** Having both ReadOnly and WriteOnly on a field will lead to unexpected behaviour and should be replaced by SaveVar
30 changes: 1 addition & 29 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,35 +50,7 @@ DriveService service = new DriveServiceFactory().getDriveService(settings);
```

#### Marking fields
Now that you have your service you will probably want to start mapping your first classes. As an example we will map the class `GreatEntity` here.
```java
public class GreatEntity {

//No-Args constructor is required, when reading values
public GreatEntity() {
this.feet = 2;
}

public GreatEntity(String entityName, int age, String[] addresses, int feet) {
this.entityName = entityName;
this.age = age;
this.addresses = addresses;
this.feet = feet;
}

@SaveVar
private String entityName;

@SaveVar
private int age;

@SaveVar
private String[] addresses;

private int feet;
}
```
**Note** All variables, but `feet` are annotated with @SaveVar. Only those annotated will be saved and retrieved.
Read more about it [here](./annotations.md)
#### Register classes
Now we will want to go ahead and tell the conversion handler that this class can be saved. This is required to automatically transform database entries to classes. While you will not be able to store them directly into the database you can define serializers that will allow you using them as fields.
```java
Expand Down
18 changes: 18 additions & 0 deletions src/main/java/net/endrealm/realmdrive/annotations/IgnoreVar.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package net.endrealm.realmdrive.annotations;

import net.endrealm.realmdrive.inst.SimpleConversionHandler;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Marks a field to not be saved.
*
* @see SimpleConversionHandler
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface IgnoreVar {
}
20 changes: 20 additions & 0 deletions src/main/java/net/endrealm/realmdrive/annotations/ReadOnly.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package net.endrealm.realmdrive.annotations;

import net.endrealm.realmdrive.inst.SimpleConversionHandler;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Marks a field to only be loaded not saved.
* Do <b>not</b> use {@link WriteOnly} together with this.
* To enable both read and write use {@link SaveVar}.
*
* @see SimpleConversionHandler
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ReadOnly {
}
23 changes: 23 additions & 0 deletions src/main/java/net/endrealm/realmdrive/annotations/SaveAll.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package net.endrealm.realmdrive.annotations;

import net.endrealm.realmdrive.inst.SimpleConversionHandler;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Marks all fields as to be saved. This can be
* overwritten by {@link SaveVar} and {@link IgnoreVar}
*
* @see SimpleConversionHandler
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface SaveAll {
/**
* @return field names to be ignored
*/
String[] ignored() default {};
}
20 changes: 20 additions & 0 deletions src/main/java/net/endrealm/realmdrive/annotations/SaveTable.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package net.endrealm.realmdrive.annotations;

import net.endrealm.realmdrive.inst.SimpleConversionHandler;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Used to overwrite default table. Table specified in query will overwrite this behaviour.
*
* @see SimpleConversionHandler
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface SaveTable {

String tableName() default "";
}
23 changes: 21 additions & 2 deletions src/main/java/net/endrealm/realmdrive/annotations/SaveVar.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,31 @@
import java.lang.annotation.Target;

/**
* @author johannesjumpertz
* Used to mark fields to be saved into the drive system. If other objects are linked, they have to be registered as well.
*
* Used to mark fields to be saved into the drive system. If
* other objects are linked, they have to be registered as
* well.
*
* Fields with this annotation will be both read and written.
* To control this behaviour add {@link ReadOnly} or {@link WriteOnly}.
*
* @see SimpleConversionHandler
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface SaveVar {
/**
* @return name to save/load under
*/
String name() default "";

/**
* @return optional names to load from
*/
String[] aliases() default {};

/**
* @return if false exception will be thrown upon null value
*/
boolean optional() default true;
}
20 changes: 20 additions & 0 deletions src/main/java/net/endrealm/realmdrive/annotations/WriteOnly.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package net.endrealm.realmdrive.annotations;

import net.endrealm.realmdrive.inst.SimpleConversionHandler;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Marks a field to only be saved not loaded.
* Do <b>not</b> use {@link ReadOnly} together with this.
* To enable both read and write use {@link SaveVar}.
*
* @see SimpleConversionHandler
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface WriteOnly {
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
import net.endrealm.realmdrive.factory.DriveObjectFactory;
import net.endrealm.realmdrive.interfaces.*;
import net.endrealm.realmdrive.utils.ReflectionUtils;
import net.endrealm.realmdrive.utils.properties.ClassProperties;
import net.endrealm.realmdrive.utils.properties.FieldProperties;
import net.endrealm.realmdrive.utils.properties.PropertyReader;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
Expand Down Expand Up @@ -112,23 +115,48 @@ public <T> T transform(DriveObject statisticsObject, Class<T> clazz) throws Clas
throw new ClassCastException(String.format("Failed to cast! %s is not registered!", clazz.getName()));
}

ClassProperties classProperties = PropertyReader.readProperties(clazz);

try {
Constructor<T> constructor = clazz.getConstructor();
T instance = constructor.newInstance();

for(Field field : ReflectionUtils.getAllAnnotatedFields(clazz, SaveVar.class)) {
for(Field field : ReflectionUtils.getAllFields(clazz)) {
FieldProperties fieldProperties = PropertyReader.readProperties(field, classProperties);

if(!fieldProperties.isRead())
continue;

boolean protection = field.isAccessible();
field.setAccessible(true);

DriveElement value = statisticsObject.get(field.getName());
DriveElement value = statisticsObject.get(fieldProperties.getName());

// Read from aliases
{
int i = 0;
while(value == null) {
if(i >= fieldProperties.getAliases().size())
break;

value = statisticsObject.get(fieldProperties.getAliases().get(i));
i++;
}
}

// Value not found in object
if(value == null) {

// if this field is only optional skip it
if(fieldProperties.isOptional())
continue;

if(value == null)
throw new ClassCastException(String.format("Found unmapped field %s in %s!", field.getName(), field.getDeclaringClass().getName()));
}

{
Object object = getConvertedEndpoint(value, field.getType());
if(object != null) {
//noinspection unchecked
field.set(instance, object);
continue;
}
Expand Down Expand Up @@ -235,10 +263,9 @@ public DriveObject transform(Object object) throws ClassCastException {
if(!classes.contains(clazz))
throw new ClassCastException(String.format("Failed to cast! %s is not registered!", clazz.getName()));

List<Field> fieldList = ReflectionUtils.getAllAnnotatedFields(clazz, SaveVar.class);
List<Field> fieldList = ReflectionUtils.getAllFields(clazz);

if(fieldList.isEmpty())
throw new ClassCastException(String.format("Class %s must contain at least one field annotated with SaveVar", clazz.getName()));
ClassProperties classProperties = PropertyReader.readProperties(clazz);

DriveObject statisticsObject = objectFactory.createEmptyObject();

Expand All @@ -247,17 +274,31 @@ public DriveObject transform(Object object) throws ClassCastException {
statisticsObject.setPrimitive("className", object.getClass().getName());

for(Field field : fieldList) {
FieldProperties fieldProperties = PropertyReader.readProperties(field, classProperties);

if(!fieldProperties.isWrite())
continue;

boolean protection = field.isAccessible();
field.setAccessible(true);

Object value = field.get(object);

if(value == null) {

// if this field is only optional skip it
if(fieldProperties.isOptional())
continue;

throw new ClassCastException(String.format("Found empty non optional field %s in %s!", field.getName(), field.getDeclaringClass().getName()));
}

DriveElement driveElement = getElementEndpoint(value, value.getClass());
if(driveElement != null) {
statisticsObject.setObject(field.getName(), driveElement);
statisticsObject.setObject(fieldProperties.getName(), driveElement);
}
else if(PRIMITIVE_CLASSES.contains(value.getClass()))
statisticsObject.setObject(field.getName(), objectFactory.createPrimitive(value));
statisticsObject.setObject(fieldProperties.getName(), objectFactory.createPrimitive(value));
else if(List.class.isAssignableFrom(field.getType())) {
DriveElementArray array = objectFactory.createEmptyArray();
for(Object obj : (List)value) {
Expand All @@ -266,10 +307,10 @@ else if(List.class.isAssignableFrom(field.getType())) {
else
array.addObject(transform(obj));
}
statisticsObject.setObject(field.getName(), array);
statisticsObject.setObject(fieldProperties.getName(), array);
}
else {
statisticsObject.setObject(field.getName(), transform(value));
statisticsObject.setObject(fieldProperties.getName(), transform(value));
}

field.setAccessible(protection);
Expand Down
Loading

0 comments on commit 31169ce

Please sign in to comment.