/*
* Copyright 2017 the original author or authors.
*
* Licensed 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 org.springframework.data.cassandra.repository.support;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.data.cassandra.repository.MapId;
import org.springframework.util.StringUtils;
/**
* Delegate class for dynamic proxies of id interfaces; delegates to {@link BasicMapId}.
*
* @see MapIdFactory#id(Class)
* @see MapIdFactory#id(Class, ClassLoader)
* @author Matthew T. Adams
* @author Mark Paluch
*/
class MapIdProxyDelegate implements InvocationHandler {
private static final Map<Signature, Signature> MAP_ID_SIGNATURES;
static {
Method[] mapIdMethods = MapId.class.getMethods();
MAP_ID_SIGNATURES = Arrays.stream(mapIdMethods).map(m -> new Signature(m, true))
.collect(Collectors.toMap(o -> o, o -> o));
}
private MapId delegate = new BasicMapId();
private Class<?> idInterface;
public MapIdProxyDelegate(Class<?> idInterface) {
this.idInterface = idInterface;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (isMapIdMethod(method)) {
return method.invoke(delegate, args);
}
if (args != null && args.length > 1) {
throw new IllegalArgumentException(
String.format("Method [%s] on interface [%s] must take zero or one argument", method, idInterface));
}
boolean isSetter = args != null && args.length == 1;
if (isSetter) {
invokeSetter(method, args[0]);
return void.class.equals(method.getReturnType()) ? null : proxy;
}
return invokeGetter(method);
}
public boolean isMapIdMethod(Method method) {
return MAP_ID_SIGNATURES.containsKey(new Signature(method, true));
}
public Object invokeGetter(Method method) {
String name = method.getName();
if (name.startsWith("get")) {
if (name.length() == 3) {
throw new IllegalArgumentException(String.format("Method [%s] on interface [%s] must be of form "
+ "'<PropertyType> get<PropertyName>()' or " + "'<PropertyType> <propertyName>()'", name, idInterface));
}
name = StringUtils.uncapitalize(name.substring(3));
}
return delegate.get(name);
}
public void invokeSetter(Method method, Object value) {
String name = method.getName();
int minLength = 1;
boolean isSet = name.startsWith("set");
boolean isWith = name.startsWith("with");
minLength += isSet ? 3 : isWith ? 4 : 0;
int length = name.length();
if (isSet || isWith) {
if (length < minLength) {
throw new IllegalArgumentException(String.format(
"Method [%s] on interface [%s] must be of form " + "'<IdType|void> set<PropertyName>(<PropertyType>)', "
+ "'<IdType|void> with<PropertyName>(<PropertyType>)' or "
+ "'<IdType|void> <propertyName>(<PropertyType>)'",
name, idInterface));
}
name = StringUtils.uncapitalize(name.substring(minLength - 1));
}
if (value == null) {
delegate.put(name, null);
return;
}
delegate.put(name, value);
}
}
class Signature {
String name;
Class<?>[] argTypes;
Class<?> returnType;
Signature(Method method, boolean includeReturnType) {
this(method.getName(), method.getParameterTypes(), includeReturnType ? method.getReturnType() : null);
}
Signature(String name, Class<?>[] argTypes, Class<?> returnType) {
this.name = name;
this.argTypes = argTypes;
this.returnType = returnType;
}
@Override
public String toString() {
return String.format("%s %s(%s)", returnType, name, Arrays.toString(argTypes));
}
@Override
public boolean equals(Object that) {
if (that == null) {
return false;
}
if (this == that) {
return true;
}
if (!(that instanceof Signature)) {
return false;
}
Signature that_ = (Signature) that;
if (!this.name.equals(that_.name)) {
return false;
}
if ((this.argTypes == null && that_.argTypes != null) || (this.argTypes != null && that_.argTypes == null)) {
return false;
}
if (this.argTypes != null) {
if (this.argTypes.length != that_.argTypes.length) {
return false;
}
for (int i = 0; i < this.argTypes.length; i++) {
if (!this.argTypes[i].equals(that_.argTypes[i])) {
return false;
}
}
}
if (this.returnType == null) {
return that_.returnType == null;
}
return this.returnType.equals(that_.returnType);
}
@Override
public int hashCode() {
int hash = 37 ^ name.hashCode();
if (argTypes != null) {
for (Class<?> c : argTypes) {
hash ^= c.hashCode();
}
}
if (returnType != null) {
hash ^= returnType.hashCode();
}
return hash;
}
}