IuResourceKey.java

/*
 * Copyright © 2025 Indiana University
 * All rights reserved.
 *
 * BSD 3-Clause License
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * - Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 * 
 * - Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * 
 * - Neither the name of the copyright holder nor the names of its
 *   contributors may be used to endorse or promote products derived from
 *   this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package edu.iu.type;

import edu.iu.IuObject;

/**
 * Hash key for coordinating {@link IuResource} and {@link IuResourceReference}
 * instances.
 * 
 * @param <T> resource type
 */
public class IuResourceKey<T> {

	/**
	 * Gets the default name for a resource type.
	 * 
	 * <p>
	 * The default name:
	 * </p>
	 * <ul>
	 * <li>Starts with a lower case letter</li>
	 * <li>Matches the simple class name of the first non-platform interface
	 * implemented by the resource class, if present</li>
	 * <li>else matches the simple class name of the resource class</li>
	 * </ul>
	 * 
	 * @param type resource class
	 * @return default name
	 */
	public static String getDefaultResourceName(Class<?> type) {
		if (!type.isInterface())
			for (final var i : type.getInterfaces())
				if (!IuObject.isPlatformName(i.getName())) {
					type = i;
					break;
				}
		StringBuilder sb = new StringBuilder(type.getSimpleName());
		sb.setCharAt(0, Character.toLowerCase(sb.charAt(0)));
		return sb.toString();
	}

	/**
	 * Gets a resource key instance based on the type's default resource name.
	 * 
	 * @param <T>  resource type
	 * @param type resource class
	 * @return resource key
	 */
	public static <T> IuResourceKey<T> of(Class<T> type) {
		return new IuResourceKey<>(getDefaultResourceName(type), type);
	}

	/**
	 * Gets a resource key instance.
	 * 
	 * @param <T>  resource type
	 * @param name resource name
	 * @param type resource class
	 * @return resource key
	 */
	public static <T> IuResourceKey<T> of(String name, Class<T> type) {
		return new IuResourceKey<>(name, type);
	}

	/**
	 * Gets a resource key instance for a {@link IuResource}.
	 * 
	 * @param <T>      resource type
	 * @param resource resource
	 * @return resource key
	 */
	public static <T> IuResourceKey<T> from(IuResource<T> resource) {
		return new IuResourceKey<>(resource.name(), resource.type().erasedClass());
	}

	/**
	 * Gets a resource key instance for a {@link IuResourceReference}.
	 * 
	 * @param <T>               resource type
	 * @param resourceReference resource reference
	 * @return resource key
	 */
	@SuppressWarnings("unchecked")
	public static <T> IuResourceKey<T> from(IuResourceReference<?, ? extends T> resourceReference) {
		return new IuResourceKey<>(resourceReference.name(), (Class<T>) resourceReference.type().erasedClass());
	}

	private final String name;
	private final Class<T> type;

	/**
	 * Gets the resource name.
	 * 
	 * @return resource name
	 */
	public String name() {
		return name;
	}

	/**
	 * Gets the resource type
	 * 
	 * @return resource type
	 */
	public Class<T> type() {
		return type;
	}

	@Override
	public int hashCode() {
		return IuObject.hashCode(name, type);
	}

	@Override
	public boolean equals(Object obj) {
		if (!IuObject.typeCheck(this, obj))
			return false;
		IuResourceKey<?> other = (IuResourceKey<?>) obj;
		return IuObject.equals(name, other.name) && IuObject.equals(type, other.type);
	}

	@Override
	public String toString() {
		final var sb = new StringBuilder();
		sb.append(name);
		if (type != Object.class)
			sb.append('!').append(type.getName());
		return sb.toString();
	}

	private IuResourceKey(String name, Class<T> type) {
		this.name = name;
		this.type = type;
	}

}