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    /// Slightly briefer alternative to invoking the constructor.
089    ///
090    /// @param <T> the generic type
091    /// @param type the type
092    /// @return the resource request
093    ///
094    public static <T extends Resource> ResourceRequest<T>
095            requestFor(ResourceType<? extends Resources<T>> type) {
096        return new ResourceRequest<>(type);
097    }
098
099    /// Create a widened resource request by replacing the requested
100    /// top-level type with the given super type, thus widening the
101    /// request.
102    ///
103    /// @param <R> the generic type
104    /// @param type the desired super type. This should actually be
105    /// declared as `Class <R>`, but there is no way to specify a 
106    /// parameterized type as actual parameter.
107    /// @return the new resource request
108    ///
109    public <R extends Resources<T>> ResourceRequest<T> widened(
110            @SuppressWarnings("rawtypes") Class<? extends Resources> type) {
111        return new ResourceRequest<>(type().widened(type));
112    }
113
114    /// Return the requested type.
115    ///
116    /// @return the resource type
117    ///
118    public ResourceType<? extends Resources<T>> type() {
119        return type;
120    }
121
122    /// Checks if this request accepts a resource of the given type.
123    /// Short for `type().isAssignableFrom(other)`.
124    ///
125    /// @param other the other
126    /// @return true, if successful
127    ///
128    public boolean accepts(ResourceType<?> other) {
129        return type().isAssignableFrom(other);
130    }
131
132    /// Checks if the requested type is a container type and if the
133    /// contained type of the container type is assignable from the
134    /// given type. 
135    ///
136    /// @param type the type to check
137    /// @return true, if successful
138    ///
139    public boolean collects(ResourceType<?> type) {
140        return Optional.ofNullable(type().containedType())
141            .map(ct -> ct.isAssignableFrom(type)).orElse(false);
142    }
143
144    @Override
145    public int hashCode() {
146        return Objects.hash(type);
147    }
148
149    @Override
150    public boolean equals(Object obj) {
151        if (this == obj) {
152            return true;
153        }
154        if (obj == null) {
155            return false;
156        }
157        if (getClass() != obj.getClass()) {
158            return false;
159        }
160        ResourceRequest<?> other = (ResourceRequest<?>) obj;
161        return Objects.equals(type, other.type);
162    }
163
164    @Override
165    public String toString() {
166        return "ResourceRequest [type=" + type + "]";
167    }
168
169}