Skip to content

BeanCopyUtil silently drops boolean and char fields, so update(T) with a detached entity loses them #520

Description

@wisdommen

BeanCopyUtil.copyProperties copies every numeric field but silently skips every boolean and char one, so SimpleJsonDataOperator.update(T) loses a boolean change when the caller passes a detached entity.

Measured

utils/BeanCopyUtil.java reads each source field with Field.get, which boxes. For a primitive target field it then tests targetField.getType().isAssignableFrom(value.getClass()) — boolean.class.isAssignableFrom(Boolean.class) is false — and falls through to convertValue, whose branches are: same type, String, and value instanceof Number. Boolean is none of the three, so convertValue returns null and copyFieldValue returns without writing. convertNumber does handle int/long/double/float/short/byte, which is why only boolean and char are affected.

Probe against the real operator (UltiEssentials BanData, whose active is a primitive boolean, JSON backend over a temp directory, framework 6.3.0-SNAPSHOT):

PROBE copy id=747904b4-… active=true
   copy.setActive(false); store.update(copy);
PROBE after active=true      <-- the change was dropped

SimpleJsonDataOperator.update(T) is the reachable consumer: it resolves the cached instance for the incoming entity's id and calls BeanCopyUtil.copyProperties(obj, old, "id"). When a caller passes the cached instance itself, the copy is a self-copy and the mutation is already present, so nothing is lost — that is why no module has noticed. When a caller passes a detached instance with the same id (a copy, a Gson round trip, an entity rebuilt from a panel payload), every boolean field silently keeps its old value.

Suggested fix

Give convertValue a Boolean/boolean branch (and a Character/char one), or compare against the boxed equivalent of a primitive target type before deciding a conversion is needed. A test over an entity with one field of each primitive type would pin the whole set at once.

Scope

Found while writing the UltiEssentials wave-1 test fixtures for UltiKits/UltiEssentials#35 (Phase 17). Not itself a defect any current module triggers — every module passes the cached instance — so filing rather than fixing, per this phase's new-finding routing.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions