Example #1
0
public class Claimed extends AbstractMessage<ASIMOVMessageID>
    implements Serializable, JSONConvertible<Claimed> {
  /** */
  private static final long serialVersionUID = 1L;

  private static final Logger LOG = LogUtil.getLogger(Claimed.class);

  public boolean available;

  private ASIMOVMessageID replyToId;

  protected Claimed() {
    // zero argument constructor
  }

  public Claimed(
      final Claim source, final SimTime time, final AgentID senderID, final AgentID receiverID) {
    super(source.getID(), senderID.getModelID(), senderID, receiverID);
    replyToId = source.getID();
  }

  /** @return the available */
  public boolean isAvailable() {
    return available;
  }

  /** @param available the available to set */
  public void setAvailable(boolean available) {
    this.available = available;
  }

  /** @return the id */
  public ASIMOVMessageID getReplyToId() {
    return replyToId;
  }

  /** @see Identifier#toString() */
  @Override
  public String toString() {
    return String.format("%s%s", getClass().getSimpleName(), toJSON());
  }

  /** @see JSONConvertible#toJSON() */
  @Override
  public String toJSON() {
    try {
      // final JsonNode node = JsonUtil.getJOM().valueToTree(this);
      return JsonUtil.getJOM().writeValueAsString(this);
    } catch (final JsonProcessingException e) {
      LOG.warn("Problem marshalling " + getClass().getName() + " to JSON", e);
      return String.format("id=\"%s\"", getID());
    }
  }

  @Override
  public Claimed fromJSON(final String jsonValue) {
    return JsonUtil.fromJSONString(jsonValue, Claimed.class);
  }
}
Example #2
0
/** {@link JadeTest} */
public class JadeTest {

  /** */
  private static final Logger LOG = LogUtil.getLogger(JadeTest.class);

  /**
   * from: Luis Lezcano Airaldi <*****@*****.**> to: [email protected] date: Sat,
   * Jun 7, 2014 at 3:10 PM
   *
   * <p>Hello! It's very easy to use Jade as a library. Here's an example from a small application I
   * made:
   */
  public void testJade1() {
    // This is the important method. This launches the jade platform.
    final Runtime rt = Runtime.instance();

    final Profile profile = new ProfileImpl();

    // With the Profile you can set some options for the container
    profile.setParameter(Profile.PLATFORM_ID, "Platform Name");
    profile.setParameter(Profile.CONTAINER_NAME, "Container Name");

    // Create the Main Container
    final AgentContainer mainContainer = rt.createMainContainer(profile);
    final String agentName = "manager";
    final String agentType = "ia.main.AgentManager"; // FIXME
    final Object[] agentArgs = {};

    try {
      // Here I create an agent in the main container and start it.
      final AgentController ac = mainContainer.createNewAgent(agentName, agentType, agentArgs);
      ac.start();
    } catch (final StaleProxyException e) {
      LOG.error(
          String.format(
              "Problem creating/starting Jade agent '%s'" + " of type: %s with args: %s",
              agentName, agentType, Arrays.asList(agentArgs)),
          e);
    }
  }
}
  /**
   * {@link WeightedProduct} implements a {@link MultiCriteriaDecisionAnalyzer} that compares {@link
   * MultiCriteriaWeightedAlternative} s by the product of each criterion's weight with the policy's
   * respective {@link #getNormalizedWeights()} (see <a
   * href="https://www.wikiwand.com/en/Weighted_product_model">the method by Bridgman (1922) and
   * Miller & Starr (1969)</a>)
   *
   * @param <C>
   * @version $Id: 06fdeb50ce15c9a8366dc2eb3c9c54e11a324794 $
   * @author Rick van Krevelen
   */
  class WeightedProduct<A extends MultiCriteriaWeightedAlternative<C>, C>
      implements MultiCriteriaDecisionAnalyzer<A, C> {

    /** */
    private static final Logger LOG = LogUtil.getLogger(WeightedProduct.class);

    /** */
    private final Map<C, Number> weights;

    /** */
    private final Map<C, Double> normalizedWeights = new HashMap<>();

    /** */
    public WeightedProduct() {
      this(null);
    }

    /** */
    public WeightedProduct(final Map<C, Number> weights) {
      this.weights = weights;
    }

    @Override
    public Map<C, Number> getWeights() {
      return this.weights;
    }

    // @Override
    public Map<C, Double> getNormalizedWeights() {
      return this.normalizedWeights;
    }

    public void normalize() {
      double total = 0.0d;
      for (Number weight : getWeights().values()) total += Math.abs(weight.doubleValue());
      getNormalizedWeights().clear();
      for (Map.Entry<C, Number> entry : getWeights().entrySet())
        getNormalizedWeights()
            .put(
                entry.getKey(),
                total == 0.0 ? 1.0d / getWeights().size() : entry.getValue().doubleValue() / total);
      LOG.trace(
          "Normalized the criteria weights: " + getWeights() + " to: " + getNormalizedWeights());
    }

    @Override
    public String toString() {
      return String.format(
          "%s[ weights: %s, normalized: %s ]",
          getClass().getSimpleName(), getWeights(), getNormalizedWeights());
    }

    @Override
    public int compare(final A o1, final A o2) {
      double weightedProduct = 1.0d;
      for (Map.Entry<C, Double> entry : getNormalizedWeights().entrySet())
        try {
          final C criterion = entry.getKey();
          final double value1 = o1.evaluate(criterion).doubleValue();
          final double value2 = o2.evaluate(criterion).doubleValue();
          final double weight = entry.getValue().doubleValue();
          final double factor =
              value1 == 0.0d || value2 == 0.0d ? 1.0d : Math.pow(value1 / value2, weight);
          LOG.trace(
              String.format(
                  "%s factor: ( %.3f / %.3f ) ^ %.3f = %.3f",
                  criterion, value1, value2, weight, factor));
          weightedProduct *= factor;
        } catch (final Throwable e) {
          LOG.error("Problem comparing {} with {}", o1, o2, e);
        }
      final int result = Double.compare(weightedProduct, 1.0d);
      LOG.trace("Comparing {} with {}: {} >>> {}", o1, o2, weightedProduct, result);
      return result;
    }

    @Override
    public <K extends A, V> NavigableMap<K, V> apply(final Map<K, V> alternatives) {
      normalize();
      final NavigableMap<K, V> result = new ConcurrentSkipListMap<K, V>(this);
      result.putAll(alternatives);
      return result;
    }

    @Override
    public <E extends A> NavigableSet<E> apply(final Collection<E> alternatives) {
      normalize();
      final NavigableSet<E> result = new ConcurrentSkipListSet<E>(this);
      result.addAll(alternatives);
      return result;
    }
  }
