001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2017 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.api; 018 019import java.io.Serializable; 020 021import java.util.Collections; 022import java.util.HashMap; 023import java.util.Map; 024import java.util.Objects; 025 026/** 027 * A value for a configuration property, particularly as {@linkplain 028 * AmbiguousConfigurationValuesException#getValues() exposed} by an 029 * {@link AmbiguousConfigurationValuesException}. 030 * 031 * @author <a href="http://about.me/lairdnelson" 032 * target="_parent">Laird Nelson</a> 033 * 034 * @see AmbiguousConfigurationValuesException 035 */ 036public class ConfigurationValue implements Serializable { 037 038 039 /* 040 * Static fields. 041 */ 042 043 044 /** 045 * The version of this class for {@linkplain Serializable 046 * serialization} purposes. 047 */ 048 private static final long serialVersionUID = 1L; 049 050 051 /* 052 * Instance fields. 053 */ 054 055 056 /** 057 * The system that produced this {@link 058 * ConfigurationValue}. 059 * 060 * <p>This field may be {@code null}.</p> 061 */ 062 private Serializable source; 063 064 /** 065 * A {@link Map} representing the specific configuration coordinates 066 * this {@link ConfigurationValue} is selected for. 067 * 068 * <p>This field may be {@code null}.</p> 069 */ 070 private final Map<String, String> coordinates; 071 072 /** 073 * The name of the configuration property for which this {@link 074 * ConfigurationValue} is a value. 075 * 076 * <p>This field is never {@code null}.</p> 077 */ 078 private final String name; 079 080 /** 081 * The actual configuration value represented by this {@link 082 * ConfigurationValue}. 083 * 084 * <p>This field may be {@code null}.</p> 085 */ 086 private final String value; 087 088 /** 089 * Whether or not this {@link ConfigurationValue} is to be regarded 090 * as the authoritative value for the configuration property in 091 * question. 092 */ 093 private final boolean authoritative; 094 095 096 /* 097 * Constructors. 098 */ 099 100 101 /** 102 * Creates a new {@link ConfigurationValue}. 103 * 104 * @param source the system creating this {@link 105 * ConfigurationValue}; must not be {@code null} 106 * 107 * @param coordinates the configuration coordinates to which this 108 * {@link ConfigurationValue} applies; must be a subset of the 109 * configuration coordinates that resulted in this {@link 110 * ConfigurationValue} being created; may be {@code null} 111 * 112 * @param name the name of the configuration property for which this 113 * is a value; must not be {@code null} 114 * 115 * @param value the value; may be {@code null} 116 * 117 * @param authoritative whether this {@link ConfigurationValue} is 118 * to be considered authoritative 119 * 120 * @exception NullPointerException if either {@code source} 121 * or {@code name} is {@code null} 122 */ 123 public ConfigurationValue(final Serializable source, final Map<String, String> coordinates, final String name, final String value, final boolean authoritative) { 124 super(); 125 Objects.requireNonNull(source); 126 Objects.requireNonNull(name); 127 this.source = source; 128 if (coordinates == null || coordinates.isEmpty()) { 129 this.coordinates = Collections.emptyMap(); 130 } else { 131 this.coordinates = Collections.unmodifiableMap(new HashMap<>(coordinates)); 132 } 133 this.name = name; 134 this.value = value; 135 this.authoritative = authoritative; 136 } 137 138 139 /* 140 * Instance methods. 141 */ 142 143 144 /** 145 * Returns the system that created this {@link ConfigurationValue}. 146 * 147 * <p>This method never returns {@code null}.</p> 148 * 149 * @return the system that created this {@link ConfigurationValue}; 150 * never {@code null} 151 */ 152 public final Object getSource() { 153 return this.source; 154 } 155 156 /** 157 * Returns the configuration coordinates locating this {@link 158 * ConfigurationValue} in configuration space. 159 * 160 * <p>This method may return {@code null}.</p> 161 * 162 * @return the configuration coordinates locating this {@link 163 * ConfigurationValue} in configuration space, or {@code null} 164 */ 165 public final Map<String, String> getCoordinates() { 166 return this.coordinates; 167 } 168 169 /** 170 * Returns the name of the configuration property for which this 171 * {@link ConfigurationValue} is a value. 172 * 173 * <p>This method never returns {@code null}.</p> 174 * 175 * @return the name of the configuration property for which this 176 * {@link ConfigurationValue} is a value; never {@code null} 177 */ 178 public final String getName() { 179 return this.name; 180 } 181 182 /** 183 * Returns the actual value that this {@link ConfigurationValue} 184 * represents. 185 * 186 * <p>This method may return {@code null}.</p> 187 * 188 * @return the actual value that this {@link ConfigurationValue} 189 * represents, or {@code null} 190 */ 191 public final String getValue() { 192 return this.value; 193 } 194 195 /** 196 * Returns {@code true} if this {@link ConfigurationValue} is to be 197 * regarded as authoritative. 198 * 199 * @return {@code true} if this {@link ConfigurationValue} is to be 200 * regarded as authoritative; {@code false} otherwise 201 */ 202 public final boolean isAuthoritative() { 203 return this.authoritative; 204 } 205 206 /** 207 * Returns the <em>specificity</em> of this {@link 208 * ConfigurationValue}. 209 * 210 * <p>The specificity of a {@link ConfigurationValue} is equal to 211 * the {@linkplain Map#size() size} of its {@linkplain 212 * #getCoordinates() configuration coordinates 213 * <code>Map</code>}.</p> 214 * 215 * @return the specificity of this {@link ConfigurationValue}; 216 * always zero or a positive integer 217 */ 218 public final int specificity() { 219 int size = 0; 220 final Map<?, ?> coordinates = this.getCoordinates(); 221 if (coordinates != null) { 222 size = coordinates.size(); 223 } 224 return size; 225 } 226 227 /** 228 * Returns a hashcode for this {@link ConfigurationValue}. 229 * 230 * @return a hashcode for this {@link ConfigurationValue} 231 */ 232 @Override 233 public int hashCode() { 234 int hashCode = 17; 235 236 // Note: getSource() and isAuthoritative() are deliberately 237 // omitted from hashCode calculation. 238 239 final Object coordinates = this.getCoordinates(); 240 int c = coordinates == null ? 0 : coordinates.hashCode(); 241 hashCode = 37 * hashCode + c; 242 243 final Object name = this.getName(); 244 c = name == null ? 0 : name.hashCode(); 245 hashCode = 37 * hashCode + c; 246 247 final Object value = this.getValue(); 248 c = value == null ? 0 : value.hashCode(); 249 hashCode = 37 * hashCode + c; 250 251 return hashCode; 252 } 253 254 /** 255 * Returns {@code true} if the supplied {@link Object} is equal to 256 * this {@link ConfigurationValue}. 257 * 258 * <p>An {@link Object} is equal to a {@link ConfigurationValue} 259 * if:</p> 260 * 261 * <ul> 262 * 263 * <li>It is an instance of {@link ConfigurationValue}</li> 264 * 265 * <li>Its {@linkplain #getName() name} and {@link #getCoordinates() 266 * coordinates} are equal to those of the {@link ConfigurationValue} 267 * to which it is being compared</li> 268 * 269 * <li>Its {@linkplain #getValue() value} is equal to that of the 270 * {@link ConfigurationValue} to which it is being compared</li> 271 * 272 * </ul> 273 * 274 * @param other the {@link Object} to test; may be {@code null} 275 * 276 * @return {@code true} if the supplied {@link Object} is equal to 277 * this {@link ConfigurationValue}; {@code false} otherwise 278 */ 279 @Override 280 public boolean equals(final Object other) { 281 if (other == this) { 282 return true; 283 } else if (other instanceof ConfigurationValue) { 284 final ConfigurationValue her = (ConfigurationValue)other; 285 286 // Note: getSource() and isAuthoritative() are 287 // deliberately omitted from the algorithm. 288 289 final Object coordinates = this.getCoordinates(); 290 if (coordinates == null) { 291 if (her.getCoordinates() != null) { 292 return false; 293 } 294 } else if (!coordinates.equals(her.getCoordinates())) { 295 return false; 296 } 297 298 final Object name = this.getName(); 299 if (name == null) { 300 if (her.getName() != null) { 301 return false; 302 } 303 } else if (!name.equals(her.getName())) { 304 return false; 305 } 306 307 final Object value = this.getValue(); 308 if (value == null) { 309 if (her.getValue() != null) { 310 return false; 311 } 312 } else if (!value.equals(her.getValue())) { 313 return false; 314 } 315 316 return true; 317 } else { 318 return false; 319 } 320 } 321 322 /** 323 * Returns a non-{@code null} {@link String} representation of this 324 * {@link ConfigurationValue}. 325 * 326 * <p>This method never returns {@code null}.</p> 327 * 328 * <p>Overrides of this method must not return {@code null}.</p> 329 * 330 * @return a non-{@code null} {@link String} representation of this 331 * {@link ConfigurationValue} 332 */ 333 @Override 334 public String toString() { 335 final StringBuilder sb = new StringBuilder(); 336 sb.append("("); 337 sb.append(this.getSource()); 338 sb.append(") "); 339 final Map<String, String> coordinates = this.getCoordinates(); 340 if (coordinates != null && !coordinates.isEmpty()) { 341 sb.append(coordinates); 342 sb.append(" "); 343 } 344 sb.append(this.getName()); 345 sb.append("="); 346 sb.append(this.getValue()); 347 final boolean authoritative = this.isAuthoritative(); 348 if (authoritative) { 349 sb.append(" (authoritative)"); 350 } 351 return sb.toString(); 352 } 353 354}