001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2017–2019 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.configuration.spi; 018 019import java.util.Collections; 020import java.util.HashMap; 021import java.util.Map; 022import java.util.Objects; 023import java.util.Set; 024 025import java.util.function.Function; 026import java.util.function.Supplier; 027 028import org.microbean.configuration.api.ConfigurationValue; 029 030/** 031 * An {@link AbstractConfiguration} that flexibly loads some kind of 032 * resource, or uses a previously loaded one, to satisfy demands for 033 * configuration property values. 034 * 035 * @author <a href="https://about.me/lairdnelson" 036 * target="_parent">Laird Nelson</a> 037 * 038 * @see #AbstractResourceLoadingConfiguration(Function) 039 * 040 * @see #getValue(Resource, Map, String) 041 */ 042public abstract class AbstractResourceLoadingConfiguration<T> extends AbstractConfiguration implements Ranked { 043 044 045 /* 046 * Instance fields. 047 */ 048 049 050 private final Function<? super Map<? extends String, ? extends String>, ? extends Resource<? extends T>> resourceLoader; 051 052 053 /* 054 * Constructors. 055 */ 056 057 058 /** 059 * Creates a new {@link AbstractResourceLoadingConfiguration}. 060 * 061 * @param resourceLoader a {@link Function} that accepts a {@link 062 * Map} of requested configuration coordinates and returns a {@link 063 * Resource} that can {@linkplain Resource#get() supply} a source of 064 * configuration values for use by the {@link #getValue(Resource, 065 * Map, String)} method; may be {@code null} in which case all 066 * invocations of the {@link #getValue(Map, String)} method will 067 * return {@code null} 068 * 069 * @see #getValue(Resource, Map, String) 070 * 071 * @see #getValue(Map, String) 072 * 073 * @see Resource 074 */ 075 protected AbstractResourceLoadingConfiguration(final Function<? super Map<? extends String, ? extends String>, ? extends Resource<? extends T>> resourceLoader) { 076 super(); 077 this.resourceLoader = resourceLoader; 078 } 079 080 081 /* 082 * Instance methods. 083 */ 084 085 086 @Override 087 public int getRank() { 088 final int returnValue; 089 if (this.resourceLoader == null) { 090 returnValue = 100; 091 } else { 092 returnValue = this.getRank(this.resourceLoader.apply(null)); 093 } 094 return returnValue; 095 } 096 097 protected int getRank(final Resource<? extends T> resource) { 098 return 100; 099 } 100 101 /** 102 * {@inheritDoc} 103 * 104 * <p>This implementation calls the {@link #getValue(Resource, Map, 105 * String)} method with the return value resulting from the 106 * invocation of the {@link Function} supplied {@linkplain 107 * #AbstractResourceLoadingConfiguration(Function) at construction 108 * time}. 109 * 110 * @param coordinates the requested configuration coordinates; may be {@code null} 111 * 112 * @param name the name of a configuration property for which a 113 * value should be returned; must not be {@code null} 114 * 115 * @exception NullPointerException if {@code name} is {@code null} 116 * 117 * @see #getValue(Resource, Map, String) 118 */ 119 @Override 120 public ConfigurationValue getValue(final Map<String, String> coordinates, final String name) { 121 final ConfigurationValue returnValue; 122 if (this.resourceLoader == null) { 123 returnValue = null; 124 } else { 125 returnValue = this.getValue(this.resourceLoader.apply(coordinates), coordinates, name); 126 } 127 return returnValue; 128 } 129 130 @Override 131 public Set<String> getNames() { 132 final Set<String> returnValue; 133 if (this.resourceLoader == null) { 134 returnValue = Collections.emptySet(); 135 } else { 136 returnValue = this.getNames(this.resourceLoader.apply(null)); 137 } 138 return returnValue; 139 } 140 141 /** 142 * Returns a {@link ConfigurationValue} suitable for the supplied 143 * {@code name} normally sourced in some fashion from the supplied 144 * {@link Resource}, or {@code null} if no such {@link 145 * ConfigurationValue} can be found. 146 * 147 * <p>Implementations of this method are permitted to return {@code 148 * null}.</p> 149 * 150 * <p>Implementations of this method must not call the {@link 151 * #getValue(Map, String)} method or undefined behavior will 152 * result.</p> 153 * 154 * @param resource a {@link Resource} hopefully providing access to 155 * the ultimate source of configuration values for the requested 156 * coordinates; must not be {@code null} 157 * 158 * @param requestedCoordinates for convenience, the same {@link Map} 159 * supplied to the {@link #getValue(Map, String)} method is supplied 160 * here representing the configuration coordinates for which a value 161 * is requested; note that these may very well be different from 162 * {@linkplain Resource#getCoordinates() the configuration 163 * coordinates actually pertaining to the resource}; most 164 * implementations will not need to reference this parameter 165 * 166 * @param name the name of the configuration property for which a 167 * value is to be sought; must not be {@code null} 168 * 169 * @return a suitable {@link ConfigurationValue} or {@code null} 170 * 171 * @exception NullPointerException if {@code resource} or {@code 172 * name} is {@code null} 173 * 174 * @see #getValue(Map, String) 175 * 176 * @see Resource 177 */ 178 protected abstract ConfigurationValue getValue(final Resource<? extends T> resource, final Map<String, String> requestedCoordinates, final String name); 179 180 protected abstract Set<String> getNames(final Resource<? extends T> resource); 181 182 /* 183 * Inner and nested classes. 184 */ 185 186 187 /** 188 * A {@link Supplier} of a particular kind of resource from which 189 * configuration property values may be retrieved. 190 * 191 * @author <a href="https://about.me/lairdnelson" 192 * target="_parent">Laird Nelson</a> 193 */ 194 public static final class Resource<T> implements Supplier<T> { 195 196 197 /* 198 * Instance fields. 199 */ 200 201 202 private final T resource; 203 204 private final Map<String, String> coordinates; 205 206 207 /* 208 * Constructors. 209 */ 210 211 212 /** 213 * Creates a new {@link Resource}. 214 * 215 * @param resource the actual underlying source of configuration 216 * property values; may be {@code null} 217 * 218 * @param coordinates the configuration coordinates this {@link 219 * Resource} provides values for; may be {@code null} 220 */ 221 public Resource(final T resource, final Map<String, String> coordinates) { 222 super(); 223 this.resource = resource; 224 if (coordinates == null) { 225 this.coordinates = null; 226 } else if (coordinates.isEmpty()) { 227 this.coordinates = Collections.emptyMap(); 228 } else { 229 this.coordinates = Collections.unmodifiableMap(new HashMap<>(coordinates)); 230 } 231 } 232 233 234 /* 235 * Instance methods. 236 */ 237 238 239 /** 240 * Returns the actual underlying source of configuration property 241 * values, typically for use by implementations of the {@link 242 * AbstractResourceLoadingConfiguration#getValue(Resource, Map, 243 * String)} method. 244 * 245 * <p>This method may return {@code null}.</p> 246 * 247 * @return the the actual underlying source of configuration 248 * property values, or {@code null} 249 */ 250 public final T get() { 251 return this.resource; 252 } 253 254 /** 255 * Returns an {@linkplain Collections#unmodifiableMap(Map) 256 * immutable} {@link Map} representing the configuration 257 * coordinates for which this {@link Resource} can assist in 258 * providing values. 259 * 260 * <p>This method may return {@code null}.</p> 261 * 262 * @return the configuration coordinates, or {@code null} 263 */ 264 public final Map<String, String> getCoordinates() { 265 return this.coordinates; 266 } 267 268 /** 269 * Returns a hashcode for this {@link Resource}. 270 * 271 * @return a hashcode for this {@link Resource} 272 * 273 * @see #equals(Object) 274 */ 275 @Override 276 public int hashCode() { 277 int hashCode = 17; 278 279 Object resource = this.get(); 280 int c = resource == null ? 0 : resource.hashCode(); 281 hashCode = 37 * hashCode + c; 282 283 Object coordinates = this.getCoordinates(); 284 c = coordinates == null ? 0 : coordinates.hashCode(); 285 hashCode = 37 * hashCode + c; 286 287 return hashCode; 288 } 289 290 /** 291 * Returns {@code true} if this {@link Resource} is equal to the 292 * supplied {@link Object}. 293 * 294 * @param other the {@link Object} to test; may be {@code null} in 295 * which case {@code false} will be returned 296 * 297 * @return {@code true} if this {@link Resource} is equal to the 298 * supplied {@link Object}; {@code false} otherwise 299 */ 300 @Override 301 public boolean equals(final Object other) { 302 if (this == other) { 303 return true; 304 } else if (other instanceof Resource) { 305 final Resource<?> her = (Resource<?>)other; 306 307 final Object myResource = this.get(); 308 if (myResource == null) { 309 if (her.get() != null) { 310 return false; 311 } 312 } else if (!myResource.equals(her.get())) { 313 return false; 314 } 315 316 final Object coordinates = this.getCoordinates(); 317 if (coordinates == null) { 318 if (her.getCoordinates() != null) { 319 return false; 320 } 321 } else if (!coordinates.equals(her.getCoordinates())) { 322 return false; 323 } 324 325 return true; 326 } else { 327 return false; 328 } 329 } 330 331 } 332 333}