Example #4
0
/**
 * {@link StageUtil} utility class for interception and extension of an injected object's life cycle
 *
 * @version $Id: 1ee12291a4f37aa3235547ac54195dc1d0e9bdaa $
 * @author Rick van Krevelen
 */
public class StageUtil implements Util {

  /** */
  private static final Logger LOG = LogUtil.getLogger(StageUtil.class);

  /** */
  private static final Map<Class<?>, Map<StageEvent, SortedMap<Method, Staged>>>
      stageEventHandlerCache = new HashMap<>();

  /** */
  private static final Map<Class<?>, Map<String, SortedMap<Method, Staged>>>
      customStageHandlerCache = new HashMap<>();

  /** */
  private static final Map<Class<?>, Set<Class<? extends Throwable>>> absorptionLevelCache =
      new HashMap<>();

  /** {@link StageUtil} singleton constructor */
  private StageUtil() {
    // utility class should not provide protected/public instances
  }

  /** decide which stage event handler to execute first */
  private static final Comparator<Method> METHOD_COMPARATOR =
      new Comparator<Method>() {

        @Override
        public int compare(final Method o1, final Method o2) {
          if (o1 == o2) return 0;

          // compare priority
          int result =
              Integer.compare(
                  o1.getAnnotation(Staged.class).priority(),
                  o2.getAnnotation(Staged.class).priority());
          if (result != 0) return result;

          // same priority; compare class hierarchy
          /*
           * result = o1.getDeclaringClass().getName()
           * .compareTo(o2.getDeclaringClass().getName()); if (result != 0) return result;
           */

          // same priority and class; compare method name
          result = o1.getName().compareTo(o2.getName());
          if (result != 0) return result;

          // same priority, class, and name; compare method name
          return toSignatureString(o1).compareTo(toSignatureString(o2));
        }
      };

