Skip to content

Commit 0a9cd49

Browse files
committed
SonarQube fixes
1 parent e729bfe commit 0a9cd49

178 files changed

Lines changed: 7361 additions & 281 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanConfig.java‎

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -638,7 +638,9 @@
638638
* Disable sorted bean properties.
639639
*
640640
* <p>
641-
* When <jk>true</jk>, bean properties are serialized and accessed in natural JVM order instead of the default alphabetical order.
641+
* When <jk>true</jk>, bean properties are serialized and accessed in declaration order instead of the default
642+
* alphabetical order: record component order for records, field declaration order (superclass first) for other
643+
* classes, then method-only properties alphabetically.
642644
*
643645
* <ul class='values'>
644646
* <li><js>"true"</js>
@@ -654,6 +656,36 @@
654656
*/
655657
String unsortedProperties() default "";
656658

659+
/**
660+
* Record component order.
661+
*
662+
* <p>
663+
* When <jk>true</jk>, the properties of {@link Record} beans use record component (declaration) order even when
664+
* other beans are sorted alphabetically. Non-record beans are unaffected.
665+
*
666+
* <h5 class='section'>Example:</h5>
667+
* <p class='bjava'>
668+
* <ja>@BeanConfig</ja>(recordComponentOrder=<js>"true"</js>)
669+
* <jk>public class</jk> MyRestResource { ... }
670+
*
671+
* <jk>public record</jk> Person(String <jv>name</jv>, <jk>int</jk> <jv>age</jv>) {}
672+
* <jc>// Serialized as {"name":"Alice","age":30} instead of {"age":30,"name":"Alice"}</jc>
673+
* </p>
674+
*
675+
* <ul class='values'>
676+
* <li><js>"true"</js>
677+
* <li><js>"false"</js> (default: record properties are sorted alphabetically like other beans)
678+
* </ul>
679+
*
680+
* <h5 class='section'>Notes:</h5><ul>
681+
* <li>
682+
* Supports <a class="doclink" href="https://juneau.apache.org/docs/topics/DefaultVarResolver">VarResolver.DEFAULT</a> (e.g. <js>"$C{myConfigVar}"</js>).
683+
* </ul>
684+
*
685+
* @return The annotation value.
686+
*/
687+
String recordComponentOrder() default "";
688+
657689
/**
658690
* Use Java Introspector.
659691
*

‎juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanConfigContext.java‎

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ public static Builder create() {
113113
private final boolean ignoreUnknownBeanProperties;
114114
private final boolean ignoreUnknownNullBeanProperties;
115115
private final boolean unsortedProperties;
116+
private final boolean recordComponentOrder;
116117
private final boolean useInterfaceProxies;
117118
private final boolean useJavaBeanIntrospector;
118119

@@ -147,6 +148,7 @@ private BeanConfigContext(Builder b) {
147148
ignoreUnknownBeanProperties = b.ignoreUnknownBeanProperties;
148149
ignoreUnknownNullBeanProperties = b.ignoreUnknownNullBeanProperties;
149150
unsortedProperties = b.unsortedProperties;
151+
recordComponentOrder = b.recordComponentOrder;
150152
useInterfaceProxies = b.useInterfaceProxies;
151153
useJavaBeanIntrospector = b.useJavaBeanIntrospector;
152154
propertyNamer = b.propertyNamer;
@@ -301,12 +303,27 @@ public Builder copy() {
301303
public boolean isIgnoreUnknownNullBeanProperties() { return ignoreUnknownNullBeanProperties; }
302304

303305
/**
304-
* Returns whether properties should preserve their JVM-discovered (non-alphabetical) order.
306+
* Returns whether properties use declaration order instead of alphabetical order.
307+
*
308+
* <p>
309+
* Declaration order means record component order for records, field declaration order (superclass first) for
310+
* other classes, followed by method-only properties in alphabetical order.
305311
*
306312
* @return <jk>true</jk> if properties remain unsorted.
307313
*/
308314
public boolean isUnsortedProperties() { return unsortedProperties; }
309315

