/** | |
* 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 java.util.List; | |
import com.google.inject.Key; | |
import com.google.inject.ProvisionException; | |
import com.google.inject.spi.ProvisionListener; | |
import com.google.inject.spi.DependencyAndSource; | |
/** | |
* 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 Key<T> key; | |
public ProvisionListenerStackCallback(Key<T> key, List<ProvisionListener> listeners) { | |
this.key = key; | |
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, key, 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 Key<T> getKey() { | |
return key; | |
} | |
@Override | |
public List<DependencyAndSource> getDependencyChain() { | |
return context.getDependencyChain(); | |
} | |
} | |
} |