  /** @param type */
  private static synchronized void findHandlers(final Class<?> type) {
    LOG.trace("Caching handlers for " + type.getName());
    final Map<StageEvent, SortedMap<Method, Staged>> handlersByEvent =
        type.getSuperclass() == Object.class || type.getSuperclass() == null
            ? new EnumMap<StageEvent, SortedMap<Method, Staged>>(StageEvent.class)
            : findEventHandlers(type.getSuperclass());
    stageEventHandlerCache.put(type, handlersByEvent);
    final Map<String, SortedMap<Method, Staged>> handlersByStage =
        type.getSuperclass() == Object.class || type.getSuperclass() == null
            ? new HashMap<String, SortedMap<Method, Staged>>()
            : findStageHandlers(type.getSuperclass());
    customStageHandlerCache.put(type, handlersByStage);

    for (Class<?> iface : type.getInterfaces()) {
      handlersByEvent.putAll(findEventHandlers(iface));
      handlersByStage.putAll(findStageHandlers(iface));
    }

    for (Method method : type.getDeclaredMethods()) {
      final Staged ann = method.getAnnotation(Staged.class);
      if (ann == null) continue;

      for (StageEvent on : ann.on()) {
        SortedMap<Method, Staged> handlers = handlersByEvent.get(on);
        if (handlers == null) {
          handlers = new TreeMap<>(METHOD_COMPARATOR);
          handlersByEvent.put(on, handlers);
        }
        handlers.put(method, ann);
      }

      for (String stage : ann.onCustom()) {
        SortedMap<Method, Staged> handlers = handlersByStage.get(stage);
        if (handlers == null) {
          handlers = new TreeMap<>(METHOD_COMPARATOR);
          handlersByStage.put(stage, handlers);
        }
        handlers.put(method, ann);
      }
    }
    // if (handlersByEvent.isEmpty() && handlersByStage.isEmpty())
    // LOG.warn("No @" + Staged.class.getSimpleName()
    // + " annotations found in target: " + type.getName());
  }

  /**
   * @param type
   * @param event
   * @return
   */
  public static synchronized Map<StageEvent, SortedMap<Method, Staged>> findEventHandlers(
      final Class<?> type) {
    Map<StageEvent, SortedMap<Method, Staged>> result = stageEventHandlerCache.get(type);

    if (result == null) {
      findHandlers(type);
      result = stageEventHandlerCache.get(type);
    }
    return result;
  }

  /**
   * @param type
   * @param event
   * @return
   */
  public static Map<Method, Staged> findEventHandlers(final Class<?> type, final StageEvent event) {
    final Map<Method, Staged> result = findEventHandlers(type).get(event);
    if (result == null) return Collections.emptyMap();
    return result;
  }

  /**
   * @param type
   * @param stage
   * @return
   */
  public static synchronized Map<String, SortedMap<Method, Staged>> findStageHandlers(
      final Class<?> type) {
    Map<String, SortedMap<Method, Staged>> result = customStageHandlerCache.get(type);

    if (result == null) {
      findHandlers(type);
      result = customStageHandlerCache.get(type);
    }
    return result;
  }

  /**
   * @param type
   * @param stage
   * @return
   */
  public static Map<Method, Staged> findStageHandlers(final Class<?> type, final String stage) {
    final Map<Method, Staged> result = findStageHandlers(type).get(stage);
    if (result == null) return Collections.emptyMap();
    return result;
  }

  /**
   * @param type
   * @return
   */
  public static synchronized SortedSet<String> findStages(
      final Class<?> type, final InjectStaged.StageSelector selector) {
    LOG.trace("Caching " + selector + " stages for " + type.getName());
    SortedSet<String> result = selector.getCache().get(type);
    if (result != null) return result;

    result = new TreeSet<>();
    selector.getCache().put(type, result);

    InjectStaged staging = type.getAnnotation(InjectStaged.class);
    if (staging != null) for (String stage : selector.selectStages(staging)) result.add(stage);

    for (Class<?> iface : type.getInterfaces()) result.addAll(findStages(iface, selector));

    if (type.getSuperclass() != Object.class && type.getSuperclass() != null)
      result.addAll(findStages(type.getSuperclass(), selector));

    return result;
  }

  /**
   * use sub-most available specification (if any) from Objects only (ignore interfaces and possibly
   * overridden specifications)
   *
   * @param type
   * @return
   */
  public static synchronized Set<Class<? extends Throwable>> findAbsorptionLevels(
      final Class<?> type) {
    LOG.trace("Caching absorption levels for " + type.getName());
    Set<Class<? extends Throwable>> result = absorptionLevelCache.get(type);
    if (result != null) return result;

    absorptionLevelCache.put(type, result);

    Class<?> stagingType = type;
    InjectStaged staging = stagingType.getAnnotation(InjectStaged.class);
    while (staging == null && stagingType.getSuperclass() != Object.class) {
      stagingType = stagingType.getSuperclass();
      staging = stagingType.getAnnotation(InjectStaged.class);
    }
    if (staging != null) result = subsume(type.getName(), staging.ignore());
    // else
    // LOG.warn("(Super)type missing @"
    // + InjectStaged.class.getSimpleName() + ": "
    // + type.getName());
    return result;
  }

