001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2020 microBean™.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
014 * implied.  See the License for the specific language governing
015 * permissions and limitations under the License.
016 */
017package org.microbean.settings;
018
019import java.io.Serializable;
020
021import java.lang.annotation.Annotation;
022
023import java.util.ArrayList;
024import java.util.Collection;
025import java.util.Collections;
026import java.util.Comparator;
027import java.util.List;
028import java.util.Objects;
029import java.util.Set;
030
031import javax.enterprise.context.ApplicationScoped;
032
033/**
034 * A simple {@link Arbiter} implementation that performs <a
035 * href="{@docRoot}/overview-summary.html#ambiguity">arbitration</a>
036 * among {@link Value}s by selecting the {@link Value} whose
037 * {@linkplain Value#getSource() affiliated <code>Source</code>}
038 * appears first in the {@link Set} of {@link Source}s supplied to the
039 * {@link #arbitrate(Set, String, Set, Collection)} method.
040 *
041 * @author <a href="https://about.me/lairdnelson"
042 * target="_parent">Laird Nelson</a>
043 *
044 * @see #arbitrate(Set, String, Set, Collection)
045 *
046 * @see Arbiter
047 */
048@ApplicationScoped
049public class SourceOrderArbiter extends Arbiter {
050
051
052  /*
053   * Static fields.
054   */
055
056
057  /*
058   * The version of this class for {@linkplain Serializable
059   * serialization purposes}.
060   */
061  private static final long serialVersionUID = 1L;
062
063
064  /*
065   * Constructors.
066   */
067
068
069  /**
070   * Creates a new {@link SourceOrderArbiter}.
071   */
072  public SourceOrderArbiter() {
073    super();
074  }
075
076
077  /*
078   * Instance methods.
079   */
080  
081
082  /**
083   * Performs value arbitration by considering {@link Value}s'
084   * {@linkplain Value#getSource() affiliated <code>Source</code>s}
085   * such that the {@link Value} whose {@link Value#getSource()
086   * Source} appears earliest in the supplied {@code sources} {@link
087   * Set} will be selected.
088   *
089   * @param sources the {@link Set} of {@link Source}s in effect
090   * during the current value acquisition operation; must not be
091   * {@code null}; must be {@linkplain
092   * Collections#unmodifiableSet(Set) unmodifiable}; must be safe for
093   * concurrent read-only access by multiple threads
094   *
095   * @param name the name of the setting value being sought; must not
096   * be {@code null}
097   *
098   * @param qualifiers the {@link Set} of qualifier {@link
099   * Annotation}s in effect during the current value acquisition
100   * operation; must not be {@code null}; must be {@linkplain
101   * Collections#unmodifiableSet(Set) unmodifiable}; must be safe for
102   * concurrent read-only access by multiple threads
103   *
104   * @param values the {@link Collection} of {@link Value}s acquired
105   * during the current value acquisition operation that were deemed
106   * to be indistinguishable; must not be {@code null}; must be
107   * {@linkplain Collections#unmodifiableSet(Set) unmodifiable}; must
108   * be safe for concurrent read-only access by multiple threads
109   *
110   * @return the result of value arbitration as a single {@link
111   * Value}, or {@code null} if this {@link SourceOrderArbiter} could
112   * not select a single {@link Value}
113   *
114   * @exception NullPointerException if any parameter value is {@code
115   * null}
116   *
117   * @exception ArbitrationException if there was a procedural problem
118   * with arbitration
119   */
120  @Override
121  public Value arbitrate(final Set<? extends Source> sources,
122                         final String name,
123                         final Set<? extends Annotation> qualifiers,
124                         final Collection<? extends Value> values) {
125    Objects.requireNonNull(sources);
126    Objects.requireNonNull(name);
127    final Value returnValue;
128    if (values == null || values.isEmpty()) {
129      returnValue = null;
130    } else if (values.size() == 1) {
131      if (values instanceof List) {
132        returnValue = ((List<? extends Value>)values).get(0);
133      } else {
134        returnValue = values.iterator().next();
135      }
136    } else {
137      returnValue = Collections.min(values, new SourceOrderComparator(sources));
138    }
139    return returnValue;
140  }
141
142
143  /*
144   * Inner and nested classes.
145   */
146
147  
148  private static final class SourceOrderComparator implements Comparator<Value>, Serializable {
149
150    private static final long serialVersionUID = 1L;
151
152    private final List<? extends Source> list;
153    
154    private SourceOrderComparator(final Set<? extends Source> sources) {
155      super();
156      this.list = new ArrayList<>(Objects.requireNonNull(sources));
157    }
158
159    @Override
160    public final int compare(final Value first, final Value second) {
161      if (first == null) {
162        throw new IllegalArgumentException("first == null");
163      } else if (second == null) {
164        throw new IllegalArgumentException("second == null");
165      }
166      final Source firstSource = first.getSource();
167      if (firstSource == null) {
168        throw new IllegalArgumentException("first.getSource() == null: " + first);
169      }
170      final Source secondSource = second.getSource();
171      if (secondSource == null) {
172        throw new IllegalArgumentException("second.getSource() == null: " + second);
173      }
174      final int firstIndex = this.list.indexOf(firstSource);
175      if (firstIndex < 0) {
176        throw new IllegalArgumentException("!(this.list.contains(first.getSource()))");
177      }
178      final int secondIndex = this.list.indexOf(secondSource);
179      if (secondIndex < 0) {
180        throw new IllegalArgumentException("!(this.list.contains(second.getSource()))");
181      }
182      if (firstIndex == secondIndex) {
183        throw new IllegalArgumentException("Arbitration was invoked on two Values from the same Source");
184      } else if (firstIndex < secondIndex) {
185        return -1;
186      } else {
187        return 1;
188      }
189    }
190    
191  }
192  
193  
194}