<commit_before>package com.vityuk.ginger;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
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.NoOp;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;

public class DefaultLocalization implements Localization {
    public static final char PROPERTY_KEY_DELIMETER = '.';
    private final LocalizationProvider localizationProvider;

    public DefaultLocalization(LocalizationProvider localizationProvider) {
        this.localizationProvider = localizationProvider;
    }

    @Override
    public <T extends Localizable> T get(Class<T> localizable) {
        return createLocalizable(localizable);
    }

    @Override
    public String getMessage(String key) {
        return null;  //To change body of implemented methods use File | Settings | File Templates.
    }

    private <T extends Localizable> T createLocalizable(Class<T> localizable) {
        Method[] methods = localizable.getDeclaredMethods();

        List<Callback> callbacks = Lists.newArrayListWithCapacity(methods.length + 1);
        final Map<Method, Integer> method2CallbackIndex = Maps.newHashMapWithExpectedSize(methods.length);

        callbacks.add(NoOp.INSTANCE);
        for (Method localizableMethod : methods) {
            callbacks.add(createCallback(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;
            }
        };

        T instance = createProxy(localizable, callbackFilter, callbacks);
        return instance;
    }

    private <T extends Localizable> T createProxy(Class<T> localizable, CallbackFilter callbackFilter, List<Callback> callbacks) {
        Enhancer enhancer = new Enhancer();
        enhancer.setInterfaces(new Class[]{localizable});
        enhancer.setCallbackFilter(callbackFilter);
        enhancer.setCallbacks(callbacks.toArray(new Callback[0]));
        enhancer.setUseFactory(false);

        return (T) enhancer.create();
    }


    private Callback createCallback(Method method) {
        String key = createKeyFromMethodName(method.getName());
        Class<?> type = method.getReturnType();
        return getCallbackByType(key, type, method);
    }

    private Callback getCallbackByType(String key, Class<?> type, Method method) {
        if (type == String.class) {
            return new StringLookupCallback(localizationProvider, key);
        }
        if (type == Boolean.class) {
            return new BooleanLookupCallback(localizationProvider, key);
        }
        if (type == Integer.class) {
            return new IntLookupCallback(localizationProvider, key);
        }
        if (type == Long.class) {
            return new LongLookupCallback(localizationProvider, key);
        }
        if (type == Float.class) {
            return new FloatLookupCallback(localizationProvider, key);
        }
        if (type == Double.class) {
            return new DoubleLookupCallback(localizationProvider, key);
        }
        if (type == List.class) {
            // TODO: generics support
            return new StringListLookupCallback(localizationProvider, key);
        }
        if (type == Map.class) {
            // TODO: generics support
            return new StringMapLookupCallback(localizationProvider, key);
        }

        throw new UnsupportedTypeException(type, method);
    }

    private static String createKeyFromMethodName(String methodName) {
        String[] words = StringUtils.splitByCharacterTypeCamelCase(methodName);
        StringBuilder keyBuilder = new StringBuilder(methodName.length() + words.length - 1);

        boolean first = true;
        for (String word : words) {
            if (first) {
                first = false;
            } else {
                keyBuilder.append(PROPERTY_KEY_DELIMETER);
            }
            keyBuilder.append(word.toLowerCase());
        }

        return keyBuilder.toString();
    }

    private static abstract class AbstractLookupCallback implements FixedValue {
        protected final LocalizationProvider localizationProvider;
        protected final String key;

        public AbstractLookupCallback(LocalizationProvider localizationProvider, String key) {
            this.localizationProvider = localizationProvider;
            this.key = key;
        }
    }

    private static class StringLookupCallback extends AbstractLookupCallback {
        public StringLookupCallback(LocalizationProvider localizationProvider, String key) {
            super(localizationProvider, key);
        }

        @Override
        public Object loadObject() throws Exception {
            return localizationProvider.getString(key);
        }
    }


    private static class BooleanLookupCallback extends AbstractLookupCallback {
        public BooleanLookupCallback(LocalizationProvider localizationProvider, String key) {
            super(localizationProvider, key);
        }

        @Override
        public Object loadObject() throws Exception {
            return localizationProvider.getBoolean(key);
        }
    }

    private static class IntLookupCallback extends AbstractLookupCallback {
        public IntLookupCallback(LocalizationProvider localizationProvider, String key) {
            super(localizationProvider, key);
        }

        @Override
        public Object loadObject() throws Exception {
            return localizationProvider.getInt(key);
        }
    }


    private static class LongLookupCallback extends AbstractLookupCallback {
        public LongLookupCallback(LocalizationProvider localizationProvider, String key) {
            super(localizationProvider, key);
        }

        @Override
        public Object loadObject() throws Exception {
            return localizationProvider.getLong(key);
        }
    }

    private static class FloatLookupCallback extends AbstractLookupCallback {
        public FloatLookupCallback(LocalizationProvider localizationProvider, String key) {
            super(localizationProvider, key);
        }

        @Override
        public Object loadObject() throws Exception {
            return localizationProvider.getFloat(key);
        }
    }


    private static class DoubleLookupCallback extends AbstractLookupCallback {
        public DoubleLookupCallback(LocalizationProvider localizationProvider, String key) {
            super(localizationProvider, key);
        }

        @Override
        public Object loadObject() throws Exception {
            return localizationProvider.getDouble(key);
        }
    }


    private static class StringListLookupCallback extends AbstractLookupCallback {
        public StringListLookupCallback(LocalizationProvider localizationProvider, String key) {
            super(localizationProvider, key);
        }

        @Override
        public Object loadObject() throws Exception {
            return localizationProvider.getStringList(key);
        }
    }


    private static class StringMapLookupCallback extends AbstractLookupCallback {
        public StringMapLookupCallback(LocalizationProvider localizationProvider, String key) {
            super(localizationProvider, key);
        }

        @Override
        public Object loadObject() throws Exception {
            return localizationProvider.getStringMap(key);
        }
    }
}
<commit_msg>Implemented caching for localizable instances<commit_after>package com.vityuk.ginger;

import com.google.common.base.Throwables;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.UncheckedExecutionException;
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.NoOp;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;

public class DefaultLocalization implements Localization {
    public static final char PROPERTY_KEY_DELIMETER = '.';

