DefaultAs.java

/*
 * *********************************************************************************************************************
 *
 * TheseFoolishThings: Miscellaneous utilities
 * http://tidalwave.it/projects/thesefoolishthings
 *
 * Copyright (C) 2009 - 2023 by Tidalwave s.a.s. (http://tidalwave.it)
 *
 * *********************************************************************************************************************
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations under the License.
 *
 * *********************************************************************************************************************
 *
 * git clone https://bitbucket.org/tidalwave/thesefoolishthings-src
 * git clone https://github.com/tidalwave-it/thesefoolishthings-src
 *
 * *********************************************************************************************************************
 */
package it.tidalwave.util.impl;

import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import it.tidalwave.util.As;
import it.tidalwave.util.Parameters;
import it.tidalwave.util.RoleFactory;
import it.tidalwave.util.spi.AsDelegate;
import it.tidalwave.util.spi.AsDelegateProvider;
import static it.tidalwave.util.Parameters.r;

/***********************************************************************************************************************
 *
 * @author  Fabrizio Giudici
 *
 **********************************************************************************************************************/
public class DefaultAs implements As
  {
    @Nonnull
    private final AsDelegate delegate;

    @Nonnull
    private final Object owner;

    @Nonnull
    private final List<Object> roles = new ArrayList<>();

    /*******************************************************************************************************************
     *
     * Constructor for use in subclassing.
     *
     ******************************************************************************************************************/
    protected DefaultAs()
      {
        owner = this;
        delegate = AsDelegateProvider.Locator.find().createAsDelegate(this);
      }

    /*******************************************************************************************************************
     *
     * Constructor for use in composition.
     *
     * @param  owner             the owner
     * @since  3.2-ALPHA-3 (refactored)
     *
     ******************************************************************************************************************/
    public DefaultAs (@Nonnull final Object owner)
      {
        this(owner, Collections.emptyList());
      }

    /*******************************************************************************************************************
     *
     * Constructor for use in composition. In addition to the mandatory owner, it accepts a single pre-instantiated
     * role, or a {@link RoleFactory} that will be invoked to create additional roles.
     *
     * @param  owner          the owner
     * @param  role           the role or {@link it.tidalwave.util.RoleFactory}
     * @since  3.2-ALPHA-3
     *
     ******************************************************************************************************************/
    public DefaultAs (@Nonnull final Object owner, @Nonnull final Object role)
      {
        this(owner, r(Parameters.mustNotBeArrayOrCollection(role, "role")));
      }

    /*******************************************************************************************************************
     *
     * Constructor for use in composition. In addition to the mandatory owner, it accepts a collection of
     * pre-instantiated roles, or instances of {@link RoleFactory} that will be invoked to create additional roles.
     *
     * @param  owner          the owner
     * @param  roles          roles or {@link it.tidalwave.util.RoleFactory} instances
     * @since  3.2-ALPHA-3 (refactored)
     *
     ******************************************************************************************************************/
    public DefaultAs (@Nonnull final Object owner, @Nonnull final Collection<Object> roles)
      {
        this(AsDelegateProvider.Locator.find()::createAsDelegate, owner, roles);
      }

    /*******************************************************************************************************************
     *
     * Constructor for use in tests. This constructor doesn't call {@link AsDelegateProvider.Locator#find()}.
     *
     * @param  asDelegateFactory  the factory
     * @param  owner          the owner
     * @param  roles          roles or {@link it.tidalwave.util.RoleFactory} instances
     * @since  3.2-ALPHA-3 (refactored)
     *
     ******************************************************************************************************************/
    public DefaultAs (@Nonnull final Function<Object, AsDelegate> asDelegateFactory,
                      @Nonnull final Object owner,
                      @Nonnull final Collection<Object> roles)
      {
        delegate = asDelegateFactory.apply(owner);
        this.owner = owner;
        this.roles.addAll(resolveFactories(roles));
      }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     * First, local roles are probed; then the owner, in case it directly implements the required role; at last,
     * the delegate is invoked.
     *
     ******************************************************************************************************************/
    @Override @Nonnull
    public <T> Optional<T> maybeAs (@Nonnull final Class<T> type)
      {
        for (final Object role : roles)
          {
            if (type.isAssignableFrom(role.getClass()))
              {
                return Optional.of(type.cast(role));
              }
          }

        final Collection<? extends T> r = delegate.as(type);
        return r.isEmpty() ? Optional.empty() : Optional.of(r.iterator().next());
      }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     * The list contains all the relevant local roles, as well as those retrieved by the delegate, in this order.
     *
     ******************************************************************************************************************/
    @Nonnull
    public <T> Collection<T> asMany (@Nonnull final Class<T> type)
      {
        final Collection<T> results = new ArrayList<>();

        for (final Object role : roles)
          {
            if (type.isAssignableFrom(role.getClass()))
              {
                results.add(type.cast(role));
              }
          }

        results.addAll(delegate.as(type));

        return results;
      }

    /*******************************************************************************************************************
     *
     * Resolve the factories: if found, they are invoked and the produced role is added to the list.
     *
     * @param  roles  the list of roles or factory roles
     * @return                   a list of roles
     *
     ******************************************************************************************************************/
    @Nonnull
    private List<Object> resolveFactories (@Nonnull final Collection<Object> roles)
      {
        final List<Object> result = new ArrayList<>();

        for (final Object roleOrFactory : roles)
          {
            if (roleOrFactory instanceof RoleFactory)
              {
                result.add(((RoleFactory<Object>)roleOrFactory).createRoleFor(owner));
              }
            else
              {
                result.add(roleOrFactory);
              }
          }

        return result;
      }
  }