316+
/**
317+
* Returns whether record properties use record component (declaration) order.
318+
*
319+
* <p>
320+
* When <jk>true</jk>, the properties of a {@link Record} are ordered by {@link Class#getRecordComponents()} even in
321+
* the default sorted mode. Non-record beans are unaffected.
322+
*
323+
* @return <jk>true</jk> if records use component order.
324+
*/
325+
public boolean isRecordComponentOrder() { return recordComponentOrder; }
326+
310327
/**
311328
* Returns whether interface proxies should be created for bean interfaces.
312329
*
@@ -453,6 +470,7 @@ public static final class Builder {
453470
private boolean ignoreUnknownBeanProperties;
454471
private boolean ignoreUnknownNullBeanProperties = true;
455472
private boolean unsortedProperties;
473+
private boolean recordComponentOrder;
456474
private boolean useInterfaceProxies = true;
457475
private boolean useJavaBeanIntrospector;
458476

@@ -489,6 +507,7 @@ private Builder(BeanConfigContext src) {
489507
ignoreUnknownBeanProperties = src.ignoreUnknownBeanProperties;
490508
ignoreUnknownNullBeanProperties = src.ignoreUnknownNullBeanProperties;
491509
unsortedProperties = src.unsortedProperties;
510+
recordComponentOrder = src.recordComponentOrder;
492511
useInterfaceProxies = src.useInterfaceProxies;
493512
useJavaBeanIntrospector = src.useJavaBeanIntrospector;
494513
propertyNamer = src.propertyNamer;
@@ -641,13 +660,21 @@ public BeanConfigContext build() {
641660
public Builder ignoreUnknownNullBeanProperties(boolean value) { ignoreUnknownNullBeanProperties = value; return this; }
642661

643662
/**
644-
* Toggles whether properties remain in JVM-discovered (non-alphabetical) order.
663+
* Toggles declaration order (instead of alphabetical order) for bean properties.
645664
*
646665
* @param value The new value.
647666
* @return This object.
648667
*/
649668
public Builder unsortedProperties(boolean value) { unsortedProperties = value; return this; }
650669

670+
/**
671+
* Toggles record component (declaration) ordering for {@link Record} beans.
672+
*
673+
* @param value The new value.
674+
* @return This object.
675+
*/
676+
public Builder recordComponentOrder(boolean value) { recordComponentOrder = value; return this; }
677+
651678
/**
652679
* Toggles automatic creation of interface proxies for bean interfaces.
653680
*

‎juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanMap.java‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ public static <T> BeanMap<T> of(T bean, BeanMeta<T> meta) {
112112
/** Temporary holding cache for beans with read-only properties. Normally null. */
113113
protected Map<String,Object> propertyCache;
114114

115+
private Set<String> presentProperties; // Names of required properties seen so far. null when the bean has no required properties.
116+
115117
/** Temporary holding cache for bean properties of array types when the add() method is being used. */
116118
protected Map<String,List<?>> arrayPropertyCache;
117119

