
org.microbean.qualifier.Binding Maven / Gradle / Ivy
/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
*
* Copyright © 2022–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.ClassDesc;
import java.lang.constant.Constable;
import java.lang.constant.ConstantDesc;
import java.lang.constant.DynamicConstantDesc;
import java.lang.constant.MethodHandleDesc;
import java.util.Iterator;
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 static java.lang.System.Logger.Level.WARNING;
import static java.lang.constant.ConstantDescs.BSM_INVOKE;
import static java.lang.constant.ConstantDescs.CD_Map;
import static java.lang.constant.ConstantDescs.CD_Object;
import static java.lang.constant.ConstantDescs.CD_String;
import static java.lang.constant.ConstantDescs.NULL;
import static java.util.Collections.emptySortedMap;
import static java.util.Collections.unmodifiableSortedMap;
/**
* An abstract {@linkplain #attributes() attributed} {@linkplain #name() name}-{@linkplain #value() value} pair.
*
* @param the type of a {@link Binding}'s {@linkplain #value() value} and of its {@linkplain #attributes() attribute
* values}
*
* @param the type of the subclass
*
* @author Laird Nelson
*/
public abstract class Binding> implements Comparable, Constable {
/*
* Static fields.
*/
private static final Logger LOGGER = System.getLogger(Binding.class.getName());
/*
* Instance fields.
*/
private final String name;
private final V value;
private final SortedMap attributes;
private final SortedMap info;
/*
* Constructors.
*/
/**
* Creates a new {@link Binding}.
*
* @param name the name; must not be {@code null}
*
* @param value the value; may be {@code null}
*
* @param attributes further describing this {@link Binding}; may be {@code null}
*
* @param info informational attributes further describing this {@link Binding} that are not considered by its {@link
* #equals(Object)} implementation; may be {@code null}
*
* @see #name()
*
* @see #value()
*
* @see #attributes()
*
* @see #info()
*/
@SuppressWarnings("unchecked")
protected Binding(final String name,
final V value,
final Map extends String, ?> attributes,
final Map extends String, ?> info) {
super();
this.name = Objects.requireNonNull(name);
this.value = value;
this.attributes =
attributes == null || attributes.isEmpty() ? emptySortedMap() : unmodifiableSortedMap(new TreeMap<>(attributes));
if (info == null || info.isEmpty()) {
this.info = emptySortedMap();
} else {
final TreeMap map = new TreeMap<>();
for (final String key : info.keySet()) {
if (!this.attributes.containsKey(key)) {
map.put(key, info.get(key));
}
}
this.info = unmodifiableSortedMap(map);
}
}
/*
* Instance methods.
*/
/**
* Returns the name of this {@link Binding}.
*
* @return the name of this {@link Binding}
*
* @nullability This method never returns {@code null}.
*
* @idempotency This method is idempotent and deterministic.
*
* @threadsafety This method is safe for concurrent use by multiple threads.
*/
public final String name() {
return this.name;
}
/**
* Returns the value of this {@link Binding}, which may be {@code null}.
*
* @return the value of this {@link Binding}
*
* @nullability This method may return {@code null}.
*
* @idempotency This method is idempotent and deterministic.
*
* @threadsafety This method is safe for concurrent use by multiple threads.
*/
public final V value() {
return this.value;
}
/**
* Returns an immutable {@link SortedMap} representing any attributes further describing this {@link Binding}.
*
* The attributes are considered by the {@link #equals(Object)} method.
*
* @return the attributes of this {@link Binding}
*
* @nullability This method never returns {@code null}.
*
* @idempotency This method is idempotent and deterministic.
*
* @threadsafety This method is safe for concurrent use by multiple threads.
*/
public final SortedMap attributes() {
return this.attributes;
}
/**
* Returns an immutable {@link SortedMap} representing any informational-only attributes further describing this
* {@link Binding}.
*
* The attributes are not considered by the {@link #equals(Object)} method.
*
* @return the informational attributes of this {@link Binding}
*
* @nullability This method never returns {@code null}.
*
* @idempotency This method is idempotent and deterministic.
*
* @threadsafety This method is safe for concurrent use by multiple threads.
*/
public final SortedMap info() {
return this.info;
}
/**
* Returns an {@link Optional} housing a {@link ConstantDesc} describing this {@link Binding}, if this {@link Binding}
* is capable of being represented as a dynamic
* constant, or an {@linkplain Optional#isEmpty() empty} {@link Optional} if not.
*
* @return an {@link Optional} housing a {@link ConstantDesc} describing this {@link Binding}, if this {@link Binding}
* is capable of being represented as a dynamic
* constant, or an {@linkplain Optional#isEmpty() empty} {@link Optional} if not
*
* @nullability This method never returns {@code null}.
*
* @idempotency This method is idempotent and deterministic.
*
* @threadsafety This method is safe for concurrent use by multiple threads.
*
* @see #describeConstructor()
*
* @see Dynamically-computed
* constants
*/
@Override // Constable
public Optional extends ConstantDesc> describeConstable() {
final MethodHandleDesc constructor = this.describeConstructor();
if (constructor == null) {
return Optional.empty();
}
final V value = this.value();
final ConstantDesc valueCd;
if (value == null) {
valueCd = NULL;
} else if (value instanceof Constable c) {
valueCd = c.describeConstable().orElse(null);
} else if (value instanceof ConstantDesc cd) {
valueCd = cd;
} else {
return Optional.empty();
}
final ConstantDesc attributesCd = Constables.describeConstable(this.attributes()).orElse(null);
if (attributesCd == null) {
return Optional.empty();
}
final ConstantDesc infoCd = Constables.describeConstable(this.info()).orElse(null);
if (infoCd == null) {
return Optional.empty();
}
return
Optional.of(DynamicConstantDesc.of(BSM_INVOKE, constructor, this.name(), valueCd, attributesCd, infoCd));
}
/**
* Returns a {@link MethodHandleDesc} describing the constructor or {@code static} method that will be used to create
* a dynamic
* constant representing this {@link Binding}.
*
* @return a {@link MethodHandleDesc} describing the constructor or {@code static} method that will be used to create
* a dynamic
* constant representing this {@link Binding}
*
* @nullability This method does not, and its overrides must not, return {@code null}.
*
* @idempotency This method is, and its overrides must be, idempotent and deterministic.
*
* @threadsafety This method is, and its overrides must be, safe for concurrent use by multiple threads.
*/
protected MethodHandleDesc describeConstructor() {
return
MethodHandleDesc.ofConstructor(this.getClass().describeConstable().orElseThrow(),
new ClassDesc[] { CD_String, CD_Object, CD_Map, CD_Map });
}
/**
* Returns a hashcode for this {@link Binding} that represents its {@linkplain #name() name}, {@linkplain #value()
* value} and {@linkplain #attributes() attributes}.
*
* A subclass that overrides this method must also override the {@link #equals(Object)} and {@link
* #compareTo(Binding)} methods accordingly.
*
* @return a hashcode for this {@link Binding}
*
* @idempotency This method is, and its overrides must be, idempotent and deterministic.
*
* @threadsafety This method is, and its overrides must be, safe for concurrent use by multiple threads.
*
* @see #equals(Object)
*
* @see #compareTo(Binding)
*/
@Override // Object
public int hashCode() {
int hashCode = 17;
Object v = this.name();
int c = v == null ? 0 : v.hashCode();
hashCode = 37 * hashCode + c;
v = this.value();
c = v == null ? 0 : v.hashCode();
hashCode = 37 * hashCode + c;
v = this.attributes();
c = v == null ? 0 : v.hashCode();
hashCode = 37 * hashCode + c;
return hashCode;
}
/**
* Returns an {@code int} representing a {@linkplain Comparable#compareTo(Object) comparison} of this {@link Binding}
* with the supplied {@link Binding}, by considering the {@linkplain #name() names}, {@linkplain #value() values} and
* {@linkplain #attributes() attributes} of both {@link Binding}s.
*
* Any {@linkplain #attributes() attribute values} that are not {@link Comparable} instances will be compared based
* on their {@linkplain Object#toString() string representations}. It is strongly recommended that {@linkplain
* #attributes() attribute values} implement {@link Comparable}.
*
* This method is, and its overrides must be, {@linkplain Comparable consistent with equals}.
*
* A subclass that overrides this method must also override the {@link #hashCode()} and {@link #equals(Object)}
* methods accordingly.
*
* @param other the other {@link Binding}; may be {@code null} in which case a negative value will be returned
*
* @return an {@code int} representing a {@linkplain Comparable#compareTo(Object) comparison} of this {@link Binding}
* with the supplied {@link Binding}
*
* @idempotency This method is, and its overrides must be, idempotent and deterministic.
*
* @threadsafety This method is, and its overrides must be, safe for concurrent use by multiple threads.
*
* @see #hashCode()
*
* @see #equals(Object)
*/
@Override // Comparable
@SuppressWarnings("unchecked")
public int compareTo(final B other) {
if (other == null) {
return -1;
} else if (this.equals(other)) {
return 0;
} else {
int cmp = this.name().compareTo(other.name());
if (cmp != 0) {
return cmp;
}
Object myValue = this.value();
Object otherValue = other.value();
if (value == null) {
if (otherValue != null) {
return 1;
}
} else if (otherValue == null) {
return -1;
} else if (myValue instanceof Comparable) {
try {
cmp = ((Comparable