  /**
   * @param type
   * @return
   */
  public static Set<Class<? extends Throwable>> findAbsorptionLevels(
      final Class<?> type, final Method method) {
    final Set<Class<? extends Throwable>> result;
    final Staged staged = method.getAnnotation(Staged.class);

    // override class-level annotation by method-level annotation
    if (staged == null) result = findAbsorptionLevels(type);
    else result = subsume(toSignatureString(method), staged.ignore());

    LOG.trace("Absorption for " + toSignatureString(method) + ": " + result);
    return result;
  }

  /**
   * @param source the (annotation) origin
   * @param ignore the set of {@link Throwable}s to reduce (if subsumed)
   * @return
   */
  @SafeVarargs
  public static Set<Class<? extends Throwable>> subsume(
      final String source, final Class<? extends Throwable>... ignore) {
    final Set<Class<? extends Throwable>> result = new HashSet<>();
    if (ignore != null && ignore.length != 0)
      outer:
      for (Class<? extends Throwable> cls : ignore) {
        for (Class<? extends Throwable> old : result) {
          if (old.isAssignableFrom(cls)) {
            LOG.info(
                "ignore() level '"
                    + old.getName()
                    + "' subsumes '"
                    + cls.getName()
                    + "' annotated in "
                    + source);
            continue outer;
          }
          if (cls.isAssignableFrom(old)) {
            LOG.info(
                "ignore() level '"
                    + cls.getName()
                    + "' annotated in "
                    + source
                    + " subsumes '"
                    + old.getName()
                    + "'");
            result.remove(old);
          }
        }
        result.add(cls);
      }
    return result;
  }

  /**
   * shortcut/convenience method
   *
   * @param provider
   * @return
   * @throws Throwable
   */
  public static <T> T inject(final Provider<T> provider) throws Throwable {
    return inject(provider, null);
  }

  /**
   * @param provider
   * @param stageObserver
   * @return
   * @throws Throwable
   */
  public static <T> T inject(final Provider<T> provider, final Observer<StageChange> stageObserver)
      throws Throwable {
    @SuppressWarnings("unchecked")
    final Class<T> type =
        (Class<T>) ClassUtil.getTypeArguments(Provider.class, provider.getClass()).get(0);

    boolean success = true;

    if (stageObserver != null)
      stageObserver.onNext(new StageChangeImpl(type, null, Stage.PREPARING, null));

    success &= invokeEventHandlers(stageObserver, type, null, null, StageEvent.BEFORE_PROVIDE);

    success &=
        invokeStages(
            stageObserver, type, null, findStages(type, InjectStaged.BEFORE_PROVIDE_SELECTOR));

    if (stageObserver != null)
      stageObserver.onNext(new StageChangeImpl(type, null, Stage.PROVIDING, null));

    final FinalizeDecorator<T> subject =
        FinalizeDecorator.from(stageObserver, type, provider.get());

    if (stageObserver != null)
      stageObserver.onNext(new StageChangeImpl(type, subject.getTarget(), Stage.PROVIDED, null));
    success &=
        invokeEventHandlers(
            stageObserver, type, subject.getTarget(), null, StageEvent.AFTER_PROVIDE);

    success &=
        invokeStages(
            stageObserver,
            type,
            subject.getTarget(),
            findStages(type, InjectStaged.AFTER_PROVIDE_SELECTOR));

    if (stageObserver != null && success)
      stageObserver.onNext(new StageChangeImpl(type, subject.getTarget(), Stage.STOPPED, null));
    return subject.getTarget();
  }

  public static <T> boolean invokeStages(
      final Observer<StageChange> stageObserver,
      final Class<T> type,
      final T target,
      final Collection<String> stages)
      throws Throwable {
    if (stages == null || stages.isEmpty()) return true;

    boolean success = true;
    for (String stage : stages) {
      if (stageObserver != null)
        stageObserver.onNext(new StageChangeImpl(type, target, Stage.STARTING, stage));
      success &= invokeEventHandlers(stageObserver, type, target, stage, StageEvent.BEFORE_STAGE);

      if (stageObserver != null)
        stageObserver.onNext(new StageChangeImpl(type, target, Stage.STARTED, stage));
      final List<String> nextStages = invokeStageHandlers(stageObserver, type, target, stage);
      success &= nextStages != null;

      if (stageObserver != null)
        stageObserver.onNext(new StageChangeImpl(type, target, Stage.STOPPING, stage));
      success &= invokeEventHandlers(stageObserver, type, target, stage, StageEvent.AFTER_STAGE);

      if (nextStages != null && !nextStages.isEmpty()) {
        LOG.trace("Starting *nested* stage: " + nextStages);
        invokeStages(stageObserver, type, target, nextStages);
      }
    }
    return success;
  }