@@ -138,6 +140,43 @@ public BeanMap(T bean, BeanMeta<T> meta) {
138140
if (ine(meta.getConstructorArgs()))
139141
propertyCache = new TreeMap<>();
140142
this.typePropertyName = meta.getTypePropertyName();
143+
if (meta.hasRequiredProperties())
144+
presentProperties = new HashSet<>();
145+
}
146+
147+
/**
148+
* Records that a property was written from input. Only tracked when the bean has required properties.
149+
*
150+
* @param name The property name.
151+
*/
152+
void recordPresent(String name) {
153+
if (nn(presentProperties))
154+
presentProperties.add(name);
155+
}
156+
157+
/**
158+
* Returns the {@code @BeanProp(required=true)} properties that have not been written to this map.
159+
*
160+
* <p>
161+
* A property counts as written once {@link #put(String, Object)} (or a collection {@code add}) has been called for
162+
* it, even with a <jk>null</jk> value. Parsers call this once per bean, before the bean is constructed.
163+
*
164+
* <h5 class='section'>Example:</h5>
165+
* <p class='bjava'>
166+
* <jc>// MyBean has @BeanProp(required=true) on 'name' and 'zip'.</jc>
167+
* BeanMap&lt;MyBean&gt; <jv>m</jv> = <jv>session</jv>.newBeanMap(MyBean.<jk>class</jk>);
168+
* <jv>m</jv>.put(<js>"name"</js>, <jk>null</jk>);
169+
*
170+
* <jc>// [zip]</jc>
171+
* List&lt;String&gt; <jv>missing</jv> = <jv>m</jv>.getMissingRequiredProperties();
172+
* </p>
173+
*
174+
* @return The missing required property names, in property order. Empty (never <jk>null</jk>) if none.
175+
*/
176+
public List<String> getMissingRequiredProperties() {
177+
if (presentProperties == null)
178+
return List.of();
179+
return meta.getRequiredPropertyNames().stream().filter(x -> ! presentProperties.contains(x)).toList();
141180
}
142181

