Java

Форк
0
/
ForwardingWrapperTester.java 
243 строки · 9.1 Кб
1
/*
2
 * Copyright (C) 2012 The Guava Authors
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

17
package com.google.common.testing;
18

19
import static com.google.common.base.Preconditions.checkArgument;
20
import static com.google.common.base.Preconditions.checkNotNull;
21
import static com.google.common.base.Throwables.throwIfUnchecked;
22
import static junit.framework.Assert.assertEquals;
23
import static junit.framework.Assert.fail;
24

25
import com.google.common.annotations.GwtIncompatible;
26
import com.google.common.annotations.J2ktIncompatible;
27
import com.google.common.base.Function;
28
import com.google.common.base.Throwables;
29
import com.google.common.collect.Lists;
30
import com.google.common.reflect.AbstractInvocationHandler;
31
import com.google.common.reflect.Reflection;
32
import com.google.errorprone.annotations.CanIgnoreReturnValue;
33
import java.lang.reflect.AccessibleObject;
34
import java.lang.reflect.InvocationTargetException;
35
import java.lang.reflect.Method;
36
import java.lang.reflect.Modifier;
37
import java.util.List;
38
import java.util.concurrent.atomic.AtomicInteger;
39
import org.checkerframework.checker.nullness.qual.Nullable;
40

41
/**
42
 * Tester to ensure forwarding wrapper works by delegating calls to the corresponding method with
43
 * the same parameters forwarded and return value forwarded back or exception propagated as is.
44
 *
45
 * <p>For example:
46
 *
47
 * <pre>{@code
48
 * new ForwardingWrapperTester().testForwarding(Foo.class, new Function<Foo, Foo>() {
49
 *   public Foo apply(Foo foo) {
50
 *     return new ForwardingFoo(foo);
51
 *   }
52
 * });
53
 * }</pre>
54
 *
55
 * @author Ben Yu
56
 * @since 14.0
57
 */
