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