143182
/**

‎juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanMeta.java‎

Lines changed: 106 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,20 +54,31 @@
5454
*
5555
* <h5 class='topic'>Bean property ordering</h5>
5656
*
57-
* The order of the properties are as follows:
57+
* The order of the properties is as follows:
5858
* <ul class='spaced-list'>
5959
* <li>
6060
* If {@link BeanType#properties() @BeanType(properties)} is specified on the class, the order matches the
6161
* list of properties in the annotation.
6262
* <li>
63-
* Otherwise, the order is based on the following:
63+
* Otherwise, by default, properties are sorted alphabetically.
64+
* <li>
65+
* When unsorted properties are enabled ({@link BeanConfigContext#isUnsortedProperties()},
66+
* {@link BeanType#unsorted() @BeanType(unsorted=true)}, or a bean filter), properties use declaration order:
6467
* <ul>
65-
* <li>Public fields (same order as {@code Class.getFields()}).
66-
* <li>Properties returned by {@code BeanInfo.getPropertyDescriptors()}.
67-
* <li>Non-standard getters/setters with {@link BeanProp @BeanProp} annotation defined on them.
68+
* <li>Records: record component order.
69+
* <li>Other classes: field declaration order, superclass fields first. Getter/setter properties are ranked
70+
* by their same-named field (even a private one), which is used as an ordering hint only.
71+
* <li>Method-only properties (no backing field) follow, alphabetically.
6872
* </ul>
73+
* <li>
74+
* {@link BeanConfigContext#isRecordComponentOrder()} gives records component order even when other beans are
75+
* sorted.
6976
* </ul>
7077
*
78+
* <p>
79+
* Field order comes from {@link Class#getDeclaredFields()}, which is declaration order on HotSpot and OpenJ9 but is
80+
* not guaranteed by the JLS.
81+
*
7182
* <h5 class='topic'>Thread safety</h5>
7283
*
7384
* Instances are effectively immutable after construction and are safe for concurrent read access.
@@ -368,7 +379,8 @@ private static <T> BeanMetaValue<T> notABean(String reason) {
368379
final String notABeanReason; // Readable string explaining why this class wasn't a bean.
369380
private final Map<String,BeanPropertyMeta> properties; // The properties on the target class.
370381
private final Map<Method,String> setterProps; // The setter properties on the target class.
371-
private final boolean unsortedProperties; // Whether properties should use natural JVM-dependent order.
382+
private final List<String> requiredPropertyNames; // Names of @BeanProp(required=true) properties, in property order.
383+
private final boolean unsortedProperties; // Whether properties use declaration order instead of alphabetical order.
372384
private final PropertyNamer propertyNamerOverride; // Optional per-instance namer override. Null means resolve the normal way (bean filter, then config). Non-null takes precedence over both.
373385
private final ClassInfo stopClass; // The stop class for hierarchy traversal.
374386
private final BeanPropertyMeta typeProperty; // "_type" mock bean property.
@@ -616,11 +628,23 @@ private BeanMeta(BeanInfo<T> cm, ClassInfo ci0, BeanConfigContext config, Object
616628
m.setAsConstructorArg();
617629
}
618630

631+
// @BeanProp(required=true) must be satisfiable from input (WORK-J0585).
632+
for (var p : normalProps.values())
633+
if (p.isRequired() && ! p.canSatisfyRequired())
634+
throw brex(c, "Property '%s' on class '%s' is marked @BeanProp(required=true) but cannot be written (read-only, dynamic, or no field, setter or constructor argument).", p.name, ci.getNameSimple());
635+
619636
// Make sure at least one property was found (records with no components are exempt).
620637
if (bf == null && config.isBeansRequireSomeProperties() && normalProps.isEmpty() && ! ci.isRecord())
621638
notABeanReasonTemp = "No properties detected on bean class";
622639

623-
unsortedPropertiesTemp = config.isUnsortedProperties() || bfo.map(x -> x.isUnsortedProperties()).orElse(false) || !fixedBeanProps.isEmpty();
640+
// Records opt into component order via recordComponentOrder(); that is unsorted mode scoped to records.
641+
var recordComponentOrder = ci.isRecord() && config.isRecordComponentOrder();
642+
unsortedPropertiesTemp = recordComponentOrder || config.isUnsortedProperties() || bfo.map(x -> x.isUnsortedProperties()).orElse(false) || !fixedBeanProps.isEmpty();
643+
644+
// Declaration-order seeding (WORK-J0585). Skipped when an explicit @BeanType(properties) list exists
645+
// (that list keeps absolute precedence) and on the java.beans.Introspector path.
646+
if (unsortedPropertiesTemp && fixedBeanProps.isEmpty() && ! config.isUseJavaBeanIntrospector())
647+
orderByDeclaration(normalProps);
624648

625649
propertiesValue.set(unsortedPropertiesTemp ? map() : sortedMap());
626650

@@ -682,6 +706,7 @@ private BeanMeta(BeanInfo<T> cm, ClassInfo ci0, BeanConfigContext config, Object
682706
setterProps = u(setterPropsMap);
683707
dynaProperty = dynaPropertyValue.get();
684708
unsortedProperties = unsortedPropertiesTemp;
709+
requiredPropertyNames = properties == null ? List.<String>of() : properties.values().stream().filter(BeanPropertyMeta::isRequired).map(BeanPropertyMeta::getName).toList();
685710
typeProperty = BeanPropertyMeta.builder(this, typePropertyName).canRead().canWrite().rawMetaType(String.class).build();
686711
// Map the synthetic "_type" property to the bean-level BeanRegistry so consumers calling
687712
// typeProperty.getBeanRegistry() (currently none in-tree, but a public API path) get the same
@@ -726,6 +751,54 @@ private void validateAndRegisterProperty(BeanPropertyMeta.Builder p, Class<?> c,
726751
}
727752
}
728753

754+
/**
755+
* Re-keys the discovered properties into declaration order.
756+
*
757+
* <ul class='spaced-list'>
758+
* <li>Records: record component order ({@link ClassInfo#getRecordComponents()}, defined by the JLS). A component
759+
* is matched to its property through the property's field or accessor name, so renamed components keep
760+
* their position.
761+
* <li>Other classes: the position of the property's backing field (the visible field, or the same-named private
762+
* field recorded as {@code innerField}) in a superclass-first walk of
763+
* {@link ClassInfo#getDeclaredFieldsInDeclarationOrder()}.
764+
* <li>Properties with no backing field or component (method-only) go after all ranked properties, alphabetically.
765+
* </ul>
766+
*
767+
* <p>
768+
* Visibility is unchanged: private fields only <i>rank</i> properties, they never become properties.
769+
*
770+
* @param props The discovered properties. Re-ordered in place.
771+
*/
772+
private void orderByDeclaration(Map<String,BeanPropertyMeta.Builder> props) {
773+
var rank = new HashMap<String,Integer>();
774+
if (classInfo.isRecord()) {
775+
var components = classInfo.getRecordComponents();
776+
for (var i = 0; i < components.size(); i++) {
777+
var rcName = components.get(i).getName();
778+
for (var e : props.entrySet()) {
779+
var p = e.getValue();
780+
if ((nn(p.field) && p.field.hasName(rcName)) || (nn(p.getter) && p.getter.hasName(rcName)))
781+
rank.putIfAbsent(e.getKey(), i);
782+
}
783+
}
784+
} else {
785+
var fieldRank = new HashMap<Field,Integer>();
786+
for (var c2 : classHierarchy.get())
787+
for (var f : c2.getDeclaredFieldsInDeclarationOrder())
788+
fieldRank.putIfAbsent(f.inner(), fieldRank.size());
789+
props.forEach((k, p) -> {
790+
var f = nn(p.field) ? p.field : p.innerField;
791+
if (nn(f) && fieldRank.containsKey(f.inner()))
792+
rank.put(k, fieldRank.get(f.inner()));
793+
});
794+
}
795+
var keys = new ArrayList<>(props.keySet());
796+
keys.sort(Comparator.<String>comparingInt(k -> rank.getOrDefault(k, Integer.MAX_VALUE)).thenComparing(Comparator.naturalOrder()));
797+
var copy = new LinkedHashMap<>(props);
798+
props.clear();
799+
keys.forEach(k -> props.put(k, copy.get(k)));
800+
}
801+
729802
@Override /* Overridden from Object */
730803
public boolean equals(Object o) {
731804
return (o instanceof BeanMeta<?> o2) && eq(this, o2, (x, y) -> eq(x.classInfo, y.classInfo));
@@ -1085,10 +1158,35 @@ public boolean hasConstructor() {
10851158
return beanConstructor.constructor().isPresent();
10861159
}
10871160

1161+
/**
1162+
* Returns whether any property of this bean is marked {@code @BeanProp(required=true)}.
1163+
*
1164+
* @return <jk>true</jk> if at least one property is required.
1165+
*/
1166+
public boolean hasRequiredProperties() { return ! requiredPropertyNames.isEmpty(); }
1167+
1168+
/**
1169+
* Returns the names of the properties marked {@code @BeanProp(required=true)}.
1170+
*
1171+
* <p>
1172+
* Hidden and excluded properties are not included, since input can never set them.
1173+
*
1174+
* <h5 class='section'>Example:</h5>
1175+
* <p class='bjava'>
1176+
* <jk>public record</jk> Person(<ja>@BeanProp</ja>(required=<jk>true</jk>) String <jv>name</jv>, Integer <jv>age</jv>) {}
1177+
*
1178+
* <jc>// [name]</jc>
1179+
* List&lt;String&gt; <jv>required</jv> = BeanMeta.<jsm>of</jsm>(Person.<jk>class</jk>, BeanConfigContext.<jsf>DEFAULT</jsf>).getRequiredPropertyNames();
1180+
* </p>
1181+
*
1182+
* @return An unmodifiable list of property names, in property order. Never <jk>null</jk>.
1183+
*/
1184+
public List<String> getRequiredPropertyNames() { return requiredPropertyNames; }
1185+
10881186
/**
10891187
* Returns whether this bean has opted out of alphabetical property sorting.
10901188
*
1091-
* @return <jk>true</jk> if properties should use natural JVM-dependent order.
1189+
* @return <jk>true</jk> if properties use declaration order (see the class Javadoc).
10921190
*/
10931191
protected boolean isUnsortedProperties() { return unsortedProperties; }
10941192

0 commit comments

Comments
 (0)