1 package com.lexicalscope.fluentreflection;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 import static ch.lambdaj.Lambda.*;
20 import static com.lexicalscope.fluentreflection.Visibility.visibilityFromModifiers;
21 import static java.lang.String.format;
22 import static java.lang.System.arraycopy;
23
24 import java.lang.reflect.InvocationTargetException;
25 import java.lang.reflect.Method;
26 import java.lang.reflect.Modifier;
27 import java.lang.reflect.TypeVariable;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.List;
31
32 import org.apache.commons.lang3.builder.EqualsBuilder;
33 import org.apache.commons.lang3.builder.HashCodeBuilder;
34
35 import com.google.inject.TypeLiteral;
36
37 final class FluentMethodImpl extends AbstractFluentAnnotated implements FluentMethod {
38 private final ReflectedTypeFactory reflectedTypeFactory;
39 private final FluentClass<?> reflectedClass;
40 private final TypeLiteral<?> typeLiteral;
41 private final Method method;
42
43 public FluentMethodImpl(
44 final ReflectedTypeFactory reflectedTypeFactory,
45 final FluentClass<?> reflectedClass,
46 final TypeLiteral<?> typeLiteral,
47 final Method method) {
48 super(reflectedTypeFactory, method);
49 this.reflectedTypeFactory = reflectedTypeFactory;
50 this.reflectedClass = reflectedClass;
51 this.typeLiteral = typeLiteral;
52 this.method = method;
53 }
54
55 @Override public String name() {
56 return method.getName();
57 }
58
59 @Override public List<FluentClass<?>> args() {
60 final List<FluentClass<?>> result = new ArrayList<FluentClass<?>>();
61 result.addAll(convert(
62 typeLiteral.getParameterTypes(method),
63 new ConvertTypeLiteralToReflectedType(reflectedTypeFactory)));
64 return result;
65 }
66
67 @Override public int argCount() {
68 final int parameterCount = method.getParameterTypes().length;
69 if (isStatic()) {
70 return parameterCount;
71 }
72 return parameterCount;
73 }
74
75 @Override public FluentClass<?> declarer() {
76 return reflectedTypeFactory.reflect(typeLiteral);
77 }
78
79 @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public FluentObject<?> call(final Object... args) {
80 final Object object = callRaw(args);
81 if(object == null) {
82 return null;
83 }
84 return reflectedTypeFactory.reflect((Class) object.getClass(), object);
85 }
86
87 @Override public Object callRaw(final Object... args) {
88 if (isStatic()) {
89 return invokeMethod(null, args);
90 } else {
91 if (args.length < 1) {
92 throw new IllegalArgumentException("target instance must be specified as first argument when calling "
93 + method);
94 }
95
96 final Object[] remainingArguments = new Object[args.length - 1];
97 arraycopy(args, 1, remainingArguments, 0, args.length - 1);
98 return invokeMethod(args[0], remainingArguments);
99 }
100 }
101
102 private Object invokeMethod(final Object instance, final Object[] arguments) {
103 try {
104 if (!method.isAccessible()) {
105 method.setAccessible(true);
106 }
107 return method.invoke(instance, arguments);
108 } catch (final IllegalArgumentException e) {
109 throw new IllegalArgumentRuntimeException(e, method, instance, arguments);
110 } catch (final IllegalAccessException e) {
111 throw new IllegalAccessRuntimeException(e, method);
112 } catch (final InvocationTargetException e) {
113 throw new InvocationTargetRuntimeException(e, method);
114 }
115 }
116
117 @Override public boolean isStatic() {
118 return Modifier.isStatic(method.getModifiers());
119 }
120
121 @Override public boolean isFinal() {
122 return Modifier.isFinal(method.getModifiers());
123 }
124
125 @Override public <T> FluentCall<T> as(final Class<T> returnType) {
126 return new AbstractCall<T>(reflectedTypeFactory) {
127 @Override public T callRaw(final Object... args) {
128 return returnType.cast(FluentMethodImpl.this.callRaw(args));
129 }
130 };
131 }
132
133 @Override public FluentClass<?> type() {
134 final TypeLiteral<?> returnType = typeLiteral.getReturnType(method);
135 if (returnType == null) {
136 return null;
137 }
138 return reflectedTypeFactory.reflect(returnType);
139 }
140
141 @Override public String property() {
142 final String name = name();
143
144 if (name.length() > 2) {
145 if (name.length() > 3) {
146 if (name.startsWith("get") || name.startsWith("set")) {
147 return initialLowerCase(name.substring(3));
148 }
149 }
150 if (name.startsWith("is")) {
151 return initialLowerCase(name.substring(2));
152 }
153 }
154 return method.getName();
155 }
156
157 private String initialLowerCase(final String substring) {
158 return substring.substring(0, 1).toLowerCase() + substring.substring(1);
159 }
160
161 @Override public Method member() {
162 return method;
163 }
164
165 @Override public Visibility visibility() {
166 return visibilityFromModifiers(method.getModifiers());
167 }
168
169 private List<TypeVariable<Method>> typeParameters()
170 {
171 return Arrays.asList(method.getTypeParameters());
172 }
173
174 @Override public String toString() {
175 final String visibility;
176 if (visibility().toString().isEmpty())
177 {
178 visibility = visibility().toString();
179 }
180 else
181 {
182 visibility = visibility().toString() + " ";
183 }
184
185 final String staticModifier;
186 if (isStatic())
187 {
188 staticModifier = "static ";
189 }
190 else
191 {
192 staticModifier = "";
193 }
194
195 final String finalModifier;
196 if (isFinal())
197 {
198 finalModifier = "final ";
199 }
200 else
201 {
202 finalModifier = "";
203 }
204
205 final String typeParameters;
206 if (typeParameters().isEmpty())
207 {
208 typeParameters = "";
209 }
210 else
211 {
212 typeParameters = "<" + joinFrom(typeParameters(), ", ").toString() + "> ";
213 }
214
215 final String arguments;
216 if (argCount() > 0) {
217 arguments = joinFrom(args(), ", ").toString();
218 } else {
219 arguments = "";
220 }
221
222 return format(
223 "%s%s%s%s%s %s(%s)",
224 visibility,
225 staticModifier,
226 finalModifier,
227 typeParameters,
228 type(),
229 method.getName(),
230 arguments);
231 }
232
233 @Override public boolean equals(final Object obj) {
234 if (obj != null && this.getClass().equals(obj.getClass())) {
235 final FluentMethodImpl that = (FluentMethodImpl) obj;
236 return new EqualsBuilder()
237 .append(this.method, that.method)
238 .append(this.typeLiteral, that.typeLiteral)
239 .isEquals();
240 }
241 return false;
242 }
243
244 @Override public int hashCode() {
245 return new HashCodeBuilder().append(method).append(typeLiteral).toHashCode();
246 }
247 }