2424
2525import org .apache .commons .logging .Log ;
2626import org .apache .commons .logging .LogFactory ;
27- import org .jspecify .annotations .NonNull ;
2827import org .jspecify .annotations .Nullable ;
2928
3029import org .springframework .data .mapping .Parameter ;
3130import org .springframework .data .mapping .PreferredConstructor ;
3231import org .springframework .data .mapping .model .PreferredConstructorDiscoverer ;
3332import org .springframework .data .projection .ProjectionFactory ;
3433import org .springframework .data .projection .ProjectionInformation ;
34+ import org .springframework .data .util .Lazy ;
3535import org .springframework .lang .Contract ;
3636import org .springframework .util .Assert ;
3737import org .springframework .util .ClassUtils ;
@@ -83,7 +83,7 @@ public static ReturnedType of(Class<?> returnedType, Class<?> domainType, Projec
8383 }
8484
8585 /**
86- * Returns the entity type.
86+ * Return the entity type.
8787 *
8888 * @return
8989 */
@@ -92,7 +92,7 @@ public final Class<?> getDomainType() {
9292 }
9393
9494 /**
95- * Returns whether the given source object is an instance of the returned type.
95+ * Return whether the given source object is an instance of the returned type.
9696 *
9797 * @param source can be {@literal null}.
9898 * @return
@@ -103,63 +103,77 @@ public final boolean isInstance(@Nullable Object source) {
103103 }
104104
105105 /**
106- * Returns whether the type is projecting, i.e. not of the domain type .
106+ * Return the type of the individual objects to return .
107107 *
108108 * @return
109109 */
110- public abstract boolean isProjecting ();
110+ public abstract Class <?> getReturnedType ();
111111
112112 /**
113- * Returns the type of the individual objects to return .
113+ * Return whether the type is projecting, i.e. not of the domain type .
114114 *
115115 * @return
116116 */
117- public abstract Class <?> getReturnedType ();
117+ public abstract boolean isProjecting ();
118118
119119 /**
120- * Returns whether the returned type will require custom construction .
120+ * Return whether the type is an interface-projection .
121121 *
122- * @return
122+ * @since 4.0.1
123123 */
124- public abstract boolean needsCustomConstruction ();
124+ public boolean isInterfaceProjection () {
125+ return isProjecting () && getReturnedType ().isInterface ();
126+ }
125127
126128 /**
127- * Returns the type that the query execution is supposed to pass to the underlying infrastructure. {@literal null} is
128- * returned to indicate a generic type (a map or tuple-like type) shall be used.
129+ * Return whether the type is a DTO projection.
129130 *
130- * @return
131+ * @since 4.0.1
131132 */
132- public abstract @ Nullable Class <?> getTypeToRead ();
133+ public boolean isDtoProjection () {
134+ return isProjecting () && !getReturnedType ().isInterface ();
135+ }
133136
134137 /**
135- * Returns the properties required to be used to populate the result.
138+ * Return the properties required to be used to populate the result.
136139 *
137- * @return
138140 * @see ProjectionInformation#getInputProperties()
139141 */
140142 public abstract List <String > getInputProperties ();
141143
142144 /**
143- * Returns whether the returned type has input properties.
145+ * Return whether the returned type has input properties.
144146 *
145- * @return
146147 * @since 3.3.5
147148 * @see ProjectionInformation#hasInputProperties()
148149 */
149150 public boolean hasInputProperties () {
150151 return !CollectionUtils .isEmpty (getInputProperties ());
151152 }
152153
154+ /**
155+ * Return whether the returned type will require custom construction.
156+ */
157+ public abstract boolean needsCustomConstruction ();
158+
159+ /**
160+ * Return the type that the query execution is supposed to pass to the underlying infrastructure. {@literal null} is
161+ * returned to indicate a generic type (a map or tuple-like type) shall be used.
162+ */
163+ public abstract @ Nullable Class <?> getTypeToRead ();
164+
153165 /**
154166 * A {@link ReturnedType} that's backed by an interface.
155167 *
156168 * @author Oliver Gierke
169+ * @author Mark Paluch
157170 * @since 1.12
158171 */
159172 private static final class ReturnedInterface extends ReturnedType {
160173
161174 private final ProjectionInformation information ;
162175 private final Class <?> domainType ;
176+ private final boolean isProjecting ;
163177 private final List <String > inputProperties ;
164178
165179 /**
@@ -176,6 +190,7 @@ public ReturnedInterface(ProjectionInformation information, Class<?> domainType)
176190
177191 this .information = information ;
178192 this .domainType = domainType ;
193+ this .isProjecting = !information .getType ().isAssignableFrom (domainType );
179194 this .inputProperties = detectInputProperties (information );
180195 }
181196
@@ -198,31 +213,43 @@ public Class<?> getReturnedType() {
198213 }
199214
200215 @ Override
201- public boolean needsCustomConstruction () {
202- return isProjecting () && information . isClosed () ;
216+ public boolean isProjecting () {
217+ return isProjecting ;
203218 }
204219
205220 @ Override
206- public boolean isProjecting () {
207- return ! information . getType (). isAssignableFrom ( domainType );
221+ public boolean isInterfaceProjection () {
222+ return isProjecting ( );
208223 }
209224
210225 @ Override
211- public @ Nullable Class <?> getTypeToRead () {
212- return isProjecting () && information . isClosed () ? null : domainType ;
226+ public boolean isDtoProjection () {
227+ return false ;
213228 }
214229
215230 @ Override
216231 public List <String > getInputProperties () {
217232 return inputProperties ;
218233 }
234+
235+ @ Override
236+ public boolean needsCustomConstruction () {
237+ return isProjecting () && information .isClosed ();
238+ }
239+
240+ @ Override
241+ public @ Nullable Class <?> getTypeToRead () {
242+ return isProjecting () && information .isClosed () ? null : domainType ;
243+ }
244+
219245 }
220246
221247 /**
222248 * A {@link ReturnedType} that's backed by an actual class.
223249 *
224250 * @author Oliver Gierke
225251 * @author Mikhail Polivakha
252+ * @author Mark Paluch
226253 * @since 1.12
227254 */
228255 private static final class ReturnedClass extends ReturnedType {
@@ -231,7 +258,8 @@ private static final class ReturnedClass extends ReturnedType {
231258
232259 private final Class <?> type ;
233260 private final boolean isDto ;
234- private final List <String > inputProperties ;
261+ private final @ Nullable PreferredConstructor <?, ?> constructor ;
262+ private final Lazy <List <String >> inputProperties ;
235263
236264 /**
237265 * Creates a new {@link ReturnedClass} instance for the given returned type and domain type.
@@ -256,7 +284,13 @@ public ReturnedClass(Class<?> returnedType, Class<?> domainType) {
256284 !VOID_TYPES .contains (type ) && //
257285 !type .getPackage ().getName ().startsWith ("java." );
258286
259- this .inputProperties = detectConstructorParameterNames (returnedType );
287+ this .constructor = detectConstructor (type );
288+
289+ if (this .constructor == null ) {
290+ this .inputProperties = Lazy .of (Collections .emptyList ());
291+ } else {
292+ this .inputProperties = Lazy .of (this ::detectConstructorParameterNames );
293+ }
260294 }
261295
262296 @ Override
@@ -265,33 +299,53 @@ public Class<?> getReturnedType() {
265299 }
266300
267301 @ Override
268- @ NonNull
269- public Class <?> getTypeToRead () {
270- return type ;
302+ public boolean isProjecting () {
303+ return isDto ;
271304 }
272305
273306 @ Override
274- public boolean isProjecting () {
275- return isDto () ;
307+ public boolean isInterfaceProjection () {
308+ return false ;
276309 }
277310
278311 @ Override
279- public boolean needsCustomConstruction () {
280- return isDto () && ! inputProperties . isEmpty ();
312+ public boolean isDtoProjection () {
313+ return isProjecting ();
281314 }
282315
283316 @ Override
284317 public List <String > getInputProperties () {
285- return inputProperties ;
318+ return inputProperties . get () ;
286319 }
287320
288- private List <String > detectConstructorParameterNames (Class <?> type ) {
321+ @ Override
322+ public boolean hasInputProperties () {
323+ return this .constructor != null && this .constructor .getParameterCount () > 0 && super .hasInputProperties ();
324+ }
289325
290- if (!isDto ()) {
291- return Collections .emptyList ();
292- }
326+ @ Override
327+ public boolean needsCustomConstruction () {
328+ return isDtoProjection () && hasInputProperties ();
329+ }
293330
294- PreferredConstructor <?, ?> constructor = PreferredConstructorDiscoverer .discover (type );
331+ @ Override
332+ public Class <?> getTypeToRead () {
333+ return type ;
334+ }
335+
336+ private boolean isDomainSubtype () {
337+ return getDomainType ().equals (type ) && getDomainType ().isAssignableFrom (type );
338+ }
339+
340+ private boolean isPrimitiveOrWrapper () {
341+ return ClassUtils .isPrimitiveOrWrapper (type );
342+ }
343+
344+ private @ Nullable PreferredConstructor <?, ?> detectConstructor (Class <?> type ) {
345+ return isDtoProjection () ? PreferredConstructorDiscoverer .discover (type ) : null ;
346+ }
347+
348+ private List <String > detectConstructorParameterNames () {
295349
296350 if (constructor == null ) {
297351 return Collections .emptyList ();
@@ -310,24 +364,13 @@ private List<String> detectConstructorParameterNames(Class<?> type) {
310364 if (logger .isWarnEnabled ()) {
311365 logger .warn (("No constructor parameter names discovered. "
312366 + "Compile the affected code with '-parameters' instead or avoid its introspection: %s" )
313- .formatted (type .getName ()));
367+ .formatted (constructor . getConstructor (). getDeclaringClass () .getName ()));
314368 }
315369 }
316370
317371 return Collections .unmodifiableList (properties );
318372 }
319373
320- private boolean isDto () {
321- return isDto ;
322- }
323-
324- private boolean isDomainSubtype () {
325- return getDomainType ().equals (type ) && getDomainType ().isAssignableFrom (type );
326- }
327-
328- private boolean isPrimitiveOrWrapper () {
329- return ClassUtils .isPrimitiveOrWrapper (type );
330- }
331374 }
332375
333376 private static final class CacheKey {
@@ -396,5 +439,7 @@ public String toString() {
396439 return "ReturnedType.CacheKey(returnedType=" + this .getReturnedType () + ", domainType=" + this .getDomainType ()
397440 + ", projectionFactoryHashCode=" + this .getProjectionFactoryHashCode () + ")" ;
398441 }
442+
399443 }
444+
400445}
0 commit comments