ProvisionListenerStackCallback.java revision ba0cff043c4412778856a45381dd35e02e30235c
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.inject.internal; 18 19import com.google.common.collect.ImmutableList; 20import com.google.common.collect.Sets; 21import com.google.inject.Binding; 22import com.google.inject.ProvisionException; 23import com.google.inject.spi.DependencyAndSource; 24import com.google.inject.spi.ProvisionListener; 25 26import java.util.List; 27import java.util.Set; 28 29/** 30 * Intercepts provisions with a stack of listeners. 31 * 32 * @author sameb@google.com (Sam Berlin) 33 */ 34final class ProvisionListenerStackCallback<T> { 35 36 private static final ProvisionListener EMPTY_LISTENER[] = new ProvisionListener[0]; 37 @SuppressWarnings("rawtypes") 38 private static final ProvisionListenerStackCallback<?> EMPTY_CALLBACK = 39 new ProvisionListenerStackCallback(null /* unused, so ok */, ImmutableList.of()); 40 41 private final ProvisionListener[] listeners; 42 private final Binding<T> binding; 43 44 @SuppressWarnings("unchecked") 45 public static <T> ProvisionListenerStackCallback<T> emptyListener() { 46 return (ProvisionListenerStackCallback<T>) EMPTY_CALLBACK; 47 } 48 49 public ProvisionListenerStackCallback(Binding<T> binding, List<ProvisionListener> listeners) { 50 this.binding = binding; 51 if (listeners.isEmpty()) { 52 this.listeners = EMPTY_LISTENER; 53 } else { 54 Set<ProvisionListener> deDuplicated = Sets.newLinkedHashSet(listeners); 55 this.listeners = deDuplicated.toArray(new ProvisionListener[deDuplicated.size()]); 56 } 57 } 58 59 public boolean hasListeners() { 60 return listeners.length > 0; 61 } 62 63 public T provision(Errors errors, InternalContext context, ProvisionCallback<T> callable) 64 throws ErrorsException { 65 Provision provision = new Provision(errors, context, callable); 66 RuntimeException caught = null; 67 try { 68 provision.provision(); 69 } catch(RuntimeException t) { 70 caught = t; 71 } 72 73 if (provision.exceptionDuringProvision != null) { 74 throw provision.exceptionDuringProvision; 75 } else if (caught != null) { 76 Object listener = provision.erredListener != null ? 77 provision.erredListener.getClass() : "(unknown)"; 78 throw errors 79 .errorInUserCode(caught, "Error notifying ProvisionListener %s of %s.%n" 80 + " Reason: %s", listener, binding.getKey(), caught) 81 .toException(); 82 } else { 83 return provision.result; 84 } 85 } 86 87 // TODO(sameb): Can this be more InternalFactory-like? 88 public interface ProvisionCallback<T> { 89 public T call() throws ErrorsException; 90 } 91 92 private class Provision extends ProvisionListener.ProvisionInvocation<T> { 93 94 final Errors errors; 95 final int numErrorsBefore; 96 final InternalContext context; 97 final ProvisionCallback<T> callable; 98 int index = -1; 99 T result; 100 ErrorsException exceptionDuringProvision; 101 ProvisionListener erredListener; 102 103 public Provision(Errors errors, InternalContext context, ProvisionCallback<T> callable) { 104 this.callable = callable; 105 this.context = context; 106 this.errors = errors; 107 this.numErrorsBefore = errors.size(); 108 } 109 110 @Override 111 public T provision() { 112 index++; 113 if (index == listeners.length) { 114 try { 115 result = callable.call(); 116 // Make sure we don't return the provisioned object if there were any errors 117 // injecting its field/method dependencies. 118 errors.throwIfNewErrors(numErrorsBefore); 119 } catch(ErrorsException ee) { 120 exceptionDuringProvision = ee; 121 throw new ProvisionException(errors.merge(ee.getErrors()).getMessages()); 122 } 123 } else if (index < listeners.length) { 124 int currentIdx = index; 125 try { 126 listeners[index].onProvision(this); 127 } catch(RuntimeException re) { 128 erredListener = listeners[currentIdx]; 129 throw re; 130 } 131 if (currentIdx == index) { 132 // Our listener didn't provision -- do it for them. 133 provision(); 134 } 135 } else { 136 throw new IllegalStateException("Already provisioned in this listener."); 137 } 138 return result; 139 } 140 141 @Override 142 public Binding<T> getBinding() { 143 // TODO(sameb): Because so many places cast directly to BindingImpl & subclasses, 144 // we can't decorate this to prevent calling getProvider().get(), which means 145 // if someone calls that they'll get strange errors. 146 return binding; 147 } 148 149 @Override 150 public List<DependencyAndSource> getDependencyChain() { 151 return context.getDependencyChain(); 152 } 153 } 154} 155