org.jvnet.hk2.component.Habitat Maven / Gradle / Ivy
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2007-2011 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package org.jvnet.hk2.component;
import com.sun.hk2.component.CompanionSeed;
import static com.sun.hk2.component.CompanionSeed.Registerer.createCompanion;
import com.sun.hk2.component.ExistingSingletonInhabitant;
import com.sun.hk2.component.FactoryCreator;
import com.sun.hk2.component.InjectInjectionResolver;
import com.sun.hk2.component.InjectionResolver;
import com.sun.hk2.component.LeadInjectionResolver;
import static com.sun.hk2.component.InhabitantsFile.CAGE_BUILDER_KEY;
import com.sun.hk2.component.ScopeInstance;
import org.jvnet.hk2.annotations.Contract;
import org.jvnet.hk2.annotations.ContractProvided;
import org.jvnet.hk2.annotations.FactoryFor;
import org.jvnet.hk2.component.HabitatListener.EventType;
import org.jvnet.hk2.component.InhabitantTracker.Callback;
import org.jvnet.hk2.component.internal.runlevel.DefaultRunLevelService;
import java.lang.annotation.Annotation;
import java.util.Map.Entry;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A set of templates that constitute a world of objects.
*
* @author Kohsuke Kawaguchi
* @author Jerome Dochez
*/
@SuppressWarnings("unchecked")
public class Habitat {
/**
* Name to use to programmatically store default instances of a particular service.
*/
public final String DEFAULT_NAME = "_HABITAT_DEFAULT";
/**
* Contract type FQCN to their corresponding inhabitants.
*
* Can't use {@link Class} as the key so that we can create index without
* loading contract types. Once populated upfront, it works as a read-only map.
*/
private final MultiMap byContract;
/**
* Index by {@link Inhabitant#type()}.
*/
private final MultiMap byType;
public final ScopeInstance singletonScope;
// TODO: toggle to use concurrency controls as the default (or after habitat is initially built)
// TODO: Jerome to review performance costs
static final boolean CONCURRENCY_CONTROLS_DEFAULT =
// "true".equals(System.getProperty("hk2.concurrency.controls", "true"));
Boolean.getBoolean("hk2.concurrency.controls");
private final boolean concurrencyControls;
// Listeners and trackers use this executor for notification of habitat
// changes. Many systems will require same-thread notifications. This is
// why OSGi defaults to synchronous service tracker customizers, etc.
// We also, therefore, will default to a synchronous model.
static boolean ASYNC_EXECUTOR =
Boolean.getBoolean("hk2.async.executor");
final ExecutorService exec;
private boolean initialized;
public Habitat() {
this(null, null);
}
Habitat(ExecutorService exec, Boolean concurrency_controls) {
this.concurrencyControls =
(null == concurrency_controls)
? CONCURRENCY_CONTROLS_DEFAULT : concurrency_controls;
this.byContract = new MultiMap(this.concurrencyControls);
this.byType = new MultiMap(this.concurrencyControls);
this.singletonScope = new ScopeInstance("singleton", new HashMap());
if (null == exec) {
if (ASYNC_EXECUTOR) {
exec = Executors.newCachedThreadPool(new ThreadFactory() {
@Override
public Thread newThread(Runnable runnable) {
Thread t = Executors.defaultThreadFactory().newThread(runnable);
t.setDaemon(true);
return t;
}
});
} else {
exec = new SameThreadExecutor();
}
}
this.exec = exec;
// make the listeners available very early in lifecycle
addHabitatListener(new SelfListener());
// add the set of injection resolvers
add(new ExistingSingletonInhabitant(InjectionResolver.class,
new InjectInjectionResolver(this)));
add(new ExistingSingletonInhabitant(InjectionResolver.class,
new LeadInjectionResolver(this)));
// make the habitat itself available
add(new ExistingSingletonInhabitant(Habitat.class,this));
add(new ExistingSingletonInhabitant(CompanionSeed.Registerer.class,
new CompanionSeed.Registerer(this)));
add(new ExistingSingletonInhabitant(CageBuilder.Registerer.class,
new CageBuilder.Registerer(this)));
// the default RunLevelService
DefaultRunLevelService rls = new DefaultRunLevelService(this);
ExistingSingletonInhabitant rlsI =
new ExistingSingletonInhabitant(RunLevelService.class, rls);
add(rlsI);
addIndex(rlsI, RunLevelService.class.getName(), DefaultRunLevelService.NAME);
}
/**
* Add a habitat listener with no contract-level filtering.
* This API is primarily intended for internal cases within
* Hk2.
*
* The listener with no contract-level filtering will be
* called for all change events within the habitat pertaining
* to inhabitants.
*
* @see {@link #addHabitatListener(HabitatListener, String...)}
* is recommended for most cases
*
* @param listener
* The habitat Listener to be added
*
* @since 3.1
*/
public void addHabitatListener(HabitatListener listener) {
addHabitatListener(listener, DEFAULT_NAME);
}
/**
* Add a habitat listener with contract-level filtering.
*
* The listener will be called based on the set of contract
* filters provided.
*
* @param listener
* The habitat Listener to be added
* @param typeNames
* The contracts to filter on; this should be non-null
*
* @since 3.1
*/
public void addHabitatListener(HabitatListener listener, String... typeNames) {
if (null == typeNames || 0 == typeNames.length) throw new IllegalArgumentException();
addHabitatListener(listener, new HashSet(Arrays.asList(typeNames)));
}
protected void addHabitatListener(HabitatListener listener, Set typeNames) {
ExistingSingletonInhabitant inhabitant =
new ExistingSingletonInhabitant(HabitatListener.class,
listener, metaData(typeNames));
add(inhabitant);
// track listeners by type - support of filtered notification
for (String contract : typeNames) {
ListenersByTypeInhabitant sameListeners;
synchronized (byContract) {
sameListeners = (ListenersByTypeInhabitant)
getInhabitantByContract(ListenersByTypeInhabitant.class.getName(),
contract);
if (null == sameListeners) {
sameListeners = new ListenersByTypeInhabitant(contract);
addIndex(sameListeners, ListenersByTypeInhabitant.class.getName(),
contract,
false); // don't send notifications for this type (no particular reason really)
}
}
sameListeners.add(listener);
}
}
/**
* Remove a habitat listener.
*
* @param listener
* The habitat Listener to be removed
* @param typeNames
* The contracts to filter on
*
* @return true; if the listener was indeed removed
*
* @since 3.1
*/
public boolean removeHabitatListener(HabitatListener listener) {
List list = byType._get(HabitatListener.class.getName());
List releaseList = new ArrayList();
Iterator iter = list.iterator();
while (iter.hasNext()) {
Inhabitant existing = iter.next();
if (existing.get() == listener) {
releaseList.add(existing);
if (concurrencyControls) {
list.remove(existing);
} else {
iter.remove();
}
}
}
// need to release the per-listener-contract entries too
for (Inhabitant released : releaseList) {
MultiMap metadata = released.metadata();
if (null != metadata) {
List filters = metadata.get(Constants.OBJECTCLASS);
for (String contract : filters) {
ListenersByTypeInhabitant sameListeners;
synchronized (byContract) {
sameListeners = (ListenersByTypeInhabitant)
getInhabitantByContract(ListenersByTypeInhabitant.class.getName(),
contract);
}
sameListeners.remove(listener);
}
}
released.release();
notify(released, EventType.INHABITANT_REMOVED, null, released);
}
return !releaseList.isEmpty();
}
/**
* Convert a set of type names into OBJECTCLASS keys within metadata
*/
private MultiMap metaData(Set typeNames) {
if (null == typeNames) {
return null;
}
MultiMap metadata = new MultiMap();
for (String typeName : typeNames) {
metadata.add(Constants.OBJECTCLASS, typeName);
}
return metadata;
}
/**
* Registers a dependency on the inhabitant with the given tracker context.
*
* Once the criteria is met, any callback provided is called. This callback may
* occur asynchronously from the thread initiating the event.
*
* @param itc
* The tracking criteria.
* @param callback
* Optionally the callback.
* @return
* The tracker
* @throws ComponentException
*
* @since 3.1
*/
public InhabitantTracker track(InhabitantTrackerContext itc,
Callback callback) throws ComponentException {
if (null == itc) throw new IllegalArgumentException();
return new InhabitantTrackerImpl(this, itc, callback);
}
/**
* Returns a future that can be checked asynchronously, and multiple times.
*
* Implementation Note: The Future that is returned does not behave in
* the traditional sense in that it is NOT directly submitted to an
* ExecutorService. Each call to get() or get(timeout) may result in a
* [re]submission to an internally held executor. This means that a
* call to get(...) may return a tracker, and a subsequent call to get(...) may
* return null, or vice versa. This is true until the underlying tracker is
* released at which point a tracker is no longer usable.
*
* @param itc
* The tracking criteria.
* @return
* The tracker
* @throws ComponentException
*
* @since 3.1
*/
public Future trackFuture(InhabitantTrackerContext itc) throws ComponentException {
if (null == itc) throw new IllegalArgumentException();
return new InhabitantTrackerJob(this, itc);
}
/*
Why initialize/processInhabitantDecorations didn't work:
when a new CageBuilder comes into the habitat, it needs to build cages for all existing components.
when a caged component comes into the habitat, it checks existing cage builders in the habitat.
Now, when a cage builder and a component are both initialized first and then processInhabitantDecorations
runs for both of them later, then you end up creating a cage twice, because the builder think it got into
habitat after than the component, and the component think it got into habitat after the builder.
*/
/**
* Removes all inhabitants for a particular type
*
* @param type of the component
*
* @return true if any inhabitants were removed
*/
public boolean removeAllByType(Class> type) {
boolean removed = false;
String name = type.getName();
// remove all existing inhabitants by type
List list = new ArrayList(byType.get(name));
for (Inhabitant existing : list) {
removed |= remove(existing);
}
return removed;
}
/**
* Adds a new inhabitant.
*
*
* See {@link Inhabitants} for typical ways to create {@link Inhabitant}s.
*/
public void add(final Inhabitant> i) {
String name = i.typeName();
byType.add(name,i);
// for each companion, create an inhabitant that goes with the lead and hook them up
List companions=null;
for(Inhabitant> c : getInhabitantsByAnnotation(CompanionSeed.class,name)) {
if (companions==null) {
companions = new ArrayList();
}
companions.add(createCompanion(this,i,c));
}
i.setCompanions(companions);
String cageBuilderName = i.metadata().getOne(CAGE_BUILDER_KEY);
if(cageBuilderName!=null) {
Inhabitant cageBuilder = byType.getOne(cageBuilderName);
if (cageBuilder!=null) {
((CageBuilder)cageBuilder.get()).onEntered(i);
}
// if cageBuilder==null, we can't cage this component now, but
// we'll do that when cageBuilder comes into the habitat.
// that happens because every CageBuilder implementations are caged by
// CageBuilder.Registerer.
}
notify(i, EventType.INHABITANT_ADDED, null, null);
}
/**
* Adds a new index to look up the given inhabitant.
*
* @param index
* Primary index name, such as contract FQCN.
* @param name
* Name that identifies the inhabitant among other inhabitants
* in the same index. Can be null for unnamed inhabitants.
*/
public void addIndex(Inhabitant> i, String index, String name) {
addIndex(i, index, name, true);
}
protected void addIndex(Inhabitant> i, String index, String name, boolean notify) {
byContract.add(index,new NamedInhabitant(name,i));
if (notify) {
notify(i, EventType.INHABITANT_INDEX_ADDED, index, name, null, null);
}
}
protected static Long getServiceRanking(Inhabitant> i, boolean wantNonNull) {
MultiMap meta = i.metadata();
String sr = meta.getOne(Constants.SERVICE_RANKING);
if (null == sr) {
return (wantNonNull) ? 0L : null;
}
return Long.valueOf(sr);
}
/**
* Removes an inhabitant
*
* @param inhabitant
* inhabitant to be removed
*/
public boolean remove(Inhabitant> inhabitant) {
String name = inhabitant.typeName();
if (byType.remove(name, inhabitant)) {
notify(inhabitant, EventType.INHABITANT_REMOVED, null, null);
inhabitant.release();
return true;
}
return false;
}
/**
* Removes a NamedInhabitant for a specific contract
*
* @param index contract name
* @param name instance name
* @return true if the removal was successful
*/
public boolean removeIndex(String index, String name) {
boolean removed = false;
if (byContract.containsKey(index)) {
List contracted = byContract._get(index);
Iterator iter = contracted.iterator();
while (iter.hasNext()) {
NamedInhabitant i = iter.next();
if ((i.name == null && name == null) ||
(i.name != null && i.name.equals(name))) {
if (concurrencyControls) {
removed = contracted.remove(i);
assert(removed);
} else {
iter.remove();
}
removed = true;
notify(i.inhabitant, EventType.INHABITANT_INDEX_REMOVED,
index, name, null, null);
// remember to remove the components stored under its type
remove(i.inhabitant);
}
}
}
return removed;
}
/**
* Removes a Contracted service
*
* @param index the contract name
* @param serviceOrInhabitant the service instance, or an Inhabitant instance
*/
public boolean removeIndex(String index, Object serviceOrInhabitant) {
boolean removed = false;
if (byContract.containsKey(index)) {
List contracted = byContract._get(index);
Iterator iter = contracted.iterator();
while (iter.hasNext()) {
NamedInhabitant i = iter.next();
if (matches(i.inhabitant, serviceOrInhabitant)) {
if (concurrencyControls) {
removed = contracted.remove(i);
assert(removed);
} else {
iter.remove();
}
removed = true;
notify(i.inhabitant, EventType.INHABITANT_INDEX_REMOVED,
index, null, service(serviceOrInhabitant), null);
// remember to remove the components stored under its type
remove(i.inhabitant);
}
}
}
return removed;
}
protected boolean matches(Inhabitant> inhabitant, Object serviceOrInhabitant) {
boolean matches;
if (serviceOrInhabitant instanceof Inhabitant) {
matches = (serviceOrInhabitant == inhabitant);
} else {
// call to isInstantiated is necessary to avoid loading of services that are not yet loaded.
matches = inhabitant.isInstantiated() && inhabitant.get()==serviceOrInhabitant;
}
return matches;
}
protected Object service(Object serviceOrInhabitant) {
return (serviceOrInhabitant instanceof Inhabitant)
? ((Inhabitant)serviceOrInhabitant).get() : serviceOrInhabitant;
}
protected static interface NotifyCall {
boolean inhabitantChanged(HabitatListener listener);
}
/**
* Trigger a notification that an inhabitant has changed.
*
* @param inhabitant the inhabitant that has changed
* @param contracts the contracts associated with the inhabitant
*/
public void notifyInhabitantChanged(Inhabitant> inhabitant,
String... contracts) {
if (null == contracts || 0 == contracts.length) {
// generic inhabitant modification (only general listeners will get notification)
notify(inhabitant, EventType.INHABITANT_MODIFIED, null, null);
} else {
// all scoped-listeners will get modification
for (String contract : contracts) {
notify(inhabitant, EventType.INHABITANT_MODIFIED, contract, null);
}
}
}
/**
* FOR INTERNAL USE ONLY
*/
public void initialized() {
if (initialized) throw new RuntimeException("already initialized");
initialized = true;
notify(null, EventType.HABITAT_INITIALIZED, null, null);
}
public boolean isInitialized() {
return initialized;
}
protected void notify(final Inhabitant> inhabitant,
final EventType event,
final String index,
final Inhabitant extraListenerToBeNotified) {
NotifyCall innerCall = new NotifyCall() {
public boolean inhabitantChanged(HabitatListener listener) {
return listener.inhabitantChanged(event, Habitat.this, inhabitant);
}
};
notify(innerCall, inhabitant, event, index, extraListenerToBeNotified);
}
protected void notify(final Inhabitant> inhabitant,
final EventType event,
final String index,
final String name,
final Object service,
final Inhabitant extraListenerToBeNotified) {
NotifyCall innerCall = new NotifyCall() {
public boolean inhabitantChanged(HabitatListener listener) {
return listener.inhabitantIndexChanged(event, Habitat.this, inhabitant,
index, name, service);
}
};
notify(innerCall, inhabitant, event, index, extraListenerToBeNotified);
}
protected void notify(final NotifyCall innerCall,
final Inhabitant> inhabitant,
final EventType event,
final String index,
final Inhabitant extraListenerToBeNotified) {
// do scoped listeners first
if (null != index) {
doNotify(innerCall, inhabitant, event, index, null);
}
// do general listeners next
doNotify(innerCall, inhabitant, event, DEFAULT_NAME, extraListenerToBeNotified);
}
private void doNotify(final NotifyCall innerCall,
final Inhabitant> inhabitant,
final EventType event,
final String index,
final Inhabitant extraListenerToBeNotified) {
final ListenersByTypeInhabitant sameListeners =
(ListenersByTypeInhabitant)getInhabitantByContract(
ListenersByTypeInhabitant.class.getName(),
index);
if (null != sameListeners && !sameListeners.listeners.isEmpty()) {
exec.execute(new Runnable() {
public void run() {
Iterator iter = sameListeners.listeners.iterator();
while (iter.hasNext()) {
Object entry = iter.next();
HabitatListener listener =
(entry instanceof HabitatListener) ?
(HabitatListener)entry :
(HabitatListener)((Inhabitant)entry).get();
try {
boolean keepMe = innerCall.inhabitantChanged(listener);
if (!keepMe) {
removeHabitatListener(listener);
}
} catch (Exception e){
// don't percolate the exception since it may negatively impact processing
Logger.getLogger(Habitat.class.getName()).
log(Level.WARNING, "exception caught from listener: ", e);
}
}
// we take the extraListenerToBeNotified because
// (a) we want to have all notifications in the executor, and
// (b) it might trigger an exception that we want to trap
if (null != extraListenerToBeNotified) {
extraListenerToBeNotified.get().inhabitantChanged(event, Habitat.this, extraListenerToBeNotified);
}
}
});
}
}
/**
* Checks if the given type is a contract interface that has some implementations in this {@link Habitat}.
*
*
* There are two ways for a type to be marked as a contract.
* Either it has {@link Contract}, or it's marked by {@link ContractProvided} from the implementation.
*
*
* Note that just having {@link Contract} is not enough to make this method return true.
* It can still return false if the contract has no implementation in this habitat.
*
*
* This method is useful during the injection to determine what lookup to perform,
* and it handles the case correctly when the type is marked as a contract by {@link ContractProvided}.
*/
public boolean isContract(Class> type) {
return byContract.containsKey(type.getName());
}
public boolean isContract(String fullyQualifiedClassName) {
return byContract.containsKey(fullyQualifiedClassName);
}
/**
* Gets all the inhabitants registered under the given {@link Contract}.
* This is an example of heterogeneous type-safe container.
*
* @return
* can be empty but never null.
*/
public Collection getAllByContract(Class contractType) {
return getAllByContract(contractType.getName());
}
public Collection getAllByContract(String contractType) {
final List l = byContract.get(contractType);
return new AbstractList() {
public T get(int index) {
return (T)l.get(index).inhabitant.get();
}
public int size() {
return l.size();
}
};
}
public Collection> getAllInhabitantsByContract(String contractType) {
final List l = byContract.get(contractType);
return new AbstractList>() {
public Inhabitant> get(int index) {
return l.get(index).inhabitant;
}
public int size() {
return l.size();
}
};
}
/**
* Gets the object of the given type.
*
* @return
* can be empty but never null.
*/
public Collection getAllByType(Class implType) {
final List l = byType.get(implType.getName());
return new AbstractList() {
public T get(int index) {
return (T)l.get(index).get();
}
public int size() {
return l.size();
}
};
}
/**
* Add an already instantiated component to this manager. The component has
* been instantiated by external code, however dependency injection, PostConstruct
* invocation and dependency extraction will be performed on this instance before
* it is store in the relevant scope's resource manager.
*
* @param name name of the component, could be default name
* @param component component instance
* @throws ComponentException if the passed object is not an HK2 component or
* injection/extraction failed.
*/
// TODO: mutating Habitat after it's created poses synchronization issue
public void addComponent(String name, T component) throws ComponentException {
add(new ExistingSingletonInhabitant(component));
}
/**
* Obtains a reference to the component inside the manager.
*
*
* This is the "new Foo()" equivalent in the IoC world.
*
*
* Depending on the {@link Scope} of the component, a new instance
* might be created, or an existing instance might be returned.
*
* @return
* non-null.
* @throws ComponentException
* If failed to obtain a requested instance.
* In practice, failure only happens when we try to create a
* new instance of the component.
*/
public T getComponent(Class clazz) throws ComponentException {
if(isContract(clazz))
return getByContract(clazz);
else
return getByType(clazz);
}
/**
* Loads a component that implements the given contract and has the given name.
*
* @param name
* can be null, in which case it'll only match to the unnamed component.
* @return
* null if no such servce exists.
*/
public T getComponent(Class contract, String name) throws ComponentException {
if (name!=null && name.length()==0)
name=null;
Inhabitant i = getInhabitant(contract, name);
if(i!=null)
try {
return contract.cast(i.get());
} catch (ClassCastException e) {
Logger.getAnonymousLogger().severe("ClassCastException between contract " + contract + " and service " + i.get());
Logger.getAnonymousLogger().severe("Contract class loader " + contract.getClassLoader());
Logger.getAnonymousLogger().severe("Service class loader " + i.get().getClass().getClassLoader());
throw e;
}
else
return null;
}
public Object getComponent(String fullQualifiedName, String name) {
if (name!=null && name.length()==0) {
name=null;
}
Inhabitant i = isContract(fullQualifiedName)?
getInhabitantByContract(fullQualifiedName, name):getInhabitantByType(fullQualifiedName);
return i==null?null:i.get();
}
/**
* Gets a lazy reference to the component.
*
*
* This method defers the actual instantiation of the component
* until {@link Inhabitant#get()} is invoked.
*
* @return null
* if no such component is found.
*/
public Inhabitant getInhabitant(Class contract, String name) throws ComponentException {
return getInhabitantByContract(contract.getName(), name);
}
/**
* Gets a lazy reference to the component.
*
*
* This method defers the actual instantiation of the component
* until {@link Inhabitant#get()} is invoked.
*
* @return null
* if no such component is found.
*/
public Inhabitant getInhabitantByType(Class implType) {
return (Inhabitant)getInhabitantByType(implType.getName());
}
public Inhabitant> getInhabitantByType(String fullyQualifiedClassName) {
List list = byType.get(fullyQualifiedClassName);
if(list.isEmpty()) return null;
return list.get(0);
}
/**
* Gets the inhabitant that has the given contract annotation and the given name.
*
*
* This method defers the actual instantiation of the component
* until {@link Inhabitant#get()} is invoked.
*
* @return null
* if no such component is found.
*/
public Inhabitant> getInhabitantByAnnotation(Class extends Annotation> contract, String name) throws ComponentException {
return getInhabitantByContract(contract.getName(), name);
}
/**
* Gets all the inhabitants that has the given contract.
*/
public Collection> getInhabitants(Class contract) throws ComponentException {
final List l = byContract.get(contract.getName());
return new AbstractList>() {
public Inhabitant extends T> get(int index) {
return l.get(index).inhabitant;
}
public int size() {
return l.size();
}
};
}
/**
* Gets all the inhabitants that has the given implementation type.
*/
public Collection> getInhabitantsByType(Class implType) throws ComponentException {
return (Collection)byType.get(implType.getName());
}
/**
* Gets all the inhabitants that has the given implementation type name.
*/
public Collection> getInhabitantsByType(String fullyQualifiedClassName) {
return (Collection)byType.get(fullyQualifiedClassName);
}
/**
* Get the first inhabitant by contract
*
* @param fullyQualifiedClassName
* @return
*/
public Inhabitant> getInhabitantByContract(String typeName) {
final List services = byContract._get(typeName);
return (null == services || services.isEmpty()) ? null : services.get(0).inhabitant;
}
public Collection> getInhabitantsByContract(String fullyQualifiedClassName) {
final List services = byContract.get(fullyQualifiedClassName);
return new AbstractList>() {
public Inhabitant> get(int index) {
return services.get(index).inhabitant;
}
public int size() {
return services.size();
}
};
}
private class MultiMapIterator implements Iterator {
final Iterator>> itr;
MultiMapIterator(MultiMap map) {
itr = map.entrySet().iterator();
}
public boolean hasNext() {
return itr.hasNext();
}
public Object next() {
return itr.next().getKey();
}
public void remove() {
throw new UnsupportedOperationException();
}
}
public Iterator getAllContracts() {
return new MultiMapIterator(byContract);
}
public Iterator getAllTypes() {
return new MultiMapIterator(byType);
}
public Inhabitant getInhabitantByContract(String fullyQualifiedName, String name) {
// TODO: faster implementation needed
for (NamedInhabitant i : byContract.get(fullyQualifiedName)) {
if(eq(i.name,name))
return i.inhabitant;
}
return null;
}
/**
* Gets all the inhabitants that has the given contract and the given name
*
*
* This method defers the actual instantiation of the component
* until {@link Inhabitant#get()} is invoked.
*
* @return
* Can be empty but never null.
*/
public Iterable> getInhabitants(Class contract, String name) throws ComponentException {
return _getInhabitants(contract,name);
}
/**
* Gets all the inhabitants that has the given contract annotation and the given name.
*
*
* This method defers the actual instantiation of the component
* until {@link Inhabitant#get()} is invoked.
*
* @return
* Can be empty but never null.
*/
public Iterable> getInhabitantsByAnnotation(Class extends Annotation> contract, String name) throws ComponentException {
return _getInhabitants(contract, name);
}
// intentionally not generified so that the getInhabitants methods can choose the right signature w/o error
private Iterable _getInhabitants(final Class contract, final String name) {
return new Iterable() {
private final Iterable base = byContract.get(contract.getName());
public Iterator iterator() {
return new Iterator() {
private Inhabitant i = null;
private final Iterator itr = base.iterator();
public boolean hasNext() {
while(i==null && itr.hasNext()) {
NamedInhabitant ni = itr.next();
if(eq(ni.name,name))
i = ni.inhabitant;
}
return i!=null;
}
public Inhabitant next() {
if(i==null)
throw new NoSuchElementException();
Inhabitant r = i;
i = null;
return r;
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
};
}
private static boolean eq(String a, String b) {
if(a==null && b==null) return true;
if(a==null || b==null) return false;
return a.equals(b);
}
/**
* Gets the object of the given type.
*
* @return null if not found.
*/
public T getByType(Class implType) {
return getBy(implType, byType);
}
/**
* Gets the object that has the given contract.
*
*
* If there are more than one of them, this method arbitrarily return
* one of them.
*/
public T getByContract(Class contractType) {
List l = byContract.get(contractType.getName());
if(l.isEmpty()) return null;
else return (T)l.get(0).inhabitant.get();
}
private T getBy(Class implType, MultiMap index) {
List l = index.get(implType.getName());
if(l.isEmpty()) return null;
else return (T)l.get(0).get();
}
/**
* Releases all the components.
* Should be called for orderly shut-down of the system.
*
* TODO: more javadoc needed
*/
public void release() {
// TODO: synchronization story?
for (Entry> e : byType.entrySet()) {
for (Inhabitant i : e.getValue()) {
i.release();
notify(i, EventType.INHABITANT_REMOVED, null, null);
}
}
}
private static final class NamedInhabitant {
/**
* Name. Can be null.
*/
final String name;
final Inhabitant inhabitant;
public NamedInhabitant(String name, Inhabitant inhabitant) {
this.name = name;
this.inhabitant = inhabitant;
}
}
final class ListenersByTypeInhabitant
extends ExistingSingletonInhabitant {
private final String name;
private final CopyOnWriteArrayList listeners =
new CopyOnWriteArrayList();
private volatile boolean released;
protected ListenersByTypeInhabitant(String name) {
super(new ExistingSingletonInhabitant(ListenersByTypeInhabitant.class, null));
this.name = name;
}
@Override
public String toString() {
return getClass().getSimpleName() + "(" + name + ")";
}
public void add(HabitatListener listener) {
listeners.add(0, listener);
}
public boolean remove(HabitatListener listener) {
return listeners.remove(listener);
}
public int size() {
return listeners.size();
}
@Override
public void release() {
if (!released) {
released = true;
for (HabitatListener listener : listeners) {
removeHabitatListener(listener);
}
}
super.release();
}
}
private static final class SelfListener implements HabitatListener {
public boolean inhabitantChanged(EventType eventType, Habitat habitat,
Inhabitant> inhabitant) {
return true;
}
public boolean inhabitantIndexChanged(EventType eventType, Habitat habitat,
Inhabitant> i, String index, String name, Object service) {
// for each FactoryFor component, insert inhabitant for components created by the factory
if (index.equals(FactoryFor.class.getName())) {
FactoryFor ff = i.type().getAnnotation(FactoryFor.class);
Class> targetClass = ff.value();
FactoryCreator target = new FactoryCreator(targetClass, i, habitat, MultiMap.emptyMap());
habitat.add(target);
habitat.addIndex(target, targetClass.getName(), null);
}
// System.out.println("Habitat Index Changed: " + eventType + "; " + i + "; index=" + index + "; name=" + name);
return true;
}
}
}