blob: 4870ca67172d9bd52f6c7799bc7bc908c3e1ad3f [file] [log] [blame]
/**
* 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.inject.Binding;
import com.google.inject.ProvisionException;
import com.google.inject.spi.DependencyAndSource;
import com.google.inject.spi.ProvisionListener;
import java.util.List;
/**
* 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];
private final ProvisionListener[] listeners;
private final Binding<T> binding;
public ProvisionListenerStackCallback(Binding<T> binding, List<ProvisionListener> listeners) {
this.binding = binding;
if (listeners.isEmpty()) {
this.listeners = EMPTY_LISTENER;
} else {
this.listeners = listeners.toArray(new ProvisionListener[listeners.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 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;
}
@Override
public T provision() {
index++;
if (index == listeners.length) {
try {
result = callable.call();
} 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;
}
@Override
public List<DependencyAndSource> getDependencyChain() {
return context.getDependencyChain();
}
}
}