/* * Copyright (C) 2011 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.inject.internal; import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; import com.google.inject.Binding; import com.google.inject.ProvisionException; import com.google.inject.spi.ProvisionListener; import java.util.List; import java.util.Set; /** * Intercepts provisions with a stack of listeners. * * @author sameb@google.com (Sam Berlin) */ final class ProvisionListenerStackCallback<T> { private static final ProvisionListener EMPTY_LISTENER[] = new ProvisionListener[0]; @SuppressWarnings("rawtypes") private static final ProvisionListenerStackCallback<?> EMPTY_CALLBACK = new ProvisionListenerStackCallback(null /* unused, so ok */, ImmutableList.of()); private final ProvisionListener[] listeners; private final Binding<T> binding; @SuppressWarnings("unchecked") public static <T> ProvisionListenerStackCallback<T> emptyListener() { return (ProvisionListenerStackCallback<T>) EMPTY_CALLBACK; } public ProvisionListenerStackCallback(Binding<T> binding, List<ProvisionListener> listeners) { this.binding = binding; if (listeners.isEmpty()) { this.listeners = EMPTY_LISTENER; } else { Set<ProvisionListener> deDuplicated = Sets.newLinkedHashSet(listeners); this.listeners = deDuplicated.toArray(new ProvisionListener[deDuplicated.size()]); } } public boolean hasListeners() { return listeners.length > 0; } public T provision(Errors errors, InternalContext context, ProvisionCallback<T> callable) throws ErrorsException { Provision provision = new Provision(errors, context, callable); RuntimeException caught = null; try { provision.provision(); } catch (RuntimeException t) { caught = t; } if (provision.exceptionDuringProvision != null) { throw provision.exceptionDuringProvision; } else if (caught != null) { Object listener = provision.erredListener != null ? provision.erredListener.getClass() : "(unknown)"; throw errors .errorInUserCode( caught, "Error notifying ProvisionListener %s of %s.%n Reason: %s", listener, binding.getKey(), caught) .toException(); } else { return provision.result; } } // TODO(sameb): Can this be more InternalFactory-like? public interface ProvisionCallback<T> { public T call() throws ErrorsException; } private class Provision extends ProvisionListener.ProvisionInvocation<T> { final Errors errors; final int numErrorsBefore; final InternalContext context; final ProvisionCallback<T> callable; int index = -1; T result; ErrorsException exceptionDuringProvision; ProvisionListener erredListener; public Provision(Errors errors, InternalContext context, ProvisionCallback<T> callable) { this.callable = callable; this.context = context; this.errors = errors; this.numErrorsBefore = errors.size(); } @Override public T provision() { index++; if (index == listeners.length) { try { result = callable.call(); // Make sure we don't return the provisioned object if there were any errors // injecting its field/method dependencies. errors.throwIfNewErrors(numErrorsBefore); } catch (ErrorsException ee) { exceptionDuringProvision = ee; throw new ProvisionException(errors.merge(ee.getErrors()).getMessages()); } } else if (index < listeners.length) { int currentIdx = index; try { listeners[index].onProvision(this); } catch (RuntimeException re) { erredListener = listeners[currentIdx]; throw re; } if (currentIdx == index) { // Our listener didn't provision -- do it for them. provision(); } } else { throw new IllegalStateException("Already provisioned in this listener."); } return result; } @Override public Binding<T> getBinding() { // TODO(sameb): Because so many places cast directly to BindingImpl & subclasses, // we can't decorate this to prevent calling getProvider().get(), which means // if someone calls that they'll get strange errors. return binding; } @Deprecated @Override public List<com.google.inject.spi.DependencyAndSource> getDependencyChain() { return context.getDependencyChain(); } } }