/*
* Copyright 2013 Andriy Vityuk
*
* 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 com.vityuk.ginger.proxy;
import com.vityuk.ginger.InvalidParameterTypeException;
import com.vityuk.ginger.InvalidReturnTypeException;
import com.vityuk.ginger.Localizable;
import com.vityuk.ginger.PluralCount;
import com.vityuk.ginger.Select;
import com.vityuk.ginger.provider.LocalizationProvider;
import com.vityuk.ginger.util.GingerUtils;
import com.vityuk.ginger.util.MiscUtils;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.FixedValue;
import net.sf.cglib.proxy.InvocationHandler;
import net.sf.cglib.proxy.NoOp;
import org.apache.commons.lang3.ArrayUtils;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class CglibProxyBuilder implements ProxyBuilder {
@Override
public <T> T createProxy(Class<T> localizable, LocalizationProvider localizationProvider) {
Method[] methods = localizable.getDeclaredMethods();
List<Callback> callbacks = new ArrayList<Callback>(methods.length + 1);
final Map<Method, Integer> method2CallbackIndex = new HashMap<Method, Integer>(methods.length);
callbacks.add(NoOp.INSTANCE);
for (Method localizableMethod : methods) {
callbacks.add(createCallback(localizationProvider, localizableMethod));
method2CallbackIndex.put(localizableMethod, callbacks.size() - 1);
}
CallbackFilter callbackFilter = new CallbackFilter() {
@Override
public int accept(Method method) {
Integer index = method2CallbackIndex.get(method);
return index == null ? 0 : index;
}
};
Enhancer enhancer = new Enhancer();
enhancer.setInterfaces(new Class[]{localizable});
enhancer.setCallbackFilter(callbackFilter);
enhancer.setCallbacks(callbacks.toArray(new Callback[callbacks.size()]));
enhancer.setUseFactory(false);
@SuppressWarnings("unchecked")
T instance = (T) enhancer.create();
return instance;
}
private Callback createCallback(LocalizationProvider localizationProvider, Method method) {
String key = GingerUtils.createKeyFromMethod(method);
Class<?> type = method.getReturnType();
if (GingerUtils.isConstantMethod(method)) {
return getConstantCallback(localizationProvider, method, type, key);
} else {
return getMessageCallback(localizationProvider, method, type, key);
}
}
private Callback getMessageCallback(LocalizationProvider localizationProvider, Method method, Class<?> type, String key) {
if (type != String.class) {
throw new InvalidReturnTypeException(type, method);
}
int selectorParameterIndex = GingerUtils.indexOfParameterAnnotation(method, Select.class);
if (selectorParameterIndex != -1) {
return createSelectorMessageLookupCallback(localizationProvider, method, key, selectorParameterIndex);
}
int pluralCountParameterIndex = GingerUtils.indexOfParameterAnnotation(method, PluralCount.class);
if (pluralCountParameterIndex != -1) {
return createPluralMessageLookupCallback(localizationProvider, method, key, pluralCountParameterIndex);
}
return new MessageLookupCallback(localizationProvider, key);
}
private Callback createSelectorMessageLookupCallback(LocalizationProvider localizationProvider, Method method, String key, int parameterIndex) {
Class<?> parameterType = method.getParameterTypes()[parameterIndex];
if (parameterType.isPrimitive() || MiscUtils.isWrapperType(parameterType)) {
// TODO: consider more informative exception
throw new InvalidParameterTypeException(parameterType, method);
}
return new SelectorMessageLookupCallback(localizationProvider, key, parameterIndex);
}
private Callback createPluralMessageLookupCallback(LocalizationProvider localizationProvider, Method method, String key, int parameterIndex) {
Class<?> parameterType = method.getParameterTypes()[parameterIndex];
if (!GingerUtils.isIntNumericType(parameterType)) {
// TODO: consider more informative exception
throw new InvalidParameterTypeException(parameterType, method);
}
return new PluralMessageLookupCallback(localizationProvider, key, parameterIndex);
}
private Callback getConstantCallback(LocalizationProvider localizationProvider, Method method, Class<?> type, String key) {
if (type == String.class) {
return new StringConstantLookupCallback(localizationProvider, key);
}
if (type == Boolean.class) {
return new BooleanConstantLookupCallback(localizationProvider, key);
}
if (type == Integer.class) {
return new IntegerConstantLookupCallback(localizationProvider, key);
}
if (type == Long.class) {
return new LongConstantLookupCallback(localizationProvider, key);
}
if (type == Float.class) {
return new FloatConstantLookupCallback(localizationProvider, key);
}
if (type == Double.class) {
return new DoubleConstantLookupCallback(localizationProvider, key);
}
if (type == List.class) {
// TODO: generics support
return new StringListConstantLookupCallback(localizationProvider, key);
}
if (type == Map.class) {
// TODO: generics support
return new StringMapConstantLookupCallback(localizationProvider, key);
}
throw new InvalidReturnTypeException(type, method);
}
private static abstract class AbstractLookupCallback implements Callback {
protected final LocalizationProvider localizationProvider;
protected final String key;
public AbstractLookupCallback(LocalizationProvider localizationProvider, String key) {
this.localizationProvider = localizationProvider;
this.key = key;
}
}
private static abstract class AbstractConstantLookupCallback extends AbstractLookupCallback implements FixedValue {
public AbstractConstantLookupCallback(LocalizationProvider localizationProvider, String key) {
super(localizationProvider, key);
}
}
private static class StringConstantLookupCallback extends AbstractConstantLookupCallback {
public StringConstantLookupCallback(LocalizationProvider localizationProvider, String key) {
super(localizationProvider, key);
}
@Override
public Object loadObject() throws Exception {
return localizationProvider.getString(key);
}
}
private static class BooleanConstantLookupCallback extends AbstractConstantLookupCallback {
public BooleanConstantLookupCallback(LocalizationProvider localizationProvider, String key) {
super(localizationProvider, key);
}
@Override
public Object loadObject() throws Exception {
return localizationProvider.getBoolean(key);
}
}
private static class IntegerConstantLookupCallback extends AbstractConstantLookupCallback {
public IntegerConstantLookupCallback(LocalizationProvider localizationProvider, String key) {
super(localizationProvider, key);
}
@Override
public Object loadObject() throws Exception {
return localizationProvider.getInteger(key);
}
}
private static class LongConstantLookupCallback extends AbstractConstantLookupCallback {
public LongConstantLookupCallback(LocalizationProvider localizationProvider, String key) {
super(localizationProvider, key);
}
@Override
public Object loadObject() throws Exception {
return localizationProvider.getLong(key);
}
}
private static class FloatConstantLookupCallback extends AbstractConstantLookupCallback {
public FloatConstantLookupCallback(LocalizationProvider localizationProvider, String key) {
super(localizationProvider, key);
}
@Override
public Object loadObject() throws Exception {
return localizationProvider.getFloat(key);
}
}
private static class DoubleConstantLookupCallback extends AbstractConstantLookupCallback {
public DoubleConstantLookupCallback(LocalizationProvider localizationProvider, String key) {
super(localizationProvider, key);
}
@Override
public Object loadObject() throws Exception {
return localizationProvider.getDouble(key);
}
}
private static class StringListConstantLookupCallback extends AbstractConstantLookupCallback {
public StringListConstantLookupCallback(LocalizationProvider localizationProvider, String key) {
super(localizationProvider, key);
}
@Override
public Object loadObject() throws Exception {
return localizationProvider.getStringList(key);
}
}
private static class StringMapConstantLookupCallback extends AbstractConstantLookupCallback {
public StringMapConstantLookupCallback(LocalizationProvider localizationProvider, String key) {
super(localizationProvider, key);
}
@Override
public Object loadObject() throws Exception {
return localizationProvider.getStringMap(key);
}
}
private static class MessageLookupCallback extends AbstractLookupCallback implements InvocationHandler {
public MessageLookupCallback(LocalizationProvider localizationProvider, String key) {
super(localizationProvider, key);
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
return localizationProvider.getMessage(key, objects);
}
}
private static class SelectorMessageLookupCallback extends AbstractLookupCallback implements InvocationHandler {
private final int selectorParameterIndex;
public SelectorMessageLookupCallback(LocalizationProvider localizationProvider, String key,
int selectorParameterIndex) {
super(localizationProvider, key);
this.selectorParameterIndex = selectorParameterIndex;
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
String selector = extractSelector(objects);
Object[] parameters = extractParameters(objects);
return localizationProvider.getSelectedMessage(key, selector, parameters);
}
private String extractSelector(Object[] objects) {
return String.valueOf(objects[selectorParameterIndex]);
}
private Object[] extractParameters(Object[] objects) {
return ArrayUtils.remove(objects, selectorParameterIndex);
}
}
private static class PluralMessageLookupCallback extends AbstractLookupCallback implements InvocationHandler {
private final int pluralCountParameterIndex;
public PluralMessageLookupCallback(LocalizationProvider localizationProvider, String key,
int pluralCountParameterIndex) {
super(localizationProvider, key);
this.pluralCountParameterIndex = pluralCountParameterIndex;
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
Number pluralCount = extractPluralCount(objects);
Object[] parameters = extractParameters(objects);
return localizationProvider.getPluralMessage(key, pluralCount, parameters);
}
private Number extractPluralCount(Object[] objects) {
return (Number) objects[pluralCountParameterIndex];
}
private Object[] extractParameters(Object[] objects) {
return ArrayUtils.remove(objects, pluralCountParameterIndex);
}
}
}