Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hazelcast.org.apache.calcite.util;
import com.hazelcast.org.apache.calcite.sql.validate.SqlConformance;
import com.hazelcast.org.apache.calcite.sql.validate.SqlConformanceEnum;
import com.hazelcast.com.google.common.collect.ImmutableMap;
import com.hazelcast.com.google.common.collect.ImmutableSortedMap;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.invoke.MethodHandle;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
/** Utilities for creating immutable beans. */
public class ImmutableBeans {
private ImmutableBeans() {}
/** Creates an immutable bean that implements a given interface. */
public static T create(Class beanClass) {
if (!beanClass.isInterface()) {
throw new IllegalArgumentException("must be interface");
}
final ImmutableSortedMap.Builder propertyNameBuilder =
ImmutableSortedMap.naturalOrder();
final ImmutableMap.Builder> handlers =
ImmutableMap.builder();
final Set requiredPropertyNames = new HashSet<>();
// First pass, add "get" methods and build a list of properties.
for (Method method : beanClass.getMethods()) {
if (!Modifier.isPublic(method.getModifiers())) {
continue;
}
final Property property = method.getAnnotation(Property.class);
if (property == null) {
continue;
}
final boolean hasNonnull = hasAnnotation(method, "javax.annotation.Nonnull");
final Mode mode;
final Object defaultValue = getDefault(method);
final String methodName = method.getName();
final String propertyName;
if (methodName.startsWith("get")) {
propertyName = methodName.substring("get".length());
mode = Mode.GET;
} else if (methodName.startsWith("is")) {
propertyName = methodName.substring("is".length());
mode = Mode.GET;
} else if (methodName.startsWith("with")) {
continue;
} else {
propertyName = methodName.substring(0, 1).toUpperCase(Locale.ROOT)
+ methodName.substring(1);
mode = Mode.GET;
}
final Class> propertyType = method.getReturnType();
if (method.getParameterCount() > 0) {
throw new IllegalArgumentException("method '" + methodName
+ "' has too many parameters");
}
final boolean required = property.required()
|| propertyType.isPrimitive()
|| hasNonnull;
if (required) {
requiredPropertyNames.add(propertyName);
}
propertyNameBuilder.put(propertyName, propertyType);
final Object defaultValue2 =
convertDefault(defaultValue, propertyName, propertyType);
handlers.put(method, (bean, args) -> {
switch (mode) {
case GET:
final Object v = bean.map.get(propertyName);
if (v != null) {
return v;
}
if (required && defaultValue == null) {
throw new IllegalArgumentException("property '" + propertyName
+ "' is required and has no default value");
}
return defaultValue2;
default:
throw new AssertionError();
}
});
}
// Second pass, add "with" methods if they correspond to a property.
final ImmutableMap propertyNames =
propertyNameBuilder.build();
for (Method method : beanClass.getMethods()) {
if (!Modifier.isPublic(method.getModifiers())
|| method.isDefault()) {
continue;
}
final Mode mode;
final String propertyName;
final String methodName = method.getName();
if (methodName.startsWith("get")) {
continue;
} else if (methodName.startsWith("is")) {
continue;
} else if (methodName.startsWith("with")) {
propertyName = methodName.substring("with".length());
mode = Mode.WITH;
} else if (methodName.startsWith("set")) {
propertyName = methodName.substring("set".length());
mode = Mode.SET;
} else {
continue;
}
final Class propertyClass = propertyNames.get(propertyName);
if (propertyClass == null) {
throw new IllegalArgumentException("cannot find property '"
+ propertyName + "' for method '" + methodName
+ "'; maybe add a method 'get" + propertyName + "'?'");
}
switch (mode) {
case WITH:
if (method.getReturnType() != beanClass) {
throw new IllegalArgumentException("method '" + methodName
+ "' should return the bean class '" + beanClass
+ "', actually returns '" + method.getReturnType() + "'");
}
break;
case SET:
if (method.getReturnType() != void.class) {
throw new IllegalArgumentException("method '" + methodName
+ "' should return void, actually returns '"
+ method.getReturnType() + "'");
}
}
if (method.getParameterCount() != 1) {
throw new IllegalArgumentException("method '" + methodName
+ "' should have one parameter, actually has "
+ method.getParameterCount());
}
final Class propertyType = propertyNames.get(propertyName);
if (!method.getParameterTypes()[0].equals(propertyType)) {
throw new IllegalArgumentException("method '" + methodName
+ "' should have parameter of type " + propertyType
+ ", actually has " + method.getParameterTypes()[0]);
}
final boolean required = requiredPropertyNames.contains(propertyName);
handlers.put(method, (bean, args) -> {
switch (mode) {
case WITH:
final Object v = bean.map.get(propertyName);
final ImmutableMap.Builder mapBuilder;
if (v != null) {
if (v.equals(args[0])) {
return bean.asBean();
}
// the key already exists; painstakingly copy all entries
// except the one with this key
mapBuilder = ImmutableMap.builder();
bean.map.forEach((key, value) -> {
if (!key.equals(propertyName)) {
mapBuilder.put(key, value);
}
});
} else {
// the key does not exist; put the whole map into the builder
mapBuilder = ImmutableMap.builder()
.putAll(bean.map);
}
if (args[0] != null) {
mapBuilder.put(propertyName, args[0]);
} else {
if (required) {
throw new IllegalArgumentException("cannot set required "
+ "property '" + propertyName + "' to null");
}
}
final ImmutableMap map = mapBuilder.build();
return bean.withMap(map).asBean();
default:
throw new AssertionError();
}
});
}
// Third pass, add default methods.
for (Method method : beanClass.getMethods()) {
if (method.isDefault()) {
final MethodHandle methodHandle;
try {
methodHandle = Compatible.INSTANCE.lookupPrivate(beanClass)
.unreflectSpecial(method, beanClass);
} catch (Throwable throwable) {
throw new RuntimeException("while binding method " + method,
throwable);
}
handlers.put(method, (bean, args) -> {
try {
return methodHandle.bindTo(bean.asBean())
.invokeWithArguments(args);
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable throwable) {
throw new RuntimeException("while invoking method " + method,
throwable);
}
});
}
}
handlers.put(getMethod(Object.class, "toString"),
(bean, args) -> new TreeMap<>(bean.map).toString());
handlers.put(getMethod(Object.class, "hashCode"),
(bean, args) -> new TreeMap<>(bean.map).hashCode());
handlers.put(getMethod(Object.class, "equals", Object.class),
(bean, args) -> bean == args[0]
// Use a little arg-swap trick because it's difficult to get inside
// a proxy
|| beanClass.isInstance(args[0])
&& args[0].equals(bean.map)
// Strictly, a bean should not equal a Map but it's convenient
|| args[0] instanceof Map
&& bean.map.equals(args[0]));
return makeBean(beanClass, handlers.build(), ImmutableMap.of());
}
/** Looks for an annotation by class name.
* Useful if you don't want to depend on the class
* (e.g. "javax.annotation.Nonnull") at compile time. */
private static boolean hasAnnotation(Method method, String className) {
for (Annotation annotation : method.getDeclaredAnnotations()) {
if (annotation.annotationType().getName().equals(className)) {
return true;
}
}
return false;
}
private static Object getDefault(Method method) {
Object defaultValue = null;
final IntDefault intDefault = method.getAnnotation(IntDefault.class);
if (intDefault != null) {
defaultValue = intDefault.value();
}
final BooleanDefault booleanDefault =
method.getAnnotation(BooleanDefault.class);
if (booleanDefault != null) {
defaultValue = booleanDefault.value();
}
final StringDefault stringDefault =
method.getAnnotation(StringDefault.class);
if (stringDefault != null) {
defaultValue = stringDefault.value();
}
final EnumDefault enumDefault =
method.getAnnotation(EnumDefault.class);
if (enumDefault != null) {
defaultValue = enumDefault.value();
}
return defaultValue;
}
private static Object convertDefault(Object defaultValue, String propertyName,
Class> propertyType) {
if (propertyType.equals(SqlConformance.class)) {
// Workaround for SqlConformance because it is actually not a Enum.
propertyType = SqlConformanceEnum.class;
}
if (defaultValue == null || !propertyType.isEnum()) {
return defaultValue;
}
for (Object enumConstant : propertyType.getEnumConstants()) {
if (((Enum) enumConstant).name().equals(defaultValue)) {
return enumConstant;
}
}
throw new IllegalArgumentException("property '" + propertyName
+ "' is an enum but its default value " + defaultValue
+ " is not a valid enum constant");
}
private static Method getMethod(Class