blob: f77a6152bb4714d23393f20b8f49b8b133049f65 [file] [log] [blame]
package com.fasterxml.jackson.databind.introspect;
import java.util.Map;
import com.fasterxml.jackson.databind.AnnotationIntrospector;
import com.fasterxml.jackson.databind.PropertyMetadata;
import com.fasterxml.jackson.databind.PropertyName;
import com.fasterxml.jackson.databind.util.BeanUtil;
/**
* Helper class used for aggregating information about a single
* potential POJO property.
*/
public class POJOPropertyBuilder
extends BeanPropertyDefinition
implements Comparable<POJOPropertyBuilder>
{
/**
* Whether property is being composed for serialization
* (true) or deserialization (false)
*/
protected final boolean _forSerialization;
protected final AnnotationIntrospector _annotationIntrospector;
/**
* External name of logical property; may change with
* renaming (by new instance being constructed using
* a new name)
*/
protected final PropertyName _name;
/**
* Original internal name, derived from accessor, of this
* property. Will not be changed by renaming.
*/
protected final PropertyName _internalName;
protected Linked<AnnotatedField> _fields;
protected Linked<AnnotatedParameter> _ctorParameters;
protected Linked<AnnotatedMethod> _getters;
protected Linked<AnnotatedMethod> _setters;
public POJOPropertyBuilder(PropertyName internalName,
AnnotationIntrospector annotationIntrospector, boolean forSerialization)
{
_internalName = internalName;
_name = internalName;
_annotationIntrospector = annotationIntrospector;
_forSerialization = forSerialization;
}
@Deprecated // since 2.3
public POJOPropertyBuilder(String simpleInternalName,
AnnotationIntrospector annotationIntrospector, boolean forSerialization)
{
this(new PropertyName(simpleInternalName), annotationIntrospector, forSerialization);
}
public POJOPropertyBuilder(POJOPropertyBuilder src, PropertyName newName)
{
_internalName = src._internalName;
_name = newName;
_annotationIntrospector = src._annotationIntrospector;
_fields = src._fields;
_ctorParameters = src._ctorParameters;
_getters = src._getters;
_setters = src._setters;
_forSerialization = src._forSerialization;
}
/*
/**********************************************************
/* Fluent factory methods
/**********************************************************
*/
@Deprecated // since 2.3
@Override
public POJOPropertyBuilder withName(String newName) {
return withSimpleName(newName);
}
@Override
public POJOPropertyBuilder withName(PropertyName newName) {
return new POJOPropertyBuilder(this, newName);
}
@Override
public POJOPropertyBuilder withSimpleName(String newSimpleName)
{
PropertyName newName = _name.withSimpleName(newSimpleName);
return (newName == _name) ? this : new POJOPropertyBuilder(this, newName);
}
/*
/**********************************************************
/* Comparable implementation: sort alphabetically, except
/* that properties with constructor parameters sorted
/* before other properties
/**********************************************************
*/
@Override
public int compareTo(POJOPropertyBuilder other)
{
// first, if one has ctor params, that should come first:
if (_ctorParameters != null) {
if (other._ctorParameters == null) {
return -1;
}
} else if (other._ctorParameters != null) {
return 1;
}
/* otherwise sort by external name (including sorting of
* ctor parameters)
*/
return getName().compareTo(other.getName());
}
/*
/**********************************************************
/* BeanPropertyDefinition implementation, name/type
/**********************************************************
*/
@Override
public String getName() {
return (_name == null) ? null : _name.getSimpleName();
}
@Override
public PropertyName getFullName() {
return _name;
}
@Override
public String getInternalName() { return _internalName.getSimpleName(); }
@Override
public PropertyName getWrapperName() {
/* 13-Mar-2013, tatu: Accessing via primary member SHOULD work,
* due to annotation merging. However, I have seen some problems
* with this access (for other annotations)... so if this should
* occur, try commenting out full traversal code
*/
AnnotatedMember member = getPrimaryMember();
return (member == null || _annotationIntrospector == null) ? null
: _annotationIntrospector.findWrapperName(member);
/*
return fromMemberAnnotations(new WithMember<PropertyName>() {
@Override
public PropertyName withMember(AnnotatedMember member) {
return _annotationIntrospector.findWrapperName(member);
}
});
*/
}
@Override
public boolean isExplicitlyIncluded() {
return _anyExplicits(_fields)
|| _anyExplicits(_getters)
|| _anyExplicits(_setters)
|| _anyExplicits(_ctorParameters)
;
}
@Override
public boolean isExplicitlyNamed() {
return _anyExplicitNames(_fields)
|| _anyExplicitNames(_getters)
|| _anyExplicitNames(_setters)
|| _anyExplicitNames(_ctorParameters)
;
}
/*
/**********************************************************
/* BeanPropertyDefinition implementation, accessor access
/**********************************************************
*/
@Override
public boolean hasGetter() { return _getters != null; }
@Override
public boolean hasSetter() { return _setters != null; }
@Override
public boolean hasField() { return _fields != null; }
@Override
public boolean hasConstructorParameter() { return _ctorParameters != null; }
@Override
public boolean couldSerialize() {
return (_getters != null) || (_fields != null);
}
@Override
public AnnotatedMethod getGetter()
{
if (_getters == null) {
return null;
}
// If multiple, verify that they do not conflict...
AnnotatedMethod getter = _getters.value;
Linked<AnnotatedMethod> next = _getters.next;
for (; next != null; next = next.next) {
/* [JACKSON-255] Allow masking, i.e. report exception only if
* declarations in same class, or there's no inheritance relationship
* (sibling interfaces etc)
*/
AnnotatedMethod nextGetter = next.value;
Class<?> getterClass = getter.getDeclaringClass();
Class<?> nextClass = nextGetter.getDeclaringClass();
if (getterClass != nextClass) {
if (getterClass.isAssignableFrom(nextClass)) { // next is more specific
getter = nextGetter;
continue;
}
if (nextClass.isAssignableFrom(getterClass)) { // getter more specific
continue;
}
}
/* [Issue#238]: Also, regular getters have precedence over "is-getters", so
* latter can be skipped to resolve otherwise conflict.
* This is bit ugly as we have to re-process naming (as determination of type
* is not retained), but should work.
*/
boolean thisIsGetter = BeanUtil.okNameForIsGetter(getter, getter.getName()) != null;
boolean nextIsGetter = BeanUtil.okNameForIsGetter(nextGetter, nextGetter.getName()) != null;
if (thisIsGetter != nextIsGetter) {
if (thisIsGetter) {
getter = nextGetter;
}
continue;
}
throw new IllegalArgumentException("Conflicting getter definitions for property \""+getName()+"\": "
+getter.getFullName()+" vs "+nextGetter.getFullName());
}
return getter;
}
@Override
public AnnotatedMethod getSetter()
{
if (_setters == null) {
return null;
}
// If multiple, verify that they do not conflict...
AnnotatedMethod setter = _setters.value;
Linked<AnnotatedMethod> next = _setters.next;
for (; next != null; next = next.next) {
/* [JACKSON-255] Allow masking, i.e. report exception only if
* declarations in same class, or there's no inheritance relationship
* (sibling interfaces etc)
*/
AnnotatedMethod nextSetter = next.value;
Class<?> setterClass = setter.getDeclaringClass();
Class<?> nextClass = nextSetter.getDeclaringClass();
if (setterClass != nextClass) {
if (setterClass.isAssignableFrom(nextClass)) { // next is more specific
setter = nextSetter;
continue;
}
if (nextClass.isAssignableFrom(setterClass)) { // getter more specific
continue;
}
}
throw new IllegalArgumentException("Conflicting setter definitions for property \""+getName()+"\": "
+setter.getFullName()+" vs "+nextSetter.getFullName());
}
return setter;
}
@Override
public AnnotatedField getField()
{
if (_fields == null) {
return null;
}
// If multiple, verify that they do not conflict...
AnnotatedField field = _fields.value;
Linked<AnnotatedField> next = _fields.next;
for (; next != null; next = next.next) {
AnnotatedField nextField = next.value;
Class<?> fieldClass = field.getDeclaringClass();
Class<?> nextClass = nextField.getDeclaringClass();
if (fieldClass != nextClass) {
if (fieldClass.isAssignableFrom(nextClass)) { // next is more specific
field = nextField;
continue;
}
if (nextClass.isAssignableFrom(fieldClass)) { // getter more specific
continue;
}
}
throw new IllegalArgumentException("Multiple fields representing property \""+getName()+"\": "
+field.getFullName()+" vs "+nextField.getFullName());
}
return field;
}
@Override
public AnnotatedParameter getConstructorParameter()
{
if (_ctorParameters == null) {
return null;
}
/* Hmmh. Checking for constructor parameters is trickier; for one,
* we must allow creator and factory method annotations.
* If this is the case, constructor parameter has the precedence.
*
* So, for now, just try finding the first constructor parameter;
* if none, first factory method. And don't check for dups, if we must,
* can start checking for them later on.
*/
Linked<AnnotatedParameter> curr = _ctorParameters;
do {
if (curr.value.getOwner() instanceof AnnotatedConstructor) {
return curr.value;
}
curr = curr.next;
} while (curr != null);
return _ctorParameters.value;
}
@Override
public AnnotatedMember getAccessor()
{
AnnotatedMember m = getGetter();
if (m == null) {
m = getField();
}
return m;
}
@Override
public AnnotatedMember getMutator()
{
AnnotatedMember m = getConstructorParameter();
if (m == null) {
m = getSetter();
if (m == null) {
m = getField();
}
}
return m;
}
@Override
public AnnotatedMember getNonConstructorMutator() {
AnnotatedMember m = getSetter();
if (m == null) {
m = getField();
}
return m;
}
@Override
public AnnotatedMember getPrimaryMember() {
if (_forSerialization) {
return getAccessor();
}
return getMutator();
}
/*
/**********************************************************
/* Implementations of refinement accessors
/**********************************************************
*/
@Override
public Class<?>[] findViews() {
return fromMemberAnnotations(new WithMember<Class<?>[]>() {
@Override
public Class<?>[] withMember(AnnotatedMember member) {
return _annotationIntrospector.findViews(member);
}
});
}
@Override
public AnnotationIntrospector.ReferenceProperty findReferenceType() {
return fromMemberAnnotations(new WithMember<AnnotationIntrospector.ReferenceProperty>() {
@Override
public AnnotationIntrospector.ReferenceProperty withMember(AnnotatedMember member) {
return _annotationIntrospector.findReferenceType(member);
}
});
}
@Override
public boolean isTypeId() {
Boolean b = fromMemberAnnotations(new WithMember<Boolean>() {
@Override
public Boolean withMember(AnnotatedMember member) {
return _annotationIntrospector.isTypeId(member);
}
});
return (b != null) && b.booleanValue();
}
@Override
public PropertyMetadata getMetadata() {
final Boolean b = _findRequired();
final String desc = _findDescription();
if (b == null) {
return (desc == null) ? PropertyMetadata.STD_REQUIRED_OR_OPTIONAL
: PropertyMetadata.STD_REQUIRED_OR_OPTIONAL.withDescription(desc);
}
return PropertyMetadata.construct(b.booleanValue(), _findDescription());
}
protected Boolean _findRequired() {
Boolean b = fromMemberAnnotations(new WithMember<Boolean>() {
@Override
public Boolean withMember(AnnotatedMember member) {
return _annotationIntrospector.hasRequiredMarker(member);
}
});
return b;
}
protected String _findDescription() {
return fromMemberAnnotations(new WithMember<String>() {
@Override
public String withMember(AnnotatedMember member) {
return _annotationIntrospector.findPropertyDescription(member);
}
});
}
@Override
public ObjectIdInfo findObjectIdInfo() {
return fromMemberAnnotations(new WithMember<ObjectIdInfo>() {
@Override
public ObjectIdInfo withMember(AnnotatedMember member) {
ObjectIdInfo info = _annotationIntrospector.findObjectIdInfo(member);
if (info != null) {
info = _annotationIntrospector.findObjectReferenceInfo(member, info);
}
return info;
}
});
}
/*
/**********************************************************
/* Data aggregation
/**********************************************************
*/
public void addField(AnnotatedField a, PropertyName name, boolean explName, boolean visible, boolean ignored) {
_fields = new Linked<AnnotatedField>(a, _fields, name, explName, visible, ignored);
}
public void addCtor(AnnotatedParameter a, PropertyName name, boolean explName, boolean visible, boolean ignored) {
_ctorParameters = new Linked<AnnotatedParameter>(a, _ctorParameters, name, explName, visible, ignored);
}
public void addGetter(AnnotatedMethod a, PropertyName name, boolean explName, boolean visible, boolean ignored) {
_getters = new Linked<AnnotatedMethod>(a, _getters, name, explName, visible, ignored);
}
public void addSetter(AnnotatedMethod a, PropertyName name, boolean explName, boolean visible, boolean ignored) {
_setters = new Linked<AnnotatedMethod>(a, _setters, name, explName, visible, ignored);
}
/**
* Method for adding all property members from specified collector into
* this collector.
*/
public void addAll(POJOPropertyBuilder src)
{
_fields = merge(_fields, src._fields);
_ctorParameters = merge(_ctorParameters, src._ctorParameters);
_getters= merge(_getters, src._getters);
_setters = merge(_setters, src._setters);
}
private static <T> Linked<T> merge(Linked<T> chain1, Linked<T> chain2)
{
if (chain1 == null) {
return chain2;
}
if (chain2 == null) {
return chain1;
}
return chain1.append(chain2);
}
// // Deprecated variants that do not take 'explName': to be removed in a later version
// // (but are used at least by 2.3 and earlier versions of Scala module at least so
// // need to be careful with phasing out if before 3.0)
/**
* @deprecated Since 2.4 call method that takes additional 'explName' argument, to indicate
* whether name of property was provided by annotation (and not derived from accessor name);
* this method simply passes 'true' for it.
*/
@Deprecated
public void addField(AnnotatedField a, String name, boolean visible, boolean ignored) {
addField(a, _propName(name), true, visible, ignored);
}
@Deprecated
public void addField(AnnotatedField a, String name, boolean explName, boolean visible, boolean ignored) {
addField(a, _propName(name), explName, visible, ignored);
}
/**
* @deprecated Since 2.4 call method that takes additional 'explName' argument, to indicate
* whether name of property was provided by annotation (and not derived from accessor name);
* this method simply passes 'true' for it.
*/
@Deprecated
public void addCtor(AnnotatedParameter a, String name, boolean visible, boolean ignored) {
addCtor(a, _propName(name), true, visible, ignored);
}
@Deprecated
public void addCtor(AnnotatedParameter a, String name, boolean explName, boolean visible, boolean ignored) {
addCtor(a, _propName(name), explName, visible, ignored);
}
/**
* @deprecated Since 2.4 call method that takes additional 'explName' argument, to indicate
* whether name of property was provided by annotation (and not derived from accessor name);
* this method simply passes 'true' for it.
*/
@Deprecated
public void addGetter(AnnotatedMethod a, String name, boolean visible, boolean ignored) {
addGetter(a, _propName(name), true, visible, ignored);
}
@Deprecated
public void addGetter(AnnotatedMethod a, String name, boolean explName, boolean visible, boolean ignored) {
addGetter(a, _propName(name), explName, visible, ignored);
}
/**
* @deprecated Since 2.4 call method that takes additional 'explName' argument, to indicate
* whether name of property was provided by annotation (and not derived from accessor name);
* this method simply passes 'true' for it.
*/
@Deprecated
public void addSetter(AnnotatedMethod a, String name, boolean visible, boolean ignored) {
addSetter(a, _propName(name), true, visible, ignored);
}
@Deprecated
public void addSetter(AnnotatedMethod a, String name, boolean explName, boolean visible, boolean ignored) {
addSetter(a, _propName(name), explName, visible, ignored);
}
private PropertyName _propName(String simple) {
return PropertyName.construct(simple, null);
}
/*
/**********************************************************
/* Modifications
/**********************************************************
*/
/**
* Method called to remove all entries that are marked as
* ignored.
*/
public void removeIgnored()
{
_fields = _removeIgnored(_fields);
_getters = _removeIgnored(_getters);
_setters = _removeIgnored(_setters);
_ctorParameters = _removeIgnored(_ctorParameters);
}
public void removeNonVisible(boolean force)
{
/* 21-Aug-2011, tatu: This is tricky part -- if and when allow
* non-visible property elements to be "pulled in" by visible
* counterparts?
* For now, we will only do this to pull in setter or field used
* as setter, if an explicit getter is found.
*/
/*
* 28-Mar-2013, tatu: Also, as per [Issue#195], may force removal
* if inferred properties are NOT supported.
*/
_getters = _removeNonVisible(_getters);
_ctorParameters = _removeNonVisible(_ctorParameters);
if (force || (_getters == null)) {
_fields = _removeNonVisible(_fields);
_setters = _removeNonVisible(_setters);
}
}
/**
* Method called to trim unnecessary entries, such as implicit
* getter if there is an explict one available. This is important
* for later stages, to avoid unnecessary conflicts.
*/
public void trimByVisibility()
{
_fields = _trimByVisibility(_fields);
_getters = _trimByVisibility(_getters);
_setters = _trimByVisibility(_setters);
_ctorParameters = _trimByVisibility(_ctorParameters);
}
@SuppressWarnings("unchecked")
public void mergeAnnotations(boolean forSerialization)
{
if (forSerialization) {
if (_getters != null) {
AnnotationMap ann = _mergeAnnotations(0, _getters, _fields, _ctorParameters, _setters);
_getters = _getters.withValue(_getters.value.withAnnotations(ann));
} else if (_fields != null) {
AnnotationMap ann = _mergeAnnotations(0, _fields, _ctorParameters, _setters);
_fields = _fields.withValue(_fields.value.withAnnotations(ann));
}
} else { // for deserialization
if (_ctorParameters != null) {
AnnotationMap ann = _mergeAnnotations(0, _ctorParameters, _setters, _fields, _getters);
_ctorParameters = _ctorParameters.withValue(_ctorParameters.value.withAnnotations(ann));
} else if (_setters != null) {
AnnotationMap ann = _mergeAnnotations(0, _setters, _fields, _getters);
_setters = _setters.withValue(_setters.value.withAnnotations(ann));
} else if (_fields != null) {
AnnotationMap ann = _mergeAnnotations(0, _fields, _getters);
_fields = _fields.withValue(_fields.value.withAnnotations(ann));
}
}
}
private AnnotationMap _mergeAnnotations(int index, Linked<? extends AnnotatedMember>... nodes)
{
AnnotationMap ann = nodes[index].value.getAllAnnotations();
++index;
for (; index < nodes.length; ++index) {
if (nodes[index] != null) {
return AnnotationMap.merge(ann, _mergeAnnotations(index, nodes));
}
}
return ann;
}
private <T> Linked<T> _removeIgnored(Linked<T> node)
{
if (node == null) {
return node;
}
return node.withoutIgnored();
}
private <T> Linked<T> _removeNonVisible(Linked<T> node)
{
if (node == null) {
return node;
}
return node.withoutNonVisible();
}
private <T> Linked<T> _trimByVisibility(Linked<T> node)
{
if (node == null) {
return node;
}
return node.trimByVisibility();
}
/*
/**********************************************************
/* Accessors for aggregate information
/**********************************************************
*/
private <T> boolean _anyExplicits(Linked<T> n)
{
for (; n != null; n = n.next) {
if (n.name != null && n.name.hasSimpleName()) {
return true;
}
}
return false;
}
private <T> boolean _anyExplicitNames(Linked<T> n)
{
for (; n != null; n = n.next) {
if (n.name != null && n.isNameExplicit) {
return true;
}
}
return false;
}
public boolean anyVisible() {
return _anyVisible(_fields)
|| _anyVisible(_getters)
|| _anyVisible(_setters)
|| _anyVisible(_ctorParameters)
;
}
private <T> boolean _anyVisible(Linked<T> n)
{
for (; n != null; n = n.next) {
if (n.isVisible) {
return true;
}
}
return false;
}
public boolean anyIgnorals() {
return _anyIgnorals(_fields)
|| _anyIgnorals(_getters)
|| _anyIgnorals(_setters)
|| _anyIgnorals(_ctorParameters)
;
}
private <T> boolean _anyIgnorals(Linked<T> n)
{
for (; n != null; n = n.next) {
if (n.isMarkedIgnored) {
return true;
}
}
return false;
}
/**
* @since 2.4 Use {@link #findNewNames} instead
*/
/*
@Deprecated
public String findNewName()
{
Map<String,POJOPropertyBuilder> r = findRenamed();
if (r == null) {
return null;
}
return r.entrySet().iterator().next().getKey();
}
*/
/**
* Method called to check whether property represented by this collector
* should be renamed from the implicit name; and also verify that there
* are no conflicting rename definitions.
*/
// public Map<String,POJOPropertyBuilder> findRenamed()
public String findNewName()
{
// Map<String,POJOPropertyBuilder> renamed = null;
Linked<? extends AnnotatedMember> renamed = null;
renamed = findRenamed(_fields, renamed);
renamed = findRenamed(_getters, renamed);
renamed = findRenamed(_setters, renamed);
renamed = findRenamed(_ctorParameters, renamed);
return (renamed == null) ? null : renamed.name.getSimpleName();
}
private Linked<? extends AnnotatedMember> findRenamed(Linked<? extends AnnotatedMember> node,
Linked<? extends AnnotatedMember> renamed)
{
for (; node != null; node = node.next) {
if (!node.isNameExplicit) {
continue;
}
PropertyName name = node.name;
// different from default name?
/* 14-Mar-2014, tatu: As per [#369], Must match local name... but,
* shouldn't really exclude namespace. Not sure what's the best
* fix but for now, let's not worry about that.
*/
if (name.equals(_name)) { // nope, skip
continue;
}
if (renamed == null) {
renamed = node;
} else {
// different from an earlier renaming? problem
if (!name.equals(renamed.name)) {
throw new IllegalStateException("Conflicting property name definitions: '"
+renamed.name+"' (for "+renamed.value+") vs '"
+node.name+"' (for "+node.value+")");
}
}
}
return renamed;
}
// For trouble-shooting
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("[Property '").append(_name)
.append("'; ctors: ").append(_ctorParameters)
.append(", field(s): ").append(_fields)
.append(", getter(s): ").append(_getters)
.append(", setter(s): ").append(_setters)
;
sb.append("]");
return sb.toString();
}
/*
/**********************************************************
/* Helper methods
/**********************************************************
*/
/**
* Helper method used for finding annotation values, from accessors
* relevant to current usage (deserialization, serialization)
*/
protected <T> T fromMemberAnnotations(WithMember<T> func)
{
T result = null;
if (_annotationIntrospector != null) {
if (_forSerialization) {
if (_getters != null) {
result = func.withMember(_getters.value);
}
} else {
if (_ctorParameters != null) {
result = func.withMember(_ctorParameters.value);
}
if (result == null && _setters != null) {
result = func.withMember(_setters.value);
}
}
if (result == null && _fields != null) {
result = func.withMember(_fields.value);
}
}
return result;
}
/*
/**********************************************************
/* Helper classes
/**********************************************************
*/
private interface WithMember<T> {
public T withMember(AnnotatedMember member);
}
/**
* Node used for creating simple linked lists to efficiently store small sets
* of things.
*/
private final static class Linked<T>
{
public final T value;
public final Linked<T> next;
public final PropertyName name;
public final boolean isNameExplicit;
public final boolean isVisible;
public final boolean isMarkedIgnored;
public Linked(T v, Linked<T> n,
PropertyName name, boolean explName, boolean visible, boolean ignored)
{
value = v;
next = n;
// ensure that we'll never have missing names
this.name = (name == null || !name.hasSimpleName()) ? null : name;
isNameExplicit = explName;
isVisible = visible;
isMarkedIgnored = ignored;
}
public Linked<T> withValue(T newValue) {
if (newValue == value) {
return this;
}
return new Linked<T>(newValue, next, name, isNameExplicit, isVisible, isMarkedIgnored);
}
public Linked<T> withNext(Linked<T> newNext) {
if (newNext == next) {
return this;
}
return new Linked<T>(value, newNext, name, isNameExplicit, isVisible, isMarkedIgnored);
}
public Linked<T> withoutIgnored() {
if (isMarkedIgnored) {
return (next == null) ? null : next.withoutIgnored();
}
if (next != null) {
Linked<T> newNext = next.withoutIgnored();
if (newNext != next) {
return withNext(newNext);
}
}
return this;
}
public Linked<T> withoutNonVisible() {
Linked<T> newNext = (next == null) ? null : next.withoutNonVisible();
return isVisible ? withNext(newNext) : newNext;
}
/**
* Method called to append given node(s) at the end of this
* node chain.
*/
private Linked<T> append(Linked<T> appendable) {
if (next == null) {
return withNext(appendable);
}
return withNext(next.append(appendable));
}
public Linked<T> trimByVisibility() {
if (next == null) {
return this;
}
Linked<T> newNext = next.trimByVisibility();
if (name != null) { // this already has highest; how about next one?
if (newNext.name == null) { // next one not, drop it
return withNext(null);
}
// both have it, keep
return withNext(newNext);
}
if (newNext.name != null) { // next one has higher, return it...
return newNext;
}
// neither has explicit name; how about visibility?
if (isVisible == newNext.isVisible) { // same; keep both in current order
return withNext(newNext);
}
return isVisible ? withNext(null) : newNext;
}
@Override
public String toString() {
String msg = value.toString()+"[visible="+isVisible+",ignore="+isMarkedIgnored
+",explicitName="+isNameExplicit+"]";
if (next != null) {
msg = msg + ", "+next.toString();
}
return msg;
}
}
}