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}