001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2022 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.loader.jackson; 018 019import java.util.Collection; 020import java.util.Iterator; 021import java.util.LinkedHashMap; 022import java.util.List; 023import java.util.Map; 024import java.util.Map.Entry; 025import java.util.Objects; 026import java.util.TreeSet; 027 028import com.fasterxml.jackson.annotation.JsonAnySetter; 029import com.fasterxml.jackson.annotation.JsonAutoDetect; 030 031import org.microbean.development.annotation.Experimental; 032import org.microbean.development.annotation.Incomplete; 033 034import org.microbean.qualifier.Qualifier; 035import org.microbean.qualifier.Qualifiers; 036 037/** 038 * A Java object that represents a tree-like configuration structure, 039 * using Jackson mapping constructs to make things easier. 040 * 041 * @author <a href="https://about.me/lairdnelson" 042 * target="_parent">Laird Nelson</a> 043 */ 044@Experimental 045@Incomplete 046@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, 047 fieldVisibility = JsonAutoDetect.Visibility.NONE, 048 getterVisibility = JsonAutoDetect.Visibility.NONE, 049 isGetterVisibility = JsonAutoDetect.Visibility.NONE, 050 setterVisibility = JsonAutoDetect.Visibility.NONE) 051public class Configuration { 052 053 private Map<? extends String, ?> qualifiersMap; 054 055 private final Map<String, Object> any; 056 057 /** 058 * Creates a new {@link Configuration}. 059 */ 060 public Configuration() { 061 super(); 062 this.qualifiersMap = Map.of(); 063 this.any = new LinkedHashMap<>(); 064 } 065 066 /** 067 * Returns a {@link Qualifiers} representing all qualifiers in the 068 * {@link Configuration}. 069 * 070 * @return a {@link Qualifiers} representing all qualifiers in the 071 * {@link Configuration} 072 * 073 * @nullability This method never returns {@code null}. 074 * 075 * @idempotency This method is idempotent and deterministic. 076 * 077 * @threadsafety This method is <strong>not</strong> safe for 078 * concurrent use by multiple threads. 079 */ 080 public final Qualifiers<? extends String, ?> qualifiers() { 081 return this.qualifiers(List.of()); 082 } 083 084 /** 085 * Returns a {@link Qualifiers} representing all qualifiers in the 086 * {@link Configuration} associated with the supplied sequence of 087 * names. 088 * 089 * @param names a sequence of names identifying a path to the root 090 * of a tree of qualifiers; may be {@code null} 091 * 092 * @return a {@link Qualifiers} representing all qualifiers in the 093 * {@link Configuration} associated with the supplied sequence of 094 * names 095 * 096 * @nullability This method never returns {@code null}. 097 * 098 * @idempotency This method is idempotent and deterministic. 099 * 100 * @threadsafety This method is <strong>not</strong> safe for 101 * concurrent use by multiple threads. 102 */ 103 @SuppressWarnings("unchecked") 104 public final Qualifiers<? extends String, ?> qualifiers(final Iterable<? extends String> names) { 105 Map<? extends String, ?> map = this.qualifiersMap; 106 if (map == null || map.isEmpty()) { 107 return Qualifiers.of(); 108 } 109 map = new LinkedHashMap<>(map); 110 if (names != null) { 111 for (final String name : names) { 112 Object value = map.get(name); 113 if (value == null) { 114 return Qualifiers.of(); 115 } else if (value instanceof Map) { 116 map = (Map<? extends String, ?>)value; 117 } 118 } 119 } 120 final Collection<Qualifier<String, Object>> c = new TreeSet<>(); 121 for (final Entry<? extends String, ?> e : map.entrySet()) { 122 final Object value = e.getValue(); 123 if (!(value instanceof Map)) { 124 c.add(Qualifier.of(e.getKey(), value)); 125 } 126 } 127 return Qualifiers.of(c); 128 } 129 130 /** 131 * Returns an {@link Object} in this {@link Configuration} indexed 132 * under the supplied {@code name}. 133 * 134 * @param name the name; must not be {@code null} 135 * 136 * @return the associated {@link Object}, or {@code null} 137 * 138 * @nullability This method may return {@code null}. 139 * 140 * @idempotency This method is idempotent and deterministic. 141 * 142 * @threadsafety This method is <strong>not</strong> safe for 143 * concurrent use by multiple threads. 144 */ 145 protected final Object any(final String name) { 146 return this.any.get(name); 147 } 148 149 @JsonAnySetter 150 @SuppressWarnings("unchecked") 151 private final void any(final String name, final Object value) { 152 if (name.equals("@qualifiers")) { 153 if (value == null) { 154 this.qualifiersMap = Map.of(); 155 } else if (value instanceof Map) { 156 this.qualifiersMap = (Map<? extends String, ?>)value; 157 } else { 158 this.any.put(name, value); 159 } 160 } else { 161 this.any.put(name, value); 162 } 163 } 164 165 /** 166 * Returns a hashcode for this {@link Configuration}. 167 * 168 * @return a hashcode for this {@link Configuration} 169 * 170 * @idempotency This method is, and its overrides must be, 171 * idempotent and deterministic. 172 * 173 * @threadsafety This method is <strong>not</strong> safe for 174 * concurrent use by multiple threads. 175 */ 176 @Override // Object 177 public int hashCode() { 178 return Objects.hash(this.qualifiersMap, this.any); 179 } 180 181 /** 182 * Returns {@code true} if the supplied {@link Object} is equal to 183 * this {@link Configuration}. 184 * 185 * @param other the {@link Object} to test; may be {@code null} 186 * 187 * @return {@code true} if the supplied {@link Object} is equal to 188 * this {@link Configuration} 189 * 190 * @idempotency This method is, and its overrides must be, 191 * idempotent and deterministic. 192 * 193 * @threadsafety This method is <strong>not</strong> safe for 194 * concurrent use by multiple threads. 195 */ 196 @Override // Object 197 public boolean equals(final Object other) { 198 if (other == this) { 199 return true; 200 } else if (other != null && other.getClass() == this.getClass()) { 201 final Configuration her = (Configuration)other; 202 return 203 Objects.equals(this.qualifiersMap, her.qualifiersMap) && 204 Objects.equals(this.any, her.any); 205 } else { 206 return false; 207 } 208 } 209 210 /** 211 * Returns a {@link String} representation of this {@link 212 * Configuration}. 213 * 214 * <p>The format of the returned {@link String} is deliberately 215 * undefined and subject to change between revisions of this class 216 * without notice.</p> 217 * 218 * @return a {@link String} representation of this {@link 219 * Configuration} 220 * 221 * @nullability This method does not, and its overrides must not, 222 * return {@code null}. 223 * 224 * @idempotency This method is, and its overrides must be, 225 * idempotent and deterministic. 226 * 227 * @threadsafety This method is <strong>not</strong> safe for 228 * concurrent use by multiple threads. 229 */ 230 @Override // Object 231 public String toString() { 232 return this.qualifiersMap + " " + this.any; 233 } 234 235}