
org.microbean.qualifier.NamedAttributeMap Maven / Gradle / Ivy
/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
*
* Copyright © 2023 microBean™.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.microbean.qualifier;
import java.lang.System.Logger;
import java.lang.constant.Constable;
import java.lang.constant.DynamicConstantDesc;
import java.lang.constant.MethodHandleDesc;
import java.lang.constant.MethodTypeDesc;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.SortedMap;
import java.util.TreeMap;
import org.microbean.constant.Constables;
import org.microbean.invoke.ContentHashable;
import static java.lang.System.Logger.Level.DEBUG;
import static java.lang.System.Logger.Level.WARNING;
import static java.lang.constant.ConstantDescs.BSM_INVOKE;
import static java.lang.constant.ConstantDescs.CD_Collection;
import static java.lang.constant.ConstantDescs.CD_Map;
import static java.lang.constant.ConstantDescs.CD_String;
import static java.lang.constant.DirectMethodHandleDesc.Kind.STATIC;
import static org.microbean.qualifier.ConstantDescs.CD_NamedAttributeMap;
public record NamedAttributeMap(String name,
Map attributes,
Map info,
Collection> metadata)
implements Comparable>, Constable, ContentHashable {
private static final Logger LOGGER = System.getLogger(NamedAttributeMap.class.getName());
public NamedAttributeMap(final String name) {
this(name, Map.of(), Map.of(), List.of());
}
public NamedAttributeMap(final String name, final V value) {
this(name, Map.of("value", value), Map.of(), List.of());
}
public NamedAttributeMap(final String name, final Map attributes) {
this(name, attributes, Map.of(), List.of());
}
@SuppressWarnings("unchecked")
public NamedAttributeMap {
Objects.requireNonNull(name, "name");
if (attributes.isEmpty()) {
attributes = Collections.emptySortedMap();
} else {
final SortedMap m =
attributes instanceof TreeMap cloneMe ?
(SortedMap)cloneMe.clone() :
new TreeMap<>(attributes instanceof SortedMap sm ? sm.comparator() : null);
m.putAll(attributes);
attributes = Collections.unmodifiableSortedMap(m);
}
if (info.isEmpty()) {
info = Collections.emptySortedMap();
} else {
final SortedMap m =
info instanceof TreeMap cloneMe ?
(TreeMap)cloneMe.clone() :
new TreeMap<>(info instanceof SortedMap sm ? sm.comparator() : null);
m.putAll(info);
info = Collections.unmodifiableSortedMap(m);
}
if (metadata.isEmpty()) {
metadata = List.of();
} else {
final List> l =
metadata instanceof ArrayList> cloneMe ?
(List>)cloneMe.clone() :
new ArrayList<>(metadata);
Collections.sort(l);
metadata = Collections.unmodifiableList(l);
}
}
public final boolean isEmpty() {
return this.attributes().isEmpty();
}
public final boolean containsKey(final String k) {
return this.attributes().containsKey(k);
}
public final V get(final String k) {
return this.attributes().get(k);
}
@Override // ContentHashable
public final Optional contentHashInput() {
final StringBuilder sb = new StringBuilder(this.name());
for (final Entry e : this.attributes().entrySet()) {
sb.append(e.getKey());
final Object value = e.getValue();
if (value instanceof ContentHashable ch) {
final CharSequence cs = ch.contentHashInput().orElse(null);
if (cs == null) {
return Optional.empty();
}
sb.append(cs);
} else if (value != null) {
sb.append(value);
}
}
return Optional.of(sb.toString());
}
// Consistent with equals().
@Override // Comparable
@SuppressWarnings("unchecked")
public final int compareTo(final NamedAttributeMap other) {
if (other == null) {
return -1;
}
int cmp = this.name().compareTo(other.name());
if (cmp != 0) {
return cmp;
}
final Map attributes = this.attributes();
final Map herAttributes = other.attributes();
cmp = Integer.signum(attributes.size() - herAttributes.size());
if (cmp != 0) {
return cmp;
}
final Iterator> myIterator = attributes.entrySet().iterator();
final Iterator> herIterator = herAttributes.entrySet().iterator();
while (myIterator.hasNext()) {
final Entry myEntry = myIterator.next();
final Entry herEntry = herIterator.next();
final String myKey = myEntry.getKey();
final String herKey = herEntry.getKey();
if (myKey == null) {
if (herKey != null) {
return 1;
}
} else if (herKey == null) {
return -1;
}
cmp = myKey.compareTo(herKey);
if (cmp != 0) {
return cmp;
}
final V myValue = myEntry.getValue();
final V herValue = herEntry.getValue();
if (myValue == null) {
if (herValue != null) {
return 1;
}
} else if (herValue == null) {
return -1;
} else if (myValue instanceof Comparable) {
try {
cmp = ((Comparable)myValue).compareTo(herValue);
} catch (final ClassCastException ohWell) {
if (LOGGER.isLoggable(WARNING)) {
LOGGER.log(WARNING, ohWell);
}
}
} else if (!myValue.equals(herValue)) {
if (LOGGER.isLoggable(DEBUG)) {
LOGGER.log(DEBUG, "Using toString() for value comparison: {0} <=> {1}", myValue.toString(), herValue.toString());
}
cmp = myValue.toString().compareTo(herValue.toString());
}
if (cmp != 0) {
return cmp;
}
}
// Note: we do not consider info or metadata on purpose
assert cmp == 0;
return 0;
}
@Override
public final boolean equals(final Object other) {
if (other == this) {
return true;
} else if (other != null && other.getClass() == this.getClass()) {
final NamedAttributeMap> her = (NamedAttributeMap>)other;
// Note: no info or metadata on purpose
return
this.name().equals(her.name()) &&
this.attributes().equals(her.attributes());
} else {
return false;
}
}
@Override
public final int hashCode() {
int hashCode = 17;
hashCode = 31 * hashCode + this.name().hashCode();
return 31 * hashCode + this.attributes().hashCode();
}
@Override // Constable
public final Optional>> describeConstable() {
return Constables.describeConstable(this.attributes())
.flatMap(attributesDesc -> Constables.describeConstable(this.info())
.flatMap(infoDesc -> Constables.describeConstable(this.metadata())
.map(mdDesc -> DynamicConstantDesc.of(BSM_INVOKE,
MethodHandleDesc.ofMethod(STATIC,
CD_NamedAttributeMap,
"of",
MethodTypeDesc.of(CD_NamedAttributeMap,
CD_String,
CD_Map,
CD_Map,
CD_Collection)),
this.name(),
attributesDesc,
infoDesc,
mdDesc))));
}
// Called by describeConstable().
public static final NamedAttributeMap of(final String name,
final Map attributes,
final Map info,
final Collection> metadata) {
return new NamedAttributeMap<>(name, attributes, info, metadata);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy