Package org.coliper.ibean

Core package of the IBean framework containing the core framework classes IBean and IBeanFactory.

Development Guide

Introduction

The IBean framework enables to specify Java beans as interfaces. Main purpose is to reduce the amount of boilerplate code that is necessary for writing Java beans including setters, getters and Object methods (Object.toString(), Object.equals(Object), Object.hashCode()). See following example how to declare a bean with IBean. You just specify the getter and setter methods in an interface class.

 public interface Person {
     String getFirstName();
     void setFirstName(String n);
     
     String getLastName();
     void setLastName(String n);
     
     Date getDateOfBirth();
     void setDateOfBirth(Date d);
 }
 

To retrieve and instance of this bean you call IBean.newOf(Class):


     Person personInstance = IBean.newOf(Person.class);
 

The returned instance has expected behavior for the specified getters and setters and also provides type specific implementation of Object.toString(), Object.equals(Object) and Object.hashCode().

Bean Interfaces

Any interface that only consists of getter and setter methods can be used as an IBean. No configuration or annotation is necessary. Only prerequisite is that you have a getter for each setter and vice versa. Details about IBean compatible interfaces can be found in the documentation of type IBeanFactory.

Getters and setters may be defined in different levels of an interface hierarchy. The type that is provided to the factory needs to contain complete getter and setter tuples.
For example you might want to declare all setters in a "builder" subclass to force immutability. Like this:


 public interface Person {
     String getFirstName();
     String getLastName();
     Date getDateOfBirth();
     
     public static interface Builder extends Person {
         void setFirstName(String n);
         void setLastName(String n);
         void setDateOfBirth(Date d);
     }
 }
 

You can then create the IBean via its builder:


     Person personInstance = IBean.newOf(Person.Builder.class);
 

Following code will not work:


     // will throw an exception as provided class Person contains no setters
     Person personInstance = IBean.newOf(Person.class);
 

You might wonder how the setters will be called in the example above. Builders in subclasses make most sense if you use a different BeanStyle that allows chained setters. See the chapter for for bean styles below.

Bean Factories

A bean factory creates IBean instances from provided interfaces. As seen in the examples before you can use the default factory that is set as static field in class IBean.

     Person personInstance = IBean.newOf(Person.class);
 

The IBean framework currently contains one implementation of IBeanFactory which is based on Java proxies: ProxyIBeanFactory. Future versions of IBean will contain more factory types.
If you need different settings than provided by the default factory inside IBean you can create your own factory instance:


     IBeanFactory factory = ProxyIBeanFactory.builder().withBeanStyle(BeanStyle.MODERN).build();
     Person personInstance = factory.create(Person.class);
 

In the previous example the factory is created with a different bean style. More about bean styles in the related chapter below below
If you create your own factory you can put it into IBean for global usage:


     IBeanFactory factory = ProxyIBeanFactory.builder().withBeanStyle(BeanStyle.MODERN).build();
     IBean.setFactory(factory);
     ...
     Person personInstance = IBean.newOf(Person.class);
 

For general information about bean factories see IBeanFactory. Documentation how to customize a factory can be found in ProxyIBeanFactory.

Object Methods

IBeans provide implementations of Object.equals(Object), Object.hashCode() and Object.toString(). Details about the implementations and how to customize them can be found in this chapter.

toString()

IBeans internally use ToStringBuilder to implement toString() methods. The default ToStringStyle is ToStringStyle.SHORT_PREFIX_STYLE. See this style to get an impression how a toString() output of an IBean looks like.

You can adopt the behavior of toString() by customizing the factory. This is described at ProxyIBeanFactory.Builder.withToStringStyle(org.apache.commons.lang3.builder.ToStringStyle).

equals()</code> and <code>hashCode()

IBeans also provide implementations for equals() and hashCode().
hashCode() will return a hash that is calculated from the hash codes of all field values.
equals() will return true when comparing two IBeans when
  • both beans are IBeans, that is, were created from the same IBeanFactory,
  • both beans were created for the same bean interface and
  • all field values of both beans are equal (equal comparison and not identity check).

equals() and hashCode() can both be customized. This is done by providing default interface methods named _equals respectively _hashCode with exactly the same signatures as the default Object methods. You can provide both methods or only just one but as equals() and hashCode() are related you have to take care that equal beans return the same hash code.
You can either provide customizations for individual instances or you can also create a super interface with _equals and _hashCode.

Following example shows a use case. You might want that beans equal if they have the same identifier regardless of their field values. So you create following base interface:


 public interface BeanWithIdentifier {
     Long getId();
     void setId(Long id);
     
     default boolean _equals(Object other) {
         if (other == null) return false;
         if (!this.getClass().equals(other.getClass())) return false;
         return Objects.equals(this.getId(), ((BeanWithIdentifier)other).getId());
     }
     
     default int _hashCode() {
         if (this.getId() ==  null) return -1;
        return this.getId().hashCode();
     }
 }
 

Extension Interfaces

Extension interfaces are a very powerful feature of the IBean framework. The behavior of an IBean can be enhanced by adding so called extension interfaces to bean type definitions. These extension interfaces
  • influence the way how getters or setters work and/or
  • add new methods to the beans.
The nature of these extension interfaces is that they are of general purpose so they can be used with a wide range of bean types regardless of the semantics of these types. So you can see the extension interfaces as a sort of aspect oriented extension but using interface extension rather than annotations.

For example following bean interface uses two extension interfaces ( NullSafe and Freezable):


 public interface Person extends NullSafe, Freezable {
    ...
 }
 

NullSafe does not add any own methods but influences the way how getter methods work. Freezable adopts the behavior of setter methods and also adds new methods to the bean. See the javadoc for both interfaces for more details.

The IBean framework already contains a set of extension interfaces but it is possible to create your own extensions. A list of predefined extension interfaces can be found in package org.coliper.ibean.extension. How to create a new extension interface is described in ExtensionHandler

Default Bean Interface

Often it makes sense that all bean interfaces use the same set of extension interfaces. If this is the case it is recommended to create a base interface that extends all extension interfaces that are commonly used. The concrete interfaces then have that base interface as their common super interface.

Bean Styles

Bean styles are a feature of the IBean framework to allow getters and setters to have different names and signatures as known from the Java Bean Specification. In the JavaBean specification setters are always prefixed with "set", have no parameter and return void. Similar rules exist for getters.

IBeans optionally now allow also a different style. For example the bean that was used in the examples above can also be defined like this:


 public interface Person {
     String firstName();
     String lastName();
     Date dateOfBirth();
     
     public static interface Builder extends Person {
         Builder firstName(String n);
         Builder lastName(String n);
         Builder dateOfBirth(Date d);
     }
 }
 

You can then create the IBean and immediately initialize it via chained setters:


     Person personInstance = IBean.newOf(Person.Builder.class).
          firstName("Al").
          lastName("Bundy").
          dateOfBirth(new Date());
 

The bean style used here (ModernBeanStyle) uses getters and setters without prefixes and returns the bean type from its setters. More details about bean styles in general can be found in the BeanStyle type documentation.

Jackson and Gson Support

IBeans can be serialized or deserialized to respectively from JSON. For this purpose the IBean framework provides an integration into the two most popular frameworks in that area, Jackson 2 and Gson.

Both integrations do not work out-of-the box. You need to integrate the bean factory into your Jackson respectively Gson settings. How this is done is described in GsonSerializerDeserializerForIBeans and Jackson2ModuleForIBeans.