Java tutorial
/* * 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.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import javax.annotation.Nullable; import com.google.common.base.CaseFormat; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.Maps; import io.netty.handler.codec.http.QueryStringDecoder; import org.glowroot.common.Reflections; import static com.google.common.base.Preconditions.checkNotNull; class QueryStrings { private static LoadingCache<Class<?>, Map<String, Method>> settersCache = CacheBuilder.newBuilder() .build(new CacheLoader<Class<?>, Map<String, Method>>() { @Override public Map<String, Method> load(Class<?> key) throws Exception { return loadSetters(key); } }); private QueryStrings() { } static <T> T decode(String queryString, Class<T> clazz) throws Exception { Method builderMethod = Reflections.getDeclaredMethod(clazz, "builder"); Object builder = Reflections.invokeStatic(builderMethod); checkNotNull(builder); Class<?> immutableBuilderClass = builder.getClass(); Map<String, Method> setters = settersCache.getUnchecked(immutableBuilderClass); QueryStringDecoder decoder = new QueryStringDecoder('?' + queryString); for (Entry<String, List<String>> 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); Class<?> valueClass = setter.getParameterTypes()[0]; Object value; if (valueClass == Iterable.class) { // only lists of type string supported value = entry.getValue(); } else { value = parseString(entry.getValue().get(0), valueClass); } 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<? extends Enum>) 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<String, Method> loadSetters(Class<?> immutableBuilderClass) { Map<String, Method> 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; } }