This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/master by this push:
new 2a40bd1ab7 Add view-based projection (@JsonView analog) to bean
marshalling; adopt isEmpty utility across the reactor.
2a40bd1ab7 is described below
commit 2a40bd1ab7fcfa9faecfa7a29b21c3b31ab90ddd
Author: James Bognar <[email protected]>
AuthorDate: Tue Jun 16 13:11:09 2026 -0400
Add view-based projection (@JsonView analog) to bean marshalling; adopt
isEmpty utility across the reactor.
---
.../bean/jsonschema/JsonSchemaValidator.java | 4 +-
.../org/apache/juneau/commons/bean/BeanMap.java | 32 +-
.../juneau/commons/bean/BeanPropertyMeta.java | 18 ++
.../apache/juneau/commons/bean/BeanSession.java | 20 ++
.../org/apache/juneau/commons/lang/CharHolder.java | 2 +-
.../org/apache/juneau/commons/runtime/Args.java | 4 +-
.../juneau/marshall/jena/RdfSerializerSession.java | 2 +-
.../marshall/jena/RdfStreamSerializerSession.java | 2 +-
.../org/apache/juneau/marshall/MarshalledProp.java | 45 +++
.../juneau/marshall/MarshalledPropAnnotation.java | 19 ++
.../marshall/MarshalledPropertyPostProcessor.java | 34 +-
.../apache/juneau/marshall/MarshallingContext.java | 149 +++++++--
.../juneau/marshall/MarshallingContextable.java | 51 +++
.../apache/juneau/marshall/MarshallingSession.java | 66 +++-
.../juneau/marshall/csv/CsvParserSession.java | 4 +-
.../juneau/marshall/ini/IniSerializerSession.java | 2 +-
.../org/apache/juneau/marshall/ini/IniWriter.java | 4 +-
.../juneau/marshall/json/JsonTokenWriter.java | 3 +-
.../markdown/MarkdownDocParserSession.java | 4 +-
.../marshall/markdown/MarkdownParserSession.java | 2 +-
.../marshall/parquet/ParquetParserSession.java | 2 +-
.../marshall/parquet/ParquetSchemaBuilder.java | 5 +-
.../juneau/marshall/xml/XmlBeanPropertyMeta.java | 8 +-
.../org/apache/juneau/ViewProjection_Test.java | 353 +++++++++++++++++++++
.../org/apache/juneau/ViewProjection_Test.java | 294 +++++++++++++++++
.../microservice/jetty/JettyServerComponent.java | 2 +-
.../microservice/tomcat/TomcatServerComponent.java | 4 +-
.../apache/juneau/microservice/Microservice.java | 2 +-
.../juneau/http/resource/HttpResourceBean.java | 2 +-
.../juneau/rest/mock/MockServletRequest.java | 2 +-
.../micrometer/MicrometerMetricsRecorder.java | 4 +-
.../apache/juneau/rest/server/RestOpContext.java | 8 +-
.../org/apache/juneau/rest/server/RestRequest.java | 6 +-
.../apache/juneau/rest/server/RestResponse.java | 2 +-
.../rest/server/convention/VersionProvider.java | 4 +-
.../rest/server/httppart/RequestFormParamList.java | 2 +-
.../swagger/BasicSwaggerProviderSession.java | 4 +-
37 files changed, 1102 insertions(+), 69 deletions(-)
diff --git
a/juneau-bean/juneau-bean-jsonschema/src/main/java/org/apache/juneau/bean/jsonschema/JsonSchemaValidator.java
b/juneau-bean/juneau-bean-jsonschema/src/main/java/org/apache/juneau/bean/jsonschema/JsonSchemaValidator.java
index 513ea65389..1031f6a161 100644
---
a/juneau-bean/juneau-bean-jsonschema/src/main/java/org/apache/juneau/bean/jsonschema/JsonSchemaValidator.java
+++
b/juneau-bean/juneau-bean-jsonschema/src/main/java/org/apache/juneau/bean/jsonschema/JsonSchemaValidator.java
@@ -192,7 +192,7 @@ public final class JsonSchemaValidator implements
PropertyValidator {
private static void validateEnum(JsonSchema s, Object value) {
var enums = s.getEnum();
- if (enums == null || enums.isEmpty())
+ if (e(enums))
return;
for (var e : enums) {
if (jsonEquals(e, value))
@@ -217,7 +217,7 @@ public final class JsonSchemaValidator implements
PropertyValidator {
return;
}
var arr = s.getTypeAsJsonTypeArray();
- if (arr == null || arr.isEmpty())
+ if (e(arr))
return;
for (var t : arr) {
if (matchesType(t, value))
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanMap.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanMap.java
index 897c12a980..e98d286431 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanMap.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanMap.java
@@ -266,15 +266,32 @@ public class BeanMap<T> extends
AbstractMap<String,Object> implements Delegate<T
/**
* Performs an action on each property in this bean map.
*
+ * <p>
+ * If a {@link BeanSession} is attached and has an active view, only
properties that are visible under
+ * that view (as determined by {@link
BeanSession#isPropertyInActiveView(BeanPropertyMeta)}) are visited.
+ *
* @param filter The filter to apply to properties.
* @param action The action.
* @return This object.
*/
public BeanMap<T> forEachProperty(Predicate<BeanPropertyMeta> filter,
Consumer<BeanPropertyMeta> action) {
-
meta.getProperties().values().stream().filter(filter).forEach(action);
+
meta.getProperties().values().stream().filter(filter).filter(this::isInActiveView).forEach(action);
return this;
}
+ /**
+ * Returns <jk>true</jk> if the property is visible under the active
view on this bean map's session.
+ *
+ * <p>
+ * When no session is attached, or no active view is set, all
properties are visible.
+ *
+ * @param pMeta The property to test.
+ * @return <jk>true</jk> if the property is in-view.
+ */
+ protected boolean isInActiveView(BeanPropertyMeta pMeta) {
+ return session == null || session.isPropertyInActiveView(pMeta);
+ }
+
/**
* Invokes all the getters on this bean and consumes the results.
*
@@ -599,11 +616,20 @@ public class BeanMap<T> extends
AbstractMap<String,Object> implements Delegate<T
/**
* Returns the metadata on the specified property.
*
+ * <p>
+ * Returns <jk>null</jk> if the property does not exist on the bean
<em>or</em> if the property exists but is
+ * excluded by the current active view. The latter allows the caller
(e.g. {@link #put(String, Object)}) to
+ * route out-of-view input through the existing
unknown/ignored-property path governed by
+ * {@link
org.apache.juneau.commons.bean.BeanConfigContext#isIgnoreUnknownBeanProperties()}.
+ *
* @param propertyName The name of the bean property.
- * @return Metadata on the specified property, or <jk>null</jk> if that
property does not exist.
+ * @return Metadata on the specified property, or <jk>null</jk> if that
property does not exist or is out of view.
*/
public BeanPropertyMeta getPropertyMeta(String propertyName) {
- return meta.getPropertyMeta(propertyName);
+ var p = meta.getPropertyMeta(propertyName);
+ if (p != null && ! isInActiveView(p))
+ return null;
+ return p;
}
/**
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanPropertyMeta.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanPropertyMeta.java
index 2757c4bcab..78e37cd0cd 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanPropertyMeta.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanPropertyMeta.java
@@ -95,6 +95,7 @@ public class BeanPropertyMeta implements
Comparable<BeanPropertyMeta> {
public BiFunction<BeanSession,Object,Object> readTransform; //
Package-private; defaults to identity if null. Typed against the commons.bean
SPI seam; marshalling-side installers cast the session argument back to {@code
MarshallingSession} where needed (see {@code
MarshalledPropertyPostProcessor#installSwapAwareTransforms}).
public BiFunction<BeanSession,Object,Object> writeTransform; //
Package-private; defaults to identity if null. Typed against the commons.bean
SPI seam (see readTransform note).
public List<ClassInfo> dictionaryClasses; // Package-private
for BeanMeta access; @MarshalledProp(dictionary={}) classes scanned during
validate().
+ public Set<String> views; // Named views this property belongs
to; null = untagged (in all views when defaultViewInclusion is enabled). Set
via MarshalledPropertyPostProcessor.
private boolean isConstructorArg;
public boolean isUri; // Package-private so
MarshalledPropertyPostProcessor can set @Uri-derived flag. Mirrors
rawTypeMeta.isUri() plus @Uri annotation reads on field/getter/setter.
private boolean isDyna;
@@ -520,6 +521,7 @@ public class BeanPropertyMeta implements
Comparable<BeanPropertyMeta> {
private final BeanInfo<?> typeMeta; // The
transformed class type of the bean property. Concrete instances are always
{@code ClassMeta}; typed against the bean-modeling SPI seam.
private final BiFunction<BeanSession,Object,Object> writeTransform; //
Applied to incoming value before raw setter; identity by default. Typed
against the commons.bean SPI seam.
private final boolean writeOnly; //
True if this property is write-only.
+ private final Set<String> views; //
Named views this property belongs to; null = untagged (included in all views
when defaultViewInclusion is enabled).
/**
* Creates a new BeanPropertyMeta using the contents of the specified
builder.
@@ -548,6 +550,7 @@ public class BeanPropertyMeta implements
Comparable<BeanPropertyMeta> {
setter = b.setter;
typeMeta = b.typeMeta;
writeOnly = b.writeOnly;
+ views = b.views != null ? Collections.unmodifiableSet(b.views)
: null;
readTransform = b.readTransform != null ? b.readTransform :
(session, o) -> o;
writeTransform = b.writeTransform != null ? b.writeTransform :
(session, o) -> o;
@@ -1044,6 +1047,21 @@ public class BeanPropertyMeta implements
Comparable<BeanPropertyMeta> {
*/
public boolean isUri() { return isUri; }
+ /**
+ * Returns the set of named views this property belongs to, or
<jk>null</jk> if the property is untagged.
+ *
+ * <p>
+ * An untagged property (one with no {@code @MarshalledProp(view=...)}
annotation) follows the
+ * default-view-inclusion policy: it is included under every active
view when the policy is enabled
+ * (the default behavior), or excluded from all views when the policy
is disabled.
+ *
+ * <p>
+ * A tagged property is included only when the active view name is
contained in this set.
+ *
+ * @return The set of named views, or <jk>null</jk> if no view
membership was declared.
+ */
+ public Set<String> getViews() { return views; }
+
/**
* Equivalent to calling {@link BeanMap#put(String, Object)}, but is
faster since it avoids looking up the property
* meta.
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanSession.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanSession.java
index 7fde720c7a..e949e4dc65 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanSession.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanSession.java
@@ -131,4 +131,24 @@ public interface BeanSession {
* @return A new bean map (typically a {@code BeanMap}) wrapping the
supplied bean.
*/
<T> Object toBeanMap(T bean);
+
+ /**
+ * Returns <jk>true</jk> if the specified bean property is visible
under the current active view.
+ *
+ * <p>
+ * When no active view is set on the session, this method always
returns <jk>true</jk> (all properties visible).
+ * The marshalling-side {@code MarshallingSession} overrides this to
implement the actual view-membership check
+ * using the property's declared view set and the active view name.
+ *
+ * <p>
+ * Default-view-inclusion policy (configurable): an untagged property —
one whose view set is empty — is
+ * considered in-view under every active view unless the
+ * {@link
org.apache.juneau.marshall.MarshallingContext.Builder#disableDefaultViewInclusion()}
flag is set.
+ *
+ * @param pMeta The property metadata. Must not be <jk>null</jk>.
+ * @return <jk>true</jk> if the property should be included under the
current active view.
+ */
+ default boolean isPropertyInActiveView(BeanPropertyMeta pMeta) {
+ return true; // HTT — default SPI method; MarshallingSession
always overrides, so this body is unreachable in practice.
+ }
}
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/lang/CharHolder.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/lang/CharHolder.java
index 360e6fe8a0..d789b4f730 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/lang/CharHolder.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/lang/CharHolder.java
@@ -275,7 +275,7 @@ public class CharHolder extends Holder<Character> {
* @return <jk>true</jk> if the current value matches any character in
the string.
*/
public boolean isAny(String values) {
- if (values == null || values.isEmpty())
+ if (e(values))
return false;
var current = get();
if (current == null)
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/runtime/Args.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/runtime/Args.java
index c40906415e..60c8061c55 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/runtime/Args.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/runtime/Args.java
@@ -138,7 +138,7 @@ public class Args {
*/
public Optional<String> get(String key) {
var v = options.get(normalize(key));
- if (v == null || v.isEmpty())
+ if (e(v))
return opte();
return opt(v.get(0));
}
@@ -379,7 +379,7 @@ public class Args {
if (allowShortFlags)
p.add("-");
}
- p.removeIf(x -> x == null || x.isEmpty());
+ p.removeIf(x -> e(x));
p.sort((a,b) -> Integer.compare(b.length(),
a.length()));
return p;
}
diff --git
a/juneau-core/juneau-marshall-rdf/src/main/java/org/apache/juneau/marshall/jena/RdfSerializerSession.java
b/juneau-core/juneau-marshall-rdf/src/main/java/org/apache/juneau/marshall/jena/RdfSerializerSession.java
index 224ca67da2..16b50a19a5 100644
---
a/juneau-core/juneau-marshall-rdf/src/main/java/org/apache/juneau/marshall/jena/RdfSerializerSession.java
+++
b/juneau-core/juneau-marshall-rdf/src/main/java/org/apache/juneau/marshall/jena/RdfSerializerSession.java
@@ -189,7 +189,7 @@ public class RdfSerializerSession extends
WriterSerializerSession {
String s = null;
if (nn(uri))
s = uri.toString();
- if ((s == null || s.isEmpty()) && nn(uri2))
+ if (e(s) && nn(uri2))
s = uri2.toString();
if (s == null)
return null;
diff --git
a/juneau-core/juneau-marshall-rdf/src/main/java/org/apache/juneau/marshall/jena/RdfStreamSerializerSession.java
b/juneau-core/juneau-marshall-rdf/src/main/java/org/apache/juneau/marshall/jena/RdfStreamSerializerSession.java
index 4f495de54c..4f57bfd4f7 100644
---
a/juneau-core/juneau-marshall-rdf/src/main/java/org/apache/juneau/marshall/jena/RdfStreamSerializerSession.java
+++
b/juneau-core/juneau-marshall-rdf/src/main/java/org/apache/juneau/marshall/jena/RdfStreamSerializerSession.java
@@ -264,7 +264,7 @@ public class RdfStreamSerializerSession extends
OutputStreamSerializerSession {
String s = null;
if (nn(uri))
s = uri.toString();
- if ((s == null || s.isEmpty()) && nn(uri2))
+ if (e(s) && nn(uri2))
s = uri2.toString();
if (s == null)
return null;
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/MarshalledProp.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/MarshalledProp.java
index 9b255d3909..186975d3e8 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/MarshalledProp.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/MarshalledProp.java
@@ -61,6 +61,51 @@ public @interface MarshalledProp {
*/
String[] description() default {};
+ /**
+ * Declares the named views this property belongs to.
+ *
+ * <p>
+ * When an active view is selected (via
+ * {@link
org.apache.juneau.marshall.MarshallingContext.Builder#activeView(String)} or
the per-call session
+ * override), this property is included only when its declared view set
contains the active view name.
+ *
+ * <p>
+ * If this member is empty (the default), the property follows the
default-view-inclusion policy:
+ * by default an untagged property is included under every active view
(matching Jackson's
+ * {@code DEFAULT_VIEW_INCLUSION} behavior). The policy can be flipped
via
+ * {@link
org.apache.juneau.marshall.MarshallingContext.Builder#disableDefaultViewInclusion()}.
+ *
+ * <p>
+ * Multiple view names are supported — a property tagged {@code
view={"summary","detail"}} is included
+ * when <em>either</em> {@code "summary"} or {@code "detail"} is the
active view (union semantics).
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jk>public class</jk> MyBean {
+ *
+ * <jc>// Included in all views (untagged = default
inclusion)</jc>
+ * <jk>public</jk> String <jf>id</jf>;
+ *
+ * <jc>// Included only in the "summary" and "detail"
views</jc>
+ * <ja>@MarshalledProp</ja>(view={<js>"summary"</js>,
<js>"detail"</js>})
+ * <jk>public</jk> String <jf>name</jf>;
+ *
+ * <jc>// Included only in the "detail" view</jc>
+ * <ja>@MarshalledProp</ja>(view=<js>"detail"</js>)
+ * <jk>public</jk> String <jf>description</jf>;
+ * }
+ * </p>
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * <li class='jm'>{@link
org.apache.juneau.marshall.MarshallingContext.Builder#activeView(String)}
+ * <li class='link'><a class="doclink"
href="https://juneau.apache.org/docs/topics/ViewProjection">View-based
Projection</a>
+ * </ul>
+ *
+ * @return The annotation value.
+ * @since 10.0.0
+ */
+ String[] view() default {};
+
/**
* Bean dictionary.
*
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/MarshalledPropAnnotation.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/MarshalledPropAnnotation.java
index aeae3e56c7..7fba4e6a60 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/MarshalledPropAnnotation.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/MarshalledPropAnnotation.java
@@ -45,6 +45,7 @@ public class MarshalledPropAnnotation {
private String[] description = {};
private Class<?>[] dictionary = new Class[0];
private String format = "";
+ private String[] view = {};
private DurationFormat durationFormat = DurationFormat.NOT_SET;
private PeriodFormat periodFormat = PeriodFormat.NOT_SET;
private CalendarFormat calendarFormat = CalendarFormat.NOT_SET;
@@ -275,6 +276,17 @@ public class MarshalledPropAnnotation {
return this;
}
+ /**
+ * Sets the {@link MarshalledProp#view()} property on this
annotation.
+ *
+ * @param value The new value for this property.
+ * @return This object.
+ */
+ public Builder view(String...value) {
+ view = value;
+ return this;
+ }
+
}
@SuppressWarnings({
@@ -285,6 +297,7 @@ public class MarshalledPropAnnotation {
private final String[] description;
private final Class<?>[] dictionary;
private final String format;
+ private final String[] view;
private final DurationFormat durationFormat;
private final PeriodFormat periodFormat;
private final CalendarFormat calendarFormat;
@@ -306,6 +319,7 @@ public class MarshalledPropAnnotation {
description = copyOf(b.description);
dictionary = copyOf(b.dictionary);
format = b.format;
+ view = copyOf(b.view);
durationFormat = b.durationFormat;
periodFormat = b.periodFormat;
calendarFormat = b.calendarFormat;
@@ -412,6 +426,11 @@ public class MarshalledPropAnnotation {
public String[] description() {
return description;
}
+
+ @Override /* Overridden from MarshalledProp */
+ public String[] view() {
+ return view;
+ }
}
/** Default value */
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/MarshalledPropertyPostProcessor.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/MarshalledPropertyPostProcessor.java
index 033a24731d..662750cafe 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/MarshalledPropertyPostProcessor.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/MarshalledPropertyPostProcessor.java
@@ -105,6 +105,7 @@ final class MarshalledPropertyPostProcessor implements
BeanPropertyPostProcessor
static void process(MarshallingContext bc, BeanPropertyMeta.Builder b) {
var ap = bc.getAnnotationProvider();
var bdClasses = new ArrayList<Class<?>>();
+ Set<String> viewNames = null;
var propertyClass = propertyClass(b);
// XMLGregorianCalendar always uses XML format regardless of
any CalendarFormat setting.
@@ -122,6 +123,8 @@ final class MarshalledPropertyPostProcessor implements
BeanPropertyPostProcessor
});
ap.find(Swap.class,
b.innerField).stream().findFirst().ifPresent(x -> b.swap = swapSwap(x));
b.isUri |= ap.has(Uri.class, b.innerField);
+ for (var ai : ap.find(MarshalledProp.class,
b.innerField))
+ viewNames = addViews(viewNames,
ai.inner().view());
}
if (nn(b.getter)) {
@@ -134,6 +137,8 @@ final class MarshalledPropertyPostProcessor implements
BeanPropertyPostProcessor
});
ap.find(Swap.class, b.getter).forEach(x -> b.swap =
swapSwap(x));
b.isUri |= ap.has(Uri.class, b.getter);
+ for (var ai : ap.find(MarshalledProp.class, b.getter))
+ viewNames = addViews(viewNames,
ai.inner().view());
}
if (nn(b.setter)) {
@@ -146,8 +151,13 @@ final class MarshalledPropertyPostProcessor implements
BeanPropertyPostProcessor
});
ap.find(Swap.class, b.setter).forEach(x -> b.swap =
swapSwap(x));
b.isUri |= ap.has(Uri.class, b.setter);
+ for (var ai : ap.find(MarshalledProp.class, b.setter))
+ viewNames = addViews(viewNames,
ai.inner().view());
}
+ if (viewNames != null)
+ b.views = viewNames;
+
ClassInfo ownerClass = owningClass(b);
if (nn(ownerClass)) {
ap.find(Marshalled.class,
ownerClass).stream().findFirst().ifPresent(x -> {
@@ -311,7 +321,7 @@ final class MarshalledPropertyPostProcessor implements
BeanPropertyPostProcessor
if (nn(b.setter))
for (var ai : ap.find(Schema.class, b.setter))
merged = applyToMap(merged, ai.inner());
- if (merged == null || merged.isEmpty())
+ if (e(merged))
return;
var validator = factory.create(merged, propertyClass(b));
if (validator == null)
@@ -353,7 +363,7 @@ final class MarshalledPropertyPostProcessor implements
BeanPropertyPostProcessor
} catch (ParseException e) {
throw new RuntimeException(e);
}
- if (m == null || m.isEmpty())
+ if (e(m))
return acc;
if (acc == null)
return new JsonMap(m);
@@ -1054,6 +1064,26 @@ final class MarshalledPropertyPostProcessor implements
BeanPropertyPostProcessor
return (Class) Class.class;
}
+ /**
+ * Accumulates non-empty view names into a set, creating it lazily on
first use.
+ *
+ * @param acc The current accumulator (may be null on first call).
+ * @param views The view names to add (may be empty array).
+ * @return The updated accumulator (non-null if any names were added).
+ */
+ private static Set<String> addViews(Set<String> acc, String[] views) {
+ if (views == null || views.length == 0) // HTT — views==null
branch: annotation arrays are never null in Java; defensive guard only.
+ return acc;
+ for (var v : views) {
+ if (v != null && ! v.isEmpty()) { // HTT — v==null
branch: annotation String[] elements are never null in Java; defensive guard
only.
+ if (acc == null)
+ acc = new LinkedHashSet<>();
+ acc.add(v);
+ }
+ }
+ return acc;
+ }
+
@SuppressWarnings({
"java:S112" // Rewrap DatatypeConfigurationException as
RuntimeException; we cannot recover at the swap level
})
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/MarshallingContext.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/MarshallingContext.java
index 8858170e85..8c063de659 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/MarshallingContext.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/MarshallingContext.java
@@ -302,7 +302,9 @@ public class MarshallingContext extends Context implements
ConversionFinder, Bea
private Set<ClassInfo> notBeanClasses;
private Set<String> notBeanPackages;
private BeanStore beanStore;
-
+ private String activeView;
+ private boolean disableDefaultViewInclusion;
+
/**
* Constructor.
*
@@ -411,6 +413,8 @@ public class MarshallingContext extends Context implements
ConversionFinder, Bea
useJavaBeanIntrospector =
copyFrom.useJavaBeanIntrospector;
validateSchema = copyFrom.validateSchema;
beanStore = copyFrom.beanStore;
+ activeView = copyFrom.activeView;
+ disableDefaultViewInclusion = !
copyFrom.defaultViewInclusion;
}
/**
@@ -467,6 +471,8 @@ public class MarshallingContext extends Context implements
ConversionFinder, Bea
useJavaBeanIntrospector =
copyFrom.useJavaBeanIntrospector;
validateSchema = copyFrom.validateSchema;
beanStore = copyFrom.beanStore;
+ activeView = copyFrom.activeView;
+ disableDefaultViewInclusion =
copyFrom.disableDefaultViewInclusion;
}
/**
@@ -2315,25 +2321,27 @@ public class MarshallingContext extends Context
implements ConversionFinder, Bea
swaps,
notBeanClasses,
notBeanPackages,
- integer(
- disableBeansRequireSomeProperties,
- beanMapPutReturnsOldValue,
- beansRequireDefaultConstructor,
- beansRequireSerializable,
- beansRequireSettersForGetters,
- disableIgnoreTransientFields,
- disableIgnoreUnknownNullBeanProperties,
- disableIgnoreMissingSetters,
- disableInterfaceProxies,
- findFluentSetters,
- ignoreInvocationExceptionsOnGetters,
- ignoreInvocationExceptionsOnSetters,
- ignoreUnknownBeanProperties,
- ignoreUnknownEnumValues,
- unsortedProperties,
- useJavaBeanIntrospector,
- validateSchema
- ),
+ integer(
+ disableBeansRequireSomeProperties,
+ beanMapPutReturnsOldValue,
+ beansRequireDefaultConstructor,
+ beansRequireSerializable,
+ beansRequireSettersForGetters,
+ disableIgnoreTransientFields,
+ disableIgnoreUnknownNullBeanProperties,
+ disableIgnoreMissingSetters,
+ disableInterfaceProxies,
+ findFluentSetters,
+ ignoreInvocationExceptionsOnGetters,
+ ignoreInvocationExceptionsOnSetters,
+ ignoreUnknownBeanProperties,
+ ignoreUnknownEnumValues,
+ unsortedProperties,
+ useJavaBeanIntrospector,
+ validateSchema,
+ disableDefaultViewInclusion
+ ),
+ activeView,
typePropertyName,
mediaType,
timeZone,
@@ -2360,6 +2368,77 @@ public class MarshallingContext extends Context
implements ConversionFinder, Bea
// @formatter:on
}
+ /**
+ * Sets the default active view name for all sessions created
from this context.
+ *
+ * <p>
+ * When set, only bean properties whose declared {@link
MarshalledProp#view()} set contains this view name
+ * will be included during serialization. On the parse side,
properties outside the active view are routed
+ * through the existing unknown/ignored-property mechanism
governed by
+ * {@link #ignoreUnknownBeanProperties()}.
+ *
+ * <p>
+ * Untagged properties (those whose declared view set is empty)
follow the default-view-inclusion policy:
+ * included under every active view unless {@link
#disableDefaultViewInclusion()} has been called.
+ *
+ * <p>
+ * This value can be overridden per serialization or parse call
via the session builder (e.g.
+ * {@link MarshallingSession.Builder#activeView(String)}).
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * JsonSerializer <jv>serializer</jv> =
JsonSerializer.<jsm>create</jsm>()
+ * .activeView(<js>"summary"</js>)
+ * .build();
+ * </p>
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * <li class='ja'>{@link MarshalledProp#view()}
+ * <li class='jm'>{@link
MarshallingSession.Builder#activeView(String)}
+ * </ul>
+ *
+ * @param value The active view name. Use <jk>null</jk> to
disable view filtering.
+ * @return This object.
+ * @since 10.0.0
+ */
+ public Builder activeView(String value) {
+ activeView = value;
+ return this;
+ }
+
+ /**
+ * Disables the default-view-inclusion policy.
+ *
+ * <p>
+ * By default, bean properties that carry no {@link
MarshalledProp#view()} declaration are included under
+ * every active view (matching Jackson's {@code
DEFAULT_VIEW_INCLUSION} behavior).
+ * Calling this method reverses the policy: untagged properties
are <em>excluded</em> when any active view
+ * is set.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * <li class='ja'>{@link MarshalledProp#view()}
+ * <li class='jm'>{@link #activeView(String)}
+ * </ul>
+ *
+ * @return This object.
+ * @since 10.0.0
+ */
+ public Builder disableDefaultViewInclusion() {
+ return disableDefaultViewInclusion(true);
+ }
+
+ /**
+ * Same as {@link #disableDefaultViewInclusion()} but allows
you to explicitly specify the value.
+ *
+ * @param value The value for this setting.
+ * @return This object.
+ * @since 10.0.0
+ */
+ public Builder disableDefaultViewInclusion(boolean value) {
+ disableDefaultViewInclusion = value;
+ return this;
+ }
+
/**
* Ignore invocation errors on getters.
*
@@ -3977,6 +4056,8 @@ public class MarshallingContext extends Context
implements ConversionFinder, Bea
private final Visibility beanFieldVisibility;
private final Visibility beanMethodVisibility;
private final BeanStore beanStore;
+ private final String activeView;
+ private final boolean defaultViewInclusion;
/**
* Constructor.
@@ -4033,6 +4114,8 @@ public class MarshallingContext extends Context
implements ConversionFinder, Bea
useJavaBeanIntrospector = builder.useJavaBeanIntrospector;
validateSchema = builder.validateSchema;
beanStore = builder.beanStore;
+ activeView = builder.activeView;
+ defaultViewInclusion = ! builder.disableDefaultViewInclusion;
var builderNotBeanClasses = new
ArrayList<>(builder.notBeanClasses);
notBeanClasses = builderNotBeanClasses.isEmpty() ?
DEFAULT_NOTBEAN_CLASSES : Stream.concat(builderNotBeanClasses.stream(),
DEFAULT_NOTBEAN_CLASSES.stream()).distinct().toList();
@@ -5105,6 +5188,32 @@ public class MarshallingContext extends Context
implements ConversionFinder, Bea
*/
protected final Locale getLocale() { return locale; }
+ /**
+ * The default active view for sessions created from this context.
+ *
+ * <p>
+ * Returns the view name set via {@link Builder#activeView(String)}, or
<jk>null</jk> if no default
+ * active view has been configured (all properties visible).
+ *
+ * @return The default active view, or <jk>null</jk>.
+ * @since 10.0.0
+ */
+ public final String getActiveView() { return activeView; }
+
+ /**
+ * Default-view-inclusion policy.
+ *
+ * <p>
+ * Returns <jk>true</jk> (the default) when untagged properties — those
carrying no
+ * {@link MarshalledProp#view()} declaration — are included under every
active view.
+ * Returns <jk>false</jk> when {@link
Builder#disableDefaultViewInclusion()} has been called,
+ * in which case untagged properties are excluded when any active view
is in effect.
+ *
+ * @return <jk>true</jk> if untagged properties are included under
every active view.
+ * @since 10.0.0
+ */
+ public final boolean isDefaultViewInclusion() { return
defaultViewInclusion; }
+
/**
* Media type.
*
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/MarshallingContextable.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/MarshallingContextable.java
index 2f955dea12..bb103a06c4 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/MarshallingContextable.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/MarshallingContextable.java
@@ -1655,6 +1655,57 @@ public abstract class MarshallingContextable extends
Context {
return self();
}
+ /**
+ * Sets the default active view name for serializer/parser
sessions created from this context.
+ *
+ * <p>
+ * When set, only bean properties whose declared {@link
MarshalledProp#view()} set contains this view name
+ * will be included during serialization. On the parse side,
out-of-view properties are routed through
+ * the existing unknown/ignored-property mechanism governed by
{@link #ignoreUnknownBeanProperties()}.
+ *
+ * <p>
+ * Untagged properties follow the default-view-inclusion
policy: included under every active view unless
+ * {@link #disableDefaultViewInclusion()} has been called.
+ *
+ * <p>
+ * This value can be overridden per call via {@link
MarshallingSession.Builder#activeView(String)}.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * <li class='ja'>{@link MarshalledProp#view()}
+ * <li class='jm'>{@link
MarshallingContext.Builder#activeView(String)}
+ * <li class='jm'>{@link
MarshallingSession.Builder#activeView(String)}
+ * </ul>
+ *
+ * @param value The active view name. Use <jk>null</jk> to
disable view filtering.
+ * @return This object.
+ * @since 10.0.0
+ */
+ public SELF activeView(String value) {
+ bcBuilder.activeView(value);
+ return self();
+ }
+
+ /**
+ * Disables the default-view-inclusion policy.
+ *
+ * <p>
+ * By default, bean properties that carry no {@link
MarshalledProp#view()} declaration are included under
+ * every active view. Calling this method reverses the policy:
untagged properties are <em>excluded</em>
+ * when any active view is set.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * <li class='ja'>{@link MarshalledProp#view()}
+ * <li class='jm'>{@link
MarshallingContext.Builder#disableDefaultViewInclusion()}
+ * </ul>
+ *
+ * @return This object.
+ * @since 10.0.0
+ */
+ public SELF disableDefaultViewInclusion() {
+ bcBuilder.disableDefaultViewInclusion();
+ return self();
+ }
+
/**
* POJO example.
*
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/MarshallingSession.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/MarshallingSession.java
index 9765c2404d..71db8444c4 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/MarshallingSession.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/MarshallingSession.java
@@ -60,9 +60,11 @@ public class MarshallingSession extends ContextSession
implements ConverterSessi
private static final String PROP_locale = "locale";
private static final String PROP_mediaType = "mediaType";
private static final String PROP_timeZone = "timeZone";
+ private static final String PROP_activeView = "activeView";
private static final String PROP_BeanSession_locale =
"MarshallingSession.locale";
private static final String PROP_BeanSession_mediaType =
"MarshallingSession.mediaType";
private static final String PROP_BeanSession_timeZone =
"MarshallingSession.timeZone";
+ private static final String PROP_BeanSession_activeView =
"MarshallingSession.activeView";
// Argument name constants for assertArgNotNull
private static final String ARG_ctx = "ctx";
@@ -82,6 +84,7 @@ public class MarshallingSession extends ContextSession
implements ConverterSessi
private Locale locale;
private MediaType mediaType;
private TimeZone timeZone;
+ private String activeView;
/**
* Constructor
@@ -94,6 +97,7 @@ public class MarshallingSession extends ContextSession
implements ConverterSessi
this.ctx = ctx;
mediaType = ctx.getMediaType();
timeZone = ctx.getTimeZone();
+ activeView = ctx.getActiveView();
}
/**
@@ -168,6 +172,32 @@ public class MarshallingSession extends ContextSession
implements ConverterSessi
return self();
}
+ /**
+ * Overrides the active view for this session.
+ *
+ * <p>
+ * When set, only bean properties whose declared {@link
MarshalledProp#view()} set contains this view name
+ * will be included during serialization. On the parse side,
out-of-view properties are routed through
+ * the existing unknown/ignored-property mechanism.
+ *
+ * <p>
+ * This is a per-call override. If not set, the value from
+ * {@link MarshallingContext.Builder#activeView(String)} is
used.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * <li class='ja'>{@link MarshalledProp#view()}
+ * <li class='jm'>{@link
MarshallingContext.Builder#activeView(String)}
+ * </ul>
+ *
+ * @param value The active view name. Use <jk>null</jk> to
disable view filtering for this call.
+ * @return This object.
+ * @since 10.0.0
+ */
+ public SELF activeView(String value) {
+ activeView = value;
+ return self();
+ }
+
@Override /* Overridden from Builder */
public SELF property(String key, Object value) {
if (key == null) {
@@ -181,6 +211,8 @@ public class MarshallingSession extends ContextSession
implements ConverterSessi
return mediaType(cvt(value,
MediaType.class));
case PROP_timeZone, PROP_BeanSession_timeZone:
return timeZone(cvt(value,
TimeZone.class));
+ case PROP_activeView,
PROP_BeanSession_activeView:
+ return activeView(cvt(value,
String.class));
default:
super.property(key, value);
return self();
@@ -261,8 +293,8 @@ public class MarshallingSession extends ContextSession
implements ConverterSessi
private final MarshallingContext ctx;
private final Locale locale;
private final MediaType mediaType;
-
private final TimeZone timeZone;
+ private final String activeView;
/**
* Constructor.
@@ -275,6 +307,7 @@ public class MarshallingSession extends ContextSession
implements ConverterSessi
locale = opt(builder.locale).orElse(ctx.getLocale());
mediaType = opt(builder.mediaType).orElse(builder.mediaType);
timeZone = opt(builder.timeZone).orElse(builder.timeZone);
+ activeView = builder.activeView;
}
@Override /* ConverterSession */
@@ -686,6 +719,34 @@ public class MarshallingSession extends ContextSession
implements ConverterSessi
*/
public Locale getLocale() { return locale; }
+ /**
+ * Active view.
+ *
+ * <p>
+ * Returns the active view name for this session. When
non-<jk>null</jk>, only bean properties whose
+ * declared {@link MarshalledProp#view()} set contains this name are
included during serialization, and
+ * out-of-view properties encountered during parsing are treated as
unknown.
+ *
+ * <p>
+ * This is the per-call override value, which defaults to {@link
MarshallingContext#getActiveView()}.
+ *
+ * @see MarshallingContext.Builder#activeView(String)
+ * @see MarshallingSession.Builder#activeView(String)
+ * @return The active view for this session, or <jk>null</jk> if view
filtering is not active.
+ * @since 10.0.0
+ */
+ public final String getActiveView() { return activeView; }
+
+ @Override /* BeanSession */
+ public boolean isPropertyInActiveView(BeanPropertyMeta pMeta) {
+ if (activeView == null)
+ return true;
+ var views = pMeta.getViews();
+ if (e(views))
+ return ctx.isDefaultViewInclusion();
+ return views.contains(activeView);
+ }
+
/**
* Media type.
*
@@ -1281,7 +1342,8 @@ public class MarshallingSession extends ContextSession
implements ConverterSessi
return super.properties()
.a(PROP_locale, locale)
.a(PROP_mediaType, mediaType)
- .a(PROP_timeZone, timeZone);
+ .a(PROP_timeZone, timeZone)
+ .a(PROP_activeView, activeView);
}
/**
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/csv/CsvParserSession.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/csv/CsvParserSession.java
index 712eb550a2..4a19afe2d1 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/csv/CsvParserSession.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/csv/CsvParserSession.java
@@ -160,7 +160,7 @@ public class CsvParserSession extends ReaderParserSession
implements RecordReada
// Read header row
var headers = r.readRow();
- if (headers == null || headers.isEmpty())
+ if (e(headers))
return null;
Object o = null;
@@ -366,7 +366,7 @@ public class CsvParserSession extends ReaderParserSession
implements RecordReada
* CSV-specific parsing for byte[] and primitive arrays. Returns null
if not applicable.
*/
private Object parseCsvCellValue(String val, ClassMeta<?> eType) throws
ParseException {
- if (val == null || val.isEmpty())
+ if (e(val))
return null;
if (eType.isByteArray()) {
if (byteArrayFormat ==
CsvByteArrayCellFormat.SEMICOLON_DELIMITED) {
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/ini/IniSerializerSession.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/ini/IniSerializerSession.java
index 90fd0719df..ec3c5bcc58 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/ini/IniSerializerSession.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/ini/IniSerializerSession.java
@@ -302,7 +302,7 @@ public class IniSerializerSession extends
WriterSerializerSession implements Rec
}
private static boolean needsQuoting(String s) {
- if (s == null || s.isEmpty())
+ if (e(s))
return true;
if (s.equals("null") || s.equalsIgnoreCase("true") ||
s.equalsIgnoreCase("false"))
return true;
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/ini/IniWriter.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/ini/IniWriter.java
index 0d6c40dfbe..b59497e3fd 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/ini/IniWriter.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/ini/IniWriter.java
@@ -16,6 +16,8 @@
*/
package org.apache.juneau.marshall.ini;
+import static org.apache.juneau.commons.utils.Utils.*;
+
import java.io.*;
import org.apache.juneau.marshall.*;
@@ -84,7 +86,7 @@ public class IniWriter extends SerializerWriter {
*/
public IniWriter comment(String text) {
w("# ");
- if (text != null && !text.isEmpty()) {
+ if (ne(text)) {
var lines = text.split("\n");
for (var i = 0; i < lines.length; i++) {
if (i > 0)
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/json/JsonTokenWriter.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/json/JsonTokenWriter.java
index 7a72c13fcc..0321e3045b 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/json/JsonTokenWriter.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/json/JsonTokenWriter.java
@@ -17,6 +17,7 @@
package org.apache.juneau.marshall.json;
import static org.apache.juneau.commons.utils.AssertionUtils.*;
+import static org.apache.juneau.commons.utils.Utils.*;
import java.io.*;
import java.math.*;
@@ -230,7 +231,7 @@ public class JsonTokenWriter implements TokenWriter {
* and must not be one of a small set of JavaScript reserved words.
*/
private static boolean isSafeBareIdentifier(String s) {
- if (s == null || s.isEmpty())
+ if (e(s))
return false;
var first = s.charAt(0);
if (!(Character.isLetter(first) || first == '_'))
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/markdown/MarkdownDocParserSession.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/markdown/MarkdownDocParserSession.java
index 57048dd2fb..051d0147f0 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/markdown/MarkdownDocParserSession.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/markdown/MarkdownDocParserSession.java
@@ -16,6 +16,8 @@
*/
package org.apache.juneau.marshall.markdown;
+import static org.apache.juneau.commons.utils.Utils.*;
+
import java.io.*;
import java.util.*;
@@ -176,7 +178,7 @@ public class MarkdownDocParserSession extends
MarkdownParserSession {
// The "root" section (before any sub-heading) holds the
key/value table
var rootLines = sections.get("");
- if (rootLines != null && !rootLines.isEmpty()) {
+ if (ne(rootLines)) {
// Filter out the top-level heading (level)
var tableLines = rootLines.stream()
.filter(l -> !isHeadingLine(l, level))
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/markdown/MarkdownParserSession.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/markdown/MarkdownParserSession.java
index fe6dc4ff83..916a840642 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/markdown/MarkdownParserSession.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/markdown/MarkdownParserSession.java
@@ -421,7 +421,7 @@ public class MarkdownParserSession extends
ReaderParserSession implements Record
int typeColIndex = headers.indexOf(CONST_type);
if (typeColIndex >= 0 && typeColIndex < cells.size()) {
var typeName = cells.get(typeColIndex);
- if (typeName != null && !typeName.isEmpty()) {
+ if (ne(typeName)) {
var registry = eType != null ?
eType.getBeanRegistry() : null;
var resolved = registry != null ?
registry.getClassMeta(typeName) : null;
if (resolved != null)
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/parquet/ParquetParserSession.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/parquet/ParquetParserSession.java
index 4671ad5cb0..9961649444 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/parquet/ParquetParserSession.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/parquet/ParquetParserSession.java
@@ -610,7 +610,7 @@ public class ParquetParserSession extends
InputStreamParserSession implements Re
/** Returns true if rows are key-value pairs (Map with non-String keys
format). */
private static boolean isKeyValuePairFormat(List<?> rows) {
- if (rows == null || rows.isEmpty())
+ if (e(rows))
return false;
var first = rows.get(0);
if (!(first instanceof Map<?, ?> m))
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/parquet/ParquetSchemaBuilder.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/parquet/ParquetSchemaBuilder.java
index 7e50077f57..36c58c7bfa 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/parquet/ParquetSchemaBuilder.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/parquet/ParquetSchemaBuilder.java
@@ -17,6 +17,7 @@
package org.apache.juneau.marshall.parquet;
import static org.apache.juneau.commons.utils.ThrowableUtils.*;
+import static org.apache.juneau.commons.utils.Utils.*;
import static org.apache.juneau.marshall.parquet.ParquetSchemaElement.*;
import java.util.*;
@@ -232,7 +233,7 @@ public final class ParquetSchemaBuilder {
// Resolve element type from sample when generics are erased
(et is Object) for proper list-of-bean
// expansion into leaf columns (e.g. members.list.element.name,
members.list.element.age)
var sampleCollection = extractSampleCollection(sampleBean);
- if (sampleCollection != null && !sampleCollection.isEmpty()) {
+ if (ne(sampleCollection)) {
var first = sampleCollection.iterator().next();
if (first != null)
et =
marshallingContext.getClassMeta(first.getClass());
@@ -241,7 +242,7 @@ public final class ParquetSchemaBuilder {
elements.add(new ParquetSchemaElement(name, null, null, isRoot
? null : OPTIONAL, 1, CONVERTED_LIST, null, null, null, null));
elements.add(new ParquetSchemaElement("list", null, null,
REPEATED, 1, null, null, null, null, null));
Object elementSample = null;
- if (sampleCollection != null && !sampleCollection.isEmpty())
+ if (ne(sampleCollection))
elementSample = sampleCollection.iterator().next();
addSchemaElements(elements, et, "element", listPath + ".list",
false, elementSample, typesInProgress);
}
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/xml/XmlBeanPropertyMeta.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/xml/XmlBeanPropertyMeta.java
index 197f7d14da..f762b989d1 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/xml/XmlBeanPropertyMeta.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/xml/XmlBeanPropertyMeta.java
@@ -138,10 +138,10 @@ public class XmlBeanPropertyMeta extends
ExtendedBeanPropertyMeta {
if (isCollection) {
if (cen.isEmpty() && nn(xmlMetaProvider))
cen =
xmlMetaProvider.getXmlClassMeta(cmProperty).getChildName();
- if (cen == null || cen.isEmpty())
- cen =
cmProperty.getElementType().getBeanDictionaryName();
- if (cen == null || cen.isEmpty())
- cen = name;
+ if (e(cen))
+ cen =
cmProperty.getElementType().getBeanDictionaryName();
+ if (e(cen))
+ cen = name;
} else {
throw bex(cmBean.inner(), "Annotation error on
property ''{0}''. @Xml.format=COLLAPSED can only be specified on collections
and arrays.", name);
}
diff --git
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/ViewProjection_Test.java
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/ViewProjection_Test.java
new file mode 100644
index 0000000000..740fb1f026
--- /dev/null
+++
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/ViewProjection_Test.java
@@ -0,0 +1,353 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau;
+
+import static org.apache.juneau.TestUtils.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.apache.juneau.commons.bean.*;
+import org.apache.juneau.marshall.*;
+import org.apache.juneau.marshall.json5.*;
+import org.apache.juneau.marshall.msgpack.*;
+import org.apache.juneau.marshall.xml.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * Tests for view-based property projection.
+ *
+ * <p>
+ * Covers: property-level view membership via {@link MarshalledProp#view()},
active-view selector on
+ * {@link MarshallingContext.Builder} and per-call {@link
MarshallingSession.Builder}, default-view-inclusion
+ * policy, multi-view union semantics, interaction with read-only/write-only
and {@link MarshalledIgnore},
+ * the {@code *Config}/{@code @ContextApply} path for unmodifiable classes,
and cross-format spot checks.
+ */
+class ViewProjection_Test extends TestBase {
+
+
//------------------------------------------------------------------------------------------------------------------
+ // A: baseline — no active view → all properties visible
+
//------------------------------------------------------------------------------------------------------------------
+
+ public static class A {
+ public String id = "1";
+
+ @MarshalledProp(view = "summary")
+ public String name = "Alice";
+
+ @MarshalledProp(view = "detail")
+ public String description = "A person";
+
+ static A create() {
+ return new A();
+ }
+ }
+
+ @Test void a01_noActiveView_allPropertiesVisible() {
+ assertJson("{description:'A person',id:'1',name:'Alice'}",
A.create());
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // B: summary view — tagged summary + untagged
+
//------------------------------------------------------------------------------------------------------------------
+
+ @Test void b01_summaryView_serializer() {
+ var s =
Json5Serializer.DEFAULT.copy().activeView("summary").build();
+ assertSerialized(A.create(), s, "{id:'1',name:'Alice'}");
+ }
+
+ @Test void b02_summaryView_parser_outOfViewIgnored() {
+ var p =
Json5Parser.DEFAULT.copy().activeView("summary").ignoreUnknownBeanProperties().build();
+ var x = p.parse("{id:'2',name:'Bob',description:'ignored'}",
A.class);
+ assertEquals("2", x.id);
+ assertEquals("Bob", x.name);
+ assertEquals("A person", x.description); // unchanged — never
set
+ }
+
+ @Test void b03_detailView_serializer() {
+ var s =
Json5Serializer.DEFAULT.copy().activeView("detail").build();
+ assertSerialized(A.create(), s, "{description:'A
person',id:'1'}");
+ }
+
+ @Test void b04_unknownView_onlyUntaggedProperties() {
+ var s =
Json5Serializer.DEFAULT.copy().activeView("other").build();
+ assertSerialized(A.create(), s, "{id:'1'}");
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // C: multi-view union membership
+
//------------------------------------------------------------------------------------------------------------------
+
+ public static class C {
+ public String id = "1";
+
+ @MarshalledProp(view = {"summary", "detail"})
+ public String name = "Alice";
+
+ @MarshalledProp(view = "detail")
+ public String description = "A person";
+
+ static C create() {
+ return new C();
+ }
+ }
+
+ @Test void c01_summaryView_unionIncludesNameNotDescription() {
+ var s =
Json5Serializer.DEFAULT.copy().activeView("summary").build();
+ assertSerialized(C.create(), s, "{id:'1',name:'Alice'}");
+ }
+
+ @Test void c02_detailView_unionIncludesBoth() {
+ var s =
Json5Serializer.DEFAULT.copy().activeView("detail").build();
+ assertSerialized(C.create(), s, "{description:'A
person',id:'1',name:'Alice'}");
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // D: disableDefaultViewInclusion — untagged properties excluded when
view active
+
//------------------------------------------------------------------------------------------------------------------
+
+ @Test void d01_disableDefaultViewInclusion_summaryView() {
+ var s =
Json5Serializer.DEFAULT.copy().activeView("summary").disableDefaultViewInclusion().build();
+ assertSerialized(A.create(), s, "{name:'Alice'}");
+ }
+
+ @Test void d02_disableDefaultViewInclusion_detailView() {
+ var s =
Json5Serializer.DEFAULT.copy().activeView("detail").disableDefaultViewInclusion().build();
+ assertSerialized(A.create(), s, "{description:'A person'}");
+ }
+
+ @Test void d03_disableDefaultViewInclusion_unknownView_nothingVisible()
{
+ var s =
Json5Serializer.DEFAULT.copy().activeView("other").disableDefaultViewInclusion().build();
+ assertSerialized(A.create(), s, "{}");
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // E: per-session override — context sets default, session overrides
per-call
+
//------------------------------------------------------------------------------------------------------------------
+
+ @Test void e01_perSessionOverride_overridesContextDefault() throws
Exception {
+ var s =
Json5Serializer.DEFAULT.copy().activeView("summary").build();
+
+ // default from context: summary
+ assertEquals("{id:'1',name:'Alice'}", s.serialize(A.create()));
+
+ // per-call override to detail
+ assertEquals("{description:'A person',id:'1'}",
s.createSession().activeView("detail").build().serialize(A.create()));
+
+ // per-call override to null → all properties
+ assertEquals("{description:'A person',id:'1',name:'Alice'}",
s.createSession().activeView(null).build().serialize(A.create()));
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // F: precedence — @MarshalledIgnore overrides view
+
//------------------------------------------------------------------------------------------------------------------
+
+ public static class F {
+ public String id = "1";
+
+ @BeanIgnore
+ @MarshalledProp(view = "summary")
+ public String ignored = "x";
+
+ static F create() {
+ return new F();
+ }
+ }
+
+ @Test void f01_beanIgnore_precedenceOverView() {
+ var s =
Json5Serializer.DEFAULT.copy().activeView("summary").build();
+ assertSerialized(F.create(), s, "{id:'1'}");
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // G: precedence — readOnly/writeOnly compose with view
+
//------------------------------------------------------------------------------------------------------------------
+
+ public static class G {
+ @BeanProp(ro = "true")
+ @MarshalledProp(view = "summary")
+ public String name = "Alice";
+
+ @BeanProp(wo = "true")
+ @MarshalledProp(view = "detail")
+ public String description = "A person";
+
+ static G create() {
+ return new G();
+ }
+ }
+
+ @Test void g01_readOnlyPlusView_serializer_onlyNameVisible() {
+ var s =
Json5Serializer.DEFAULT.copy().activeView("summary").build();
+ assertSerialized(G.create(), s, "{name:'Alice'}");
+ }
+
+ @Test void g02_writeOnlyPlusView_serializer_notVisible() {
+ var s =
Json5Serializer.DEFAULT.copy().activeView("detail").build();
+ assertSerialized(G.create(), s, "{}");
+ }
+
+ @Test void g03_readOnlyPlusView_parser_nameIgnored_descriptionSet() {
+ var p =
Json5Parser.DEFAULT.copy().activeView("detail").ignoreUnknownBeanProperties().build();
+ var x = p.parse("{name:'Bob',description:'New'}", G.class);
+ assertEquals("Alice", x.name); // read-only: not written
+ assertEquals("New", x.description); // in detail view and
write-only
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // H: view via *Config / @ContextApply on unmodifiable class
+
//------------------------------------------------------------------------------------------------------------------
+
+ public static class H {
+ public String id = "1";
+ public String name = "Alice";
+ public String description = "A person";
+
+ static H create() {
+ return new H();
+ }
+ }
+
+ @MarshalledPropApply(on = "H.name", value = @MarshalledProp(view =
"summary"))
+ @MarshalledPropApply(on = "H.description", value = @MarshalledProp(view
= "detail"))
+ private static class HConfig {}
+
+ @Test void h01_configApply_summaryView() {
+ var s =
Json5Serializer.DEFAULT.copy().applyAnnotations(HConfig.class).activeView("summary").build();
+ assertSerialized(H.create(), s, "{id:'1',name:'Alice'}"); // i
before n alphabetically
+ }
+
+ @Test void h02_configApply_detailView() {
+ var s =
Json5Serializer.DEFAULT.copy().applyAnnotations(HConfig.class).activeView("detail").build();
+ assertSerialized(H.create(), s, "{description:'A
person',id:'1'}");
+ }
+
+ @Test void h03_configApply_parser_outOfView() {
+ var p =
Json5Parser.DEFAULT.copy().applyAnnotations(HConfig.class).activeView("summary").ignoreUnknownBeanProperties().build();
+ var x = p.parse("{id:'2',name:'Bob',description:'ignored'}",
H.class);
+ assertEquals("2", x.id);
+ assertEquals("Bob", x.name);
+ assertEquals("A person", x.description); // unchanged
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // I: cross-format — XML spot check
+
//------------------------------------------------------------------------------------------------------------------
+
+ public static class I {
+ public String id = "1";
+
+ @MarshalledProp(view = "summary")
+ public String name = "Alice";
+
+ static I create() {
+ return new I();
+ }
+ }
+
+ @Test void i01_xmlFormat_summaryView() {
+ var s =
XmlSerializer.DEFAULT_NS_SQ.copy().activeView("summary").build();
+ var xml = s.serialize(I.create());
+ // id (untagged, always included) and name (in summary) should
appear
+ assertTrue(xml.contains("1"), "Expected id value in XML: " +
xml);
+ assertTrue(xml.contains("Alice"), "Expected name value in XML:
" + xml);
+ }
+
+ @Test void i02_xmlFormat_detailView_nameNotVisible() {
+ var s =
XmlSerializer.DEFAULT_NS_SQ.copy().activeView("detail").build();
+ var xml = s.serialize(I.create());
+ // name is only in summary view, not detail → should not appear
+ assertFalse(xml.contains("Alice"), "name should not appear in
detail view: " + xml);
+ assertTrue(xml.contains("1"), "Expected id value in XML: " +
xml);
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // J: cross-format — MsgPack binary spot check
+
//------------------------------------------------------------------------------------------------------------------
+
+ @Test void j01_msgpackFormat_summaryView_serializer() {
+ // MsgPack with summary view — only id (untagged) and name (in
summary) should appear
+ var s =
MsgPackSerializer.DEFAULT.copy().activeView("summary").build();
+ // Serialize to hex string and verify it's shorter than a full
serialization (fewer properties)
+ var fullBytes = MsgPackSerializer.DEFAULT.serialize(A.create());
+ var summaryBytes = s.serialize(A.create());
+ // The summary view should produce fewer bytes (omits
description)
+ assertNotEquals(fullBytes, summaryBytes);
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // K: view interacts with beanProperties* (intersection: view ∩ filter)
+
//------------------------------------------------------------------------------------------------------------------
+
+ @Test void k01_viewAndBeanPropertiesFilter_onlyUntaggedSurvivesBoth() {
+ // beanProperties restricts visible set to {id, name}
+ // view "detail" restricts to {id (untagged), description (in
detail)}
+ // intersection: only id (untagged, in beanProperties) survives
both filters
+ var s = Json5Serializer.DEFAULT.copy()
+ .beanProperties(A.class, "id,name")
+ .activeView("detail")
+ .build();
+ assertSerialized(A.create(), s, "{id:'1'}");
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // L: parse-side — out-of-view property with
ignoreUnknownBeanProperties=false throws
+
//------------------------------------------------------------------------------------------------------------------
+
+ @Test void l01_parseSide_outOfViewThrowsWhenIgnoreDisabled() {
+ var p =
Json5Parser.DEFAULT.copy().activeView("summary").build();
+ assertThrows(Exception.class, () ->
p.parse("{id:'1',description:'x'}", A.class));
+ }
+
+ @Test void l02_parseSide_outOfViewIgnoredWhenIgnoreEnabled() {
+ var p =
Json5Parser.DEFAULT.copy().activeView("summary").ignoreUnknownBeanProperties().build();
+ assertDoesNotThrow(() -> p.parse("{id:'1',description:'x'}",
A.class));
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // M: context-level default view is part of hashKey (different contexts
cached separately)
+
//------------------------------------------------------------------------------------------------------------------
+
+ @Test void m01_differentActiveViews_differentContexts() {
+ var s1 =
Json5Serializer.DEFAULT.copy().activeView("summary").build();
+ var s2 =
Json5Serializer.DEFAULT.copy().activeView("detail").build();
+ var s3 =
Json5Serializer.DEFAULT.copy().activeView("summary").build();
+ // same active view → same output (different instances, but
same serialization result)
+ assertEquals(s1.serialize(A.create()),
s3.serialize(A.create()));
+ // different active view → different output
+ assertNotEquals(s1.serialize(A.create()),
s2.serialize(A.create()));
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // N: MarshalledIgnore annotation on a field with no view tag — never
visible
+
//------------------------------------------------------------------------------------------------------------------
+
+ public static class N {
+ public String id = "1";
+
+ @BeanIgnore
+ public String secret = "x";
+
+ static N create() {
+ return new N();
+ }
+ }
+
+ @Test void n01_beanIgnoreNoView_neverVisible() {
+ assertJson("{id:'1'}", N.create());
+ var s =
Json5Serializer.DEFAULT.copy().activeView("summary").build();
+ assertSerialized(N.create(), s, "{id:'1'}");
+ }
+}
diff --git
a/juneau-integration-tests/src/test/java/org/apache/juneau/ViewProjection_Test.java
b/juneau-integration-tests/src/test/java/org/apache/juneau/ViewProjection_Test.java
new file mode 100644
index 0000000000..df62c78d46
--- /dev/null
+++
b/juneau-integration-tests/src/test/java/org/apache/juneau/ViewProjection_Test.java
@@ -0,0 +1,294 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau;
+
+import static org.apache.juneau.TestUtils.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.apache.juneau.commons.bean.*;
+import org.apache.juneau.marshall.*;
+import org.apache.juneau.marshall.json5.*;
+import org.junit.jupiter.api.*;
+
+import static org.apache.juneau.marshall.MarshalledPropApplyAnnotation.create;
+
+/**
+ * Integration-suite coverage tests for view-based property projection.
+ *
+ * <p>
+ * These tests mirror the unit-level {@code ViewProjection_Test} (in
juneau-core/juneau-marshall) and are
+ * placed here so their execution is captured by the integration-tests JaCoCo
exec file, making coverage
+ * of {@link MarshalledProp#view()}, {@link
MarshallingContext.Builder#activeView(String)},
+ * {@link MarshallingContext.Builder#disableDefaultViewInclusion()},
+ * {@link MarshallingSession#isPropertyInActiveView}, and
+ * {@code MarshalledPropertyPostProcessor.addViews()} visible to the coverage
reporter.
+ */
+class ViewProjection_Test extends TestBase {
+
+
//------------------------------------------------------------------------------------------------------------------
+ // A: baseline — no active view → all properties visible
+
//------------------------------------------------------------------------------------------------------------------
+
+ public static class A {
+ public String id = "1";
+
+ @MarshalledProp(view = "summary")
+ public String name = "Alice";
+
+ @MarshalledProp(view = "detail")
+ public String description = "A person";
+
+ static A create() {
+ return new A();
+ }
+ }
+
+ @Test void a01_noActiveView_allPropertiesVisible() {
+ assertJson("{description:'A person',id:'1',name:'Alice'}",
A.create());
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // B: summary / detail / unknown views — covers
MarshallingContextable.activeView(),
+ // MarshallingContext.Builder.activeView(),
MarshallingSession.isPropertyInActiveView() non-null path,
+ // and MarshalledPropertyPostProcessor.addViews() loop body.
+
//------------------------------------------------------------------------------------------------------------------
+
+ @Test void b01_summaryView_includesTaggedAndUntagged() {
+ // MarshallingContextable.Builder.activeView() at line 1684 and
+ // MarshallingContext.Builder.activeView() at line 2405 are
exercised here.
+ // MarshallingSession.isPropertyInActiveView() non-null
activeView path (lines 744-747)
+ // is exercised for: untagged 'id' (views==null →
ctx.isDefaultViewInclusion()),
+ // 'name' tagged summary (views.contains → true), 'description'
tagged detail (views.contains → false).
+ var s =
Json5Serializer.DEFAULT.copy().activeView("summary").build();
+ assertSerialized(A.create(), s, "{id:'1',name:'Alice'}");
+ }
+
+ @Test void b02_detailView_includesTaggedAndUntagged() {
+ var s =
Json5Serializer.DEFAULT.copy().activeView("detail").build();
+ assertSerialized(A.create(), s, "{description:'A
person',id:'1'}");
+ }
+
+ @Test void b03_unknownView_onlyUntaggedProperties() {
+ var s =
Json5Serializer.DEFAULT.copy().activeView("other").build();
+ assertSerialized(A.create(), s, "{id:'1'}");
+ }
+
+ @Test void b04_activeView_null_resetToAll() {
+ // Explicitly passing null disables view filtering — same as no
activeView.
+ var s = Json5Serializer.DEFAULT.copy().activeView(null).build();
+ assertSerialized(A.create(), s, "{description:'A
person',id:'1',name:'Alice'}");
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // C: disableDefaultViewInclusion — covers
MarshallingContextable.Builder.disableDefaultViewInclusion()
+ // at line 1705 and
MarshallingContext.Builder.disableDefaultViewInclusion() /
defaultViewInclusion=false path.
+
//------------------------------------------------------------------------------------------------------------------
+
+ @Test void c01_disableDefaultViewInclusion_summaryView() {
+ // MarshallingContextable.Builder.disableDefaultViewInclusion()
at line 1705 is exercised here.
+ // isDefaultViewInclusion() returns false → untagged 'id'
excluded.
+ var s =
Json5Serializer.DEFAULT.copy().activeView("summary").disableDefaultViewInclusion().build();
+ assertSerialized(A.create(), s, "{name:'Alice'}");
+ }
+
+ @Test void c02_disableDefaultViewInclusion_detailView() {
+ var s =
Json5Serializer.DEFAULT.copy().activeView("detail").disableDefaultViewInclusion().build();
+ assertSerialized(A.create(), s, "{description:'A person'}");
+ }
+
+ @Test void c03_disableDefaultViewInclusion_unknownView_nothingVisible()
{
+ var s =
Json5Serializer.DEFAULT.copy().activeView("other").disableDefaultViewInclusion().build();
+ assertSerialized(A.create(), s, "{}");
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // D: multi-view union membership — covers addViews() 'acc non-null'
branch (accumulator is reused
+ // across multiple view names on the same property) and covers
acc.add(v) with acc != null.
+
//------------------------------------------------------------------------------------------------------------------
+
+ public static class D {
+ public String id = "1";
+
+ @MarshalledProp(view = {"summary", "detail"})
+ public String name = "Alice";
+
+ @MarshalledProp(view = "detail")
+ public String description = "A person";
+
+ static D create() {
+ return new D();
+ }
+ }
+
+ @Test void d01_multiView_summaryViewIncludesName() {
+ var s =
Json5Serializer.DEFAULT.copy().activeView("summary").build();
+ assertSerialized(D.create(), s, "{id:'1',name:'Alice'}");
+ }
+
+ @Test void d02_multiView_detailViewIncludesBoth() {
+ // addViews() is called twice for 'name': once for "summary"
(acc becomes non-null),
+ // once for "detail" (acc != null branch taken — adds to
existing set).
+ var s =
Json5Serializer.DEFAULT.copy().activeView("detail").build();
+ assertSerialized(D.create(), s, "{description:'A
person',id:'1',name:'Alice'}");
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // E: MarshalledPropAnnotation programmatic builder — covers
MarshalledPropAnnotation.create(),
+ // Builder.view(), Object.view(), Builder.build(), and Object
constructor.
+
//------------------------------------------------------------------------------------------------------------------
+
+ public static class E {
+ public String id = "1";
+ public String name = "Alice";
+ public String description = "A person";
+
+ static E create() {
+ return new E();
+ }
+ }
+
+ @Test void e01_programmaticAnnotation_summaryView() {
+ // MarshalledPropAnnotation.create() at line 444,
Builder.view() at line 285,
+ // Object.view() at line 431, and Builder.build() / Object
constructor are all exercised here
+ // via
MarshalledPropApplyAnnotation.Builder.value(MarshalledProp).
+ var applyName =
create("ViewProjection_Test$E.name").value(MarshalledPropAnnotation.create().view("summary").build()).build();
+ var applyDesc =
create("ViewProjection_Test$E.description").value(MarshalledPropAnnotation.create().view("detail").build()).build();
+ var s = Json5Serializer.DEFAULT.copy()
+ .annotations(applyName, applyDesc)
+ .activeView("summary")
+ .build();
+ assertSerialized(E.create(), s, "{id:'1',name:'Alice'}");
+ }
+
+ @Test void e02_programmaticAnnotation_detailView() {
+ var applyName =
create("ViewProjection_Test$E.name").value(MarshalledPropAnnotation.create().view("summary").build()).build();
+ var applyDesc =
create("ViewProjection_Test$E.description").value(MarshalledPropAnnotation.create().view("detail").build()).build();
+ var s = Json5Serializer.DEFAULT.copy()
+ .annotations(applyName, applyDesc)
+ .activeView("detail")
+ .build();
+ assertSerialized(E.create(), s, "{description:'A
person',id:'1'}");
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // F: addViews() edge case — empty-string view name is silently ignored.
+ // Covers the v.isEmpty() → T branch inside the addViews() for-loop.
+
//------------------------------------------------------------------------------------------------------------------
+
+ public static class F {
+ public String id = "1";
+
+ @MarshalledProp(view = {"", "summary"})
+ public String name = "Alice";
+
+ static F create() {
+ return new F();
+ }
+ }
+
+ @Test void f01_emptyStringViewName_ignored() {
+ // @MarshalledProp(view = {"", "summary"}) — the empty string
is ignored by addViews(),
+ // exercising the v.isEmpty() → true branch (skips the empty
entry, adds "summary").
+ var s =
Json5Serializer.DEFAULT.copy().activeView("summary").build();
+ assertSerialized(F.create(), s, "{id:'1',name:'Alice'}");
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // G: per-session override — context default overridden per-call via
MarshallingSession.Builder.activeView().
+
//------------------------------------------------------------------------------------------------------------------
+
+ @Test void g01_perSessionOverride_overridesContextDefault() throws
Exception {
+ var s =
Json5Serializer.DEFAULT.copy().activeView("summary").build();
+
+ // Default from context: summary view.
+ assertEquals("{id:'1',name:'Alice'}", s.serialize(A.create()));
+
+ // Per-call override to detail view.
+ assertEquals("{description:'A person',id:'1'}",
s.createSession().activeView("detail").build().serialize(A.create()));
+
+ // Per-call override to null → all properties visible.
+ assertEquals("{description:'A person',id:'1',name:'Alice'}",
s.createSession().activeView(null).build().serialize(A.create()));
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // H: parser-side view — out-of-view properties treated as unknown
during parsing.
+
//------------------------------------------------------------------------------------------------------------------
+
+ @Test void h01_parseSide_summaryView_outOfViewIgnored() {
+ var p =
Json5Parser.DEFAULT.copy().activeView("summary").ignoreUnknownBeanProperties().build();
+ var x = p.parse("{id:'2',name:'Bob',description:'ignored'}",
A.class);
+ assertEquals("2", x.id);
+ assertEquals("Bob", x.name);
+ assertEquals("A person", x.description); // unchanged —
out-of-view during parse
+ }
+
+ @Test void h02_parseSide_outOfViewThrowsWhenIgnoreDisabled() {
+ var p =
Json5Parser.DEFAULT.copy().activeView("summary").build();
+ assertThrows(Exception.class, () ->
p.parse("{id:'1',description:'x'}", A.class));
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // I: hashKey isolation — different activeView settings produce
different contexts (are cached separately).
+
//------------------------------------------------------------------------------------------------------------------
+
+ @Test void i01_differentActiveViews_differentContexts() {
+ var s1 =
Json5Serializer.DEFAULT.copy().activeView("summary").build();
+ var s2 =
Json5Serializer.DEFAULT.copy().activeView("detail").build();
+ var s3 =
Json5Serializer.DEFAULT.copy().activeView("summary").build();
+ // Same active view → same serialization result.
+ assertEquals(s1.serialize(A.create()),
s3.serialize(A.create()));
+ // Different active view → different serialization result.
+ assertNotEquals(s1.serialize(A.create()),
s2.serialize(A.create()));
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // I2: MarshallingSession.getActiveView() — covers the getter body
(line 738 in MarshallingSession).
+
//------------------------------------------------------------------------------------------------------------------
+
+ @Test void i02_getActiveView_returnsConfiguredValue() throws Exception {
+ var s =
Json5Serializer.DEFAULT.copy().activeView("summary").build();
+ var session = (MarshallingSession) s.createSession().build();
+ assertEquals("summary", session.getActiveView());
+ }
+
+ @Test void i03_getActiveView_nullByDefault() throws Exception {
+ var s = Json5Serializer.DEFAULT.copy().build();
+ var session = (MarshallingSession) s.createSession().build();
+ assertNull(session.getActiveView());
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // J: BeanIgnore precedence — @BeanIgnore overrides view membership.
+
//------------------------------------------------------------------------------------------------------------------
+
+ public static class J {
+ public String id = "1";
+
+ @BeanIgnore
+ @MarshalledProp(view = "summary")
+ public String ignored = "x";
+
+ static J create() {
+ return new J();
+ }
+ }
+
+ @Test void j01_beanIgnore_precedenceOverView() {
+ var s =
Json5Serializer.DEFAULT.copy().activeView("summary").build();
+ assertSerialized(J.create(), s, "{id:'1'}");
+ }
+}
diff --git
a/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/JettyServerComponent.java
b/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/JettyServerComponent.java
index 55f292ea9d..0cb615bb68 100644
---
a/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/JettyServerComponent.java
+++
b/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/JettyServerComponent.java
@@ -108,7 +108,7 @@ public class JettyServerComponent implements
MicroserviceListener {
Optional<String> serverPortEnv = opte();
private static int[] parseIntArray(String csv) {
- if (csv == null || csv.isEmpty())
+ if (e(csv))
return new int[0];
var parts = csv.split(",");
var out = new int[parts.length];
diff --git
a/juneau-microservice/juneau-microservice-tomcat/src/main/java/org/apache/juneau/microservice/tomcat/TomcatServerComponent.java
b/juneau-microservice/juneau-microservice-tomcat/src/main/java/org/apache/juneau/microservice/tomcat/TomcatServerComponent.java
index 3e910be388..d7da247d65 100644
---
a/juneau-microservice/juneau-microservice-tomcat/src/main/java/org/apache/juneau/microservice/tomcat/TomcatServerComponent.java
+++
b/juneau-microservice/juneau-microservice-tomcat/src/main/java/org/apache/juneau/microservice/tomcat/TomcatServerComponent.java
@@ -117,7 +117,7 @@ public class TomcatServerComponent implements
MicroserviceListener {
Optional<String> serverPortEnv = opte();
private static int[] parseIntArray(String csv) {
- if (csv == null || csv.isEmpty())
+ if (e(csv))
return new int[0];
var parts = csv.split(",");
var out = new int[parts.length];
@@ -509,7 +509,7 @@ public class TomcatServerComponent implements
MicroserviceListener {
public URI getURI() {
var cp = getContextPath();
try {
- return new URI(getProtocol(), null, getHostName(),
getPort(), (cp == null || cp.isEmpty()) ? null : cp, null, null);
+ return new URI(getProtocol(), null, getHostName(),
getPort(), e(cp) ? null : cp, null, null);
} catch (URISyntaxException e) {
throw toRex(e);
}
diff --git
a/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/Microservice.java
b/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/Microservice.java
index d630a4dbff..c39b9627f9 100755
---
a/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/Microservice.java
+++
b/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/Microservice.java
@@ -145,7 +145,7 @@ public class Microservice implements ConfigEventListener {
*/
@Inject
public void
initWorkingDirFromEnv(@Value("${juneau.workingDir}") String workingDirEnv) {
- if (workingDir == null && workingDirEnv != null &&
!workingDirEnv.isEmpty())
+ if (workingDir == null && ne(workingDirEnv))
workingDir = new File(workingDirEnv);
}
diff --git
a/juneau-rest/juneau-rest-common/src/main/java/org/apache/juneau/http/resource/HttpResourceBean.java
b/juneau-rest/juneau-rest-common/src/main/java/org/apache/juneau/http/resource/HttpResourceBean.java
index c5dad6c493..b5f1eb49c6 100644
---
a/juneau-rest/juneau-rest-common/src/main/java/org/apache/juneau/http/resource/HttpResourceBean.java
+++
b/juneau-rest/juneau-rest-common/src/main/java/org/apache/juneau/http/resource/HttpResourceBean.java
@@ -134,7 +134,7 @@ public final class HttpResourceBean implements HttpResource
{
* @return A new instance. Never <jk>null</jk>.
*/
public HttpResourceBean withHeaders(List<HttpHeader> toAdd) {
- if (toAdd == null || toAdd.isEmpty())
+ if (e(toAdd))
return this;
var newHeaders = new ArrayList<>(headers);
for (var h : toAdd)
diff --git
a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockServletRequest.java
b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockServletRequest.java
index c87911c602..8cd35bf84e 100644
---
a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockServletRequest.java
+++
b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockServletRequest.java
@@ -375,7 +375,7 @@ public class MockServletRequest implements
HttpServletRequest {
@Override /* Overridden from HttpServletRequest */
public int getIntHeader(String name) {
var s = getHeader(name);
- return s == null || s.isEmpty() ? 0 : Integer.parseInt(s);
+ return e(s) ? 0 : Integer.parseInt(s);
}
@Override /* Overridden from HttpServletRequest */
diff --git
a/juneau-rest/juneau-rest-server-metrics-micrometer/src/main/java/org/apache/juneau/rest/server/metrics/micrometer/MicrometerMetricsRecorder.java
b/juneau-rest/juneau-rest-server-metrics-micrometer/src/main/java/org/apache/juneau/rest/server/metrics/micrometer/MicrometerMetricsRecorder.java
index 5294285d3e..b8d49d23fb 100644
---
a/juneau-rest/juneau-rest-server-metrics-micrometer/src/main/java/org/apache/juneau/rest/server/metrics/micrometer/MicrometerMetricsRecorder.java
+++
b/juneau-rest/juneau-rest-server-metrics-micrometer/src/main/java/org/apache/juneau/rest/server/metrics/micrometer/MicrometerMetricsRecorder.java
@@ -159,13 +159,13 @@ public class MicrometerMetricsRecorder implements
MetricsRecorder {
})
@Override /* MetricsRecorder */
public void record(String opName, String httpMethod, String
uriTemplate, int statusCode, Duration elapsed, Throwable error, String
metricName, String metricTags) {
- var effectiveName = (metricName != null &&
!metricName.isEmpty()) ? metricName : timerName;
+ var effectiveName = ne(metricName) ? metricName : timerName;
var builder = Timer.builder(effectiveName)
.tag(TAG_METHOD, defaultIfBlank(httpMethod, ""))
.tag(TAG_URI, defaultIfBlank(uriTemplate, ""))
.tag(TAG_STATUS, Integer.toString(statusCode))
.tag(TAG_EXCEPTION, exceptionTag(error));
- if (metricTags != null && !metricTags.isEmpty())
+ if (ne(metricTags))
for (var pair : metricTags.split(",")) {
var kv = pair.split("=", 2);
if (kv.length == 2)
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/RestOpContext.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/RestOpContext.java
index 8998e78ebf..a79959efb3 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/RestOpContext.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/RestOpContext.java
@@ -296,7 +296,7 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
return Charset.forName(v.get());
if (isInherited(PROPERTY_defaultCharset)) {
var rv =
restContext().mergeReplacedStringAttribute(PROPERTY_defaultCharset, null);
- if (rv != null && !rv.isEmpty())
+ if (ne(rv))
return Charset.forName(rv);
}
return Charset.forName(defaultCharsetName);
@@ -558,7 +558,7 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
var vr = varResolver();
for (var ai : getRestOpAnnotations()) {
var v = httpMethodFromAnnotation(ai.inner(), vr);
- if (v != null && !v.isEmpty())
+ if (ne(v))
return normalizeHttpMethod(v);
}
return normalizeHttpMethod(HttpUtils.detectHttpMethod(method(),
true, "GET"));
@@ -627,7 +627,7 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
return parseLongWithSuffix(v.get());
if (isInherited(PROPERTY_maxInput)) {
var rv =
restContext().mergeReplacedStringAttribute(PROPERTY_maxInput, null);
- if (rv != null && !rv.isEmpty())
+ if (ne(rv))
return parseLongWithSuffix(rv);
}
return parseLongWithSuffix(defaultMaxInputString);
@@ -1240,7 +1240,7 @@ public class RestOpContext extends Context implements
Comparable<RestOpContext>
return "options";
if (a instanceof RestOp r) {
var m = vr.resolve(r.method());
- if (m != null && !m.isEmpty())
+ if (ne(m))
return m;
var s = vr.resolve(r.value());
if (s != null) {
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/RestRequest.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/RestRequest.java
index 0030bb3fa9..198615d41b 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/RestRequest.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/RestRequest.java
@@ -1714,7 +1714,7 @@ public class RestRequest extends
HttpServletRequestWrapper {
* @return This object.
*/
public RestRequest setSerializerSessionProperties(Map<String,Object>
values) {
- if (values != null && !values.isEmpty()) {
+ if (ne(values)) {
if (serializerSessionProperties == null)
serializerSessionProperties = new
LinkedHashMap<>();
serializerSessionProperties.putAll(values);
@@ -1729,7 +1729,7 @@ public class RestRequest extends
HttpServletRequestWrapper {
* @return This object.
*/
public RestRequest setParserSessionProperties(Map<String,Object>
values) {
- if (values != null && !values.isEmpty()) {
+ if (ne(values)) {
if (parserSessionProperties == null)
parserSessionProperties = new LinkedHashMap<>();
parserSessionProperties.putAll(values);
@@ -1831,7 +1831,7 @@ public class RestRequest extends
HttpServletRequestWrapper {
private static Map<String,Object>
mergeSessionMaps(Map<String,Object>...maps) {
Map<String,Object> result = null;
for (var map : maps) {
- if (map != null && !map.isEmpty()) {
+ if (ne(map)) {
if (result == null)
result = new LinkedHashMap<>(map);
else
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/RestResponse.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/RestResponse.java
index aae6d44c54..706267b92a 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/RestResponse.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/RestResponse.java
@@ -402,7 +402,7 @@ public class RestResponse extends
HttpServletResponseWrapper {
var encoders = request.getOpContext().getEncoders();
var ae =
request.getHeaderParam("Accept-Encoding").orElse(null);
- if (! (ae == null || ae.isEmpty())) {
+ if (ne(ae)) {
var match = encoders.getEncoderMatch(ae);
if (match == null) {
// Identity should always match unless
"identity;q=0" or "*;q=0" is specified.
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/convention/VersionProvider.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/convention/VersionProvider.java
index d5df7a6aee..7fc7d3d708 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/convention/VersionProvider.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/convention/VersionProvider.java
@@ -341,12 +341,12 @@ public class VersionProvider {
private static void ifNotEmpty(Map<String,String> attrs, String
key, Consumer<String> sink) {
var v = attrs.get(key);
- if (v != null && !v.isEmpty())
+ if (ne(v))
sink.accept(v);
}
private static void ifNotEmptyValue(String v, Consumer<String>
sink) {
- if (v != null && !v.isEmpty())
+ if (ne(v))
sink.accept(v);
}
}
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/httppart/RequestFormParamList.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/httppart/RequestFormParamList.java
index 1addc4d4e3..25b23c19fd 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/httppart/RequestFormParamList.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/httppart/RequestFormParamList.java
@@ -165,7 +165,7 @@ public class RequestFormParamList extends
ArrayList<RequestFormParam> {
}
} else {
c = req.getHttpServletRequest().getParts();
- if (c == null || c.isEmpty())
+ if (e(c))
m =
req.getHttpServletRequest().getParameterMap();
}
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/swagger/BasicSwaggerProviderSession.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/swagger/BasicSwaggerProviderSession.java
index b8793fcf46..c5abbfcf83 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/swagger/BasicSwaggerProviderSession.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/swagger/BasicSwaggerProviderSession.java
@@ -163,11 +163,11 @@ public class BasicSwaggerProviderSession {
}
private static MarshalledList nullIfEmpty(MarshalledList l) {
- return (l == null || l.isEmpty() ? null : l);
+ return e(l) ? null : l;
}
private static MarshalledMap nullIfEmpty(MarshalledMap m) {
- return (m == null || m.isEmpty() ? null : m);
+ return e(m) ? null : m;
}
static String joinnl(String[]...s) {