58
@GwtIncompatible
59
@J2ktIncompatible
60
@ElementTypesAreNonnullByDefault
61
public final class ForwardingWrapperTester {
62

63
  private boolean testsEquals = false;
64

65
  /**
66
   * Asks for {@link Object#equals} and {@link Object#hashCode} to be tested. That is, forwarding
67
   * wrappers of equal instances should be equal.
68
   */
69
  @CanIgnoreReturnValue
70
  public ForwardingWrapperTester includingEquals() {
71
    this.testsEquals = true;
72
    return this;
73
  }
74

75
  /**
76
   * Tests that the forwarding wrapper returned by {@code wrapperFunction} properly forwards method
77
   * calls with parameters passed as is, return value returned as is, and exceptions propagated as
78
   * is.
79
   */
80
  public <T> void testForwarding(
81
      Class<T> interfaceType, Function<? super T, ? extends T> wrapperFunction) {
82
    checkNotNull(wrapperFunction);
83
    checkArgument(interfaceType.isInterface(), "%s isn't an interface", interfaceType);
84
    Method[] methods = getMostConcreteMethods(interfaceType);
85
    AccessibleObject.setAccessible(methods, true);
86
    for (Method method : methods) {
87
      // Under java 8, interfaces can have default methods that aren't abstract.
88
      // No need to verify them.
89
      // Can't check isDefault() for JDK 7 compatibility.
90
      if (!Modifier.isAbstract(method.getModifiers())) {
91
        continue;
92
      }
93
      // The interface could be package-private or private.
94
      // filter out equals/hashCode/toString
95
      if (method.getName().equals("equals")
96
          && method.getParameterTypes().length == 1
97
          && method.getParameterTypes()[0] == Object.class) {
98
        continue;
99
      }
100
      if (method.getName().equals("hashCode") && method.getParameterTypes().length == 0) {
101
        continue;
102
      }
103
      if (method.getName().equals("toString") && method.getParameterTypes().length == 0) {
104
        continue;
105
      }
106
      testSuccessfulForwarding(interfaceType, method, wrapperFunction);
107
      testExceptionPropagation(interfaceType, method, wrapperFunction);
108
    }
109
    if (testsEquals) {
110
      testEquals(interfaceType, wrapperFunction);
111
    }
112
    testToString(interfaceType, wrapperFunction);
113
  }
114

115
  /** Returns the most concrete public methods from {@code type}. */
116
  private static Method[] getMostConcreteMethods(Class<?> type) {
117
    Method[] methods = type.getMethods();
118
    for (int i = 0; i < methods.length; i++) {
119
      try {
120
        methods[i] = type.getMethod(methods[i].getName(), methods[i].getParameterTypes());
121
      } catch (Exception e) {
122
        throwIfUnchecked(e);
123
        throw new RuntimeException(e);
124
      }
125
    }
126
    return methods;
127
  }
128

129
  private static <T> void testSuccessfulForwarding(
130
      Class<T> interfaceType, Method method, Function<? super T, ? extends T> wrapperFunction) {
131
    new InteractionTester<T>(interfaceType, method).testInteraction(wrapperFunction);
132
  }
133

134
  private static <T> void testExceptionPropagation(
135
      Class<T> interfaceType, Method method, Function<? super T, ? extends T> wrapperFunction) {
136
    RuntimeException exception = new RuntimeException();
137
    T proxy =
138
        Reflection.newProxy(
139
            interfaceType,
140
            new AbstractInvocationHandler() {
141
              @Override
142
              protected Object handleInvocation(Object p, Method m, @Nullable Object[] args)
143
                  throws Throwable {
144
                throw exception;
145
              }
146
            });
147
    T wrapper = wrapperFunction.apply(proxy);
148
    try {
149
      method.invoke(wrapper, getParameterValues(method));
150
      fail(method + " failed to throw exception as is.");
151
    } catch (InvocationTargetException e) {
152
      if (exception != e.getCause()) {
153
        throw new RuntimeException(e);
154
      }
155
    } catch (IllegalAccessException e) {
156
      throw new AssertionError(e);
157
    }
158
  }
159

160
  private static <T> void testEquals(
161
      Class<T> interfaceType, Function<? super T, ? extends T> wrapperFunction) {
162
    FreshValueGenerator generator = new FreshValueGenerator();
163
    T instance = generator.newFreshProxy(interfaceType);
164
    new EqualsTester()
165
        .addEqualityGroup(wrapperFunction.apply(instance), wrapperFunction.apply(instance))
166
        .addEqualityGroup(wrapperFunction.apply(generator.newFreshProxy(interfaceType)))
167
        // TODO: add an overload to EqualsTester to print custom error message?
168
        .testEquals();
169
  }
170

171
  private static <T> void testToString(
172
      Class<T> interfaceType, Function<? super T, ? extends T> wrapperFunction) {
173
    T proxy = new FreshValueGenerator().newFreshProxy(interfaceType);
174
    assertEquals(
175
        "toString() isn't properly forwarded",
176
        proxy.toString(),
177
        wrapperFunction.apply(proxy).toString());
178
  }
179

180
  private static @Nullable Object[] getParameterValues(Method method) {
181
    FreshValueGenerator paramValues = new FreshValueGenerator();
182
    List<@Nullable Object> passedArgs = Lists.newArrayList();
183
    for (Class<?> paramType : method.getParameterTypes()) {
184
      passedArgs.add(paramValues.generateFresh(paramType));
185
    }
186
    return passedArgs.toArray();
187
  }
188

189
  /** Tests a single interaction against a method. */
190
  private static final class InteractionTester<T> extends AbstractInvocationHandler {
191

192
    private final Class<T> interfaceType;
193
    private final Method method;
194
    private final @Nullable Object[] passedArgs;
195
    private final @Nullable Object returnValue;
196
    private final AtomicInteger called = new AtomicInteger();
197

198
    InteractionTester(Class<T> interfaceType, Method method) {
199
      this.interfaceType = interfaceType;
200
      this.method = method;
201
      this.passedArgs = getParameterValues(method);
202
      this.returnValue = new FreshValueGenerator().generateFresh(method.getReturnType());
203
    }
204

205
    @Override
206
    protected @Nullable Object handleInvocation(
207
        Object p, Method calledMethod, @Nullable Object[] args) throws Throwable {
208
      assertEquals(method, calledMethod);
209
      assertEquals(method + " invoked more than once.", 0, called.get());
210
      for (int i = 0; i < passedArgs.length; i++) {
211
        assertEquals(
212
            "Parameter #" + i + " of " + method + " not forwarded", passedArgs[i], args[i]);
213
      }
214
      called.getAndIncrement();
215
      return returnValue;
216
    }
217

218
    void testInteraction(Function<? super T, ? extends T> wrapperFunction) {
219
      T proxy = Reflection.newProxy(interfaceType, this);
220
      T wrapper = wrapperFunction.apply(proxy);
221
      boolean isPossibleChainingCall = interfaceType.isAssignableFrom(method.getReturnType());
222
      try {
223
        Object actualReturnValue = method.invoke(wrapper, passedArgs);
224
        // If we think this might be a 'chaining' call then we allow the return value to either
225
        // be the wrapper or the returnValue.
226
        if (!isPossibleChainingCall || wrapper != actualReturnValue) {
227
          assertEquals(
228
              "Return value of " + method + " not forwarded", returnValue, actualReturnValue);
229
        }
230
      } catch (IllegalAccessException e) {
231
        throw new RuntimeException(e);
232
      } catch (InvocationTargetException e) {
233
        throw Throwables.propagate(e.getCause());
234
      }
235
      assertEquals("Failed to forward to " + method, 1, called.get());
236
    }
237

238
    @Override
239
    public String toString() {
240
      return "dummy " + interfaceType.getSimpleName();
241
    }
242
  }
243
}
244

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.