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..06489ed 100644 --- a/pom.xml +++ b/pom.xml @@ -1,110 +1,123 @@ - 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 - + + 11 + 11 + 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/ - - + + + + + + jitpack.io + https://jitpack.io + + - - - junit - junit - 4.13.2 - test - - + + + com.github.regbo + lfp-picoservice + f91ff54 + + + 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/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 4f68f7d..d121412 100644 --- a/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java +++ b/src/main/java/com/github/throwable/beanref/BeanPropertyResolver.java @@ -1,336 +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.Arrays; +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; -final class BeanPropertyResolver { - @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; - } - - private 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)); - } - - - private 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)); - } - - - private 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; - } - - - 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; - } - - - 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; - } - } - } - - - private 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") - private 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()); - } - }; - } - } - - - private 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); - } - } +import com.github.throwable.beanref.lfp.BeanRefCache; +import com.github.throwable.beanref.lfp.BeanRefUtils; + +public class BeanPropertyResolver { + + protected BeanPropertyResolver() {} + + protected static BeanProperty resolveBeanProperty( + MethodReferenceLambda methodReferenceLambda) { + Objects.requireNonNull(methodReferenceLambda); + String hash = BeanRefUtils.hash(methodReferenceLambda); + Object key = Arrays.asList(hash, "resolveBeanProperty"); + return BeanRefCache.instance().get(key, nil -> { + return resolveBeanPropertyImpl(methodReferenceLambda); + }); + } + + 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)); + Object key = Arrays.asList(hash, "resolveCollectionBeanProperty"); + return BeanRefCache.instance().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 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) { + String className = getContainingClassName(lambda).replaceAll("/", "."); + return (Class) BeanRefUtils.classForName(className, false); + } + + 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()); + } + } + + /** + * @return the setterMethod + */ + public Method getSetterMethod() { + return setterMethod; + } + } + + 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 5ef1e0b..bc4b3df 100644 --- a/src/main/java/com/github/throwable/beanref/DynamicBeanPropertyResolver.java +++ b/src/main/java/com/github/throwable/beanref/DynamicBeanPropertyResolver.java @@ -3,104 +3,113 @@ 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.Optional; -import java.util.concurrent.ConcurrentHashMap; +import java.util.Objects; 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.throwable.beanref.lfp.BeanRefCache; +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; - } + @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) { - Map beanProperties = resolvedBeanPropertiesCache.get(beanClass); - if (beanProperties == null) { - beanProperties = resolveAllBeanPropertiesImpl(beanClass); - beanProperties = Optional.ofNullable( - resolvedBeanPropertiesCache.putIfAbsent(beanClass, beanProperties) - ).orElse(beanProperties); - } - return beanProperties; - } + static Map> resolveAllBeanProperties(Class beanClass) { + { + 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); + }); + } + 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 GetterReadAccessor(Method getterMethod) { + this.getterMethod = getterMethod; + } - public static class GetterReadAccessor implements Function { - private final Method 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()); + } + } - public GetterReadAccessor(Method getterMethod) { - this.getterMethod = getterMethod; - } + /** + * @return the getterMethod + */ + public Method getGetterMethod() { + return 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()); - } - } - } + } } 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..0ed855c --- /dev/null +++ b/src/main/java/com/github/throwable/beanref/lfp/BeanRefCache.java @@ -0,0 +1,150 @@ +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.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; +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 classLoaders = Stream.concat(Stream.of((ClassLoader) null), BeanRefUtils.streamDefaultClassLoaders()) + .distinct(); + var serviceProviers = classLoaders.flatMap(cl -> { + var serviceLoader = cl == null ? ServiceLoader.load(IPicoRegistration.class) + : ServiceLoader.load(IPicoRegistration.class, cl); + return serviceLoader.stream(); + }).distinct(); + 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(registration.getAnnotatedClass())) + return false; + var anno = ent.getValue(); + return Stream.of(anno.requiredClassNames()).allMatch(v -> BeanRefUtils.classForName(v, true) != null); + }); + registrationAnnos = registrationAnnos + .sorted(Comparator., Integer>comparing(ent -> { + var anno = ent.getValue(); + return -1 * anno.priority(); + })); + var beanRefCache = registrationAnnos.map(Entry::getKey).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; + return createBeanRefCache(); + } + + private static BeanRefCache createBeanRefCache() { + return new BeanRefCache() { + + private final Map lockMap = new ConcurrentHashMap(); + private final Map>> storeMap = new WeakHashMap<>(); + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + 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(); + } + }; + } + + } + +} 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..509ec67 --- /dev/null +++ b/src/main/java/com/github/throwable/beanref/lfp/BeanRefUtils.java @@ -0,0 +1,116 @@ +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.Proxy; +import java.security.DigestOutputStream; +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; + +@SuppressWarnings("unchecked") +public class BeanRefUtils { + + private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + + 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); + } + } + + private static boolean isNamedClassType(Class classType) { + if (classType == null) + return false; + if (classType.isAnonymousClass()) + return false; + if (classType.isSynthetic()) + return false; + if (Proxy.isProxyClass(classType)) + return false; + return true; + } + + 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) + return (Class) getNamedClassType(superclass); + } else if (ifaces.length == 1) + return (Class) getNamedClassType(ifaces[0]); + // if proxy/system class 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(); + } + + 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; + } +} 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..914cb7f --- /dev/null +++ b/src/test/java/com/github/throwable/beanref/BeanRefTestLFP.java @@ -0,0 +1,35 @@ +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.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(new HashSet>(Arrays.asList(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))); + System.out.println(BeanRefUtils.hash(mrl0)); + System.out.println(BeanRefUtils.hash(mrl1)); + System.out.println(BeanRefUtils.hash((MethodReferenceLambda) Date::getDay)); + } + +}