All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jvnet.hk2.component.Habitat Maven / Gradle / Ivy

There is a newer version: 1.6.9
Show newest version
/*
 * 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 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 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 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; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy