/modules/elasticsearch/src/main/java/org/elasticsearch/common/inject/internal/Errors.java
Java | 649 lines | 468 code | 112 blank | 69 comment | 75 complexity | 7c5cb0c72c97bc3f8d08c10ed6817ea4 MD5 | raw file
1/**
2 * Copyright (C) 2006 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 org.elasticsearch.common.inject.internal;
18
19import org.elasticsearch.common.collect.ImmutableList;
20import org.elasticsearch.common.collect.ImmutableSet;
21import org.elasticsearch.common.collect.Lists;
22import org.elasticsearch.common.inject.ConfigurationException;
23import org.elasticsearch.common.inject.CreationException;
24import org.elasticsearch.common.inject.Key;
25import org.elasticsearch.common.inject.MembersInjector;
26import org.elasticsearch.common.inject.Provider;
27import org.elasticsearch.common.inject.ProvisionException;
28import org.elasticsearch.common.inject.Scope;
29import org.elasticsearch.common.inject.TypeLiteral;
30import org.elasticsearch.common.inject.spi.Dependency;
31import org.elasticsearch.common.inject.spi.InjectionListener;
32import org.elasticsearch.common.inject.spi.InjectionPoint;
33import org.elasticsearch.common.inject.spi.Message;
34import org.elasticsearch.common.inject.spi.TypeListenerBinding;
35
36import java.io.PrintWriter;
37import java.io.Serializable;
38import java.io.StringWriter;
39import java.lang.annotation.Annotation;
40import java.lang.reflect.Constructor;
41import java.lang.reflect.Field;
42import java.lang.reflect.Member;
43import java.lang.reflect.Type;
44import java.util.Collection;
45import java.util.Collections;
46import java.util.Comparator;
47import java.util.Formatter;
48import java.util.List;
49
50/**
51 * A collection of error messages. If this type is passed as a method parameter, the method is
52 * considered to have executed succesfully only if new errors were not added to this collection.
53 *
54 * <p>Errors can be chained to provide additional context. To add context, call {@link #withSource}
55 * to create a new Errors instance that contains additional context. All messages added to the
56 * returned instance will contain full context.
57 *
58 * <p>To avoid messages with redundant context, {@link #withSource} should be added sparingly. A
59 * good rule of thumb is to assume a ethod's caller has already specified enough context to
60 * identify that method. When calling a method that's defined in a different context, call that
61 * method with an errors object that includes its context.
62 *
63 * @author jessewilson@google.com (Jesse Wilson)
64 */
65public final class Errors implements Serializable {
66
67 /**
68 * The root errors object. Used to access the list of error messages.
69 */
70 private final Errors root;
71
72 /**
73 * The parent errors object. Used to obtain the chain of source objects.
74 */
75 private final Errors parent;
76
77 /**
78 * The leaf source for errors added here.
79 */
80 private final Object source;
81
82 /**
83 * null unless (root == this) and error messages exist. Never an empty list.
84 */
85 private List<Message> errors; // lazy, use getErrorsForAdd()
86
87 public Errors() {
88 this.root = this;
89 this.parent = null;
90 this.source = SourceProvider.UNKNOWN_SOURCE;
91 }
92
93 public Errors(Object source) {
94 this.root = this;
95 this.parent = null;
96 this.source = source;
97 }
98
99 private Errors(Errors parent, Object source) {
100 this.root = parent.root;
101 this.parent = parent;
102 this.source = source;
103 }
104
105 /**
106 * Returns an instance that uses {@code source} as a reference point for newly added errors.
107 */
108 public Errors withSource(Object source) {
109 return source == SourceProvider.UNKNOWN_SOURCE
110 ? this
111 : new Errors(this, source);
112 }
113
114 /**
115 * We use a fairly generic error message here. The motivation is to share the
116 * same message for both bind time errors:
117 * <pre><code>Guice.createInjector(new AbstractModule() {
118 * public void configure() {
119 * bind(Runnable.class);
120 * }
121 * }</code></pre>
122 * ...and at provide-time errors:
123 * <pre><code>Guice.createInjector().getInstance(Runnable.class);</code></pre>
124 * Otherwise we need to know who's calling when resolving a just-in-time
125 * binding, which makes things unnecessarily complex.
126 */
127 public Errors missingImplementation(Key key) {
128 return addMessage("No implementation for %s was bound.", key);
129 }
130
131 public Errors converterReturnedNull(String stringValue, Object source,
132 TypeLiteral<?> type, MatcherAndConverter matchingConverter) {
133 return addMessage("Received null converting '%s' (bound at %s) to %s%n"
134 + " using %s.",
135 stringValue, convert(source), type, matchingConverter);
136 }
137
138 public Errors conversionTypeError(String stringValue, Object source, TypeLiteral<?> type,
139 MatcherAndConverter matchingConverter, Object converted) {
140 return addMessage("Type mismatch converting '%s' (bound at %s) to %s%n"
141 + " using %s.%n"
142 + " Converter returned %s.",
143 stringValue, convert(source), type, matchingConverter, converted);
144 }
145
146 public Errors conversionError(String stringValue, Object source,
147 TypeLiteral<?> type, MatcherAndConverter matchingConverter, RuntimeException cause) {
148 return errorInUserCode(cause, "Error converting '%s' (bound at %s) to %s%n"
149 + " using %s.%n"
150 + " Reason: %s",
151 stringValue, convert(source), type, matchingConverter, cause);
152 }
153
154 public Errors ambiguousTypeConversion(String stringValue, Object source, TypeLiteral<?> type,
155 MatcherAndConverter a, MatcherAndConverter b) {
156 return addMessage("Multiple converters can convert '%s' (bound at %s) to %s:%n"
157 + " %s and%n"
158 + " %s.%n"
159 + " Please adjust your type converter configuration to avoid overlapping matches.",
160 stringValue, convert(source), type, a, b);
161 }
162
163 public Errors bindingToProvider() {
164 return addMessage("Binding to Provider is not allowed.");
165 }
166
167 public Errors subtypeNotProvided(Class<? extends Provider<?>> providerType,
168 Class<?> type) {
169 return addMessage("%s doesn't provide instances of %s.", providerType, type);
170 }
171
172 public Errors notASubtype(Class<?> implementationType, Class<?> type) {
173 return addMessage("%s doesn't extend %s.", implementationType, type);
174 }
175
176 public Errors recursiveImplementationType() {
177 return addMessage("@ImplementedBy points to the same class it annotates.");
178 }
179
180 public Errors recursiveProviderType() {
181 return addMessage("@ProvidedBy points to the same class it annotates.");
182 }
183
184 public Errors missingRuntimeRetention(Object source) {
185 return addMessage("Please annotate with @Retention(RUNTIME).%n"
186 + " Bound at %s.", convert(source));
187 }
188
189 public Errors missingScopeAnnotation() {
190 return addMessage("Please annotate with @ScopeAnnotation.");
191 }
192
193 public Errors optionalConstructor(Constructor constructor) {
194 return addMessage("%s is annotated @Inject(optional=true), "
195 + "but constructors cannot be optional.", constructor);
196 }
197
198 public Errors cannotBindToGuiceType(String simpleName) {
199 return addMessage("Binding to core guice framework type is not allowed: %s.", simpleName);
200 }
201
202 public Errors scopeNotFound(Class<? extends Annotation> scopeAnnotation) {
203 return addMessage("No scope is bound to %s.", scopeAnnotation);
204 }
205
206 public Errors scopeAnnotationOnAbstractType(
207 Class<? extends Annotation> scopeAnnotation, Class<?> type, Object source) {
208 return addMessage("%s is annotated with %s, but scope annotations are not supported "
209 + "for abstract types.%n Bound at %s.", type, scopeAnnotation, convert(source));
210 }
211
212 public Errors misplacedBindingAnnotation(Member member, Annotation bindingAnnotation) {
213 return addMessage("%s is annotated with %s, but binding annotations should be applied "
214 + "to its parameters instead.", member, bindingAnnotation);
215 }
216
217 private static final String CONSTRUCTOR_RULES =
218 "Classes must have either one (and only one) constructor "
219 + "annotated with @Inject or a zero-argument constructor that is not private.";
220
221 public Errors missingConstructor(Class<?> implementation) {
222 return addMessage("Could not find a suitable constructor in %s. " + CONSTRUCTOR_RULES,
223 implementation);
224 }
225
226 public Errors tooManyConstructors(Class<?> implementation) {
227 return addMessage("%s has more than one constructor annotated with @Inject. "
228 + CONSTRUCTOR_RULES, implementation);
229 }
230
231 public Errors duplicateScopes(Scope existing,
232 Class<? extends Annotation> annotationType, Scope scope) {
233 return addMessage("Scope %s is already bound to %s. Cannot bind %s.", existing,
234 annotationType, scope);
235 }
236
237 public Errors voidProviderMethod() {
238 return addMessage("Provider methods must return a value. Do not return void.");
239 }
240
241 public Errors missingConstantValues() {
242 return addMessage("Missing constant value. Please call to(...).");
243 }
244
245 public Errors cannotInjectInnerClass(Class<?> type) {
246 return addMessage("Injecting into inner classes is not supported. "
247 + "Please use a 'static' class (top-level or nested) instead of %s.", type);
248 }
249
250 public Errors duplicateBindingAnnotations(Member member,
251 Class<? extends Annotation> a, Class<? extends Annotation> b) {
252 return addMessage("%s has more than one annotation annotated with @BindingAnnotation: "
253 + "%s and %s", member, a, b);
254 }
255
256 public Errors duplicateScopeAnnotations(
257 Class<? extends Annotation> a, Class<? extends Annotation> b) {
258 return addMessage("More than one scope annotation was found: %s and %s.", a, b);
259 }
260
261 public Errors recursiveBinding() {
262 return addMessage("Binding points to itself.");
263 }
264
265 public Errors bindingAlreadySet(Key<?> key, Object source) {
266 return addMessage("A binding to %s was already configured at %s.", key, convert(source));
267 }
268
269 public Errors childBindingAlreadySet(Key<?> key) {
270 return addMessage("A binding to %s already exists on a child injector.", key);
271 }
272
273 public Errors errorInjectingMethod(Throwable cause) {
274 return errorInUserCode(cause, "Error injecting method, %s", cause);
275 }
276
277 public Errors errorNotifyingTypeListener(TypeListenerBinding listener,
278 TypeLiteral<?> type, Throwable cause) {
279 return errorInUserCode(cause,
280 "Error notifying TypeListener %s (bound at %s) of %s.%n"
281 + " Reason: %s",
282 listener.getListener(), convert(listener.getSource()), type, cause);
283 }
284
285 public Errors errorInjectingConstructor(Throwable cause) {
286 return errorInUserCode(cause, "Error injecting constructor, %s", cause);
287 }
288
289 public Errors errorInProvider(RuntimeException runtimeException) {
290 return errorInUserCode(runtimeException, "Error in custom provider, %s", runtimeException);
291 }
292
293 public Errors errorInUserInjector(
294 MembersInjector<?> listener, TypeLiteral<?> type, RuntimeException cause) {
295 return errorInUserCode(cause, "Error injecting %s using %s.%n"
296 + " Reason: %s", type, listener, cause);
297 }
298
299 public Errors errorNotifyingInjectionListener(
300 InjectionListener<?> listener, TypeLiteral<?> type, RuntimeException cause) {
301 return errorInUserCode(cause, "Error notifying InjectionListener %s of %s.%n"
302 + " Reason: %s", listener, type, cause);
303 }
304
305 public void exposedButNotBound(Key<?> key) {
306 addMessage("Could not expose() %s, it must be explicitly bound.", key);
307 }
308
309 public static Collection<Message> getMessagesFromThrowable(Throwable throwable) {
310 if (throwable instanceof ProvisionException) {
311 return ((ProvisionException) throwable).getErrorMessages();
312 } else if (throwable instanceof ConfigurationException) {
313 return ((ConfigurationException) throwable).getErrorMessages();
314 } else if (throwable instanceof CreationException) {
315 return ((CreationException) throwable).getErrorMessages();
316 } else {
317 return ImmutableSet.of();
318 }
319 }
320
321 public Errors errorInUserCode(Throwable cause, String messageFormat, Object... arguments) {
322 Collection<Message> messages = getMessagesFromThrowable(cause);
323
324 if (!messages.isEmpty()) {
325 return merge(messages);
326 } else {
327 return addMessage(cause, messageFormat, arguments);
328 }
329 }
330
331 public Errors cannotInjectRawProvider() {
332 return addMessage("Cannot inject a Provider that has no type parameter");
333 }
334
335 public Errors cannotInjectRawMembersInjector() {
336 return addMessage("Cannot inject a MembersInjector that has no type parameter");
337 }
338
339 public Errors cannotInjectTypeLiteralOf(Type unsupportedType) {
340 return addMessage("Cannot inject a TypeLiteral of %s", unsupportedType);
341 }
342
343 public Errors cannotInjectRawTypeLiteral() {
344 return addMessage("Cannot inject a TypeLiteral that has no type parameter");
345 }
346
347 public Errors cannotSatisfyCircularDependency(Class<?> expectedType) {
348 return addMessage(
349 "Tried proxying %s to support a circular dependency, but it is not an interface.",
350 expectedType);
351 }
352
353 public void throwCreationExceptionIfErrorsExist() {
354 if (!hasErrors()) {
355 return;
356 }
357
358 throw new CreationException(getMessages());
359 }
360
361 public void throwConfigurationExceptionIfErrorsExist() {
362 if (!hasErrors()) {
363 return;
364 }
365
366 throw new ConfigurationException(getMessages());
367 }
368
369 public void throwProvisionExceptionIfErrorsExist() {
370 if (!hasErrors()) {
371 return;
372 }
373
374 throw new ProvisionException(getMessages());
375 }
376
377 private Message merge(Message message) {
378 List<Object> sources = Lists.newArrayList();
379 sources.addAll(getSources());
380 sources.addAll(message.getSources());
381 return new Message(sources, message.getMessage(), message.getCause());
382 }
383
384 public Errors merge(Collection<Message> messages) {
385 for (Message message : messages) {
386 addMessage(merge(message));
387 }
388 return this;
389 }
390
391 public Errors merge(Errors moreErrors) {
392 if (moreErrors.root == root || moreErrors.root.errors == null) {
393 return this;
394 }
395
396 merge(moreErrors.root.errors);
397 return this;
398 }
399
400 public List<Object> getSources() {
401 List<Object> sources = Lists.newArrayList();
402 for (Errors e = this; e != null; e = e.parent) {
403 if (e.source != SourceProvider.UNKNOWN_SOURCE) {
404 sources.add(0, e.source);
405 }
406 }
407 return sources;
408 }
409
410 public void throwIfNewErrors(int expectedSize) throws ErrorsException {
411 if (size() == expectedSize) {
412 return;
413 }
414
415 throw toException();
416 }
417
418 public ErrorsException toException() {
419 return new ErrorsException(this);
420 }
421
422 public boolean hasErrors() {
423 return root.errors != null;
424 }
425
426 public Errors addMessage(String messageFormat, Object... arguments) {
427 return addMessage(null, messageFormat, arguments);
428 }
429
430 private Errors addMessage(Throwable cause, String messageFormat, Object... arguments) {
431 String message = format(messageFormat, arguments);
432 addMessage(new Message(getSources(), message, cause));
433 return this;
434 }
435
436 public Errors addMessage(Message message) {
437 if (root.errors == null) {
438 root.errors = Lists.newArrayList();
439 }
440 root.errors.add(message);
441 return this;
442 }
443
444 public static String format(String messageFormat, Object... arguments) {
445 for (int i = 0; i < arguments.length; i++) {
446 arguments[i] = Errors.convert(arguments[i]);
447 }
448 return String.format(messageFormat, arguments);
449 }
450
451 public List<Message> getMessages() {
452 if (root.errors == null) {
453 return ImmutableList.of();
454 }
455
456 List<Message> result = Lists.newArrayList(root.errors);
457 Collections.sort(result, new Comparator<Message>() {
458 public int compare(Message a, Message b) {
459 return a.getSource().compareTo(b.getSource());
460 }
461 });
462
463 return result;
464 }
465
466 /**
467 * Returns the formatted message for an exception with the specified messages.
468 */
469 public static String format(String heading, Collection<Message> errorMessages) {
470 Formatter fmt = new Formatter().format(heading).format(":%n%n");
471 int index = 1;
472 boolean displayCauses = getOnlyCause(errorMessages) == null;
473
474 for (Message errorMessage : errorMessages) {
475 fmt.format("%s) %s%n", index++, errorMessage.getMessage());
476
477 List<Object> dependencies = errorMessage.getSources();
478 for (int i = dependencies.size() - 1; i >= 0; i--) {
479 Object source = dependencies.get(i);
480 formatSource(fmt, source);
481 }
482
483 Throwable cause = errorMessage.getCause();
484 if (displayCauses && cause != null) {
485 StringWriter writer = new StringWriter();
486 cause.printStackTrace(new PrintWriter(writer));
487 fmt.format("Caused by: %s", writer.getBuffer());
488 }
489
490 fmt.format("%n");
491 }
492
493 if (errorMessages.size() == 1) {
494 fmt.format("1 error");
495 } else {
496 fmt.format("%s errors", errorMessages.size());
497 }
498
499 return fmt.toString();
500 }
501
502 /**
503 * Returns {@code value} if it is non-null allowed to be null. Otherwise a message is added and
504 * an {@code ErrorsException} is thrown.
505 */
506 public <T> T checkForNull(T value, Object source, Dependency<?> dependency)
507 throws ErrorsException {
508 if (value != null || dependency.isNullable()) {
509 return value;
510 }
511
512 int parameterIndex = dependency.getParameterIndex();
513 String parameterName = (parameterIndex != -1)
514 ? "parameter " + parameterIndex + " of "
515 : "";
516 addMessage("null returned by binding at %s%n but %s%s is not @Nullable",
517 source, parameterName, dependency.getInjectionPoint().getMember());
518
519 throw toException();
520 }
521
522 /**
523 * Returns the cause throwable if there is exactly one cause in {@code messages}. If there are
524 * zero or multiple messages with causes, null is returned.
525 */
526 public static Throwable getOnlyCause(Collection<Message> messages) {
527 Throwable onlyCause = null;
528 for (Message message : messages) {
529 Throwable messageCause = message.getCause();
530 if (messageCause == null) {
531 continue;
532 }
533
534 if (onlyCause != null) {
535 return null;
536 }
537
538 onlyCause = messageCause;
539 }
540
541 return onlyCause;
542 }
543
544 public int size() {
545 return root.errors == null ? 0 : root.errors.size();
546 }
547
548 private static abstract class Converter<T> {
549
550 final Class<T> type;
551
552 Converter(Class<T> type) {
553 this.type = type;
554 }
555
556 boolean appliesTo(Object o) {
557 return type.isAssignableFrom(o.getClass());
558 }
559
560 String convert(Object o) {
561 return toString(type.cast(o));
562 }
563
564 abstract String toString(T t);
565 }
566
567 private static final Collection<Converter<?>> converters = ImmutableList.of(
568 new Converter<Class>(Class.class) {
569 public String toString(Class c) {
570 return c.getName();
571 }
572 },
573 new Converter<Member>(Member.class) {
574 public String toString(Member member) {
575 return MoreTypes.toString(member);
576 }
577 },
578 new Converter<Key>(Key.class) {
579 public String toString(Key key) {
580 if (key.getAnnotationType() != null) {
581 return key.getTypeLiteral() + " annotated with "
582 + (key.getAnnotation() != null ? key.getAnnotation() : key.getAnnotationType());
583 } else {
584 return key.getTypeLiteral().toString();
585 }
586 }
587 }
588 );
589
590 public static Object convert(Object o) {
591 for (Converter<?> converter : converters) {
592 if (converter.appliesTo(o)) {
593 return converter.convert(o);
594 }
595 }
596 return o;
597 }
598
599 public static void formatSource(Formatter formatter, Object source) {
600 if (source instanceof Dependency) {
601 Dependency<?> dependency = (Dependency<?>) source;
602 InjectionPoint injectionPoint = dependency.getInjectionPoint();
603 if (injectionPoint != null) {
604 formatInjectionPoint(formatter, dependency, injectionPoint);
605 } else {
606 formatSource(formatter, dependency.getKey());
607 }
608
609 } else if (source instanceof InjectionPoint) {
610 formatInjectionPoint(formatter, null, (InjectionPoint) source);
611
612 } else if (source instanceof Class) {
613 formatter.format(" at %s%n", StackTraceElements.forType((Class<?>) source));
614
615 } else if (source instanceof Member) {
616 formatter.format(" at %s%n", StackTraceElements.forMember((Member) source));
617
618 } else if (source instanceof TypeLiteral) {
619 formatter.format(" while locating %s%n", source);
620
621 } else if (source instanceof Key) {
622 Key<?> key = (Key<?>) source;
623 formatter.format(" while locating %s%n", convert(key));
624
625 } else {
626 formatter.format(" at %s%n", source);
627 }
628 }
629
630 public static void formatInjectionPoint(Formatter formatter, Dependency<?> dependency,
631 InjectionPoint injectionPoint) {
632 Member member = injectionPoint.getMember();
633 Class<? extends Member> memberType = MoreTypes.memberType(member);
634
635 if (memberType == Field.class) {
636 dependency = injectionPoint.getDependencies().get(0);
637 formatter.format(" while locating %s%n", convert(dependency.getKey()));
638 formatter.format(" for field at %s%n", StackTraceElements.forMember(member));
639
640 } else if (dependency != null) {
641 formatter.format(" while locating %s%n", convert(dependency.getKey()));
642 formatter.format(" for parameter %s at %s%n",
643 dependency.getParameterIndex(), StackTraceElements.forMember(member));
644
645 } else {
646 formatSource(formatter, injectionPoint.getMember());
647 }
648 }
649}