  public static <T> boolean invokeEventHandlers(
      final Observer<StageChange> stageObserver,
      final Class<T> type,
      final T target,
      final String currentStage,
      final StageEvent event)
      throws Throwable {
    Object result;
    boolean failed = false;
    for (Map.Entry<Method, Staged> entry : findEventHandlers(type, event).entrySet()) {
      LOG.trace(
          "Calling 'on=" + event.name() + "' handler method: " + toSignatureString(entry.getKey()));
      result = invoke(stageObserver, currentStage, type, target, entry.getKey());
      failed |= result instanceof Throwable;
    }
    return !failed;
  }

  /**
   * @param stageObserver
   * @param type
   * @param target
   * @param currentStage
   * @return the next stage, or {@code null} if failed
   * @throws Throwable
   */
  public static <T> List<String> invokeStageHandlers(
      final Observer<StageChange> stageObserver,
      final Class<T> type,
      final T target,
      final String currentStage)
      throws Throwable {
    Object result;
    boolean failed = false;
    final List<String> nextStages = new ArrayList<>();
    for (Map.Entry<Method, Staged> entry : findStageHandlers(type, currentStage).entrySet()) {
      LOG.trace(
          "Calling 'onCustom="
              + currentStage
              + "' handler method: "
              + toSignatureString(entry.getKey()));
      result = invoke(stageObserver, currentStage, type, target, entry.getKey());
      if (result instanceof Throwable) failed = true;
      else if (entry.getValue().returnsNextStage()) {
        final String nextStage = result == null ? null : result.toString();
        if (nextStage == null)
          LOG.error(
              "Illegal (void or null) next stage returned by " + toSignatureString(entry.getKey()));
        else nextStages.add(nextStage);
      }
      failed |= result instanceof Throwable;
    }
    return failed ? null : nextStages;
  }

  /**
   * @param stageObserver
   * @param type
   * @param currentStage
   * @param method
   * @param target
   * @param args
   * @return
   * @throws Throwable
   */
  public static <T> Object invoke(
      final Observer<StageChange> stageObserver,
      final String currentStage,
      final Class<T> type,
      final T target,
      final Method method,
      final Object... args)
      throws Throwable {
    try {
      return method.invoke(target, args);
    } catch (Throwable t) {
      if (stageObserver != null)
        stageObserver.onNext(
            new StageChangeImpl(target.getClass(), target, Stage.FAILING, currentStage));
      if (t instanceof InvocationTargetException) t = t.getCause();
      for (Map.Entry<Method, Staged> entry :
          findEventHandlers(type, StageEvent.BEFORE_FAIL).entrySet()) {
        if (target == null && !Modifier.isStatic(entry.getKey().getModifiers())) {
          LOG.warn(
              "Can't invoke non-static 'on="
                  + StageEvent.BEFORE_FAIL
                  + "' method: "
                  + toSignatureString(entry.getKey()),
              t);
          continue;
        }
        LOG.trace(
            "Calling 'on=" + StageEvent.BEFORE_FAIL + "' method: " + toSignatureString(method));
        try {
          if (entry.getKey().getParameterTypes().length == 0) entry.getKey().invoke(target);
          else if (entry.getKey().getParameterTypes()[0].isAssignableFrom(t.getClass()))
            entry.getKey().invoke(target, t);
          else
            LOG.error(
                "Signature of 'on="
                    + StageEvent.BEFORE_FAIL
                    + "' method: "
                    + toSignatureString(entry.getKey())
                    + " does not match parameter: "
                    + t.getClass().getName());
        } catch (Throwable t2) {
          if (t2 instanceof InvocationTargetException) t2 = t2.getCause();
          LOG.error(
              "Uncaught errors for 'on="
                  + StageEvent.BEFORE_FAIL
                  + "' method: "
                  + toSignatureString(entry.getKey()),
              t2);
        }
      }
      if (stageObserver != null) {
        stageObserver.onNext(
            new StageChangeImpl(target.getClass(), target, Stage.FAILED, currentStage));
        if (stageObserver != null) stageObserver.onError(t);
      }
      for (Class<? extends Throwable> sup : findAbsorptionLevels(target.getClass(), method))
        if (sup.isAssignableFrom(t.getClass())) {
          LOG.trace("Absorbed " + t.getClass().getSimpleName() + ": " + t.getMessage());
          return t;
        }
      throw t;
    }
  }

