1/* 2 * Copyright (C) 2011 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.google.caliper.util; 18 19import com.google.common.collect.ImmutableList; 20import com.google.common.primitives.Primitives; 21 22import java.lang.reflect.Constructor; 23import java.lang.reflect.InvocationTargetException; 24import java.lang.reflect.Method; 25import java.text.ParseException; 26import java.util.List; 27 28public class Parsers { 29 public static final Parser<String> IDENTITY = new Parser<String>() { 30 @Override public String parse(CharSequence in) { 31 return in.toString(); 32 } 33 }; 34 35 private static final List<String> CONVERSION_METHOD_NAMES = 36 ImmutableList.of("fromString", "decode", "valueOf"); 37 38 /** 39 * Parser that tries, in this order: 40 * <ul> 41 * <li>ResultType.fromString(String) 42 * <li>ResultType.decode(String) 43 * <li>ResultType.valueOf(String) 44 * <li>new ResultType(String) 45 * </ul> 46 */ 47 public static <T> Parser<T> conventionalParser(Class<T> resultType) 48 throws NoSuchMethodException { 49 if (resultType == String.class) { 50 @SuppressWarnings("unchecked") // T == String 51 Parser<T> identity = (Parser<T>) IDENTITY; 52 return identity; 53 } 54 55 final Class<T> wrappedResultType = Primitives.wrap(resultType); 56 57 for (String methodName : CONVERSION_METHOD_NAMES) { 58 try { 59 final Method method = wrappedResultType.getDeclaredMethod(methodName, String.class); 60 61 if (Util.isStatic(method) && wrappedResultType.isAssignableFrom(method.getReturnType())) { 62 method.setAccessible(true); // to permit inner enums, etc. 63 return new InvokingParser<T>() { 64 @Override protected T invoke(String input) throws Exception { 65 return wrappedResultType.cast(method.invoke(null, input)); 66 } 67 }; 68 } 69 } catch (Exception tryAgain) { 70 } 71 } 72 73 final Constructor<T> constr = wrappedResultType.getDeclaredConstructor(String.class); 74 constr.setAccessible(true); 75 return new InvokingParser<T>() { 76 @Override protected T invoke(String input) throws Exception { 77 return wrappedResultType.cast(constr.newInstance(input)); 78 } 79 }; 80 } 81 82 abstract static class InvokingParser<T> implements Parser<T> { 83 @Override public T parse(CharSequence input) throws ParseException { 84 try { 85 return invoke(input.toString()); 86 } catch (InvocationTargetException e) { 87 Throwable cause = e.getCause(); 88 String desc = firstNonNull(cause.getMessage(), cause.getClass().getSimpleName()); 89 throw newParseException(desc, cause); 90 } catch (Exception e) { 91 throw newParseException("Unknown parsing problem", e); 92 } 93 } 94 95 protected abstract T invoke(String input) throws Exception; 96 } 97 98 public static ParseException newParseException(String message, Throwable cause) { 99 ParseException pe = newParseException(message); 100 pe.initCause(cause); 101 return pe; 102 } 103 104 public static ParseException newParseException(String message) { 105 return new ParseException(message, 0); 106 } 107 108 private static <T> T firstNonNull(T first, T second) { 109 return (first != null) ? first : second; 110 } 111} 112