    private final LocalizationProvider localizationProvider;
    private final LoadingCache<Class<Localizable>, Localizable> localizableCache;

    public DefaultLocalization(LocalizationProvider localizationProvider) {
        this.localizationProvider = localizationProvider;

        localizableCache = CacheBuilder.newBuilder().build(new CacheLoader<Class<Localizable>, Localizable>() {
            @Override
            public Localizable load(Class<Localizable> localizable) throws Exception {
                return createLocalizableInstance(localizable);
            }
        });
    }

    @Override
    public <T extends Localizable> T get(Class<T> localizable) {
        try {
            T localizableInstance = (T) localizableCache.getUnchecked((Class<Localizable>) localizable);
            return localizableInstance;
        } catch (UncheckedExecutionException e) {
            throw Throwables.propagate(e.getCause());
        }
    }

    @Override
    public String getMessage(String key) {
        return null;  //To change body of implemented methods use File | Settings | File Templates.
    }

    private <T extends Localizable> T createLocalizableInstance(Class<T> localizable) {
        Method[] methods = localizable.getDeclaredMethods();

        List<Callback> callbacks = Lists.newArrayListWithCapacity(methods.length + 1);
        final Map<Method, Integer> method2CallbackIndex = Maps.newHashMapWithExpectedSize(methods.length);

        callbacks.add(NoOp.INSTANCE);
        for (Method localizableMethod : methods) {
            callbacks.add(createCallback(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;
            }
        };

        T instance = createProxy(localizable, callbackFilter, callbacks);
        return instance;
    }

    private <T extends Localizable> T createProxy(Class<T> localizable, CallbackFilter callbackFilter, List<Callback> callbacks) {
        Enhancer enhancer = new Enhancer();
        enhancer.setInterfaces(new Class[]{localizable});
        enhancer.setCallbackFilter(callbackFilter);
        enhancer.setCallbacks(callbacks.toArray(new Callback[0]));
        enhancer.setUseFactory(false);

        return (T) enhancer.create();
    }


    private Callback createCallback(Method method) {
        String key = createKeyFromMethodName(method.getName());
        Class<?> type = method.getReturnType();
        return getCallbackByType(key, type, method);
    }

    private Callback getCallbackByType(String key, Class<?> type, Method method) {
        if (type == String.class) {
            return new StringLookupCallback(localizationProvider, key);
        }
        if (type == Boolean.class) {
            return new BooleanLookupCallback(localizationProvider, key);
        }
        if (type == Integer.class) {
            return new IntLookupCallback(localizationProvider, key);
        }
        if (type == Long.class) {
            return new LongLookupCallback(localizationProvider, key);
        }
        if (type == Float.class) {
            return new FloatLookupCallback(localizationProvider, key);
        }
        if (type == Double.class) {
            return new DoubleLookupCallback(localizationProvider, key);
        }
        if (type == List.class) {
            // TODO: generics support
            return new StringListLookupCallback(localizationProvider, key);
        }
        if (type == Map.class) {
            // TODO: generics support
            return new StringMapLookupCallback(localizationProvider, key);
        }

        throw new UnsupportedTypeException(type, method);
    }

    private static String createKeyFromMethodName(String methodName) {
        String[] words = StringUtils.splitByCharacterTypeCamelCase(methodName);
        StringBuilder keyBuilder = new StringBuilder(methodName.length() + words.length - 1);

        boolean first = true;
        for (String word : words) {
            if (first) {
                first = false;
            } else {
                keyBuilder.append(PROPERTY_KEY_DELIMETER);
            }
            keyBuilder.append(word.toLowerCase());
        }

        return keyBuilder.toString();
    }

    private static abstract class AbstractLookupCallback implements FixedValue {
        protected final LocalizationProvider localizationProvider;
        protected final String key;

        public AbstractLookupCallback(LocalizationProvider localizationProvider, String key) {
            this.localizationProvider = localizationProvider;
            this.key = key;
        }
    }

    private static class StringLookupCallback extends AbstractLookupCallback {
        public StringLookupCallback(LocalizationProvider localizationProvider, String key) {
            super(localizationProvider, key);
        }

        @Override
        public Object loadObject() throws Exception {
            return localizationProvider.getString(key);
        }
    }


    private static class BooleanLookupCallback extends AbstractLookupCallback {
        public BooleanLookupCallback(LocalizationProvider localizationProvider, String key) {
            super(localizationProvider, key);
        }

        @Override
        public Object loadObject() throws Exception {
            return localizationProvider.getBoolean(key);
        }
    }

    private static class IntLookupCallback extends AbstractLookupCallback {
        public IntLookupCallback(LocalizationProvider localizationProvider, String key) {
            super(localizationProvider, key);
        }

        @Override
        public Object loadObject() throws Exception {
            return localizationProvider.getInt(key);
        }
    }


    private static class LongLookupCallback extends AbstractLookupCallback {
        public LongLookupCallback(LocalizationProvider localizationProvider, String key) {
            super(localizationProvider, key);
        }

        @Override
        public Object loadObject() throws Exception {
            return localizationProvider.getLong(key);
        }
    }

    private static class FloatLookupCallback extends AbstractLookupCallback {
        public FloatLookupCallback(LocalizationProvider localizationProvider, String key) {
            super(localizationProvider, key);
        }

        @Override
        public Object loadObject() throws Exception {
            return localizationProvider.getFloat(key);
        }
    }


    private static class DoubleLookupCallback extends AbstractLookupCallback {
        public DoubleLookupCallback(LocalizationProvider localizationProvider, String key) {
            super(localizationProvider, key);
        }

        @Override
        public Object loadObject() throws Exception {
            return localizationProvider.getDouble(key);
        }
    }


    private static class StringListLookupCallback extends AbstractLookupCallback {
        public StringListLookupCallback(LocalizationProvider localizationProvider, String key) {
            super(localizationProvider, key);
        }

        @Override
        public Object loadObject() throws Exception {
            return localizationProvider.getStringList(key);
        }
    }


    private static class StringMapLookupCallback extends AbstractLookupCallback {
        public StringMapLookupCallback(LocalizationProvider localizationProvider, String key) {
            super(localizationProvider, key);
        }

        @Override
        public Object loadObject() throws Exception {
            return localizationProvider.getStringMap(key);
        }
    }
}

{'fraction_non_alphanumeric': 0.07560532687651332, 'fraction_numerical': 0.0010895883777239709, 'mean_word_length': 2.73947487550928, 'pattern_counts': {'":': 0, '<': 31, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6613006', 'n_tokens_mistral': 3960, 'n_tokens_neox': 3827, 'n_words': 957}