PriorityAsSupport.java

  1. /*
  2.  * *********************************************************************************************************************
  3.  *
  4.  * blueMarine II: Semantic Media Centre
  5.  * http://tidalwave.it/projects/bluemarine2
  6.  *
  7.  * Copyright (C) 2015 - 2021 by Tidalwave s.a.s. (http://tidalwave.it)
  8.  *
  9.  * *********************************************************************************************************************
  10.  *
  11.  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
  12.  * the License. You may obtain a copy of the License at
  13.  *
  14.  *     http://www.apache.org/licenses/LICENSE-2.0
  15.  *
  16.  * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
  17.  * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the
  18.  * specific language governing permissions and limitations under the License.
  19.  *
  20.  * *********************************************************************************************************************
  21.  *
  22.  * git clone https://bitbucket.org/tidalwave/bluemarine2-src
  23.  * git clone https://github.com/tidalwave-it/bluemarine2-src
  24.  *
  25.  * *********************************************************************************************************************
  26.  */
  27. package it.tidalwave.util.spi;

  28. import javax.annotation.Nonnull;
  29. import java.util.ArrayList;
  30. import java.util.Collection;
  31. import java.util.Collections;
  32. import java.util.List;
  33. import java.util.Optional;
  34. import it.tidalwave.util.As;
  35. import it.tidalwave.util.AsException;
  36. import it.tidalwave.dci.annotation.DciRole;
  37. import lombok.extern.slf4j.Slf4j;

  38. /***********************************************************************************************************************
  39.  *
  40.  * A specialisation of {@link AsSupport} that deals with multiple roles of the same type by prioritising them; they
  41.  * are ordered from most relevant to least relevant (where relevance is associated to specialisation, that is most
  42.  * specialised roles, or roles associated via {@code @DciRole} to most specialised datum classes, are most relevant).
  43.  *
  44.  * FIXME: could be seen as a replacement to {@code AsSupport}?
  45.  *
  46.  * @author  Fabrizio Giudici
  47.  *
  48.  **********************************************************************************************************************/
  49. @Slf4j
  50. public class PriorityAsSupport extends AsSupport implements As
  51.   {
  52.     @FunctionalInterface
  53.     public static interface RoleProvider
  54.       {
  55.         @Nonnull
  56.         public <T> Collection<T> findRoles (@Nonnull final Class<T> type);
  57.       }

  58.     @Nonnull
  59.     private final Object owner; // for logging only

  60.     @Nonnull
  61.     private final Optional<RoleProvider> additionalRoleProvider;

  62.     public PriorityAsSupport (final Object owner)
  63.       {
  64.         this(owner, Collections.emptyList());
  65.       }

  66.     public PriorityAsSupport (@Nonnull final Object owner, @Nonnull final Collection<Object> rolesOrFactories)
  67.       {
  68.         super(owner, rolesOrFactories);
  69.         this.owner = owner;
  70.         this.additionalRoleProvider = Optional.empty();
  71.       }

  72.     public PriorityAsSupport (@Nonnull final Object owner,
  73.                               @Nonnull final RoleProvider additionalRoleProvider,
  74.                               @Nonnull final Collection<Object> rolesOrFactories)
  75.       {
  76.         super(owner, rolesOrFactories);
  77.         this.owner = owner;
  78.         this.additionalRoleProvider = Optional.of(additionalRoleProvider);
  79.       }

  80.     /*******************************************************************************************************************
  81.      *
  82.      * {@inheritDoc}
  83.      *
  84.      * Returned roles can be associated both to this type and to the delegate; the one with the higher priority is
  85.      * returned. See {@link #asMany(java.lang.Class)} for further details.
  86.      *
  87.      * @see #asMany(java.lang.Class)
  88.      *
  89.      ******************************************************************************************************************/
  90.     @Override @Nonnull
  91.     public <T> T as (@Nonnull final Class<T> type)
  92.       {
  93.         return as(type, As.Defaults.throwAsException(type));
  94.       }

  95.     /*******************************************************************************************************************
  96.      *
  97.      * {@inheritDoc}
  98.      *
  99.      * Returned roles can be associated both to this type and to the delegate; the one with the higher priority is
  100.      * returned. See {@link #asMany(java.lang.Class)} for further details.
  101.      *
  102.      * @see #asMany(java.lang.Class)
  103.      *
  104.      ******************************************************************************************************************/
  105.     @Override @Nonnull
  106.     public <T> T as (@Nonnull final Class<T> type, @Nonnull final NotFoundBehaviour<T> notFoundBehaviour)
  107.       {
  108.         return maybeAs(type).orElseGet(() -> notFoundBehaviour.run(new AsException(type)));
  109.       }

  110.     /*******************************************************************************************************************
  111.      *
  112.      * {@inheritDoc}
  113.      *
  114.      * Returned roles can be associated both to this type and to the delegate; the one with the higher priority is
  115.      * returned. See {@link #asMany(java.lang.Class)} for further details.
  116.      *
  117.      * @see #asMany(java.lang.Class)
  118.      *
  119.      ******************************************************************************************************************/
  120.     @Override @Nonnull
  121.     public <T> Optional<T> maybeAs (@Nonnull final Class<T> type)
  122.       {
  123.         return asMany(type).stream().findFirst();
  124.       }

  125.     /*******************************************************************************************************************
  126.      *
  127.      * {@inheritDoc}
  128.      *
  129.      * Returned roles can be associated both to this type and to the delegate; the one with the higher priority is
  130.      * returned. The ones associated to this type come with higher priority (this makes sense, being this class a
  131.      * decorator, specific roles could be associated to it). But given that the default implementation of asMany()
  132.      * doesn't guarantee ant order (see TFT-192) there's something to take care of. Currently this method contains
  133.      * some hardwired priority logics.
  134.      *
  135.      ******************************************************************************************************************/
  136.     @Override @Nonnull
  137.     public <T> Collection<T> asMany (@Nonnull final Class<T> type)
  138.       {
  139.         log.trace("asMany({}) - {}", type, owner);
  140.         final List<T> unordered = new ArrayList<>(super.asMany(type));
  141.         additionalRoleProvider.ifPresent(r -> unordered.addAll(r.findRoles(type)));
  142.         //
  143.         // Need a kind of bubble sort, because:
  144.         // a) the original sequence might have a meaning; for instance, additional roles added by
  145.         //    additionalRoleProvider are appended and, generally, they should stay low in priority.
  146.         // b) there is not always a well-defined way to define a relation order between the elements.
  147.         //
  148.         final List<T> result = new ArrayList<>();
  149.         unordered.forEach(item -> addInOrder(result, item));
  150.         log.trace(">>>> returning {}", result);

  151.         return result;
  152.       }

  153.     /*******************************************************************************************************************
  154.      *
  155.      * Adds an item to the list, just before the first existing item which whose datum class is an instance of a
  156.      * subclass of its datum class.
  157.      *
  158.      ******************************************************************************************************************/
  159.     private static <T> void addInOrder (@Nonnull final List<T> list, @Nonnull final T item)
  160.       {
  161.         log.trace(">>>> add in order {} into {}", item, list);
  162.         final Optional<T> firstAncestor = list.stream().filter(i -> isDatumAncestor(i, item)).findFirst();
  163.         final int index = firstAncestor.map(list::indexOf).orElse(list.size());
  164.         list.add(index, item);
  165.         log.trace(">>>>>>>> add in order {} ", list);
  166.       }

  167.     /*******************************************************************************************************************
  168.      *
  169.      ******************************************************************************************************************/
  170.     private static <T> boolean isDatumAncestor (@Nonnull final T a, @Nonnull final T b)
  171.       {
  172.         final DciRole aBoundDatumClass = a.getClass().getAnnotation(DciRole.class);
  173.         final DciRole bBoundDatumClass = b.getClass().getAnnotation(DciRole.class);

  174.         if ((aBoundDatumClass != null) && (bBoundDatumClass != null))
  175.           {
  176.             return aBoundDatumClass.datumType()[0].isAssignableFrom(bBoundDatumClass.datumType()[0]); // FIXME: multiple classes?
  177.           }

  178.         return a.getClass().isAssignableFrom(b.getClass());
  179.       }
  180.   }