All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.glowroot.local.ui.QueryStrings Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2014-2015 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.glowroot.local.ui;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;

import javax.annotation.Nullable;

import org.glowroot.shaded.google.common.base.CaseFormat;
import org.glowroot.shaded.google.common.cache.CacheBuilder;
import org.glowroot.shaded.google.common.cache.CacheLoader;
import org.glowroot.shaded.google.common.cache.LoadingCache;
import org.glowroot.shaded.google.common.collect.Lists;
import org.glowroot.shaded.google.common.collect.Maps;
import org.glowroot.shaded.netty.handler.codec.http.QueryStringDecoder;

import org.glowroot.common.Reflections;

import static org.glowroot.shaded.google.common.base.Preconditions.checkNotNull;

class QueryStrings {

    private static LoadingCache, Map> settersCache =
            CacheBuilder.newBuilder().build(new CacheLoader, Map>() {
                @Override
                public Map load(Class key) throws Exception {
                    return loadSetters(key);
                }
            });

    private QueryStrings() {}

    static  T decode(String queryString, Class clazz) throws Exception {
        Method builderMethod = Reflections.getDeclaredMethod(clazz, "builder");
        Object builder = Reflections.invokeStatic(builderMethod);
        checkNotNull(builder);
        Class immutableBuilderClass = builder.getClass();
        Map setters = settersCache.getUnchecked(immutableBuilderClass);
        QueryStringDecoder decoder = new QueryStringDecoder('?' + queryString);
        for (Entry> entry : decoder.parameters().entrySet()) {
            String key = entry.getKey();
            key = CaseFormat.LOWER_HYPHEN.to(CaseFormat.LOWER_CAMEL, key);
            // special rule for "-mbean" so that it will convert to "...MBean"
            key = key.replace("Mbean", "MBean");
            Method setter = setters.get(key);
            checkNotNull(setter, "Unexpected attribute: %s", key);
            Type valueType = setter.getGenericParameterTypes()[0];
            Object value;
            if (valueType instanceof ParameterizedType) {
                // only generic iterable supported
                valueType = ((ParameterizedType) valueType).getActualTypeArguments()[0];
                List parsedValues = Lists.newArrayList();
                for (String stringValue : entry.getValue()) {
                    Object parsedValue = parseString(stringValue, (Class) valueType);
                    // ignore empty query param values, e.g. the empty percentile value in
                    // percentile=&percentile=95&percentile=99
                    if (parsedValue != null) {
                        parsedValues.add(parsedValue);
                    }
                }
                value = parsedValues;
            } else {
                value = parseString(entry.getValue().get(0), (Class) valueType);
            }
            Reflections.invoke(setter, builder, value);
        }
        Method build = Reflections.getDeclaredMethod(immutableBuilderClass, "build");
        @SuppressWarnings("unchecked")
        T decoded = (T) Reflections.invoke(build, builder);
        return decoded;
    }

    private static @Nullable Object parseString(String str, Class targetClass) {
        if (str.equals("")) {
            return null;
        } else if (targetClass == String.class) {
            return str;
        } else if (isInteger(targetClass)) {
            // parse as double and truncate, just in case there is a decimal part
            return (int) Double.parseDouble(str);
        } else if (isLong(targetClass)) {
            // parse as double and truncate, just in case there is a decimal part
            return (long) Double.parseDouble(str);
        } else if (isDouble(targetClass)) {
            return Double.parseDouble(str);
        } else if (isBoolean(targetClass)) {
            return Boolean.parseBoolean(str);
        } else if (Enum.class.isAssignableFrom(targetClass)) {
            @SuppressWarnings({"unchecked", "rawtypes"})
            Enum enumValue = Enum.valueOf((Class) targetClass,
                    str.replace('-', '_').toUpperCase(Locale.ENGLISH));
            return enumValue;
        } else {
            throw new IllegalStateException("Unexpected class: " + targetClass);
        }
    }

    private static boolean isInteger(Class targetClass) {
        return targetClass == int.class || targetClass == Integer.class;
    }

    private static boolean isLong(Class targetClass) {
        return targetClass == long.class || targetClass == Long.class;
    }

    private static boolean isDouble(Class targetClass) {
        return targetClass == double.class || targetClass == Double.class;
    }

    private static boolean isBoolean(Class targetClass) {
        return targetClass == boolean.class || targetClass == Boolean.class;
    }

    private static Map loadSetters(Class immutableBuilderClass) {
        Map setters = Maps.newHashMap();
        for (Method method : immutableBuilderClass.getMethods()) {
            if (method.getName().startsWith("add") && !method.getName().startsWith("addAll")) {
                continue;
            }
            if (method.getParameterTypes().length == 1) {
                if (!isSimpleSetter(method.getParameterTypes()[0])) {
                    continue;
                }
                method.setAccessible(true);
                if (method.getName().startsWith("addAll")) {
                    String propertyName = method.getName().substring(6);
                    propertyName = Character.toLowerCase(propertyName.charAt(0))
                            + propertyName.substring(1);
                    setters.put(propertyName, method);
                } else {
                    setters.put(method.getName(), method);
                }
            }
        }
        return setters;
    }

    private static boolean isSimpleSetter(Class targetClass) {
        return targetClass == String.class
                || isInteger(targetClass)
                || isLong(targetClass)
                || isDouble(targetClass)
                || isBoolean(targetClass)
                || Enum.class.isAssignableFrom(targetClass)
                || targetClass == Iterable.class;
    }
}