001/*
002 * JDrupes Builder
003 * Copyright (C) 2025 Michael N. Lipp
004 * 
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
017 */
018
019package org.jdrupes.builder.api;
020
021import java.util.Objects;
022import java.util.Optional;
023
024/// Represents a request for [Resource]s of a specified type.
025/// The specified type provides two kinds of type information:
026///
027/// 1. The type of the [Resource]s that are actually provided.
028/// 2. The type of the "context" in which the [Resource]s are to be provided.
029///
030/// As an example, consider requests for a compile time and a runtime
031/// classpath. In both cases, the actually provided [Resource]s are
032/// of type "classpath element". However, depending on the kind of
033/// classpath, a [ResourceProvider] may deliver different collections of
034/// instances of "classpath elements". So instead of requesting
035/// "classpath element", 
036///
037/// Not all requested resource types require context information. For
038/// example, a request for [Cleanliness] usually refers to all resources
039/// that a [Generator] has created and does not depend on a context.
040/// However, in order to keep the API simple, the context is always
041/// required. 
042///
043/// @param <T> the generic type
044///
045public class ResourceRequest<T extends Resource> {
046
047    private final ResourceType<? extends Resources<T>> type;
048
049    /// Instantiates a new resource request without any restriction.
050    ///
051    /// @param type the requested type
052    ///
053    public ResourceRequest(ResourceType<? extends Resources<T>> type) {
054        this.type = type;
055    }
056
057    /// Creates a request for a resource of the given type, in the
058    /// given container type. The recommended usage pattern is
059    /// to import this method statically.
060    ///
061    /// @param <C> the generic type
062    /// @param <T> the generic type
063    /// @param container the container
064    /// @param requested the requested
065    /// @return the resource request
066    ///
067    public static <C extends Resources<T>, T extends Resource>
068            ResourceRequest<T>
069            requestFor(Class<C> container, Class<T> requested) {
070        return new ResourceRequest<>(new ResourceType<>(container,
071            new ResourceType<>(requested, null)));
072    }
073
074    /// Creates a request for a resource of the given type in a
075    /// container of type [Resources]. The recommended usage pattern
076    /// is to import this method statically.
077    ///
078    /// @param <T> the generic type
079    /// @param requested the requested
080    /// @return the resource request
081    ///
082    public static <T extends Resource>
083            ResourceRequest<T> requestFor(Class<T> requested) {
084        return new ResourceRequest<>(new ResourceType<>(Resources.class,
085            new ResourceType<>(requested, null)));
086    }
087
088    /// Create a widened resource request by replacing the requested
089    /// top-level type with the given super type, thus widening the
090    /// request.
091    ///
092    /// @param <R> the generic type
093    /// @param type the desired super type. This should actually be
094    /// declared as `Class <R>`, but there is no way to specify a 
095    /// parameterized type as actual parameter.
096    /// @return the new resource request
097    ///
098    public <R extends Resources<T>> ResourceRequest<T> widened(
099            @SuppressWarnings("rawtypes") Class<? extends Resources> type) {
100        return new ResourceRequest<>(type().widened(type));
101    }
102
103    /// Return the requested type.
104    ///
105    /// @return the resource type
106    ///
107    public ResourceType<? extends Resources<T>> type() {
108        return type;
109    }
110
111    /// Checks if this request accepts a resource of the given type.
112    /// Short for `type().isAssignableFrom(other)`.
113    ///
114    /// @param other the other
115    /// @return true, if successful
116    ///
117    public boolean wants(ResourceType<?> other) {
118        return type().isAssignableFrom(other);
119    }
120
121    /// Checks if the requested resource type includes the given type.
122    ///
123    /// @param type the type to check
124    /// @return true, if successful
125    ///
126    public boolean includes(ResourceType<?> type) {
127        return Optional.ofNullable(type().containedType())
128            .map(ct -> ct.isAssignableFrom(type)).orElse(false);
129    }
130
131    @Override
132    public int hashCode() {
133        return Objects.hash(type);
134    }
135
136    @Override
137    public boolean equals(Object obj) {
138        if (this == obj) {
139            return true;
140        }
141        if (obj == null) {
142            return false;
143        }
144        if (getClass() != obj.getClass()) {
145            return false;
146        }
147        ResourceRequest<?> other = (ResourceRequest<?>) obj;
148        return Objects.equals(type, other.type);
149    }
150
151    @Override
152    public String toString() {
153        return "ResourceRequest [type=" + type + "]";
154    }
155
156}