ComponentResourceReference.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 iu.type;

import java.util.Map;
import java.util.Optional;
import java.util.WeakHashMap;

import edu.iu.IuVisitor;
import edu.iu.type.InstanceReference;
import edu.iu.type.IuAttribute;
import edu.iu.type.IuProperty;
import edu.iu.type.IuResource;
import edu.iu.type.IuResourceReference;
import edu.iu.type.IuType;
import jakarta.annotation.Resource;

/**
 * Implementation of {@link IuResource};
 * 
 * @param <R> referrer type
 * @param <T> resource type
 */
class ComponentResourceReference<R, T> implements IuResourceReference<R, T>, InstanceReference<R> {

	private final String name;
	private final TypeTemplate<?, T> type;
	private final IuAttribute<R, ? super T> attribute;
	private final Map<R, Optional<T>> unboundValues = new WeakHashMap<>();
	private final IuVisitor<R> visitor = new IuVisitor<>();
	private volatile IuResource<T> boundResource;

	/**
	 * Constructor.
	 * 
	 * @param attribute facade for the attribute backing the resource
	 * @param resource  resource annotation associated with the attribute
	 */
	@SuppressWarnings("unchecked")
	ComponentResourceReference(DeclaredAttribute<R, ? super T> attribute, Resource resource) {
		this.attribute = attribute;

		attribute.declaringType().template.subscribe(this);

		if (resource == null)
			throw new IllegalArgumentException("Missing @Resource: " + attribute);

		String name = resource.name();
		if (name.isEmpty())
			name = attribute.name();
		this.name = name;

		TypeTemplate<?, T> type;
		if (resource.type() != Object.class)
			type = TypeFactory.resolveRawClass((Class<T>) resource.type());
		else
			type = (TypeTemplate<?, T>) attribute.type().template;

		final var erased = type.erasedClass();
		if (!attribute.type().erasedClass().isAssignableFrom(erased))
			throw new IllegalArgumentException("attribute " + attribute + " is not assignable from " + type);

		this.type = type;
	}

	@Override
	public String name() {
		return name;
	}

	@Override
	public TypeTemplate<?, T> type() {
		return type;
	}

	@Override
	public IuType<?, R> referrerType() {
		return attribute.declaringType();
	}

	@Override
	public boolean isBound() {
		return boundResource != null;
	}

	@Override
	public synchronized void bind(IuResource<T> resource) {
		if (resource != null //
				&& (!resource.name().equals(name()) //
						|| !type().erasedClass().isAssignableFrom(resource.type().erasedClass())))
			throw new IllegalArgumentException("Resource " + resource + " does not apply to " + this);

		boundResource = resource;

		visitor.visit(referrer -> {
			if (referrer != null) {
				final T resourceValue;
				if (boundResource == null)
					if (unboundValues.containsKey(referrer))
						resourceValue = unboundValues.get(referrer).orElse(null);
					else
						return null;
				else
					resourceValue = boundResource.get();

				attribute.set(referrer, resourceValue);
			}
			return null;
		});
	}

	@Override
	public synchronized void accept(R referrer) {
		if (visitor.visit(r -> r == referrer ? Optional.empty() : null) != null)
			return;

		if (!(attribute instanceof IuProperty property) //
				|| property.canRead())
			unboundValues.put(referrer, Optional.ofNullable(type.autoboxClass().cast(attribute.get(referrer))));

		if (boundResource != null)
			attribute.set(referrer, boundResource.get());

		visitor.accept(referrer);
	}

	@Override
	public T value(R referrer) {
		return type.autoboxClass().cast(attribute.get(referrer));
	}

	@Override
	public synchronized void clear(R referrer) {
		if (referrer == null)
			return;

		visitor.clear(referrer);

		if (unboundValues.containsKey(referrer))
			attribute.set(referrer, unboundValues.remove(referrer).orElse(null));
	}

	@Override
	public String toString() {
		return "ComponentResourceReference [name=" + name + ", type=" + type + ", attribute=" + attribute
				+ ", boundResource=" + boundResource + "]";
	}

}