From 47bbc110186ab0795f471b36e4905b670332bad3 Mon Sep 17 00:00:00 2001 From: regbo Date: Thu, 27 Aug 2020 12:22:01 -0400 Subject: [PATCH 01/19] better caching --- .../beanref/BeanPropertyResolver.java | 54 ++++++++----------- 1 file changed, 22 insertions(+), 32 deletions(-) diff --git a/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java b/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java index 4f68f7d..3c82c09 100644 --- a/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java +++ b/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java @@ -9,43 +9,33 @@ import java.util.function.Supplier; final class BeanPropertyResolver { - @SuppressWarnings("rawtypes") - private static final ConcurrentHashMap resolvedPropertiesCache = - new ConcurrentHashMap<>(); - @SuppressWarnings("rawtypes") - private static final ConcurrentHashMap resolvedCollectionPropertiesCache = - new ConcurrentHashMap<>(); + @SuppressWarnings("rawtypes") + private static final ConcurrentHashMap> resolvedPropertiesCache = new ConcurrentHashMap<>(); + @SuppressWarnings("rawtypes") + private static final ConcurrentHashMap> resolvedCollectionPropertiesCache = new ConcurrentHashMap<>(); private BeanPropertyResolver() {} - @SuppressWarnings({"unchecked", "rawtypes"}) - static BeanProperty resolveBeanProperty(MethodReferenceLambda methodReferenceLambda) { - BeanProperty beanProperty = resolvedPropertiesCache.get(methodReferenceLambda); - if (beanProperty == null) { - beanProperty = resolveBeanPropertyImpl(methodReferenceLambda); - beanProperty = Optional.ofNullable( - resolvedPropertiesCache.putIfAbsent(methodReferenceLambda, beanProperty) - ).orElse(beanProperty); - } - return beanProperty; - } - - @SuppressWarnings({"unchecked", "rawtypes"}) - static BeanProperty resolveCollectionBeanProperty( - MethodReferenceLambda> methodReferenceLambda, - /*Nullable*/ Supplier> collectionInstantiator) - { - BeanProperty beanProperty = resolvedCollectionPropertiesCache.get(methodReferenceLambda); - if (beanProperty == null) { - beanProperty = resolveCollectionBeanPropertyImpl(methodReferenceLambda, collectionInstantiator); - beanProperty = Optional.ofNullable( - resolvedCollectionPropertiesCache.putIfAbsent(methodReferenceLambda, beanProperty) - ).orElse(beanProperty); - } - return beanProperty; - } + @SuppressWarnings({ "unchecked", "rawtypes" }) + static BeanProperty resolveBeanProperty( + MethodReferenceLambda methodReferenceLambda) { + return resolvedPropertiesCache.computeIfAbsent(methodReferenceLambda, nil -> { + BeanProperty beanProperty = resolveBeanPropertyImpl(methodReferenceLambda); + return Optional.ofNullable(beanProperty); + }).orElse(null); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + static BeanProperty resolveCollectionBeanProperty( + MethodReferenceLambda> methodReferenceLambda, + /* Nullable */ Supplier> collectionInstantiator) { + return resolvedCollectionPropertiesCache.computeIfAbsent(methodReferenceLambda, nil -> { + BeanProperty beanProperty = resolveCollectionBeanPropertyImpl(methodReferenceLambda, collectionInstantiator); + return Optional.ofNullable(beanProperty); + }).orElse(null); + } private static BeanProperty resolveBeanPropertyImpl(MethodReferenceLambda methodReferenceLambda) { From 50b0a2044340261a2fdfb8e1a0a8c4387915a068 Mon Sep 17 00:00:00 2001 From: regbo Date: Thu, 27 Aug 2020 12:24:58 -0400 Subject: [PATCH 02/19] better caching --- .../beanref/DynamicBeanPropertyResolver.java | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/github/throwable/beanref/DynamicBeanPropertyResolver.java b/src/main/java/com/github/throwable/beanref/DynamicBeanPropertyResolver.java index 5ef1e0b..3127980 100644 --- a/src/main/java/com/github/throwable/beanref/DynamicBeanPropertyResolver.java +++ b/src/main/java/com/github/throwable/beanref/DynamicBeanPropertyResolver.java @@ -13,8 +13,8 @@ public class DynamicBeanPropertyResolver { - @SuppressWarnings("rawtypes") - private static final ConcurrentHashMap> resolvedBeanPropertiesCache = new ConcurrentHashMap<>(); + @SuppressWarnings("rawtypes") + private static final ConcurrentHashMap>> resolvedBeanPropertiesCache = new ConcurrentHashMap<>(); @SuppressWarnings("unchecked") static BeanProperty resolveBeanProperty(Class beanClass, String propertyName, Class type) { @@ -32,17 +32,13 @@ static BeanProperty resolveBeanProperty(Class beanClass } - @SuppressWarnings({"unchecked", "rawtypes"}) - static Map> resolveAllBeanProperties(Class beanClass) { - Map beanProperties = resolvedBeanPropertiesCache.get(beanClass); - if (beanProperties == null) { - beanProperties = resolveAllBeanPropertiesImpl(beanClass); - beanProperties = Optional.ofNullable( - resolvedBeanPropertiesCache.putIfAbsent(beanClass, beanProperties) - ).orElse(beanProperties); - } - return beanProperties; - } + @SuppressWarnings({ "unchecked", "rawtypes" }) + static Map> resolveAllBeanProperties(Class beanClass) { + return resolvedBeanPropertiesCache.computeIfAbsent(beanClass, nil -> { + Map beanProperties = resolveAllBeanPropertiesImpl(beanClass); + return Optional.ofNullable(beanProperties); + }).orElse(null); + } private static Map> resolveAllBeanPropertiesImpl(Class beanClass) From a0e2d2d981357316e5228ae6e661adab798782fa Mon Sep 17 00:00:00 2001 From: regbo Date: Thu, 27 Aug 2020 12:26:59 -0400 Subject: [PATCH 03/19] cast --- .../throwable/beanref/DynamicBeanPropertyResolver.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/github/throwable/beanref/DynamicBeanPropertyResolver.java b/src/main/java/com/github/throwable/beanref/DynamicBeanPropertyResolver.java index 3127980..088762e 100644 --- a/src/main/java/com/github/throwable/beanref/DynamicBeanPropertyResolver.java +++ b/src/main/java/com/github/throwable/beanref/DynamicBeanPropertyResolver.java @@ -34,9 +34,8 @@ static BeanProperty resolveBeanProperty(Class beanClass @SuppressWarnings({ "unchecked", "rawtypes" }) static Map> resolveAllBeanProperties(Class beanClass) { - return resolvedBeanPropertiesCache.computeIfAbsent(beanClass, nil -> { - Map beanProperties = resolveAllBeanPropertiesImpl(beanClass); - return Optional.ofNullable(beanProperties); + return (Map) resolvedBeanPropertiesCache.computeIfAbsent(beanClass, nil -> { + return Optional.ofNullable(resolveAllBeanPropertiesImpl(beanClass)); }).orElse(null); } From 971b38be607524de42814a28638c42ffe7702d83 Mon Sep 17 00:00:00 2001 From: regbo Date: Thu, 27 Aug 2020 12:28:42 -0400 Subject: [PATCH 04/19] more github edits --- .../throwable/beanref/DynamicBeanPropertyResolver.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/github/throwable/beanref/DynamicBeanPropertyResolver.java b/src/main/java/com/github/throwable/beanref/DynamicBeanPropertyResolver.java index 088762e..2cc9cb9 100644 --- a/src/main/java/com/github/throwable/beanref/DynamicBeanPropertyResolver.java +++ b/src/main/java/com/github/throwable/beanref/DynamicBeanPropertyResolver.java @@ -34,8 +34,9 @@ static BeanProperty resolveBeanProperty(Class beanClass @SuppressWarnings({ "unchecked", "rawtypes" }) static Map> resolveAllBeanProperties(Class beanClass) { - return (Map) resolvedBeanPropertiesCache.computeIfAbsent(beanClass, nil -> { - return Optional.ofNullable(resolveAllBeanPropertiesImpl(beanClass)); + return (Map>) resolvedBeanPropertiesCache.computeIfAbsent(beanClass, nil -> { + Map beanProperties = resolveAllBeanPropertiesImpl(beanClass); + return Optional.ofNullable(beanProperties); }).orElse(null); } From 913eb8eed63f81bdc600761cd326996a2a62c4e7 Mon Sep 17 00:00:00 2001 From: regbo Date: Thu, 27 Aug 2020 12:38:12 -0400 Subject: [PATCH 05/19] Allow subclassing --- .../beanref/BeanPropertyResolver.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java b/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java index 3c82c09..fa60d78 100644 --- a/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java +++ b/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java @@ -8,7 +8,7 @@ import java.util.function.Function; import java.util.function.Supplier; -final class BeanPropertyResolver { +public class BeanPropertyResolver { @SuppressWarnings("rawtypes") private static final ConcurrentHashMap> resolvedPropertiesCache = new ConcurrentHashMap<>(); @SuppressWarnings("rawtypes") @@ -19,7 +19,7 @@ private BeanPropertyResolver() {} @SuppressWarnings({ "unchecked", "rawtypes" }) - static BeanProperty resolveBeanProperty( + protected static BeanProperty resolveBeanProperty( MethodReferenceLambda methodReferenceLambda) { return resolvedPropertiesCache.computeIfAbsent(methodReferenceLambda, nil -> { BeanProperty beanProperty = resolveBeanPropertyImpl(methodReferenceLambda); @@ -28,7 +28,7 @@ static BeanProperty resolveBeanProperty( } @SuppressWarnings({ "unchecked", "rawtypes" }) - static BeanProperty resolveCollectionBeanProperty( + protected static BeanProperty resolveCollectionBeanProperty( MethodReferenceLambda> methodReferenceLambda, /* Nullable */ Supplier> collectionInstantiator) { return resolvedCollectionPropertiesCache.computeIfAbsent(methodReferenceLambda, nil -> { @@ -37,7 +37,7 @@ static BeanProperty resolveCollectionBeanProperty( }).orElse(null); } - private static BeanProperty resolveBeanPropertyImpl(MethodReferenceLambda methodReferenceLambda) + protected static BeanProperty resolveBeanPropertyImpl(MethodReferenceLambda methodReferenceLambda) { final SerializedLambda serialized = serialized(methodReferenceLambda); if (serialized.getImplMethodName().startsWith("lambda$")) @@ -55,7 +55,7 @@ private static BeanProperty resolveBeanPropertyImpl(Met } - private static BeanProperty resolveCollectionBeanPropertyImpl( + protected static BeanProperty resolveCollectionBeanPropertyImpl( MethodReferenceLambda> methodReferenceLambda, /*Nullable*/ Supplier> collectionInstantiator) { @@ -92,7 +92,7 @@ private static BeanProperty resolveCollectionBeanProper } - private static Method findGetterMethod(Class beanClass, String getterMethodName) { + protected static Method findGetterMethod(Class beanClass, String getterMethodName) { final Method getterMethod; try { getterMethod = beanClass.getMethod(getterMethodName); @@ -109,7 +109,7 @@ private static Method findGetterMethod(Class beanClass, String gett } - static String resolvePropertyName(String getterMethodName) + protected static String resolvePropertyName(String getterMethodName) { final String propertyName; @@ -131,7 +131,7 @@ else if (getterMethodName.startsWith("is") && getterMethodName.length() > 2 && } - static /* Nullable */ Method findSetterMethod(Class beanClass, String propertyName, Class type, String getterMethodName) { + protected static /* Nullable */ Method findSetterMethod(Class beanClass, String propertyName, Class type, String getterMethodName) { if (!getterMethodName.equals(propertyName)) { // canonical getXXX(): search for setXXX() final String setterMethodName = "set" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1); @@ -153,7 +153,7 @@ else if (getterMethodName.startsWith("is") && getterMethodName.length() > 2 && } - private static SerializedLambda serialized(Object lambda) { + protected static SerializedLambda serialized(Object lambda) { try { Method writeMethod = lambda.getClass().getDeclaredMethod("writeReplace"); writeMethod.setAccessible(true); @@ -165,7 +165,7 @@ private static SerializedLambda serialized(Object lambda) { @SuppressWarnings("unchecked") - private static Class getContainingClass(SerializedLambda lambda) { + protected static Class getContainingClass(SerializedLambda lambda) { try { String className = lambda.getImplClass().replaceAll("/", "."); //System.out.println(lambda.getInstantiatedMethodType()); @@ -244,7 +244,7 @@ private static Supplier resolveInstantiator(Class type) { } - private static Supplier>> defaultCollectionInstantiatorResolver(Class> collectionType) { + protected static Supplier>> defaultCollectionInstantiatorResolver(Class> collectionType) { if ((collectionType.getModifiers() & Modifier.ABSTRACT) != 0) { if (collectionType.isAssignableFrom(List.class)) return () -> ArrayList::new; From 4f2bbed59e985d5f945639782d3384dcb38d73eb Mon Sep 17 00:00:00 2001 From: regbo Date: Thu, 27 Aug 2020 12:40:04 -0400 Subject: [PATCH 06/19] constructor --- .../java/com/github/throwable/beanref/BeanPropertyResolver.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java b/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java index fa60d78..0635a8e 100644 --- a/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java +++ b/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java @@ -15,7 +15,7 @@ public class BeanPropertyResolver { private static final ConcurrentHashMap> resolvedCollectionPropertiesCache = new ConcurrentHashMap<>(); - private BeanPropertyResolver() {} + protected BeanPropertyResolver() {} @SuppressWarnings({ "unchecked", "rawtypes" }) From 3c0ea2f2afc95f5fe325ee634e981ab26f6597e0 Mon Sep 17 00:00:00 2001 From: reggie pierce Date: Thu, 16 Jun 2022 01:08:18 -0400 Subject: [PATCH 07/19] improved caching --- .gitignore | 32 +- pom.xml | 202 +++--- .../beanref/BeanPropertyResolver.java | 615 +++++++++--------- .../beanref/DynamicBeanPropertyResolver.java | 162 ++--- .../throwable/beanref/lfp/BeanRefUtils.java | 103 +++ .../throwable/beanref/BeanRefTestLFP.java | 30 + 6 files changed, 664 insertions(+), 480 deletions(-) create mode 100644 src/main/java/com/github/throwable/beanref/lfp/BeanRefUtils.java create mode 100644 src/test/java/com/github/throwable/beanref/BeanRefTestLFP.java diff --git a/.gitignore b/.gitignore index 744289d..23e7141 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,32 @@ # Project exclude paths -/target/ \ No newline at end of file +/target/ + +#eclipse + +*target* +*.jar +*.war +*.ear +*.class + +# eclipse specific git ignore +*.pydevproject +.project +.metadata +bin/** +tmp/** +tmp/**/* +*.tmp +*.bak +*.swp +*~.nib +local.properties +.classpath +.settings/ +.loadpath + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch \ No newline at end of file diff --git a/pom.xml b/pom.xml index 3c3615f..c4854d7 100644 --- a/pom.xml +++ b/pom.xml @@ -1,110 +1,116 @@ - 4.0.0 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 - com.github.throwable.beanref - beanref - 0.2 - beanref - A simple library that allows to access your POJOs' properties statically - https://github.com/throwable/beanref - jar + com.github.throwable.beanref + beanref + 0.2 + beanref + A simple library that allows to access your POJOs' properties statically + https://github.com/throwable/beanref + jar - - scm:git:git://github.com/throwable/beanref.git - scm:git:git://github.com/throwable/beanref.git - https://github.com/throwable/beanref - HEAD - + + scm:git:git://github.com/throwable/beanref.git + scm:git:git://github.com/throwable/beanref.git + https://github.com/throwable/beanref + HEAD + - - - owner - Anton Kuranov - ant.kuranov@gmail.com - UTC+2 - - + + + owner + Anton Kuranov + ant.kuranov@gmail.com + UTC+2 + + - - - MIT - https://opensource.org/licenses/MIT - repo - - + + + MIT + https://opensource.org/licenses/MIT + repo + + - - 1.8 - 1.8 - UTF-8 - + + 1.8 + 1.8 + UTF-8 + 2.9.3 + - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - + + - - - junit - junit - 4.13.2 - test - - + + + com.github.ben-manes.caffeine + caffeine + ${com.github.ben-manes.caffeine.caffeine.version} + + + junit + junit + 4.13.2 + test + + - - - - org.apache.maven.plugins - maven-source-plugin - 3.1.0 - - - attach-sources - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.1.1 - - - attach-javadocs - - jar - - - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.6 - - - sign-artifacts - verify - - sign - - - - - - + + + + org.apache.maven.plugins + maven-source-plugin + 3.1.0 + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.1.1 + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + sign-artifacts + verify + + sign + + + + + + diff --git a/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java b/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java index 0635a8e..9228e79 100644 --- a/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java +++ b/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java @@ -1,326 +1,335 @@ package com.github.throwable.beanref; +import java.io.Serializable; import java.lang.invoke.SerializedLambda; -import java.lang.reflect.*; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.Supplier; -public class BeanPropertyResolver { - @SuppressWarnings("rawtypes") - private static final ConcurrentHashMap> resolvedPropertiesCache = new ConcurrentHashMap<>(); - @SuppressWarnings("rawtypes") - private static final ConcurrentHashMap> resolvedCollectionPropertiesCache = new ConcurrentHashMap<>(); +import com.github.benmanes.caffeine.cache.Cache; +import com.github.throwable.beanref.lfp.BeanRefUtils; +public class BeanPropertyResolver { - protected BeanPropertyResolver() {} + private static final String RESOLVED_BEAN_PROPERTIES_KEY_PREFIX = "resolved-bp-"; + private static final String RESOLVED_COLLECTION_BEAN_PROPERTIES_KEY_PREFIX = "resolved-collection-bp-"; + private static final Cache> RESOLVED_BEAN_PROPERTIES_CACHE = BeanRefUtils.cacheBuilder() + .build(); + protected BeanPropertyResolver() {} @SuppressWarnings({ "unchecked", "rawtypes" }) protected static BeanProperty resolveBeanProperty( MethodReferenceLambda methodReferenceLambda) { - return resolvedPropertiesCache.computeIfAbsent(methodReferenceLambda, nil -> { - BeanProperty beanProperty = resolveBeanPropertyImpl(methodReferenceLambda); - return Optional.ofNullable(beanProperty); - }).orElse(null); + Objects.requireNonNull(methodReferenceLambda); + String hash = BeanRefUtils.hash(methodReferenceLambda); + String key = RESOLVED_BEAN_PROPERTIES_KEY_PREFIX + hash; + return (BeanProperty) RESOLVED_BEAN_PROPERTIES_CACHE.get(key, nil -> { + return resolveBeanPropertyImpl(methodReferenceLambda); + }); } @SuppressWarnings({ "unchecked", "rawtypes" }) protected static BeanProperty resolveCollectionBeanProperty( MethodReferenceLambda> methodReferenceLambda, /* Nullable */ Supplier> collectionInstantiator) { - return resolvedCollectionPropertiesCache.computeIfAbsent(methodReferenceLambda, nil -> { - BeanProperty beanProperty = resolveCollectionBeanPropertyImpl(methodReferenceLambda, collectionInstantiator); - return Optional.ofNullable(beanProperty); - }).orElse(null); + Objects.requireNonNull(methodReferenceLambda); + String hash = BeanRefUtils.hash(methodReferenceLambda, Optional.ofNullable(collectionInstantiator) + .filter(Serializable.class::isInstance).map(Serializable.class::cast).orElse(null)); + String key = RESOLVED_COLLECTION_BEAN_PROPERTIES_KEY_PREFIX + hash; + return (BeanProperty) RESOLVED_BEAN_PROPERTIES_CACHE.get(key, nil -> { + return resolveCollectionBeanPropertyImpl(methodReferenceLambda, collectionInstantiator); + }); + } + + protected static BeanProperty resolveBeanPropertyImpl( + MethodReferenceLambda methodReferenceLambda) { + SerializedLambda serialized = serialized(methodReferenceLambda); + final Class beanClass = getContainingClass(serialized); + final Method getterMethod = findGetterMethod(beanClass, serialized.getImplMethodName()); + @SuppressWarnings("unchecked") + final Class type = (Class) getterMethod.getReturnType(); + final String propertyName = resolvePropertyName(getterMethod.getName()); + final Method setterMethod = findSetterMethod(beanClass, propertyName, type, getterMethod.getName()); + final BiConsumer writeAccessor = setterMethod != null ? new SetterWriteAccessor<>(setterMethod) + : null; + return new BeanProperty<>(beanClass, type, propertyName, methodReferenceLambda, writeAccessor, + new InstantiatorResolver<>(type)); + } + + protected static BeanProperty resolveCollectionBeanPropertyImpl( + MethodReferenceLambda> methodReferenceLambda, + /* Nullable */ Supplier> collectionInstantiator) { + SerializedLambda serialized = serialized(methodReferenceLambda); + final Class beanClass = getContainingClass(serialized); + final Method getterMethod = findGetterMethod(beanClass, serialized.getImplMethodName()); + @SuppressWarnings("unchecked") + final Class> type = (Class>) getterMethod.getReturnType(); + final ParameterizedType genericReturnType = (ParameterizedType) getterMethod.getGenericReturnType(); + if (genericReturnType.getActualTypeArguments().length != 1) + throw new IllegalArgumentException( + "Can not determine parameter type for " + beanClass.getName() + "." + getterMethod.getName()); + @SuppressWarnings("unchecked") + final Class elementType = (Class) genericReturnType.getActualTypeArguments()[0]; + + final String propertyName = resolvePropertyName(getterMethod.getName()); + final Method setterMethod = findSetterMethod(beanClass, propertyName, type, getterMethod.getName()); + final BiConsumer> writeAccessor = setterMethod != null + ? new SetterWriteAccessor<>(setterMethod) + : null; + final CollectionElementReadAccessor beantypeCollectionElementReadAccessor = new CollectionElementReadAccessor<>( + methodReferenceLambda); + final Supplier>> collectionInstantiatorResolver; + if (collectionInstantiator == null && writeAccessor != null) + collectionInstantiatorResolver = defaultCollectionInstantiatorResolver(type); + else + collectionInstantiatorResolver = () -> collectionInstantiator; + final CollectionElementWriteAccessor beantypeCollectionElementWriteAccessor = new CollectionElementWriteAccessor<>( + methodReferenceLambda, writeAccessor, collectionInstantiatorResolver); + return new BeanProperty<>(beanClass, elementType, propertyName, beantypeCollectionElementReadAccessor, + beantypeCollectionElementWriteAccessor, new InstantiatorResolver<>(elementType)); + } + + protected static Method findGetterMethod(Class beanClass, String getterMethodName) { + final Method getterMethod; + try { + getterMethod = beanClass.getMethod(getterMethodName); + } catch (NoSuchMethodException e) { + throw new IllegalBeanPathException(e); + } + + if (getterMethod.getParameterCount() > 0) + throw new IllegalBeanPathException("Illegal getter method: " + getterMethodName); + if (Void.TYPE.equals(getterMethod.getReturnType())) + throw new IllegalBeanPathException("Illegal getter method return type: " + getterMethodName + "(" + + getterMethod.getReturnType() + ")"); + return getterMethod; + } + + protected static String resolvePropertyName(String getterMethodName) { + final String propertyName; + + if (getterMethodName.startsWith("get") && getterMethodName.length() > 3 + && Character.isUpperCase(getterMethodName.charAt(3))) { + propertyName = Character.toLowerCase(getterMethodName.charAt(3)) + getterMethodName.substring(4); + } else if (getterMethodName.startsWith("is") && getterMethodName.length() > 2 + && Character.isUpperCase(getterMethodName.charAt(2))) { + propertyName = Character.toLowerCase(getterMethodName.charAt(2)) + getterMethodName.substring(3); + } else { + // non-canonical name: use method name as-is + propertyName = getterMethodName; + } + return propertyName; + } + + protected static /* Nullable */ Method findSetterMethod(Class beanClass, String propertyName, + Class type, String getterMethodName) { + if (!getterMethodName.equals(propertyName)) { + // canonical getXXX(): search for setXXX() + final String setterMethodName = "set" + Character.toUpperCase(propertyName.charAt(0)) + + propertyName.substring(1); + try { + return beanClass.getMethod(setterMethodName, type); + } catch (NoSuchMethodException e) { + // Read-only + return null; + } + } else { + // non-canonical xxx(): search for xxx(value) + try { + return beanClass.getMethod(propertyName, type); + } catch (NoSuchMethodException e) { + // Read-only + return null; + } + } } - protected static BeanProperty resolveBeanPropertyImpl(MethodReferenceLambda methodReferenceLambda) - { - final SerializedLambda serialized = serialized(methodReferenceLambda); - if (serialized.getImplMethodName().startsWith("lambda$")) - throw new IllegalArgumentException("Not a method reference"); - final Class beanClass = getContainingClass(serialized); - final Method getterMethod = findGetterMethod(beanClass, serialized.getImplMethodName()); - @SuppressWarnings("unchecked") - final Class type = (Class) getterMethod.getReturnType(); - final String propertyName = resolvePropertyName(getterMethod.getName()); - final Method setterMethod = findSetterMethod(beanClass, propertyName, type, getterMethod.getName()); - final BiConsumer writeAccessor = setterMethod != null ? - new SetterWriteAccessor<>(setterMethod) : null; - return new BeanProperty<>(beanClass, type, propertyName, - methodReferenceLambda, writeAccessor, new InstantiatorResolver<>(type)); - } - - - protected static BeanProperty resolveCollectionBeanPropertyImpl( - MethodReferenceLambda> methodReferenceLambda, - /*Nullable*/ Supplier> collectionInstantiator) - { - final SerializedLambda serialized = serialized(methodReferenceLambda); - if (serialized.getImplMethodName().startsWith("lambda$")) - throw new IllegalArgumentException("Not a method reference"); - final Class beanClass = getContainingClass(serialized); - final Method getterMethod = findGetterMethod(beanClass, serialized.getImplMethodName()); - @SuppressWarnings("unchecked") - final Class> type = (Class>) getterMethod.getReturnType(); - final ParameterizedType genericReturnType = (ParameterizedType) getterMethod.getGenericReturnType(); - if (genericReturnType.getActualTypeArguments().length != 1) - throw new IllegalArgumentException("Can not determine parameter type for " - + beanClass.getName() + "." + getterMethod.getName()); - @SuppressWarnings("unchecked") - final Class elementType = (Class) genericReturnType.getActualTypeArguments()[0]; - - final String propertyName = resolvePropertyName(getterMethod.getName()); - final Method setterMethod = findSetterMethod(beanClass, propertyName, type, getterMethod.getName()); - final BiConsumer> writeAccessor = setterMethod != null ? - new SetterWriteAccessor<>(setterMethod) : null; - final CollectionElementReadAccessor beantypeCollectionElementReadAccessor = - new CollectionElementReadAccessor<>(methodReferenceLambda); - final Supplier>> collectionInstantiatorResolver; - if (collectionInstantiator == null && writeAccessor != null) - collectionInstantiatorResolver = defaultCollectionInstantiatorResolver(type); - else - collectionInstantiatorResolver = () -> collectionInstantiator; - final CollectionElementWriteAccessor beantypeCollectionElementWriteAccessor = - new CollectionElementWriteAccessor<>(methodReferenceLambda, writeAccessor, collectionInstantiatorResolver); - return new BeanProperty<>(beanClass, elementType, propertyName, - beantypeCollectionElementReadAccessor, beantypeCollectionElementWriteAccessor, - new InstantiatorResolver<>(elementType)); - } - - - protected static Method findGetterMethod(Class beanClass, String getterMethodName) { - final Method getterMethod; - try { - getterMethod = beanClass.getMethod(getterMethodName); - } catch (NoSuchMethodException e) { - throw new IllegalBeanPathException(e); - } - - if (getterMethod.getParameterCount() > 0) - throw new IllegalBeanPathException("Illegal getter method: " + getterMethodName); - if (Void.TYPE.equals(getterMethod.getReturnType())) - throw new IllegalBeanPathException("Illegal getter method return type: " + getterMethodName + - "(" + getterMethod.getReturnType() + ")"); - return getterMethod; - } - - - protected static String resolvePropertyName(String getterMethodName) - { - final String propertyName; - - if (getterMethodName.startsWith("get") && getterMethodName.length() > 3 && - Character.isUpperCase(getterMethodName.charAt(3))) - { - propertyName = Character.toLowerCase(getterMethodName.charAt(3)) + getterMethodName.substring(4); - } - else if (getterMethodName.startsWith("is") && getterMethodName.length() > 2 && - Character.isUpperCase(getterMethodName.charAt(2))) - { - propertyName = Character.toLowerCase(getterMethodName.charAt(2)) + getterMethodName.substring(3); - } - else { - // non-canonical name: use method name as-is - propertyName = getterMethodName; - } - return propertyName; - } - - - protected static /* Nullable */ Method findSetterMethod(Class beanClass, String propertyName, Class type, String getterMethodName) { - if (!getterMethodName.equals(propertyName)) { - // canonical getXXX(): search for setXXX() - final String setterMethodName = "set" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1); - try { - return beanClass.getMethod(setterMethodName, type); - } catch (NoSuchMethodException e) { - // Read-only - return null; - } - } else { - // non-canonical xxx(): search for xxx(value) - try { - return beanClass.getMethod(propertyName, type); - } catch (NoSuchMethodException e) { - // Read-only - return null; - } - } - } - - - protected static SerializedLambda serialized(Object lambda) { - try { - Method writeMethod = lambda.getClass().getDeclaredMethod("writeReplace"); - writeMethod.setAccessible(true); - return (SerializedLambda) writeMethod.invoke(lambda); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - - @SuppressWarnings("unchecked") - protected static Class getContainingClass(SerializedLambda lambda) { - try { - String className = lambda.getImplClass().replaceAll("/", "."); - //System.out.println(lambda.getInstantiatedMethodType()); - return (Class) Class.forName(className, true, Thread.currentThread().getContextClassLoader()); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - - public static class SetterWriteAccessor implements BiConsumer { - private final Method setterMethod; - - public SetterWriteAccessor(Method setterMethod) { - this.setterMethod = setterMethod; - } - - @Override - public void accept(BEAN bean, TYPE value) { - try { - setterMethod.invoke(bean, value); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } catch (InvocationTargetException e) { - if (e.getTargetException() instanceof RuntimeException) - // unchecked: throw as is - throw (RuntimeException) e.getTargetException(); - else - // checked: wrap into runtime - throw new RuntimeException(e.getTargetException()); - } - } - } - - - public static class InstantiatorResolver implements Supplier> { - private final Class type; - @SuppressWarnings("OptionalUsedAsFieldOrParameterType") - private volatile Optional> resolvedInstantiator; - - public InstantiatorResolver(Class type) { - this.type = type; - } - - @Override - public Supplier get() { - //noinspection OptionalAssignedToNull - if (resolvedInstantiator == null) { - resolvedInstantiator = Optional.ofNullable(resolveInstantiator(type)); - } - return resolvedInstantiator.orElse(null); - } - - private static Supplier resolveInstantiator(Class type) { - if ((type.getModifiers() & Modifier.ABSTRACT) != 0) - return null; - final Constructor constructor; - try { - constructor = type.getConstructor(); - } catch (NoSuchMethodException e) { - return null; - } - return () -> { - try { - return constructor.newInstance(); - } catch (InstantiationException | IllegalAccessException e) { - throw new RuntimeException(e); - } catch (InvocationTargetException e) { - if (e.getTargetException() instanceof RuntimeException) - throw (RuntimeException) e.getTargetException(); - else - throw new RuntimeException(e.getTargetException()); - } - }; - } - } - - - protected static Supplier>> defaultCollectionInstantiatorResolver(Class> collectionType) { - if ((collectionType.getModifiers() & Modifier.ABSTRACT) != 0) { - if (collectionType.isAssignableFrom(List.class)) - return () -> ArrayList::new; - else if (collectionType.isAssignableFrom(Set.class)) - return () -> LinkedHashSet::new; - else return null; - } else { - // instantiatable - return new InstantiatorResolver<>(collectionType); - } - } - - - public static class CollectionElementReadAccessor implements Function { - private final Function> beanPropertyReadAccessor; - - public CollectionElementReadAccessor(Function> beanPropertyReadAccessor) { - this.beanPropertyReadAccessor = beanPropertyReadAccessor; - } - - @Override - public TYPE apply(BEAN bean) { - final Collection collection = beanPropertyReadAccessor.apply(bean); - if (collection == null || collection.isEmpty()) - return null; - if (collection instanceof List) { - final List list = (List) collection; - return list.get(list.size()-1); - } else { - // Always obtain last element - final Iterator it = collection.iterator(); - TYPE elem = it.next(); - while (it.hasNext()) - elem = it.next(); - return elem; - } - } - } - - - public static class CollectionElementWriteAccessor implements BiConsumer { - private final Function> beanPropertyReadAccessor; - /* Nullable */ - private final BiConsumer> beanPropertyWriteAccessor; - /* Nullable */ - private final Supplier>> collectionInstantiatorResolver; - - public CollectionElementWriteAccessor(Function> beanPropertyReadAccessor, - /*Nullable*/BiConsumer> beanPropertyWriteAccessor, - Supplier>> collectionInstantiatorResolver) - { - this.beanPropertyReadAccessor = beanPropertyReadAccessor; - this.beanPropertyWriteAccessor = beanPropertyWriteAccessor; - this.collectionInstantiatorResolver = collectionInstantiatorResolver; - } - - @Override - public void accept(BEAN bean, TYPE value) { - Collection collection = beanPropertyReadAccessor.apply(bean); - if (collection == null) { - if (beanPropertyWriteAccessor == null) - throw new IncompletePathException(new ReadOnlyPropertyException("Collection property is read-only")); - final Supplier> collectionInstantiator = collectionInstantiatorResolver.get(); - if (collectionInstantiator == null) - throw new IncompletePathException("Cannot instantiate new collection due to unknown type"); - collection = collectionInstantiator.get(); - try { - beanPropertyWriteAccessor.accept(bean, collection); - } catch (Exception e) { - throw new IncompletePathException(e); - } - } - if (value == null) - collection.clear(); - else - collection.add(value); - } - } + protected static SerializedLambda serialized(Object lambda) { + SerializedLambda serializedLambda; + try { + Method writeMethod = lambda.getClass().getDeclaredMethod("writeReplace"); + writeMethod.setAccessible(true); + serializedLambda = (SerializedLambda) writeMethod.invoke(lambda); + } catch (Exception e) { + throw new RuntimeException(e); + } + if (serializedLambda.getImplMethodName().startsWith("lambda$")) + throw new IllegalArgumentException("Not a method reference"); + return serializedLambda; + } + + protected static String getContainingClassName(SerializedLambda lambda) { + return lambda.getImplClass(); + } + + @SuppressWarnings("unchecked") + protected static Class getContainingClass(SerializedLambda lambda) { + try { + String className = getContainingClassName(lambda).replaceAll("/", "."); + // System.out.println(lambda.getInstantiatedMethodType()); + return (Class) Class.forName(className, true, Thread.currentThread().getContextClassLoader()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static class SetterWriteAccessor implements BiConsumer { + private final Method setterMethod; + + public SetterWriteAccessor(Method setterMethod) { + this.setterMethod = setterMethod; + } + + @Override + public void accept(BEAN bean, TYPE value) { + try { + setterMethod.invoke(bean, value); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + if (e.getTargetException() instanceof RuntimeException) + // unchecked: throw as is + throw (RuntimeException) e.getTargetException(); + else + // checked: wrap into runtime + throw new RuntimeException(e.getTargetException()); + } + } + } + + public static class InstantiatorResolver implements Supplier> { + private final Class type; + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + private volatile Optional> resolvedInstantiator; + + public InstantiatorResolver(Class type) { + this.type = type; + } + + @Override + public Supplier get() { + // noinspection OptionalAssignedToNull + if (resolvedInstantiator == null) { + resolvedInstantiator = Optional.ofNullable(resolveInstantiator(type)); + } + return resolvedInstantiator.orElse(null); + } + + private static Supplier resolveInstantiator(Class type) { + if ((type.getModifiers() & Modifier.ABSTRACT) != 0) + return null; + final Constructor constructor; + try { + constructor = type.getConstructor(); + } catch (NoSuchMethodException e) { + return null; + } + return () -> { + try { + return constructor.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + if (e.getTargetException() instanceof RuntimeException) + throw (RuntimeException) e.getTargetException(); + else + throw new RuntimeException(e.getTargetException()); + } + }; + } + } + + protected static Supplier>> defaultCollectionInstantiatorResolver( + Class> collectionType) { + if ((collectionType.getModifiers() & Modifier.ABSTRACT) != 0) { + if (collectionType.isAssignableFrom(List.class)) + return () -> ArrayList::new; + else if (collectionType.isAssignableFrom(Set.class)) + return () -> LinkedHashSet::new; + else + return null; + } else { + // instantiatable + return new InstantiatorResolver<>(collectionType); + } + } + + public static class CollectionElementReadAccessor implements Function { + private final Function> beanPropertyReadAccessor; + + public CollectionElementReadAccessor(Function> beanPropertyReadAccessor) { + this.beanPropertyReadAccessor = beanPropertyReadAccessor; + } + + @Override + public TYPE apply(BEAN bean) { + final Collection collection = beanPropertyReadAccessor.apply(bean); + if (collection == null || collection.isEmpty()) + return null; + if (collection instanceof List) { + final List list = (List) collection; + return list.get(list.size() - 1); + } else { + // Always obtain last element + final Iterator it = collection.iterator(); + TYPE elem = it.next(); + while (it.hasNext()) + elem = it.next(); + return elem; + } + } + } + + public static class CollectionElementWriteAccessor implements BiConsumer { + private final Function> beanPropertyReadAccessor; + /* Nullable */ + private final BiConsumer> beanPropertyWriteAccessor; + /* Nullable */ + private final Supplier>> collectionInstantiatorResolver; + + public CollectionElementWriteAccessor(Function> beanPropertyReadAccessor, + /* Nullable */BiConsumer> beanPropertyWriteAccessor, + Supplier>> collectionInstantiatorResolver) { + this.beanPropertyReadAccessor = beanPropertyReadAccessor; + this.beanPropertyWriteAccessor = beanPropertyWriteAccessor; + this.collectionInstantiatorResolver = collectionInstantiatorResolver; + } + + @Override + public void accept(BEAN bean, TYPE value) { + Collection collection = beanPropertyReadAccessor.apply(bean); + if (collection == null) { + if (beanPropertyWriteAccessor == null) + throw new IncompletePathException( + new ReadOnlyPropertyException("Collection property is read-only")); + final Supplier> collectionInstantiator = collectionInstantiatorResolver.get(); + if (collectionInstantiator == null) + throw new IncompletePathException("Cannot instantiate new collection due to unknown type"); + collection = collectionInstantiator.get(); + try { + beanPropertyWriteAccessor.accept(bean, collection); + } catch (Exception e) { + throw new IncompletePathException(e); + } + } + if (value == null) + collection.clear(); + else + collection.add(value); + } + } } diff --git a/src/main/java/com/github/throwable/beanref/DynamicBeanPropertyResolver.java b/src/main/java/com/github/throwable/beanref/DynamicBeanPropertyResolver.java index 2cc9cb9..df99c26 100644 --- a/src/main/java/com/github/throwable/beanref/DynamicBeanPropertyResolver.java +++ b/src/main/java/com/github/throwable/beanref/DynamicBeanPropertyResolver.java @@ -6,97 +6,103 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; +import java.util.Set; import java.util.function.BiConsumer; import java.util.function.Function; -public class DynamicBeanPropertyResolver -{ - @SuppressWarnings("rawtypes") - private static final ConcurrentHashMap>> resolvedBeanPropertiesCache = new ConcurrentHashMap<>(); +import com.github.benmanes.caffeine.cache.Cache; +import com.github.throwable.beanref.lfp.BeanRefUtils; - @SuppressWarnings("unchecked") - static BeanProperty resolveBeanProperty(Class beanClass, String propertyName, Class type) { - final BeanProperty beanProperty = resolveBeanProperty(beanClass, propertyName); - if (!type.isAssignableFrom(beanProperty.getType())) - throw new IllegalArgumentException("Wrong type specified for property '" + propertyName + "' does not exist in bean " + beanClass.getSimpleName()); - return (BeanProperty) beanProperty; - } +public class DynamicBeanPropertyResolver { - static BeanProperty resolveBeanProperty(Class beanClass, String propertyName) { - final BeanProperty beanProperty = resolveAllBeanProperties(beanClass).get(propertyName); - if (beanProperty == null) - throw new IllegalArgumentException("Property '" + propertyName + "' does not exist in bean " + beanClass.getSimpleName()); - return beanProperty; - } + private static final Cache>, Map>> RESOLVED_BEAN_PROPERTIES_CACHE = BeanRefUtils + .cacheBuilder().build(); + @SuppressWarnings("unchecked") + static BeanProperty resolveBeanProperty(Class beanClass, String propertyName, + Class type) { + final BeanProperty beanProperty = resolveBeanProperty(beanClass, propertyName); + if (!type.isAssignableFrom(beanProperty.getType())) + throw new IllegalArgumentException("Wrong type specified for property '" + propertyName + + "' does not exist in bean " + beanClass.getSimpleName()); + return (BeanProperty) beanProperty; + } + + static BeanProperty resolveBeanProperty(Class beanClass, String propertyName) { + final BeanProperty beanProperty = resolveAllBeanProperties(beanClass).get(propertyName); + if (beanProperty == null) + throw new IllegalArgumentException( + "Property '" + propertyName + "' does not exist in bean " + beanClass.getSimpleName()); + return beanProperty; + } @SuppressWarnings({ "unchecked", "rawtypes" }) static Map> resolveAllBeanProperties(Class beanClass) { - return (Map>) resolvedBeanPropertiesCache.computeIfAbsent(beanClass, nil -> { - Map beanProperties = resolveAllBeanPropertiesImpl(beanClass); - return Optional.ofNullable(beanProperties); - }).orElse(null); + Set> namedClassTypes = BeanRefUtils.getNamedClassTypes(beanClass); + return (Map) RESOLVED_BEAN_PROPERTIES_CACHE.get(namedClassTypes, nil -> { + Map> beanProperties = new HashMap<>(); + for (Class namedClassType : namedClassTypes) + beanProperties.putAll(resolveAllBeanPropertiesImpl(namedClassType)); + return Collections.unmodifiableMap(beanProperties); + }); } + private static Map> resolveAllBeanPropertiesImpl(Class beanClass) { + final HashMap> map = new HashMap<>(); + final Method[] methods = beanClass.getMethods(); - private static Map> resolveAllBeanPropertiesImpl(Class beanClass) - { - final HashMap> map = new HashMap<>(); - final Method[] methods = beanClass.getMethods(); - - for (Method getterMethod : methods) { - if ((getterMethod.getModifiers() & Modifier.STATIC) != 0x0 || - getterMethod.getParameterCount() > 0 || Void.TYPE.equals(getterMethod.getReturnType())) - continue; - // skip Object.class methods - switch (getterMethod.getName()) { - case "hashCode": - case "getClass": - case "clone": // Cloneable objects may re-define it as public - case "toString": - case "notify": - case "notifyAll": - continue; - } - @SuppressWarnings("unchecked") - final Class type = (Class) getterMethod.getReturnType(); - final String propertyName = BeanPropertyResolver.resolvePropertyName(getterMethod.getName()); - final Method setterMethod = BeanPropertyResolver.findSetterMethod(beanClass, propertyName, type, getterMethod.getName()); - final BiConsumer writeAccessor = setterMethod != null ? - new BeanPropertyResolver.SetterWriteAccessor<>(setterMethod) : null; - final BeanProperty property = new BeanProperty<>(beanClass, type, propertyName, - new GetterReadAccessor<>(getterMethod), writeAccessor, - new BeanPropertyResolver.InstantiatorResolver<>(type)); - map.put(propertyName, property); - } - return Collections.unmodifiableMap(map); - } - + for (Method getterMethod : methods) { + if ((getterMethod.getModifiers() & Modifier.STATIC) != 0x0 || getterMethod.getParameterCount() > 0 + || Void.TYPE.equals(getterMethod.getReturnType())) + continue; + // skip Object.class methods + switch (getterMethod.getName()) { + case "hashCode": + case "getClass": + case "clone": // Cloneable objects may re-define it as public + case "toString": + case "notify": + case "notifyAll": + continue; + } + @SuppressWarnings("unchecked") + final Class type = (Class) getterMethod.getReturnType(); + final String propertyName = BeanPropertyResolver.resolvePropertyName(getterMethod.getName()); + final Method setterMethod = BeanPropertyResolver.findSetterMethod(beanClass, propertyName, type, + getterMethod.getName()); + final BiConsumer writeAccessor = setterMethod != null + ? new BeanPropertyResolver.SetterWriteAccessor<>(setterMethod) + : null; + final BeanProperty property = new BeanProperty<>(beanClass, type, propertyName, + new GetterReadAccessor<>(getterMethod), writeAccessor, + new BeanPropertyResolver.InstantiatorResolver<>(type)); + map.put(propertyName, property); + } + return Collections.unmodifiableMap(map); + } - public static class GetterReadAccessor implements Function { - private final Method getterMethod; + public static class GetterReadAccessor implements Function { + private final Method getterMethod; - public GetterReadAccessor(Method getterMethod) { - this.getterMethod = getterMethod; - } + public GetterReadAccessor(Method getterMethod) { + this.getterMethod = getterMethod; + } - @SuppressWarnings("unchecked") - @Override - public TYPE apply(BEAN bean) { - try { - return (TYPE) getterMethod.invoke(bean); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } catch (InvocationTargetException e) { - if (e.getTargetException() instanceof RuntimeException) - // unchecked: throw as is - throw (RuntimeException) e.getTargetException(); - else - // checked: wrap into runtime - throw new RuntimeException(e.getTargetException()); - } - } - } + @SuppressWarnings("unchecked") + @Override + public TYPE apply(BEAN bean) { + try { + return (TYPE) getterMethod.invoke(bean); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + if (e.getTargetException() instanceof RuntimeException) + // unchecked: throw as is + throw (RuntimeException) e.getTargetException(); + else + // checked: wrap into runtime + throw new RuntimeException(e.getTargetException()); + } + } + } } diff --git a/src/main/java/com/github/throwable/beanref/lfp/BeanRefUtils.java b/src/main/java/com/github/throwable/beanref/lfp/BeanRefUtils.java new file mode 100644 index 0000000..e40313d --- /dev/null +++ b/src/main/java/com/github/throwable/beanref/lfp/BeanRefUtils.java @@ -0,0 +1,103 @@ +package com.github.throwable.beanref.lfp; + +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.lang.reflect.Modifier; +import java.lang.reflect.Proxy; +import java.security.DigestOutputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.time.Duration; +import java.util.Base64; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Objects; +import java.util.Set; + +import com.github.benmanes.caffeine.cache.Caffeine; + +@SuppressWarnings("unchecked") +public class BeanRefUtils { + + private static final Duration CACHE_EXPIRE_AFTER_ACCESS = Duration.ofSeconds(5); + private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + + public static Caffeine cacheBuilder() { + return (Caffeine) Caffeine.newBuilder().expireAfterAccess(CACHE_EXPIRE_AFTER_ACCESS).softValues(); + } + + public static String hash(Serializable... serializables) { + MessageDigest messageDigest = null; + try { + if (serializables != null) + for (Serializable serializable : serializables) { + if (serializable == null) + continue; + if (messageDigest == null) + messageDigest = MessageDigest.getInstance("MD5"); + update(messageDigest, serializable); + } + } catch (NoSuchAlgorithmException | IOException e) { + throw RuntimeException.class.isInstance(e) ? RuntimeException.class.cast(e) : new RuntimeException(e); + } + byte[] digest = messageDigest != null ? messageDigest.digest() : EMPTY_BYTE_ARRAY; + return Base64.getEncoder().encodeToString(digest); + } + + private static void update(MessageDigest messageDigest, Serializable serializable) throws IOException { + try (OutputStream os = OutputStream.nullOutputStream(); + DigestOutputStream digestOutputStream = new DigestOutputStream(os, messageDigest); + ObjectOutputStream oos = new ObjectOutputStream(digestOutputStream);) { + oos.writeObject(serializable); + } + } + + public static Set> getNamedClassTypes(Class classType) { + Objects.requireNonNull(classType); + Set> classTypes = new LinkedHashSet<>(); + addNamedClassTypes(classType, classTypes); + if (classTypes.isEmpty()) + classTypes.add(classType); + return Collections.unmodifiableSet(classTypes); + } + + private static void addNamedClassTypes(Class classType, Set> classTypes) { + if (classType == null || classTypes.contains(classType)) + return; + if (isNamedClassType(classType)) { + classTypes.add(classType); + return; + } + Class[] ifaces = classType.getInterfaces(); + if (ifaces.length == 0) { + Class superclass = classType.getSuperclass(); + if (superclass != null) + addNamedClassTypes(superclass, classTypes); + return; + } + for (Class iface : ifaces) + addNamedClassTypes(iface, classTypes); + } + + private static boolean isNamedClassType(Class classType) { + if (classType == null) + return false; + if (classType.isAnonymousClass()) + return false; + if (isLambdaClassType(classType)) + return false; + if (Proxy.isProxyClass(classType)) + return false; + return true; + } + + private static boolean isLambdaClassType(Class classType) { + if (classType == null) + return false; + if (classType.isInterface() || Modifier.isAbstract(classType.getModifiers()) || classType.isAnonymousClass()) + return false; + return classType.getName().contains("$$Lambda"); + } +} diff --git a/src/test/java/com/github/throwable/beanref/BeanRefTestLFP.java b/src/test/java/com/github/throwable/beanref/BeanRefTestLFP.java new file mode 100644 index 0000000..0fc2102 --- /dev/null +++ b/src/test/java/com/github/throwable/beanref/BeanRefTestLFP.java @@ -0,0 +1,30 @@ +package com.github.throwable.beanref; + +import java.io.IOException; +import java.util.Date; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.concurrent.Future; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.github.throwable.beanref.lfp.BeanRefUtils; + +public class BeanRefTestLFP { + + public static void main(String[] args) throws IOException { + System.out.println(Stream.of(Future.class, Future.class, Runnable.class) + .collect(Collectors.toCollection(LinkedHashSet::new)).equals(Set.of(Runnable.class, Future.class))); + + Date date0 = new Date(); + Date date1 = new Date() {}; + System.out.println(BeanRef.$(date0.getClass()).all().size()); + System.out.println(BeanRef.$(date1.getClass()).all().size()); + System.out.println(BeanRef.$(Date::getTime).getPath()); + System.out.println(BeanRef.$(Date::getTime).getPath()); + MethodReferenceLambda mrl0 = Date::getTime; + MethodReferenceLambda mrl1 = Date::getTime; + System.out.println(BeanRefUtils.hash(mrl0).equals(BeanRefUtils.hash(mrl1))); + } + +} From 840d5dfafa0ace585801a00c747895e47bc19f6c Mon Sep 17 00:00:00 2001 From: reggie pierce Date: Thu, 16 Jun 2022 01:14:37 -0400 Subject: [PATCH 08/19] added null output --- .../throwable/beanref/lfp/BeanRefUtils.java | 16 +++++++++++++++- .../github/throwable/beanref/BeanRefTestLFP.java | 3 +++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/github/throwable/beanref/lfp/BeanRefUtils.java b/src/main/java/com/github/throwable/beanref/lfp/BeanRefUtils.java index e40313d..c5f97f1 100644 --- a/src/main/java/com/github/throwable/beanref/lfp/BeanRefUtils.java +++ b/src/main/java/com/github/throwable/beanref/lfp/BeanRefUtils.java @@ -47,7 +47,7 @@ public static String hash(Serializable... serializables) { } private static void update(MessageDigest messageDigest, Serializable serializable) throws IOException { - try (OutputStream os = OutputStream.nullOutputStream(); + try (OutputStream os = nullOutputStream(); DigestOutputStream digestOutputStream = new DigestOutputStream(os, messageDigest); ObjectOutputStream oos = new ObjectOutputStream(digestOutputStream);) { oos.writeObject(serializable); @@ -100,4 +100,18 @@ private static boolean isLambdaClassType(Class classType) { return false; return classType.getName().contains("$$Lambda"); } + + private static OutputStream nullOutputStream() { + return new OutputStream() { + + @Override + public void write(final byte[] b, final int off, final int len) {} + + @Override + public void write(final int b) {} + + @Override + public void write(final byte[] b) throws IOException {} + }; + } } diff --git a/src/test/java/com/github/throwable/beanref/BeanRefTestLFP.java b/src/test/java/com/github/throwable/beanref/BeanRefTestLFP.java index 0fc2102..cd27a96 100644 --- a/src/test/java/com/github/throwable/beanref/BeanRefTestLFP.java +++ b/src/test/java/com/github/throwable/beanref/BeanRefTestLFP.java @@ -25,6 +25,9 @@ public static void main(String[] args) throws IOException { MethodReferenceLambda mrl0 = Date::getTime; MethodReferenceLambda mrl1 = Date::getTime; System.out.println(BeanRefUtils.hash(mrl0).equals(BeanRefUtils.hash(mrl1))); + System.out.println(BeanRefUtils.hash(mrl0)); + System.out.println(BeanRefUtils.hash(mrl1)); + System.out.println(BeanRefUtils.hash((MethodReferenceLambda) Date::getDay)); } } From 962eccf9d5301e18fd5de031e71696054b3fcc57 Mon Sep 17 00:00:00 2001 From: reggie pierce Date: Thu, 16 Jun 2022 01:18:12 -0400 Subject: [PATCH 09/19] jdk 8 --- .../java/com/github/throwable/beanref/BeanRefTestLFP.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/github/throwable/beanref/BeanRefTestLFP.java b/src/test/java/com/github/throwable/beanref/BeanRefTestLFP.java index cd27a96..914cb7f 100644 --- a/src/test/java/com/github/throwable/beanref/BeanRefTestLFP.java +++ b/src/test/java/com/github/throwable/beanref/BeanRefTestLFP.java @@ -1,9 +1,10 @@ package com.github.throwable.beanref; import java.io.IOException; +import java.util.Arrays; import java.util.Date; +import java.util.HashSet; import java.util.LinkedHashSet; -import java.util.Set; import java.util.concurrent.Future; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -14,7 +15,8 @@ public class BeanRefTestLFP { public static void main(String[] args) throws IOException { System.out.println(Stream.of(Future.class, Future.class, Runnable.class) - .collect(Collectors.toCollection(LinkedHashSet::new)).equals(Set.of(Runnable.class, Future.class))); + .collect(Collectors.toCollection(LinkedHashSet::new)) + .equals(new HashSet>(Arrays.asList(Runnable.class, Future.class)))); Date date0 = new Date(); Date date1 = new Date() {}; From 17ced80db5f9fef3a3a1f1859a6d9e3368e0d7d2 Mon Sep 17 00:00:00 2001 From: reggie pierce Date: Mon, 27 Jun 2022 10:48:54 -0400 Subject: [PATCH 10/19] Update BeanPropertyResolver.java --- .../beanref/BeanPropertyResolver.java | 48 ++++++++++++++++--- 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java b/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java index 9228e79..fac8f76 100644 --- a/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java +++ b/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java @@ -8,6 +8,7 @@ import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashSet; @@ -18,6 +19,7 @@ import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.Supplier; +import java.util.stream.Stream; import com.github.benmanes.caffeine.cache.Cache; import com.github.throwable.beanref.lfp.BeanRefUtils; @@ -178,13 +180,24 @@ protected static String getContainingClassName(SerializedLambda lambda) { @SuppressWarnings("unchecked") protected static Class getContainingClass(SerializedLambda lambda) { - try { - String className = getContainingClassName(lambda).replaceAll("/", "."); - // System.out.println(lambda.getInstantiatedMethodType()); - return (Class) Class.forName(className, true, Thread.currentThread().getContextClassLoader()); - } catch (Exception e) { - throw new RuntimeException(e); + String className = getContainingClassName(lambda).replaceAll("/", "."); + RuntimeException error = null; + Iterator classLoaderIter = streamDefaultClassLoaders().iterator(); + while (classLoaderIter.hasNext()) { + ClassLoader classLoader = classLoaderIter.next(); + try { + return (Class) Class.forName(className, true, classLoader); + } catch (Throwable t) { + if (error == null) + if (t instanceof RuntimeException) + error = (RuntimeException) t; + else + error = new RuntimeException(t); + else + error.addSuppressed(t); + } } + throw error; } public static class SetterWriteAccessor implements BiConsumer { @@ -332,4 +345,27 @@ public void accept(BEAN bean, TYPE value) { collection.add(value); } } + + private static Stream streamDefaultClassLoaders() { + List> loaders = Arrays.asList(new Supplier() { + + @Override + public ClassLoader get() { + return Thread.currentThread().getContextClassLoader(); + } + }, new Supplier() { + + @Override + public ClassLoader get() { + return BeanPropertyResolver.class.getClassLoader(); + } + }, new Supplier() { + + @Override + public ClassLoader get() { + return ClassLoader.getSystemClassLoader(); + } + }); + return loaders.stream().filter(Objects::nonNull).map(Supplier::get).filter(Objects::nonNull).distinct(); + } } From 2cd3652dae87e5b688eee224c1b208e405b0d9ec Mon Sep 17 00:00:00 2001 From: reggie pierce Date: Mon, 27 Jun 2022 12:03:48 -0400 Subject: [PATCH 11/19] java 11 --- pom.xml | 4 +- .../beanref/BeanPropertyResolver.java | 26 +------- .../throwable/beanref/lfp/BeanRefUtils.java | 63 ++++++++++++------- 3 files changed, 45 insertions(+), 48 deletions(-) diff --git a/pom.xml b/pom.xml index c4854d7..94ea371 100644 --- a/pom.xml +++ b/pom.xml @@ -37,8 +37,8 @@ - 1.8 - 1.8 + 11 + 11 UTF-8 2.9.3 diff --git a/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java b/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java index fac8f76..51165de 100644 --- a/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java +++ b/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java @@ -8,7 +8,6 @@ import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashSet; @@ -19,7 +18,6 @@ import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.Supplier; -import java.util.stream.Stream; import com.github.benmanes.caffeine.cache.Cache; import com.github.throwable.beanref.lfp.BeanRefUtils; @@ -182,7 +180,7 @@ protected static String getContainingClassName(SerializedLambda lambda) { protected static Class getContainingClass(SerializedLambda lambda) { String className = getContainingClassName(lambda).replaceAll("/", "."); RuntimeException error = null; - Iterator classLoaderIter = streamDefaultClassLoaders().iterator(); + Iterator classLoaderIter = BeanRefUtils.streamClassLoaders().iterator(); while (classLoaderIter.hasNext()) { ClassLoader classLoader = classLoaderIter.next(); try { @@ -346,26 +344,4 @@ public void accept(BEAN bean, TYPE value) { } } - private static Stream streamDefaultClassLoaders() { - List> loaders = Arrays.asList(new Supplier() { - - @Override - public ClassLoader get() { - return Thread.currentThread().getContextClassLoader(); - } - }, new Supplier() { - - @Override - public ClassLoader get() { - return BeanPropertyResolver.class.getClassLoader(); - } - }, new Supplier() { - - @Override - public ClassLoader get() { - return ClassLoader.getSystemClassLoader(); - } - }); - return loaders.stream().filter(Objects::nonNull).map(Supplier::get).filter(Objects::nonNull).distinct(); - } } diff --git a/src/main/java/com/github/throwable/beanref/lfp/BeanRefUtils.java b/src/main/java/com/github/throwable/beanref/lfp/BeanRefUtils.java index c5f97f1..181d378 100644 --- a/src/main/java/com/github/throwable/beanref/lfp/BeanRefUtils.java +++ b/src/main/java/com/github/throwable/beanref/lfp/BeanRefUtils.java @@ -10,13 +10,18 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.time.Duration; +import java.util.Arrays; import java.util.Base64; import java.util.Collections; import java.util.LinkedHashSet; +import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Stream; import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.throwable.beanref.BeanPropertyResolver; @SuppressWarnings("unchecked") public class BeanRefUtils { @@ -28,6 +33,35 @@ public static Caffeine cacheBuilder() { return (Caffeine) Caffeine.newBuilder().expireAfterAccess(CACHE_EXPIRE_AFTER_ACCESS).softValues(); } + public static Stream streamClassLoaders() { + List> loaders = Arrays.asList(new Supplier() { + + @Override + public ClassLoader get() { + return Thread.currentThread().getContextClassLoader(); + } + }, new Supplier() { + + @Override + public ClassLoader get() { + return BeanPropertyResolver.class.getClassLoader(); + } + }, new Supplier() { + + @Override + public ClassLoader get() { + return ClassLoader.getSystemClassLoader(); + } + }, new Supplier() { + + @Override + public ClassLoader get() { + return ClassLoader.getPlatformClassLoader(); + } + }); + return loaders.stream().filter(Objects::nonNull).map(Supplier::get).filter(Objects::nonNull).distinct(); + } + public static String hash(Serializable... serializables) { MessageDigest messageDigest = null; try { @@ -46,14 +80,6 @@ public static String hash(Serializable... serializables) { return Base64.getEncoder().encodeToString(digest); } - private static void update(MessageDigest messageDigest, Serializable serializable) throws IOException { - try (OutputStream os = nullOutputStream(); - DigestOutputStream digestOutputStream = new DigestOutputStream(os, messageDigest); - ObjectOutputStream oos = new ObjectOutputStream(digestOutputStream);) { - oos.writeObject(serializable); - } - } - public static Set> getNamedClassTypes(Class classType) { Objects.requireNonNull(classType); Set> classTypes = new LinkedHashSet<>(); @@ -63,6 +89,14 @@ public static Set> getNamedClassTypes(Class classType) return Collections.unmodifiableSet(classTypes); } + private static void update(MessageDigest messageDigest, Serializable serializable) throws IOException { + try (OutputStream os = OutputStream.nullOutputStream(); + DigestOutputStream digestOutputStream = new DigestOutputStream(os, messageDigest); + ObjectOutputStream oos = new ObjectOutputStream(digestOutputStream);) { + oos.writeObject(serializable); + } + } + private static void addNamedClassTypes(Class classType, Set> classTypes) { if (classType == null || classTypes.contains(classType)) return; @@ -101,17 +135,4 @@ private static boolean isLambdaClassType(Class classType) { return classType.getName().contains("$$Lambda"); } - private static OutputStream nullOutputStream() { - return new OutputStream() { - - @Override - public void write(final byte[] b, final int off, final int len) {} - - @Override - public void write(final int b) {} - - @Override - public void write(final byte[] b) throws IOException {} - }; - } } From 0c7c253a93408058c194b2c85b17c4b5905e3b7a Mon Sep 17 00:00:00 2001 From: reggie pierce Date: Tue, 23 Aug 2022 13:28:17 -0400 Subject: [PATCH 12/19] caching service --- pom.xml | 13 +- .../beanref/BeanPropertyResolver.java | 27 ++-- .../beanref/DynamicBeanPropertyResolver.java | 21 ++-- .../throwable/beanref/lfp/BeanRefCache.java | 117 ++++++++++++++++++ .../throwable/beanref/lfp/BeanRefUtils.java | 105 ++++------------ 5 files changed, 176 insertions(+), 107 deletions(-) create mode 100644 src/main/java/com/github/throwable/beanref/lfp/BeanRefCache.java diff --git a/pom.xml b/pom.xml index 94ea371..06489ed 100644 --- a/pom.xml +++ b/pom.xml @@ -54,12 +54,19 @@ + + + + jitpack.io + https://jitpack.io + + - com.github.ben-manes.caffeine - caffeine - ${com.github.ben-manes.caffeine.caffeine.version} + com.github.regbo + lfp-picoservice + f91ff54 junit diff --git a/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java b/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java index 51165de..f191a2d 100644 --- a/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java +++ b/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java @@ -8,6 +8,7 @@ import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashSet; @@ -19,38 +20,34 @@ import java.util.function.Function; import java.util.function.Supplier; -import com.github.benmanes.caffeine.cache.Cache; +import com.github.throwable.beanref.lfp.BeanRefCache; import com.github.throwable.beanref.lfp.BeanRefUtils; public class BeanPropertyResolver { - private static final String RESOLVED_BEAN_PROPERTIES_KEY_PREFIX = "resolved-bp-"; - private static final String RESOLVED_COLLECTION_BEAN_PROPERTIES_KEY_PREFIX = "resolved-collection-bp-"; - private static final Cache> RESOLVED_BEAN_PROPERTIES_CACHE = BeanRefUtils.cacheBuilder() - .build(); - protected BeanPropertyResolver() {} - @SuppressWarnings({ "unchecked", "rawtypes" }) protected static BeanProperty resolveBeanProperty( MethodReferenceLambda methodReferenceLambda) { Objects.requireNonNull(methodReferenceLambda); String hash = BeanRefUtils.hash(methodReferenceLambda); - String key = RESOLVED_BEAN_PROPERTIES_KEY_PREFIX + hash; - return (BeanProperty) RESOLVED_BEAN_PROPERTIES_CACHE.get(key, nil -> { + Object key = Arrays.asList(hash, "resolveBeanProperty"); + return BeanRefCache.instance().get(key, nil -> { return resolveBeanPropertyImpl(methodReferenceLambda); }); } - @SuppressWarnings({ "unchecked", "rawtypes" }) protected static BeanProperty resolveCollectionBeanProperty( MethodReferenceLambda> methodReferenceLambda, /* Nullable */ Supplier> collectionInstantiator) { Objects.requireNonNull(methodReferenceLambda); - String hash = BeanRefUtils.hash(methodReferenceLambda, Optional.ofNullable(collectionInstantiator) - .filter(Serializable.class::isInstance).map(Serializable.class::cast).orElse(null)); - String key = RESOLVED_COLLECTION_BEAN_PROPERTIES_KEY_PREFIX + hash; - return (BeanProperty) RESOLVED_BEAN_PROPERTIES_CACHE.get(key, nil -> { + String hash = BeanRefUtils.hash(methodReferenceLambda, + Optional.ofNullable(collectionInstantiator) + .filter(Serializable.class::isInstance) + .map(Serializable.class::cast) + .orElse(null)); + Object key = Arrays.asList(hash, "resolveCollectionBeanProperty"); + return BeanRefCache.instance().get(key, nil -> { return resolveCollectionBeanPropertyImpl(methodReferenceLambda, collectionInstantiator); }); } @@ -180,7 +177,7 @@ protected static String getContainingClassName(SerializedLambda lambda) { protected static Class getContainingClass(SerializedLambda lambda) { String className = getContainingClassName(lambda).replaceAll("/", "."); RuntimeException error = null; - Iterator classLoaderIter = BeanRefUtils.streamClassLoaders().iterator(); + Iterator classLoaderIter = BeanRefUtils.streamDefaultClassLoaders().iterator(); while (classLoaderIter.hasNext()) { ClassLoader classLoader = classLoaderIter.next(); try { diff --git a/src/main/java/com/github/throwable/beanref/DynamicBeanPropertyResolver.java b/src/main/java/com/github/throwable/beanref/DynamicBeanPropertyResolver.java index df99c26..689f528 100644 --- a/src/main/java/com/github/throwable/beanref/DynamicBeanPropertyResolver.java +++ b/src/main/java/com/github/throwable/beanref/DynamicBeanPropertyResolver.java @@ -3,21 +3,19 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.Set; +import java.util.Objects; import java.util.function.BiConsumer; import java.util.function.Function; -import com.github.benmanes.caffeine.cache.Cache; +import com.github.throwable.beanref.lfp.BeanRefCache; import com.github.throwable.beanref.lfp.BeanRefUtils; public class DynamicBeanPropertyResolver { - private static final Cache>, Map>> RESOLVED_BEAN_PROPERTIES_CACHE = BeanRefUtils - .cacheBuilder().build(); - @SuppressWarnings("unchecked") static BeanProperty resolveBeanProperty(Class beanClass, String propertyName, Class type) { @@ -36,13 +34,14 @@ static BeanProperty resolveBeanProperty(Class beanClass return beanProperty; } - @SuppressWarnings({ "unchecked", "rawtypes" }) static Map> resolveAllBeanProperties(Class beanClass) { - Set> namedClassTypes = BeanRefUtils.getNamedClassTypes(beanClass); - return (Map) RESOLVED_BEAN_PROPERTIES_CACHE.get(namedClassTypes, nil -> { - Map> beanProperties = new HashMap<>(); - for (Class namedClassType : namedClassTypes) - beanProperties.putAll(resolveAllBeanPropertiesImpl(namedClassType)); + { + var namedBeanClass = BeanRefUtils.getNamedClassType(beanClass); + if (!Objects.equals(namedBeanClass, beanClass)) + return resolveAllBeanProperties(namedBeanClass); + } + return BeanRefCache.instance().get(Arrays.asList(beanClass, "resolveAllBeanProperties"), nil -> { + Map> beanProperties = resolveAllBeanPropertiesImpl(beanClass); return Collections.unmodifiableMap(beanProperties); }); } diff --git a/src/main/java/com/github/throwable/beanref/lfp/BeanRefCache.java b/src/main/java/com/github/throwable/beanref/lfp/BeanRefCache.java new file mode 100644 index 0000000..51e36a2 --- /dev/null +++ b/src/main/java/com/github/throwable/beanref/lfp/BeanRefCache.java @@ -0,0 +1,117 @@ +package com.github.throwable.beanref.lfp; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.InvocationTargetException; +import java.util.Comparator; +import java.util.Objects; +import java.util.ServiceLoader; +import java.util.WeakHashMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.stream.Stream; + +import de.adito.picoservice.IPicoRegistration; +import de.adito.picoservice.PicoService; + +public interface BeanRefCache { + + public static BeanRefCache instance() { + return Static.instance(); + } + + V get(K key, Function loader); + + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.TYPE }) + @PicoService + public static @interface BeanRefCacheService { + + // alias for value + String[] requiredClassNames() default {}; + + int priority() + + default 0; + + } + + static enum Static { + ; + + private static final Object INSTANCE_MUTEX = new Object(); + private static BeanRefCache _INSTANCE; + + private static BeanRefCache instance() { + if (_INSTANCE == null) + synchronized (INSTANCE_MUTEX) { + if (_INSTANCE == null) + _INSTANCE = loadInstance(); + } + return _INSTANCE; + } + + private static BeanRefCache loadInstance() { + var clStream = Stream.concat(Stream.of((ClassLoader) null), BeanRefUtils.streamDefaultClassLoaders()) + .distinct(); + var serviceProviderStream = clStream.flatMap(cl -> { + var serviceLoader = cl == null ? ServiceLoader.load(IPicoRegistration.class) + : ServiceLoader.load(IPicoRegistration.class, cl); + return serviceLoader.stream(); + }).distinct(); + var registrationStream = serviceProviderStream.map(ServiceLoader.Provider::get); + registrationStream = registrationStream + .filter(v -> BeanRefCache.class.isAssignableFrom(v.getAnnotatedClass())); + registrationStream = registrationStream.filter(registration -> { + return registration.getAnnotatedClass().getAnnotation(BeanRefCacheService.class) != null; + }); + registrationStream = registrationStream + .sorted(Comparator.comparing(registration -> { + return registration.getAnnotatedClass().getAnnotation(BeanRefCacheService.class).priority(); + })); + var beanRefCache = registrationStream.map(registration -> { + var ct = registration.getAnnotatedClass(); + var enumConstants = ct.getEnumConstants(); + if (enumConstants != null && enumConstants.length == 1) + return enumConstants[0]; + try { + return ct.getConstructor().newInstance(); + } catch (RuntimeException e) { + throw e; + } catch (InstantiationException | IllegalAccessException | InvocationTargetException + | NoSuchMethodException e) { + throw new RuntimeException(e); + } + }).map(v -> (BeanRefCache) v).findFirst().orElse(null); + if (beanRefCache != null) + return beanRefCache; + var cache = new WeakHashMap(); + var concurrentCache = new ConcurrentHashMap(); + return new BeanRefCache() { + + @SuppressWarnings("unchecked") + @Override + public V get(K key, Function loader) { + Objects.requireNonNull(key); + Objects.requireNonNull(loader); + var value = cache.get(key); + if (value == null) { + Object[] resultRef = new Object[1]; + concurrentCache.compute(key, (nilk, nilv) -> { + resultRef[0] = cache.computeIfAbsent(key, nil -> { + return loader.apply(key); + }); + return null; + }); + value = resultRef[0]; + } + return (V) value; + } + }; + } + + } + +} diff --git a/src/main/java/com/github/throwable/beanref/lfp/BeanRefUtils.java b/src/main/java/com/github/throwable/beanref/lfp/BeanRefUtils.java index 181d378..ceb256a 100644 --- a/src/main/java/com/github/throwable/beanref/lfp/BeanRefUtils.java +++ b/src/main/java/com/github/throwable/beanref/lfp/BeanRefUtils.java @@ -4,64 +4,20 @@ import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.Serializable; -import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; import java.security.DigestOutputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.time.Duration; -import java.util.Arrays; import java.util.Base64; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; import java.util.Objects; -import java.util.Set; import java.util.function.Supplier; import java.util.stream.Stream; -import com.github.benmanes.caffeine.cache.Caffeine; -import com.github.throwable.beanref.BeanPropertyResolver; - @SuppressWarnings("unchecked") public class BeanRefUtils { - private static final Duration CACHE_EXPIRE_AFTER_ACCESS = Duration.ofSeconds(5); private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; - public static Caffeine cacheBuilder() { - return (Caffeine) Caffeine.newBuilder().expireAfterAccess(CACHE_EXPIRE_AFTER_ACCESS).softValues(); - } - - public static Stream streamClassLoaders() { - List> loaders = Arrays.asList(new Supplier() { - - @Override - public ClassLoader get() { - return Thread.currentThread().getContextClassLoader(); - } - }, new Supplier() { - - @Override - public ClassLoader get() { - return BeanPropertyResolver.class.getClassLoader(); - } - }, new Supplier() { - - @Override - public ClassLoader get() { - return ClassLoader.getSystemClassLoader(); - } - }, new Supplier() { - - @Override - public ClassLoader get() { - return ClassLoader.getPlatformClassLoader(); - } - }); - return loaders.stream().filter(Objects::nonNull).map(Supplier::get).filter(Objects::nonNull).distinct(); - } - public static String hash(Serializable... serializables) { MessageDigest messageDigest = null; try { @@ -80,15 +36,6 @@ public static String hash(Serializable... serializables) { return Base64.getEncoder().encodeToString(digest); } - public static Set> getNamedClassTypes(Class classType) { - Objects.requireNonNull(classType); - Set> classTypes = new LinkedHashSet<>(); - addNamedClassTypes(classType, classTypes); - if (classTypes.isEmpty()) - classTypes.add(classType); - return Collections.unmodifiableSet(classTypes); - } - private static void update(MessageDigest messageDigest, Serializable serializable) throws IOException { try (OutputStream os = OutputStream.nullOutputStream(); DigestOutputStream digestOutputStream = new DigestOutputStream(os, messageDigest); @@ -97,42 +44,44 @@ private static void update(MessageDigest messageDigest, Serializable serializabl } } - private static void addNamedClassTypes(Class classType, Set> classTypes) { - if (classType == null || classTypes.contains(classType)) - return; - if (isNamedClassType(classType)) { - classTypes.add(classType); - return; - } - Class[] ifaces = classType.getInterfaces(); - if (ifaces.length == 0) { - Class superclass = classType.getSuperclass(); - if (superclass != null) - addNamedClassTypes(superclass, classTypes); - return; - } - for (Class iface : ifaces) - addNamedClassTypes(iface, classTypes); - } - private static boolean isNamedClassType(Class classType) { if (classType == null) return false; if (classType.isAnonymousClass()) return false; - if (isLambdaClassType(classType)) + if (classType.isSynthetic()) return false; if (Proxy.isProxyClass(classType)) return false; return true; } - private static boolean isLambdaClassType(Class classType) { - if (classType == null) - return false; - if (classType.isInterface() || Modifier.isAbstract(classType.getModifiers()) || classType.isAnonymousClass()) - return false; - return classType.getName().contains("$$Lambda"); + public static Class getNamedClassType(Class classType) { + Objects.requireNonNull(classType); + if (isNamedClassType(classType)) + return (Class) classType; + var ifaces = classType.getInterfaces(); + if (ifaces.length == 0) { + var superclass = classType.getSuperclass(); + if (superclass == null) + throw new IllegalArgumentException(String.format("superclass not found. classType:%s", classType)); + return (Class) getNamedClassType(superclass); + } else if (ifaces.length == 1) + return (Class) getNamedClassType(ifaces[0]); + else + // if proxy this is the best we can do + return (Class) classType; + } + + public static ClassLoader getDefaultClassLoader() { + return streamDefaultClassLoaders().findFirst().get(); + } + + public static Stream streamDefaultClassLoaders() { + Stream> clSupplierStream = Stream.of(Thread.currentThread()::getContextClassLoader, + BeanRefUtils.class::getClassLoader, ClassLoader::getSystemClassLoader, + ClassLoader::getPlatformClassLoader); + return clSupplierStream.map(Supplier::get).filter(Objects::nonNull).distinct(); } } From 6d07f6c533422cfd3da7a44348628eec2d47f046 Mon Sep 17 00:00:00 2001 From: reggie pierce Date: Tue, 23 Aug 2022 13:44:26 -0400 Subject: [PATCH 13/19] required classname service --- .../beanref/BeanPropertyResolver.java | 18 +---------- .../throwable/beanref/lfp/BeanRefCache.java | 32 ++++++++++++------- .../throwable/beanref/lfp/BeanRefUtils.java | 31 ++++++++++++++++++ 3 files changed, 53 insertions(+), 28 deletions(-) diff --git a/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java b/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java index f191a2d..c66e0ad 100644 --- a/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java +++ b/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java @@ -176,23 +176,7 @@ protected static String getContainingClassName(SerializedLambda lambda) { @SuppressWarnings("unchecked") protected static Class getContainingClass(SerializedLambda lambda) { String className = getContainingClassName(lambda).replaceAll("/", "."); - RuntimeException error = null; - Iterator classLoaderIter = BeanRefUtils.streamDefaultClassLoaders().iterator(); - while (classLoaderIter.hasNext()) { - ClassLoader classLoader = classLoaderIter.next(); - try { - return (Class) Class.forName(className, true, classLoader); - } catch (Throwable t) { - if (error == null) - if (t instanceof RuntimeException) - error = (RuntimeException) t; - else - error = new RuntimeException(t); - else - error.addSuppressed(t); - } - } - throw error; + return (Class) BeanRefUtils.classForName(className, false); } public static class SetterWriteAccessor implements BiConsumer { diff --git a/src/main/java/com/github/throwable/beanref/lfp/BeanRefCache.java b/src/main/java/com/github/throwable/beanref/lfp/BeanRefCache.java index 51e36a2..81c11b5 100644 --- a/src/main/java/com/github/throwable/beanref/lfp/BeanRefCache.java +++ b/src/main/java/com/github/throwable/beanref/lfp/BeanRefCache.java @@ -6,6 +6,8 @@ import java.lang.annotation.Target; import java.lang.reflect.InvocationTargetException; import java.util.Comparator; +import java.util.Map; +import java.util.Map.Entry; import java.util.Objects; import java.util.ServiceLoader; import java.util.WeakHashMap; @@ -54,24 +56,32 @@ private static BeanRefCache instance() { } private static BeanRefCache loadInstance() { - var clStream = Stream.concat(Stream.of((ClassLoader) null), BeanRefUtils.streamDefaultClassLoaders()) + var classLoaders = Stream.concat(Stream.of((ClassLoader) null), BeanRefUtils.streamDefaultClassLoaders()) .distinct(); - var serviceProviderStream = clStream.flatMap(cl -> { + var serviceProviers = classLoaders.flatMap(cl -> { var serviceLoader = cl == null ? ServiceLoader.load(IPicoRegistration.class) : ServiceLoader.load(IPicoRegistration.class, cl); return serviceLoader.stream(); }).distinct(); - var registrationStream = serviceProviderStream.map(ServiceLoader.Provider::get); - registrationStream = registrationStream - .filter(v -> BeanRefCache.class.isAssignableFrom(v.getAnnotatedClass())); - registrationStream = registrationStream.filter(registration -> { - return registration.getAnnotatedClass().getAnnotation(BeanRefCacheService.class) != null; + var registrationAnnos = serviceProviers.map(ServiceLoader.Provider::get).map(registration -> { + var anno = registration.getAnnotatedClass().getAnnotation(BeanRefCacheService.class); + if (anno == null) + return null; + return Map.entry(registration, anno); + }).filter(Objects::nonNull); + registrationAnnos = registrationAnnos.filter(ent -> { + var registration = ent.getKey(); + if (!BeanRefCache.class.isAssignableFrom(ent.getKey().getAnnotatedClass())) + return false; + var anno = ent.getValue(); + return Stream.of(anno.requiredClassNames()).allMatch(v -> BeanRefUtils.classForName(v, true) != null); }); - registrationStream = registrationStream - .sorted(Comparator.comparing(registration -> { - return registration.getAnnotatedClass().getAnnotation(BeanRefCacheService.class).priority(); + registrationAnnos = registrationAnnos + .sorted(Comparator., Integer>comparing(ent -> { + var anno = ent.getValue(); + return anno.priority(); })); - var beanRefCache = registrationStream.map(registration -> { + var beanRefCache = registrationAnnos.map(Entry::getKey).map(registration -> { var ct = registration.getAnnotatedClass(); var enumConstants = ct.getEnumConstants(); if (enumConstants != null && enumConstants.length == 1) diff --git a/src/main/java/com/github/throwable/beanref/lfp/BeanRefUtils.java b/src/main/java/com/github/throwable/beanref/lfp/BeanRefUtils.java index ceb256a..306ec93 100644 --- a/src/main/java/com/github/throwable/beanref/lfp/BeanRefUtils.java +++ b/src/main/java/com/github/throwable/beanref/lfp/BeanRefUtils.java @@ -9,6 +9,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Base64; +import java.util.Iterator; import java.util.Objects; import java.util.function.Supplier; import java.util.stream.Stream; @@ -84,4 +85,34 @@ public static Stream streamDefaultClassLoaders() { return clSupplierStream.map(Supplier::get).filter(Objects::nonNull).distinct(); } + public static Class classForName(String className, boolean suppressErrors) { + if (className == null || className.isEmpty()) { + if (suppressErrors) + return null; + throw new IllegalArgumentException("className required"); + } + RuntimeException error = null; + Iterator classLoaderIter = BeanRefUtils.streamDefaultClassLoaders().iterator(); + while (classLoaderIter.hasNext()) { + ClassLoader classLoader = classLoaderIter.next(); + try { + var classType = Class.forName(className, false, classLoader); + if (classType != null) + return classType; + } catch (Throwable t) { + if (suppressErrors) + continue; + if (error == null) + if (t instanceof RuntimeException) + error = (RuntimeException) t; + else + error = new RuntimeException(t); + else + error.addSuppressed(t); + } + } + if (suppressErrors) + return null; + throw error; + } } From 0dc6a0cad6e49501a604eac860f38370618ba433 Mon Sep 17 00:00:00 2001 From: reggie pierce Date: Tue, 23 Aug 2022 13:45:14 -0400 Subject: [PATCH 14/19] Update BeanRefCache.java --- .../java/com/github/throwable/beanref/lfp/BeanRefCache.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/github/throwable/beanref/lfp/BeanRefCache.java b/src/main/java/com/github/throwable/beanref/lfp/BeanRefCache.java index 81c11b5..86b517a 100644 --- a/src/main/java/com/github/throwable/beanref/lfp/BeanRefCache.java +++ b/src/main/java/com/github/throwable/beanref/lfp/BeanRefCache.java @@ -79,7 +79,7 @@ private static BeanRefCache loadInstance() { registrationAnnos = registrationAnnos .sorted(Comparator., Integer>comparing(ent -> { var anno = ent.getValue(); - return anno.priority(); + return -1 * anno.priority(); })); var beanRefCache = registrationAnnos.map(Entry::getKey).map(registration -> { var ct = registration.getAnnotatedClass(); From 977a6a511433653536aaeddd6accd0a524efb05b Mon Sep 17 00:00:00 2001 From: reggie pierce Date: Tue, 23 Aug 2022 14:13:33 -0400 Subject: [PATCH 15/19] Update BeanRefCache.java --- .../throwable/beanref/lfp/BeanRefCache.java | 52 ++++++++++++------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/github/throwable/beanref/lfp/BeanRefCache.java b/src/main/java/com/github/throwable/beanref/lfp/BeanRefCache.java index 86b517a..1f7a970 100644 --- a/src/main/java/com/github/throwable/beanref/lfp/BeanRefCache.java +++ b/src/main/java/com/github/throwable/beanref/lfp/BeanRefCache.java @@ -40,6 +40,32 @@ int priority() } + public static abstract class Abs implements BeanRefCache { + + private final Map lockMap = new ConcurrentHashMap(); + + @SuppressWarnings("unchecked") + @Override + public V get(K key, Function loader) { + Objects.requireNonNull(key); + Objects.requireNonNull(loader); + var value = getInternal(key); + if (value == null) { + Object[] resultRef = new Object[1]; + lockMap.compute(key, (nilk, nilv) -> { + resultRef[0] = getInternal(key, loader); + return null; + }); + value = resultRef[0]; + } + return (V) value; + } + + protected abstract Object getInternal(Object key); + + protected abstract Object getInternal(K key, Function loader); + } + static enum Static { ; @@ -98,26 +124,16 @@ private static BeanRefCache loadInstance() { if (beanRefCache != null) return beanRefCache; var cache = new WeakHashMap(); - var concurrentCache = new ConcurrentHashMap(); - return new BeanRefCache() { + return new BeanRefCache.Abs() { + + @Override + protected Object getInternal(Object key) { + return cache.get(key); + } - @SuppressWarnings("unchecked") @Override - public V get(K key, Function loader) { - Objects.requireNonNull(key); - Objects.requireNonNull(loader); - var value = cache.get(key); - if (value == null) { - Object[] resultRef = new Object[1]; - concurrentCache.compute(key, (nilk, nilv) -> { - resultRef[0] = cache.computeIfAbsent(key, nil -> { - return loader.apply(key); - }); - return null; - }); - value = resultRef[0]; - } - return (V) value; + protected Object getInternal(K key, Function loader) { + return cache.computeIfAbsent(key, nil -> loader.apply(key)); } }; } From 8d68866cd4e7866b8fb33b5415f44b76367def6c Mon Sep 17 00:00:00 2001 From: reggie pierce Date: Tue, 23 Aug 2022 14:39:23 -0400 Subject: [PATCH 16/19] Update BeanRefCache.java --- .../throwable/beanref/lfp/BeanRefCache.java | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/github/throwable/beanref/lfp/BeanRefCache.java b/src/main/java/com/github/throwable/beanref/lfp/BeanRefCache.java index 1f7a970..400394f 100644 --- a/src/main/java/com/github/throwable/beanref/lfp/BeanRefCache.java +++ b/src/main/java/com/github/throwable/beanref/lfp/BeanRefCache.java @@ -49,21 +49,28 @@ public static abstract class Abs implements BeanRefCache { public V get(K key, Function loader) { Objects.requireNonNull(key); Objects.requireNonNull(loader); - var value = getInternal(key); - if (value == null) { - Object[] resultRef = new Object[1]; - lockMap.compute(key, (nilk, nilv) -> { - resultRef[0] = getInternal(key, loader); - return null; - }); - value = resultRef[0]; + { + var value = get(key); + if (value != null) + return (V) value; } - return (V) value; + Object[] resultRef = new Object[1]; + lockMap.compute(key, (nilk, nilv) -> { + var value = get(key); + if (value == null) { + value = loader.apply(key); + put(key, value); + } + resultRef[0] = value; + return null; + }); + return (V) resultRef[0]; + } - protected abstract Object getInternal(Object key); + protected abstract Object get(Object key); - protected abstract Object getInternal(K key, Function loader); + protected abstract void put(Object key, Object value); } static enum Static { @@ -127,13 +134,13 @@ private static BeanRefCache loadInstance() { return new BeanRefCache.Abs() { @Override - protected Object getInternal(Object key) { + protected Object get(Object key) { return cache.get(key); } @Override - protected Object getInternal(K key, Function loader) { - return cache.computeIfAbsent(key, nil -> loader.apply(key)); + protected void put(Object key, Object value) { + cache.put(key, value); } }; } From 56c74a9c0a2ca1d6a21169a26fc41eff724a9334 Mon Sep 17 00:00:00 2001 From: reggie pierce Date: Tue, 23 Aug 2022 14:58:51 -0400 Subject: [PATCH 17/19] method access --- .../throwable/beanref/BeanProperty.java | 209 ++++++++++-------- .../beanref/BeanPropertyResolver.java | 7 + .../beanref/DynamicBeanPropertyResolver.java | 8 + 3 files changed, 128 insertions(+), 96 deletions(-) diff --git a/src/main/java/com/github/throwable/beanref/BeanProperty.java b/src/main/java/com/github/throwable/beanref/BeanProperty.java index 5fa1d90..9cbab05 100644 --- a/src/main/java/com/github/throwable/beanref/BeanProperty.java +++ b/src/main/java/com/github/throwable/beanref/BeanProperty.java @@ -1,109 +1,126 @@ package com.github.throwable.beanref; +import java.lang.reflect.Method; import java.util.Objects; +import java.util.Optional; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.Supplier; +import com.github.throwable.beanref.BeanPropertyResolver.SetterWriteAccessor; +import com.github.throwable.beanref.DynamicBeanPropertyResolver.GetterReadAccessor; + /** * BeanProperty represents a direct reference to a bean's property. + * * @param bean class * @param property's type */ -public class BeanProperty extends BeanPath -{ - private final Class beanClass; - private final Class type; - private final String name; - private final Function readAccessor; - /* Nullable */ - private final BiConsumer writeAccessor; - /* Resolved lazily */ - private final Supplier> instantiatorSupplier; - - - BeanProperty(Class beanClass, Class type, String name, - Function readAccessor, - /*Nullable*/ BiConsumer writeAccessor, - Supplier> instantiatorSupplier) - { - super(); - this.beanClass = beanClass; - this.type = type; - this.name = name; - this.readAccessor = readAccessor; - this.writeAccessor = writeAccessor; - this.instantiatorSupplier = instantiatorSupplier; - } - - @Override - public Class getBeanClass() { - return beanClass; - } - - @Override - public Class getType() { - return type; - } - - /** - * @return the name of bean's property - */ - public String getName() { - return name; - } - - @Override - public String getPath() { - return name; - } - - /** - * @return read accessor to a property - */ - public Function getReadAccessor() { - return readAccessor; - } - - /** - * @return write accessor to a property (mutator) or null if property is read-only - */ - public BiConsumer getWriteAccessor() { - return writeAccessor; - } - - @Override - public boolean isReadOnly() { - return writeAccessor == null; - } - - @Override - public TYPE get(BEAN bean) { - return readAccessor.apply(bean); - } - - @Override - public void set(BEAN bean, TYPE value) { - if (isReadOnly()) - throw new ReadOnlyPropertyException("Property '" + toString() + "' is read-only"); - writeAccessor.accept(bean, value); - } - - Supplier getInstantiator() { - return instantiatorSupplier.get(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - BeanProperty that = (BeanProperty) o; - return beanClass.equals(that.beanClass) && - name.equals(that.name); - } - - @Override - public int hashCode() { - return Objects.hash(beanClass, name); - } +public class BeanProperty extends BeanPath { + private final Class beanClass; + private final Class type; + private final String name; + private final Function readAccessor; + /* Nullable */ + private final BiConsumer writeAccessor; + /* Resolved lazily */ + private final Supplier> instantiatorSupplier; + + BeanProperty(Class beanClass, Class type, String name, Function readAccessor, + /* Nullable */ BiConsumer writeAccessor, Supplier> instantiatorSupplier) { + super(); + this.beanClass = beanClass; + this.type = type; + this.name = name; + this.readAccessor = readAccessor; + this.writeAccessor = writeAccessor; + this.instantiatorSupplier = instantiatorSupplier; + } + + @Override + public Class getBeanClass() { + return beanClass; + } + + @Override + public Class getType() { + return type; + } + + /** + * @return the name of bean's property + */ + public String getName() { + return name; + } + + @Override + public String getPath() { + return name; + } + + /** + * @return read accessor to a property + */ + public Function getReadAccessor() { + return readAccessor; + } + + public Optional tryGetReadMethod() { + return Optional.ofNullable(getReadAccessor()) + .filter(GetterReadAccessor.class::isInstance) + .map(GetterReadAccessor.class::cast) + .map(GetterReadAccessor::getGetterMethod); + } + + /** + * @return write accessor to a property (mutator) or null if property is + * read-only + */ + public BiConsumer getWriteAccessor() { + return writeAccessor; + } + + public Optional tryGetWriteMethod() { + return Optional.ofNullable(getWriteAccessor()) + .filter(SetterWriteAccessor.class::isInstance) + .map(SetterWriteAccessor.class::cast) + .map(SetterWriteAccessor::getSetterMethod); + } + + @Override + public boolean isReadOnly() { + return writeAccessor == null; + } + + @Override + public TYPE get(BEAN bean) { + return readAccessor.apply(bean); + } + + @Override + public void set(BEAN bean, TYPE value) { + if (isReadOnly()) + throw new ReadOnlyPropertyException("Property '" + toString() + "' is read-only"); + writeAccessor.accept(bean, value); + } + + Supplier getInstantiator() { + return instantiatorSupplier.get(); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + BeanProperty that = (BeanProperty) o; + return beanClass.equals(that.beanClass) && name.equals(that.name); + } + + @Override + public int hashCode() { + return Objects.hash(beanClass, name); + } } diff --git a/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java b/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java index c66e0ad..d121412 100644 --- a/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java +++ b/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java @@ -201,6 +201,13 @@ public void accept(BEAN bean, TYPE value) { throw new RuntimeException(e.getTargetException()); } } + + /** + * @return the setterMethod + */ + public Method getSetterMethod() { + return setterMethod; + } } public static class InstantiatorResolver implements Supplier> { diff --git a/src/main/java/com/github/throwable/beanref/DynamicBeanPropertyResolver.java b/src/main/java/com/github/throwable/beanref/DynamicBeanPropertyResolver.java index 689f528..bc4b3df 100644 --- a/src/main/java/com/github/throwable/beanref/DynamicBeanPropertyResolver.java +++ b/src/main/java/com/github/throwable/beanref/DynamicBeanPropertyResolver.java @@ -103,5 +103,13 @@ public TYPE apply(BEAN bean) { throw new RuntimeException(e.getTargetException()); } } + + /** + * @return the getterMethod + */ + public Method getGetterMethod() { + return getterMethod; + } + } } From cc74f92ca247acc82c1b6e530573531a898860a5 Mon Sep 17 00:00:00 2001 From: reggie pierce Date: Wed, 24 Aug 2022 14:51:55 -0400 Subject: [PATCH 18/19] Update BeanRefUtils.java --- .../com/github/throwable/beanref/lfp/BeanRefUtils.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/github/throwable/beanref/lfp/BeanRefUtils.java b/src/main/java/com/github/throwable/beanref/lfp/BeanRefUtils.java index 306ec93..509ec67 100644 --- a/src/main/java/com/github/throwable/beanref/lfp/BeanRefUtils.java +++ b/src/main/java/com/github/throwable/beanref/lfp/BeanRefUtils.java @@ -64,14 +64,12 @@ public static Class getNamedClassType(Class classType) { var ifaces = classType.getInterfaces(); if (ifaces.length == 0) { var superclass = classType.getSuperclass(); - if (superclass == null) - throw new IllegalArgumentException(String.format("superclass not found. classType:%s", classType)); - return (Class) getNamedClassType(superclass); + if (superclass != null) + return (Class) getNamedClassType(superclass); } else if (ifaces.length == 1) return (Class) getNamedClassType(ifaces[0]); - else - // if proxy this is the best we can do - return (Class) classType; + // if proxy/system class this is the best we can do + return (Class) classType; } public static ClassLoader getDefaultClassLoader() { From 6203cf25ec3f66353e16571bab7ecca727e49ab4 Mon Sep 17 00:00:00 2001 From: reggie pierce Date: Mon, 26 Sep 2022 20:32:53 -0400 Subject: [PATCH 19/19] cache improvements --- .../throwable/beanref/lfp/BeanRefCache.java | 84 +++++++++---------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/src/main/java/com/github/throwable/beanref/lfp/BeanRefCache.java b/src/main/java/com/github/throwable/beanref/lfp/BeanRefCache.java index 400394f..0ed855c 100644 --- a/src/main/java/com/github/throwable/beanref/lfp/BeanRefCache.java +++ b/src/main/java/com/github/throwable/beanref/lfp/BeanRefCache.java @@ -4,15 +4,19 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; import java.lang.reflect.InvocationTargetException; import java.util.Comparator; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; +import java.util.Optional; import java.util.ServiceLoader; import java.util.WeakHashMap; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.Stream; import de.adito.picoservice.IPicoRegistration; @@ -40,39 +44,6 @@ int priority() } - public static abstract class Abs implements BeanRefCache { - - private final Map lockMap = new ConcurrentHashMap(); - - @SuppressWarnings("unchecked") - @Override - public V get(K key, Function loader) { - Objects.requireNonNull(key); - Objects.requireNonNull(loader); - { - var value = get(key); - if (value != null) - return (V) value; - } - Object[] resultRef = new Object[1]; - lockMap.compute(key, (nilk, nilv) -> { - var value = get(key); - if (value == null) { - value = loader.apply(key); - put(key, value); - } - resultRef[0] = value; - return null; - }); - return (V) resultRef[0]; - - } - - protected abstract Object get(Object key); - - protected abstract void put(Object key, Object value); - } - static enum Static { ; @@ -104,7 +75,7 @@ private static BeanRefCache loadInstance() { }).filter(Objects::nonNull); registrationAnnos = registrationAnnos.filter(ent -> { var registration = ent.getKey(); - if (!BeanRefCache.class.isAssignableFrom(ent.getKey().getAnnotatedClass())) + if (!BeanRefCache.class.isAssignableFrom(registration.getAnnotatedClass())) return false; var anno = ent.getValue(); return Stream.of(anno.requiredClassNames()).allMatch(v -> BeanRefUtils.classForName(v, true) != null); @@ -130,17 +101,46 @@ private static BeanRefCache loadInstance() { }).map(v -> (BeanRefCache) v).findFirst().orElse(null); if (beanRefCache != null) return beanRefCache; - var cache = new WeakHashMap(); - return new BeanRefCache.Abs() { + return createBeanRefCache(); + } - @Override - protected Object get(Object key) { - return cache.get(key); - } + private static BeanRefCache createBeanRefCache() { + return new BeanRefCache() { + + private final Map lockMap = new ConcurrentHashMap(); + private final Map>> storeMap = new WeakHashMap<>(); + @SuppressWarnings({ "unchecked", "rawtypes" }) @Override - protected void put(Object key, Object value) { - cache.put(key, value); + public V get(K key, Function loader) { + Objects.requireNonNull(key); + Objects.requireNonNull(loader); + Optional valueOp = Optional.ofNullable(storeMap.get(key)).map(Reference::get).orElse(null); + if (valueOp == null) { + Supplier optionalLoader = () -> Optional.ofNullable(loader.apply(key)); + var valueOpRef = new Optional[1]; + lockMap.compute(key, (nilk, nilv) -> { + valueOpRef[0] = storeMap.computeIfAbsent(key, nil -> { + return new WeakReference<>(optionalLoader.get()); + }).get(); + return null; + }); + valueOp = valueOpRef[0]; + if (valueOp == null) { + lockMap.compute(key, (nilk, nilv) -> { + storeMap.compute(key, (nilk1, nilk2) -> { + valueOpRef[0] = optionalLoader.get(); + return new WeakReference<>(valueOpRef[0]); + }); + return null; + }); + valueOp = valueOpRef[0]; + } + + } + if (valueOp.isEmpty()) + return null; + return (V) valueOp.get(); } }; }