pod afBeanUtils

(Internal) Utilities and software patterns commonly associated with data objects

Mixins

NotFoundErr

Implement on user defined Errs to list available values in the stack trace.

Classes

BeanFactory

Creates Lists, Maps and other Objects, optionally setting field values.

BeanIdentity

equals(), hash() and toStr() methods using fields annotated with BeanId.

BeanProperties

Static methods to get and set bean values from property expressions.

ReflectUtils

Static methods for finding fields, methods and ctors that match given parameter types.

TypeCoercer

Coerces objects to a given type via fromXXX() / toXXX() ctors and methods.

TypeLookup

Looks up values via a type inheritance search.

Facets

BeanId

Place on fields to mark them as being important to the object's identity.

Errs

ArgNotFoundErr

A simple implementation of NotFoundErr that extends ArgErr.

Overview

Bean Utils is a support library that aids Alien-Factory in the development of other libraries, frameworks and applications. Though you are welcome to use it, you may find features are missing and the documentation incomplete.

Bean Utils is a collection of utilities and software patterns for overcoming common issues associated with data objects.

Features include:

  • Bean Identity

    Generate equals(), hash() and toStr() methods from annotated fields.

  • Bean Properties

    Get and set object properties and call methods via property expressions.

  • Type Coercer

    Convert objects, lists and maps from one type to another using Fantom's standard toXXX() and fromXXX() methods.

  • More!

    Utility methods to find matching ctors and methods.

Bean Utils is loosely named after JavaBeans,

Install

Install Bean Utils with the Fantom Repository Manager ( fanr ):

C:\> fanr install -r http://repo.status302.com/fanr/ afBeanUtils

To use in a Fantom project, add a dependency to build.fan:

depends = ["sys 1.0", ..., "afBeanUtils 1.0"]

Documentation

Full API & fandocs are available on the Status302 repository.

Bean Identity

Nobody likes writing hash() and equals() methods, so let BeanIdentity take the pain away! Simply annotate important identity fields with @BeanId and override the Obj methods.

Sample usage:

class User {
  @BeanId Int? id
  @BeanId Str? name
          Str? notUsed

  override Int hash() {
    BeanIdentity.beanHash(this)
  }

  override Bool equals(Obj? obj) {
    BeanIdentity.beanEquals(this, obj)
  }

  override Str toStr() {
    BeanIdentity.beanToStr(this)
  }
}

Bean Properties

BeanProperties is a nifty way to get and set properties, and call methods, on nested objects.

Properties are accessed via a property expression. Property expressions look like Fantom code and may traverse many objects. Their main purpose is to get and set properties, but may be used to call methods also.

string := "100 101 102"
BeanProperties.call(string, "split[1].get(2).plus(2).toChar") // --> 3

Using BeanProperties and a bit of naming convention care, it now becomes trivial to populate an object with properties submitted from a HTTP form:

formBean := BeanProperties.create(MyFormBean#, httpReq.form)

Features of property expressions include:

Field Access

The simplest use case is getting and setting basic fields. In this example we access the field Buf.capacity:

buf := Buf()
BeanProperties.get(buf, "capacity")     // --> 16
BeanProperties.set(buf, "capacity", 42) // set a new value

When setting fields, the given value is Type Coerced to fit the field type. Consider:

BeanProperties.set(buf, "charset", "UTF-16")  // string "UTF-16" is converted to a Charset object

Method Calls

Property expressions can call methods too. Like Fantom code, if the method does not take any parameters then brackets are optional:

buf := Buf()
BeanProperties.call(buf, "flush")
BeanProperties.call(buf, "flush()")

Method arguments may also form part of the expression, and like property values, are type coerced to their respective types:

BeanProperties.call(buf, "fill(255, 4)")    // --> 0xFFFFFFFF
BeanProperties.call(buf, "getRange(1..2)")  // --> 0xFFFF

Or you may pass arguments in:

BeanProperties.call(buf, "fill", [128, 4])      // --> 0x80808080
BeanProperties.call(buf, "getRange()", [1..2])  // --> 0x8080

Indexed Properties

Lists, Maps and @Operator shortcuts for get and set may all be traversed using square bracket notation:

list := Str?["a", "b", "c"]
BeanProperties.get(list, "[1]") // --> "b"

All keys and values are Type Coerced to the correct type.

Lists

When setting List items special attention is given make sure they don't throw IndexErrs. Should the list size be smaller than the given index, the list is automatically grown to accommodate:

list := Str?[,]
BeanProperties.set(list, "[1]", "b")
list.size                            // --> 2
list[0]                              // --> null
list[1]                              // --> "b"

If the list items are not nullable, then new objects are created:

list := Str[,]
BeanProperties.set(list, "[1]", "b")
list.size                            // --> 2
list[0]                              // --> ""
list[1]                              // --> "b"

Chaining

Property expressions become very powerful when chained:

obj.method(arg, arg).map[key].list[idx][operator].field

Object Creation

When traversing a property expression, the last thing you want is a NullErr half way through. With that in mind, should a property expression encounter null part way through, a new object is created and set.

Now you can happily chain your expressions with confidence!

Advanced

If you need more control over when and how intermediate objects are created, then use BeanPropertyFactory to manually parse property expressions and create your own BeanProperty instances.

Release Notes

v1.0.4

  • Chg: More nullable options in ReflectUtils.
  • Chg: TypeCoercer.canCoerce() may now take nulls.
  • Chg: Added TypeLookup.types().
  • Bug: Property makeFuncs could not return null.
  • Bug: NotFoundErr.availableValues could not hold null.

v1.0.2

  • New: Added ArgNotFoundErr, a handy impl of NotFoundErr.
  • New: Added NotFoundErr.valueMsg() so you can customise the msg.

v1.0.0

  • Chg: BeanProperties.create() takes a factoryFunc so IoC may instantiate the objects.

v0.0.4

  • New: Bean Utils is now available in JavaScript. (Added @Js to all classes.)
  • New: Use BeanProperties.create() to instantiate entire trees of objects from property expressions!
  • New: Use BeanFactory to reflectivly create instances of Lists, Maps and other Objects.
  • New: Added findFields(), findCtors() and findMethods() to ReflectUtils.
  • New: Added BeanFactory.defaultValue(type) as a replacement for Type.make.

v0.0.2

  • New: Preview release.