-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Teeny json suport #3
Merged
Merged
Changes from 11 commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
4fc821c
TeenyJson
alex-cova b6f7b02
move ReflectionUtils to implementation package
alex-cova 3185f71
added support for custom serializers & optimizations
alex-cova 6b8c3cb
Refactored TeenyJson, improved json writing and reading
alex-cova f5fca65
Merge branch 'master' into teenyJson
alex-cova 1acdd82
A lot of bug fixes, including TeenyJson can now parse jsons generated…
alex-cova e48dadd
Merge remote-tracking branch 'origin/teenyJson' into teenyJson
alex-cova 35a59ce
BugFix: TeenyJson now can parse formatted jsons, TeenyApplicationTest…
alex-cova 6fdf944
GHA infinite-loop workaround, added some JavaDoc, applied suggestions…
alex-cova 5110b35
Implemented @JsonDeserialize annotation, added JavaDoc, and bugfixes
alex-cova 4729a7e
BugFixes
alex-cova b2f5744
A lot of bug fixes, TeenyJsonMessageConverter, improved json serializ…
alex-cova f6ff848
updated readme.md and added benchmarks
alex-cova 62a8fd3
more bugfixes, removed spaces between key and values
alex-cova 1239fd7
implementation of @JsonRaw & now @JsonIncludeNonNull supports accessors
alex-cova de93b6c
BugFix: ConcurrentModificationException under heavy load
alex-cova e4de074
Update readme.md
alex-cova d8c3751
Update readme.md
alex-cova File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,9 @@ on: | |
pull_request: | ||
types: [ opened, synchronize, reopened ] | ||
|
||
env: | ||
AZURE_BLOB_STORAGE_CONNECTION_STRING: ${{ secrets.AZURE_BLOB_STORAGE_CONNECTION_STRING }} | ||
|
||
jobs: | ||
build: | ||
runs-on: ubuntu-latest | ||
|
@@ -25,6 +28,7 @@ jobs: | |
run: mvn --batch-mode -DskipTests package | ||
|
||
- name: Test | ||
timeout-minutes: 5 | ||
run: mvn --batch-mode -Dmaven.test.failure.ignore=true test | ||
|
||
- name: Report | ||
|
@@ -38,6 +42,7 @@ jobs: | |
|
||
- name: Upload to Azure Blob Storage | ||
uses: bacongobbler/[email protected] | ||
if: env.AZURE_BLOB_STORAGE_CONNECTION_STRING != null | ||
with: | ||
source_dir: 'target/apidocs' | ||
container_name: '$web' | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
88 changes: 88 additions & 0 deletions
88
src/main/java/net/jonathangiles/tools/teenyhttpd/implementation/ParameterizedTypeHelper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
package net.jonathangiles.tools.teenyhttpd.implementation; | ||
|
||
import java.lang.reflect.Type; | ||
import java.lang.reflect.WildcardType; | ||
import java.util.Arrays; | ||
import java.util.Objects; | ||
|
||
/** A helper class to hold the type arguments of a parameterized type and provide some utility methods. */ | ||
public final class ParameterizedTypeHelper implements Type { | ||
private final Class<?> firstType; | ||
private final Class<?> parentType;//nullable | ||
private final Type[] typeArguments; | ||
|
||
ParameterizedTypeHelper(Class<?> type) { | ||
this.firstType = type; | ||
this.parentType = null; | ||
this.typeArguments = null; | ||
} | ||
|
||
public ParameterizedTypeHelper(Class<?> firstType, Class<?> parentType) { | ||
this.firstType = firstType; | ||
this.parentType = parentType; | ||
this.typeArguments = null; | ||
} | ||
|
||
ParameterizedTypeHelper(Class<?> parentType, Type[] arguments) { | ||
this.firstType = getRealClass(arguments[0]); | ||
this.parentType = parentType; | ||
this.typeArguments = arguments; | ||
} | ||
|
||
private Class<?> getRealClass(Type type) { | ||
if (type instanceof WildcardType) { | ||
WildcardType wildcardType = (WildcardType) type; | ||
return (Class<?>) wildcardType.getUpperBounds()[0]; | ||
} | ||
|
||
return (Class<?>) type; | ||
} | ||
|
||
public ParameterizedTypeHelper withFirstType(Class<?> firstType) { | ||
if (typeArguments == null) { | ||
return new ParameterizedTypeHelper(firstType, parentType); | ||
} | ||
|
||
Type[] args = new Type[typeArguments.length]; | ||
|
||
args[0] = firstType; | ||
System.arraycopy(typeArguments, 1, args, 1, typeArguments.length - 1); | ||
|
||
return new ParameterizedTypeHelper(parentType, args); | ||
} | ||
|
||
@Override | ||
public String getTypeName() { | ||
return getClass().getSimpleName(); | ||
} | ||
|
||
public boolean isParentTypeOf(Class<?> type) { | ||
return parentType != null && parentType.isAssignableFrom(type); | ||
} | ||
|
||
public Type[] getTypeArguments() { | ||
return typeArguments; | ||
} | ||
|
||
public Class<?> getFirstType() { | ||
return firstType; | ||
} | ||
|
||
public Class<?> getSecondType() { | ||
Objects.requireNonNull(typeArguments, "Type is not a ParameterizedType"); | ||
return getRealClass(typeArguments[1]); | ||
} | ||
|
||
public Class<?> getParentType() { | ||
return parentType; | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return "ParameterizedTypeHelper{" + | ||
"firstType=" + firstType + | ||
", parentType=" + parentType + | ||
", typeArguments=" + Arrays.toString(typeArguments) + | ||
'}'; | ||
} | ||
} |
165 changes: 165 additions & 0 deletions
165
src/main/java/net/jonathangiles/tools/teenyhttpd/implementation/ReflectionUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
package net.jonathangiles.tools.teenyhttpd.implementation; | ||
|
||
import net.jonathangiles.tools.teenyhttpd.json.JsonAlias; | ||
|
||
import java.lang.reflect.*; | ||
import java.util.Arrays; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
import java.util.stream.Collectors; | ||
|
||
/** | ||
* A utility class to help with reflection. | ||
*/ | ||
public final class ReflectionUtils { | ||
|
||
/** | ||
* Create a new instance of the given class. | ||
* <p> | ||
* @param clazz the class to create a new instance of | ||
* @return a new instance of the given class | ||
* @param <T> the type of the class | ||
* @throws IllegalArgumentException if the class does not have a default constructor or if the constructor fails | ||
*/ | ||
@SuppressWarnings("unchecked") | ||
public static <T> T newInstance(Class<T> clazz) { | ||
Constructor<?> constructor = Arrays.stream(clazz.getDeclaredConstructors()) | ||
.filter(cons -> cons.getParameterCount() == 0) | ||
.findFirst() | ||
.orElseThrow(() -> new IllegalArgumentException("No default constructor found for " + clazz.getName())); | ||
|
||
try { | ||
constructor.setAccessible(true); | ||
return (T) constructor.newInstance(); | ||
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { | ||
throw new IllegalArgumentException("Failed to create new instance of " + clazz.getName(), e); | ||
} | ||
} | ||
|
||
public static ParameterizedTypeHelper getParameterType(Parameter parameter) { | ||
return getParameterType(parameter.getParameterizedType()); | ||
} | ||
|
||
public static ParameterizedTypeHelper getParameterType(Field field) { | ||
return getParameterType(field.getGenericType()); | ||
} | ||
|
||
/** | ||
* Creates an instance of ParameterizedTypeHelper from the given type. | ||
* <p> | ||
* @param type the type to create the instance from | ||
* @return the instance of ParameterizedTypeHelper or null if the type is not a ParameterizedType | ||
* @throws IllegalStateException if the type is not a Class or a ParameterizedType | ||
*/ | ||
public static ParameterizedTypeHelper getParameterType(Type type) { | ||
if (!(type instanceof ParameterizedType)) { | ||
return null; | ||
} | ||
|
||
ParameterizedType parameterizedType = (ParameterizedType) type; | ||
|
||
return new ParameterizedTypeHelper((Class<?>) parameterizedType.getRawType(), | ||
parameterizedType.getActualTypeArguments()); | ||
} | ||
|
||
/** | ||
* Returns a map of the writable fields of the given class, | ||
* where the key is the name of the field or his alias and the value is the field itself. | ||
* <p> | ||
* @param clazz the class to get the fields from | ||
* @return a map of the fields of the given class | ||
*/ | ||
public static Map<String, Field> getFields(Class<?> clazz) { | ||
Map<String, Method> methodMap = Arrays.stream(clazz.getDeclaredMethods()) | ||
.collect(Collectors.toMap(Method::getName, m -> m)); | ||
|
||
return Arrays.stream(clazz.getDeclaredFields()) | ||
.filter(ReflectionUtils::isWritable) | ||
.collect(Collectors.toMap(field -> getFieldName(field, methodMap), f -> f)); | ||
} | ||
|
||
/** | ||
* Here we seek for the alias key in the accessor method, if it exists. | ||
*/ | ||
private static String getFieldName(Field field, Map<String, Method> methodMap) { | ||
//base name of the field | ||
String baseName = field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1); | ||
String alias = findAlias(methodMap, baseName, field.getType()); | ||
|
||
JonathanGiles marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return Objects.requireNonNullElse(alias, field.getName()); | ||
} | ||
|
||
/** | ||
* Here we seek for the alias key in the accessor method, if it exists. | ||
*/ | ||
private static String getMutatorName(Method method, Map<String, Method> methodMap) { | ||
//base name of the method | ||
String baseName = method.getName().substring(3); | ||
//seek for the alias key in the accessor method, if it exists | ||
String alias = findAlias(methodMap, baseName, method.getParameterTypes()[0]); | ||
if (alias != null) { | ||
return alias; | ||
} | ||
|
||
return method.getName().substring(3, 4).toLowerCase() | ||
+ method.getName().substring(4); | ||
} | ||
|
||
private static String findAlias(Map<String, Method> methodMap, String baseName, Class<?> type) { | ||
Method accessorMethod; | ||
|
||
if (type == boolean.class) { | ||
accessorMethod = methodMap.get("is" + baseName); | ||
} else { | ||
accessorMethod = methodMap.get("get" + baseName); | ||
} | ||
|
||
if (accessorMethod != null) { | ||
JsonAlias jsonAlias = accessorMethod.getAnnotation(JsonAlias.class); | ||
|
||
if (jsonAlias != null) { | ||
return jsonAlias.value(); | ||
} | ||
} | ||
|
||
return null; | ||
} | ||
|
||
/** | ||
* Returns a map of the mutator methods of the given class, | ||
* where the key is the name of the mutator or his alias and the value is the method itself. | ||
* <p> | ||
* @param clazz the class to get the mutators from | ||
* @return a map of the mutators of the given class | ||
*/ | ||
public static Map<String, Method> getMutators(Class<?> clazz) { | ||
Map<String, Method> methodMap = Arrays.stream(clazz.getDeclaredMethods()) | ||
.collect(Collectors.toMap(Method::getName, m -> m)); | ||
|
||
Map<String, Method> resultMap = new HashMap<>(); | ||
|
||
for (Method method : methodMap.values()) { | ||
if (!isMutator(method)) continue; | ||
|
||
String mutatorName = getMutatorName(method, methodMap); | ||
|
||
resultMap.put(mutatorName, method); | ||
} | ||
|
||
return resultMap; | ||
} | ||
|
||
|
||
private static boolean isMutator(Method method) { | ||
return method.getName().startsWith("set") && method.getParameterCount() == 1; | ||
} | ||
|
||
private static boolean isWritable(Field field) { | ||
if (Modifier.isStatic(field.getModifiers())) { | ||
return false; | ||
} | ||
return !Modifier.isFinal(field.getModifiers()); | ||
} | ||
|
||
} |
15 changes: 15 additions & 0 deletions
15
src/main/java/net/jonathangiles/tools/teenyhttpd/json/JsonAlias.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package net.jonathangiles.tools.teenyhttpd.json; | ||
|
||
import java.lang.annotation.ElementType; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
|
||
/** | ||
* An annotation to specify an alias for a field when serializing or deserializing JSON. | ||
*/ | ||
@Retention(RetentionPolicy.RUNTIME) | ||
@Target(ElementType.METHOD) | ||
public @interface JsonAlias { | ||
JonathanGiles marked this conversation as resolved.
Show resolved
Hide resolved
|
||
String value(); | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice addition!