  /**
   * @param method
   * @return
   */
  public static String toSignatureString(final Method method) {
    final StringBuilder result =
        new StringBuilder(method.getDeclaringClass().getSimpleName())
            .append('#')
            .append(method.getName())
            .append('(');
    boolean first = true;
    for (Class<?> type : method.getParameterTypes()) {
      if (first) first = false;
      else result.append(", ");
      result.append(type.getSimpleName());
    }
    return result.append(')').toString();
  }

  /**
   * {@link StageChangeImpl}
   *
   * @version $Id: 1ee12291a4f37aa3235547ac54195dc1d0e9bdaa $
   */
  private static class StageChangeImpl implements StageChange {
    /** */
    private final Stage stage;

    /** */
    private final String custom;

    /** */
    private final Class<?> type;

    /** */
    private final int hash;

    /**
     * {@link StageChangeImpl} constructor
     *
     * @param target
     * @param stage
     * @param custom
     */
    private StageChangeImpl(
        final Class<?> type, final Object target, final Stage stage, final String custom) {
      int hashCode = -1;
      if (target != null)
        try {
          hashCode = target.hashCode();
        } catch (final Throwable t) {
          LOG.warn("hashCode() failed for " + type.getName(), t);
        }
      this.type = type;
      this.hash = hashCode;
      this.stage = stage;
      this.custom = custom;
    }

    @Override
    public Class<?> getType() {
      return this.type;
    }

    @Override
    public int getHash() {
      return this.hash;
    }

    @Override
    public Stage getStage() {
      return this.stage;
    }

    @Override
    public String getCustom() {
      return this.custom;
    }

    @Override
    public String toString() {
      return JsonUtil.toString(this);
    }
  }

  /**
   * {@link FinalizeDecorator}
   *
   * @version $Id: 1ee12291a4f37aa3235547ac54195dc1d0e9bdaa $
   * @param <T>
   */
  private static class FinalizeDecorator<T> {
    /** */
    private final Observer<StageChange> stageObserver;

    /** */
    private final Class<T> type;

    /** */
    private final T target;

    /**
     * {@link FinalizeDecorator} constructor
     *
     * @param target
     * @param obs
     * @param type
     */
    private FinalizeDecorator(
        final Observer<StageChange> obs, final Class<T> type, final T target) {
      this.stageObserver = obs;
      this.type = type;
      this.target = target;
    }

    /** @return */
    public Class<T> getType() {
      return this.type;
    }

    /** @return */
    public T getTarget() {
      return this.target;
    }

    /**
     * WARNING! Override probably introduces severe performance issues!
     *
     * @see java.lang.Object#finalize()
     */
    @Override
    public void finalize() {
      // LOG.trace("FinalizeDecorator called!");
      if (this.stageObserver != null)
        this.stageObserver.onNext(
            new StageChangeImpl(getTarget().getClass(), getTarget(), Stage.RECYCLING, null));

      for (Map.Entry<Method, Staged> entry :
          findEventHandlers(getType(), StageEvent.BEFORE_RECYCLE).entrySet()) {
        LOG.trace(
            "Calling 'on="
                + StageEvent.BEFORE_RECYCLE
                + "' method: "
                + toSignatureString(entry.getKey()));
        try {
          invoke(this.stageObserver, null, getType(), getTarget(), entry.getKey());
        } catch (final Throwable e) {
          LOG.warn(
              "Errors unhandled for 'on="
                  + StageEvent.BEFORE_RECYCLE
                  + "' method: "
                  + toSignatureString(entry.getKey()),
              e);
        }
      }
      if (this.stageObserver != null) {
        this.stageObserver.onNext(
            new StageChangeImpl(getTarget().getClass(), getTarget(), Stage.RECYCLED, null));
        this.stageObserver.onCompleted();
      }
    }

    /**
     * @param target
     * @param stageObserver
     * @param type
     * @return
     */
    public static <T> FinalizeDecorator<T> from(
        final Observer<StageChange> stageObserver, final Class<T> type, final T target) {
      return new FinalizeDecorator<T>(stageObserver, type, target);
    }
  }
}