1/**
2 * Copyright (C) 2008 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.inject.internal;
18
19import com.google.inject.TypeLiteral;
20import com.google.inject.internal.util.SourceProvider;
21import com.google.inject.matcher.AbstractMatcher;
22import com.google.inject.matcher.Matcher;
23import com.google.inject.matcher.Matchers;
24import com.google.inject.spi.TypeConverter;
25import com.google.inject.spi.TypeConverterBinding;
26
27import java.lang.reflect.InvocationTargetException;
28import java.lang.reflect.Method;
29import java.lang.reflect.Type;
30
31/**
32 * Handles {@code Binder.convertToTypes} commands.
33 *
34 * @author crazybob@google.com (Bob Lee)
35 * @author jessewilson@google.com (Jesse Wilson)
36 */
37final class TypeConverterBindingProcessor extends AbstractProcessor {
38
39  TypeConverterBindingProcessor(Errors errors) {
40    super(errors);
41  }
42
43  /** Installs default converters for primitives, enums, and class literals. */
44  static void prepareBuiltInConverters(InjectorImpl injector) {
45    // Configure type converters.
46    convertToPrimitiveType(injector, int.class, Integer.class);
47    convertToPrimitiveType(injector, long.class, Long.class);
48    convertToPrimitiveType(injector, boolean.class, Boolean.class);
49    convertToPrimitiveType(injector, byte.class, Byte.class);
50    convertToPrimitiveType(injector, short.class, Short.class);
51    convertToPrimitiveType(injector, float.class, Float.class);
52    convertToPrimitiveType(injector, double.class, Double.class);
53
54    convertToClass(injector, Character.class, new TypeConverter() {
55      public Object convert(String value, TypeLiteral<?> toType) {
56        value = value.trim();
57        if (value.length() != 1) {
58          throw new RuntimeException("Length != 1.");
59        }
60        return value.charAt(0);
61      }
62
63      @Override public String toString() {
64        return "TypeConverter<Character>";
65      }
66    });
67
68    convertToClasses(injector, Matchers.subclassesOf(Enum.class), new TypeConverter() {
69      @SuppressWarnings("unchecked")
70      public Object convert(String value, TypeLiteral<?> toType) {
71          return Enum.valueOf((Class) toType.getRawType(), value);
72        }
73
74        @Override public String toString() {
75          return "TypeConverter<E extends Enum<E>>";
76        }
77      });
78
79    internalConvertToTypes(injector, new AbstractMatcher<TypeLiteral<?>>() {
80        public boolean matches(TypeLiteral<?> typeLiteral) {
81          return typeLiteral.getRawType() == Class.class;
82        }
83
84        @Override public String toString() {
85          return "Class<?>";
86        }
87      },
88      new TypeConverter() {
89        @SuppressWarnings("unchecked")
90        public Object convert(String value, TypeLiteral<?> toType) {
91          try {
92            return Class.forName(value);
93          } catch (ClassNotFoundException e) {
94            throw new RuntimeException(e.getMessage());
95          }
96        }
97
98        @Override public String toString() {
99          return "TypeConverter<Class<?>>";
100        }
101      }
102    );
103  }
104
105  private static <T> void convertToPrimitiveType(InjectorImpl injector, Class<T> primitiveType,
106      final Class<T> wrapperType) {
107    try {
108      final Method parser = wrapperType.getMethod(
109          "parse" + capitalize(primitiveType.getName()), String.class);
110
111      TypeConverter typeConverter = new TypeConverter() {
112        @SuppressWarnings("unchecked")
113        public Object convert(String value, TypeLiteral<?> toType) {
114          try {
115            return parser.invoke(null, value);
116          } catch (IllegalAccessException e) {
117            throw new AssertionError(e);
118          } catch (InvocationTargetException e) {
119            throw new RuntimeException(e.getTargetException().getMessage());
120          }
121        }
122
123        @Override public String toString() {
124          return "TypeConverter<" + wrapperType.getSimpleName() + ">";
125        }
126      };
127
128      convertToClass(injector, wrapperType, typeConverter);
129    } catch (NoSuchMethodException e) {
130      throw new AssertionError(e);
131    }
132  }
133
134  private static <T> void convertToClass(InjectorImpl injector, Class<T> type,
135      TypeConverter converter) {
136    convertToClasses(injector, Matchers.identicalTo(type), converter);
137  }
138
139  private static void convertToClasses(InjectorImpl injector,
140      final Matcher<? super Class<?>> typeMatcher, TypeConverter converter) {
141    internalConvertToTypes(injector, new AbstractMatcher<TypeLiteral<?>>() {
142      public boolean matches(TypeLiteral<?> typeLiteral) {
143        Type type = typeLiteral.getType();
144        if (!(type instanceof Class)) {
145          return false;
146        }
147        Class<?> clazz = (Class<?>) type;
148        return typeMatcher.matches(clazz);
149      }
150
151      @Override public String toString() {
152        return typeMatcher.toString();
153      }
154    }, converter);
155  }
156
157  private static void internalConvertToTypes(InjectorImpl injector,
158      Matcher<? super TypeLiteral<?>> typeMatcher,
159      TypeConverter converter) {
160    injector.state.addConverter(
161        new TypeConverterBinding(SourceProvider.UNKNOWN_SOURCE, typeMatcher, converter));
162  }
163
164  @Override public Boolean visit(TypeConverterBinding command) {
165    injector.state.addConverter(new TypeConverterBinding(
166        command.getSource(), command.getTypeMatcher(), command.getTypeConverter()));
167    return true;
168  }
169
170  private static String capitalize(String s) {
171    if (s.length() == 0) {
172      return s;
173    }
174    char first = s.charAt(0);
175    char capitalized = Character.toUpperCase(first);
176    return (first == capitalized)
177        ? s
178        : capitalized + s.substring(1);
179  }
180
181}
182