Table of contents
The Adapter PatternIn the well-known "Design Patterns: Elements of Reusable Object-Oriented Software" book by GoF the purpose of an adapter is described in the following way: "Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces." [1]
Using the adapter pattern it is easy to expand possibilities of the system being designed during all development stages. Advantages of this approach felt on the early stages became especially strong later, when often new requirements or new product versions appear.
In our opinion, an interesting
Java implementation of the adapter
pattern as the org.eclipse.core.runtime.IAdaptaptable
interface is represented in the IBM@TM
Eclipse IDE. The Object
getAdapter(Class adapter) method defined in this
interface returns an instance of the object, which
class is delivered as an argument, or null
if the adaptable object does not provide such an
adapter.
The same functionality is also
provided by the EMF@TM org.eclipse.emf.common.notify.Notifier
interface. The EList eAdapters()
method provides instances of adapters. The
returned list contains descendants of org.eclipse.emf.common.notify.Adapter
(the base EMF adapter interface). The Factory
org.eclipse.emf.common.notify.AdapterFactory class
supports instantiation of adapters. Thus, on the
compiling level the adaptable object is not
dependent on types of the provided adapters.
The program mechanisms described above had been developed even before Java 1.5 [2] well-known by its revolutionary expansions was released . Moreover, to provide compatibility, it seems they for a long time will be oriented toward the previous Java versions.
When we were implementing the adapter pattern, it was not necessary for us to support the old Java versions. Therefore, we were free in usage of the convenient innovations performed by Java 1.5
The interfaces of the adapter
mechanism are put in the org.fishbolt.common.adapter
package. These interfaces help a developer easily
create his own interfaces on the base of the
adapters and adaptable objects.
The interface part of API includes two interfaces:
IAdapter - an
adapterIAdaptable – an
adaptable objectThe IAdapter
interface is a modifier (it does not contain any
methods). Such an interface-modifier seems to be
justified because it explicitly shows that access
to instances of a class/interface that
implements/extends IAdapter is
provided through the adapter mechanism.
This is the interface of an adaptable object. It defines only one method that provides access to adapters:
<A extends IAdapter> A
getAdapter(Class<A> adapterType)
The method parameter sets a type of the adapter, which instance we want to get. As we can see from the method declaration, there can be only one adapter of the declared type for every adaptable object
Therefore, if it is necessary to bind several objects of the same type with an adaptable object, its adapter has to "wrap up" all these the same-type objects in it and provide access to them.
The base implementation of the
adapter mechanism is placed in the org.fishbolt.common.adapter.impl
package. It consists of the following classes and
interfaces:
Adaptable - the
base implementation of an adaptable objectAdapterFactoryManager
- the manager of adapter FactoriesIAdapterFactory -
the interface of an adapter Factory (for creating
adapters)SingleAdapterFactory
- the base implementation of the IAdapterFactory
interface that can create an adapter of one type
onlyThe Adaptable class
has been designed as a base class for all
adaptable objects. Adapters are saved in the
internal registry of the adaptable object. When a
request to some adapter is received, it is being
searched in this list. If there is no such
adapter, a new instance of the adapter is created
Objects implementing the IAdapterFactory
interface are responsible for creating adapters.
To control Factories, a special class has been
provided - AdapterFactoryManager. It
contains the registry of Factories that produce
adapters. In the context of one system the AdapterFactoryManager
object generally exists as a single instance. All
adaptable objects address to it in case the
required adapter instance is absent in their
registry. The AdapterFactoryManager
object finds the Factory that is responsible for
creation of the adapter with the required type and
delegates its creation to this Factory. The newly
created object is added to the registry of the
adaptable object.
Below you can find the detailed description of the proposed classes and interfaces.
This is an abstract
implementation of the adaptable object. It
contains the (java.util.Map) registry
for instances of adapters. Every instance in the
registry is associated with its type
The getAdapter(Class)
method makes a search in the registry for an
adapter of the type defined in the argument (or an
adapter of the type that could be cast to this
type). In case the required adapter is absent in
the registry the adaptable object calls the createAdapter
method on the instance of AdapterFactoryManager
class to create a new instance of the adapter. The
newly created adapter is put into the registry.
The getAdapterFactoryManager()
method provides an instance of the AdapterFactoryManager
class. This method is abstract in Adaptable
class.
In the context of one system the
object of AdapterFactoryManager class
generally exists as a single instance. It contains
a registry of instances of classes that implement
the IAdapterFactory interface. A new
Factory object can be put into the registry with
the register(IAdapterFactory) method.
To delegate creation of the
concrete adapter for the adaptable object, the createAdapter(Class,
IAdaptable) method chooses the corresponding
Factory instance from the registry.
The classes that implement the IAdapterFactory
interface are responsible for creating adapters
for adaptable objects.
Class<?
extends IAdapter>[] getAdapterClasses() – This
method declares the types of adapters, which
instances can be created by the Factory<A extends
IAdapter> A create(Class<A> adapterType,
IAdaptable adaptable) – Performs the creation of
an adapter instance for the adaptable object. If
the Factory does not support the adapter for this
adaptable object, the returned value is null
Parameters:
The SingleAdapterFactory
implementation defines two constructors:
SingleAdapterFactory(Class<?
extends IAdapter>) – The constructor of the
Factory that produces adapters of the required
type for any adaptable objectSingleAdapterFactory(Class<?
extends IAdapter>, Class<? extends
IAdaptable> ...) - The constructor of the
Factory that produces adapters of the type
declared in the first parameter. The adapter is
created only for the adaptable objects that can
be cast to one of the types delivered as other
parameters.Note that an adapter is
instantiated with the newInstance()
method of its Class instance using reflection. The
newInstance() method invocation is
encapsulated in the instantiate(Class<A>,
IAdaptable) method. So, if a developer wants to
create adapter instances some other way he should
override the instantiate(Class<A>,
IAdaptable) method.
Let us assume that at some
development stage we created two interfaces that
present data structures – IDepartment
and IEmployee:
/**
* "Department" data object
*/
public interface IDepartment extends IAdaptable {
/**
* @return department name
*/
String getName();
/**
* @param name department name
*/
void setName(String name);
/**
* @return department employees
*/
Set<IEmployee> getEmployes();
/**
* @param employes new department employees
*/
void setEmployes(Set<IEmployee> employes);
}
/**
* "Employee" data object
*/
public interface IEmployee extends IAdaptable {
/**
* @return employee first name
*/
String getFirstName();
/**
* @param name employee first name
*/
void setFirstName(String name);
/**
* @return employee last name
*/
String getLastName();
/**
* @param name employee last name
*/
void setLastName(String name);
/**
* @return employee phone number
*/
String getPhoneNumber();
/**
* @param phoneNumber employee phone number
*/
void setPhoneNumber(String phoneNumber);
/**
* @return employee's department
*/
IDepartment getDepartment();
/**
* @param department employee's department
*/
void setDepartment(IDepartment department);
}
Then a new requirement appeared: to provide a string value of the object for its visualization in the user interface. The task of data representation is solved by the adapter
/**
* Provides string presentation for adaptable object.
*/
public interface IDisplayLabelAdapter extends IAdapter {
/**
* @param adaptable
* @return string presentation of the adaptable object
*/
String getDisplayLabel(IAdaptable adaptable);
}
Describing the relationships
between objects-structures and the adapter in
terms of MVC we can say that the IDisplayLabelAdapter
interface works as a controller.
Data objects:
/**
* Base adaptable object
*/
public class BaseDataObject extends Adaptable {
/**
* static instance of {@link AdapterFactoryManager}
* <br>
* In the context of one system the {@link AdapterFactoryManager}
* object generally exists as a single instance.
* That's why the instance is static.
*/
private static AdapterFactoryManager factoryManager;
@Override
protected AdapterFactoryManager getAdapterFactoryManager() {
if (factoryManager == null){
factoryManager = new AdapterFactoryManager();
factoryManager.register(new DisplayLabelAdapterFactory());
}
return factoryManager;
}
}
/**
* "Department" data object implementation
*/
public class Department extends BaseDataObject implements IDepartment {
private String name;
private Set<IEmployee> employes;
public Set<IEmployee> getEmployes() {
return employes;
}
public void setEmployes(Set<IEmployee> employes) {
this.employes = employes;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
/**
* "Employee" data object implementation
*/
public class Employee extends BaseDataObject implements IEmployee {
private String firstName;
private String lastName;
private String phoneNumber;
private IDepartment department;
public IDepartment getDepartment() {
return department;
}
public void setDepartment(IDepartment department) {
this.department = department;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
Representation:
/**
* {@link IAdapterFactory} which creates {@link IDisplayLabelAdapter} adapters.
*/
public class DisplayLabelAdapterFactory extends SingleAdapterFactory {
/**
* Adapter for "Employee" data object.
*/
private final IDisplayLabelAdapter employeeAdapter = new IDisplayLabelAdapter(){
public String getDisplayLabel(IAdaptable adaptable) {
IEmployee employee = (IEmployee)adaptable;
StringBuffer result = new StringBuffer(employee.getFirstName());
result.append(" ");
result.append(employee.getLastName());
return result.toString();
}
};
/**
* Adapter for "Department" data object.
*/
private final IDisplayLabelAdapter departmentAdapter = new IDisplayLabelAdapter(){
public String getDisplayLabel(IAdaptable adaptable) {
IDepartment department = (IDepartment)adaptable;
return department.getName();
}
};
/**
* Constructor
*/
@SuppressWarnings("unchecked")
public DisplayLabelAdapterFactory() {
super(IDisplayLabelAdapter.class,IEmployee.class,IDepartment.class);
}
@Override
@SuppressWarnings("unchecked")
protected <A extends IAdapter> A instantiate(Class<A> adapterType, IAdaptable object) {
return (A)( (object instanceof IEmployee)? employeeAdapter : departmentAdapter);
}
}
/**
* The Adapter usage example
*/
public class Sample {
/**
* @param args
*/
public static void main(String[] args) {
// department creation
IDepartment department = new Department();
department.setName("Sales");
printLabel(department); // department displaying
// employee creation
IEmployee employee = new Employee();
employee.setFirstName("John");
employee.setLastName("Doe");
employee.setPhoneNumber("88");
employee.setDepartment(department);
printLabel(employee); //employee displaying
}
/**
* Displays object label to the system output
* @param adaptable object to display
*/
public static void printLabel(IAdaptable adaptable){
IDisplayLabelAdapter adapter =
adaptable.getAdapter(IDisplayLabelAdapter.class);
System.out.println(adapter.getDisplayLabel(adaptable));
}
}
Let us suppose that the program product has been released, and in the next version of the graphical user interface the customer wants an icon to be put near the string visual element that represents the corresponding data object. To fulfill this new requirement we will create an icon display adapter.
Interface for the icon display adapter:
/**
* Provides an icon for adapter.
*/
public interface IDisplayIconAdapter extends IAdapter {
/**
* @param adaptable
* @return icon for adaptable instance
*/
Icon getDisplayIcon(IAdaptable adaptable);
}
And a Factory for IDisplayIconAdapter:
/**
* {@link IAdapterFactory} which creates {@link IDisplayIconAdapter} adapters.
*/
public class DisplayIconAdapterFactory extends SingleAdapterFactory {
static class DisplayIconAdapter implements IDisplayIconAdapter{
private Icon icon;
DisplayIconAdapter(String resourceName){
icon = new ImageIcon(resourceName);
}
public Icon getDisplayIcon(IAdaptable adaptable) {
return icon;
}
};
/**
* Employee icon adapter
*/
private final IDisplayIconAdapter employeeAdapter =
new DisplayIconAdapter("employee.ico");
/**
* Department icon adapter
*/
private final IDisplayIconAdapter departmentAdapter =
new DisplayIconAdapter("department.ico");
/**
* Constructor
*/
@SuppressWarnings("unchecked")
public DisplayIconAdapterFactory() {
super(IDisplayIconAdapter.class,IEmployee.class,IDepartment.class);
}
@Override
@SuppressWarnings("unchecked")
protected <A extends IAdapter> A instantiate(Class<A> adapterType, IAdaptable object) {
return (A)( (object instanceof IEmployee)? employeeAdapter : departmentAdapter);
}
}
And let us not forget to register the new Factory:
public
class BaseDataObject extends Adaptable {
…
private static AdapterFactoryManager
adapterFactoryManager() {
if (factoryManager == null){
…
factoryManager.register(new
DisplayIconAdapterFactory());
}
return factoryManager;
}
}
In such a way we have quite easily expanded the functionality practically without changing the existing program code.
The Command patternIn the "Design Patterns: Elements of Reusable Object-Oriented Software" book the description of the command pattern is provided mainly in the aspect of its usage in the graphical user interface (undo for an operation, command history).
Here we want to put an emphasis on the internal implementation of a command. The main goal of the proposed pattern version is to hide the resources used by a command (database connections, database transactions, files with data, etc.) from the user.
The main advantage of using this pattern version is flexibility in managing resources and their configuration.
Let us consider two examples that show how we can support the database functionality.
public
static void main(String[] a) throws
ClassNotFoundException, SQLException{
Class.forName("org.postgresql.Driver");
Connection connection =
DriverManager.getConnection(
"jdbc:postgresql://localhost/example",
"postgres",
"*******");
DepartmentManager m
=DepartmentManager.getInstance();
Department d = m.findDepartment(
"Research & Develoment",
connection);
if (d == null){
d = m.createNewDepartment(
"Research & Develoment",
connection);
}
m.assignEmployeeDepartment(3, d,
connection);
}
And here is the second example that uses the command pattern:
public
static void main(String[] arg){
DepartmentManager m =
DepartmentManager.getInstance();
Department d =
m.findDepartment("Research & Develoment");
if (d == null){
d =
m.createNewDepartment("Research &
Develoment");
}
m.assignEmployeeDepartment(3, d);
}
More illustrative example of
effective use of commands is presented by the org.fishbolt.hibernate
library
[3]
The interface part consists of
three interfaces: java.util.concurrent.Callable,
ICommand and ICommandProcessor.
As one might know, Callable
interface defines only one method – call()
– that returns the result of command execution. In
our case an implementation of the method should
not solve any problems connected with providing an
access to some resource.
ICommandProcessor
interface defines the only method – processCommand(Callable).
The method gets an instance of a command as an
argument and invokes its call()
method. It is the processCommand(Callable)
method that all operations responsible for
resource management are encapsulated in.
In case a command delegates its
call to another command it should not invoke that
command's call() method itself. It
will be more correct to execute the command using
the processor because it is responsible for
managing recourses. An instance of ICommandProcessor
can be passed to a command via setCommandProcessor(ICommandProcessor)
method that is declared in the IСommand
interface. (The IСommand interface
extends Callable.) The method setCommandProcessor(ICommandProcessor)
has to be called by the processor just before the
call() method invocation. Thus, the
command "knows" about an instance of the processor
that is executing it and is able to correctly
delegate the call to the other command.
As a wrong example, consider an application that exchanges data with a database. JavaBeans are used as data objects in this application.
/**
* "Department" data object
*/
public class Department{
private String name;
private Set<Employee> employees;
/**
* @return department employees
*/
public Set<Employee> getEmployees() {
return employees;
}
/**
* @param employes new department employees
*/
public void setEmployees(Set<Employee> employes) {
this.employees = employes;
}
/**
* @return department name
*/
public String getName() {
return name;
}
/**
* @param name department name
*/
public void setName(String name) {
this.name = name;
}
}
/**
* "Employee" data object
*/
public class Employee{
private int id;
private String firstName;
private String lastName;
private String phoneNumber;
private Department department;
/**
* @return employee's department
*/
public Department getDepartment() {
return department;
}
/**
* @param department employee's department
*/
public void setDepartment(Department department) {
this.department = department;
}
/**
* @return employee last name
*/
public String getLastName() {
return lastName;
}
/**
* @param lastName employee last name
*/
public void setLastName(String lastName) {
this.lastName = lastName;
}
/**
* @return employee first name
*/
public String getFirstName() {
return firstName;
}
/**
* @param firstName employee first name
*/
public void setFirstName(String firstName) {
this.firstName = firstName;
}
/**
* @return employee phone number
*/
public String getPhoneNumber() {
return phoneNumber;
}
/**
* @param phoneNumber employee phone number
*/
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
/**
* @return employee identity
*/
public int getId() {
return id;
}
/**
* @param id employee identity
*/
public void setId(int id) {
this.id = id;
}
}
Data processing operations (loading data from the database, storing data in the database, etc.) are implemented in business methods of the so-called object managers. A database connection instance is passed as an argument to every business method.
/**
* "Department" object manager. Singleton.
* <p>
* In this example the JDBC connection is directly
* delivered to every business method of the object managers.
* </p>
*
* This approach has the following disadvantages:
* <ol>
* <li>
* This implementation directly depends on JDBC,
* therefore, it can be supposed that moving to
* another database technology, for example,
* some higher-level technology, will be very laborious.
* </li>
* <li>
* There is the following problem: the JDBS connection
* still has not been implemented in the
* manager implementation. As a result, we are not
* able to control the number of connections, and there also
* is a source of additional errors connected with
* initialization of the database connection.
* </li>
* <li>
* Inconvenience in handling exceptions.
* We have to repeat the code connected with
* processing exceptions in every business method.
* Changes in the processing policy move to a lot of
* changes in the program code.
* </li>
* <li>
* There can be problems if we work with a
* manager from several threads simultaneously,
* for example, if business methods are called from
* different threads, but with the same connection instance.
* </li>
* </ol>
*/
public class DepartmentManager{
private static DepartmentManager instance;
private DepartmentManager(){}
/**
* @return manager instance
*/
public static DepartmentManager getInstance() {
return instance == null? instance = new DepartmentManager() : instance;
}
/**
* Creates department with the specified name.
*
* @param name department name
* @param connection JDDC connection to database
*
* @return new department instance
*/
public Department createNewDepartment(String name,Connection connection){
Department result = null;
CallableStatement statement = null;
try {
statement = connection.prepareCall("insert into department(name) values (?)");
statement.setString(1,name);
statement.execute();
connection.commit();
result = new Department();
result.setName(name);
} catch (SQLException e) {
try {
connection.rollback();
throw new RuntimeException(e);
} catch (SQLException e1) {
throw new RuntimeException(e1);
}
}finally{
try {
if (statement!=null) statement.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
return result;
}
/**
* Searches department with the specified name
*
* @param name department name
* @param connection JDDC connection to database
*
* @return department instance or <code>null</code>
*/
public Department findDepartment(String name,Connection connection){
Department result = null;
CallableStatement statement = null;
try {
statement = connection.prepareCall(
"select name from department where name =?");
statement.setString(1,name);
ResultSet set = statement.executeQuery();
if(set.next()){
set.close();
result = new Department();
result.setName(name);
HashSet<Employee> employess = new HashSet<Employee>();
statement = connection.prepareCall(
"select id, firstName, lastName, phoneNumber " +
"from employee where department =?");
statement.setString(1,name);
set = statement.executeQuery();
while (set.next()){
Employee employee = new Employee();
employee.setDepartment(result);
employee.setId(set.getInt(1));
employee.setFirstName(set.getString(2));
employee.setLastName(set.getString(3));
employee.setPhoneNumber(set.getString(4));
employess.add(employee);
}
result.setEmployees(employess);
}
} catch (SQLException e) {
throw new RuntimeException(e);
}finally{
try {
if (statement!=null) statement.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
return result;
}
/**
* Assigns employee to the department.
*
* @param employeeId employee identity
* @param department department
* @param connection JDDC connection to database
*/
public void assignEmployeeDepartment(int employeeId,Department department,Connection connection){
CallableStatement statement = null;
try {
statement = connection.prepareCall(
"update employee set department=? where id=?");
statement.setString(1,department.getName());
statement.setInt(2,employeeId);
statement.execute();
connection.commit();
/* Correct instances in memory if necessary
...
Employee employee =
EmployeeManager.getInstance().findEmployee(employeeId,connection);
Department oldDepartment = employee.getDepartment();
if (oldDepartment != null){
oldDepartment.getEmployes().remove(employee);
}
department.getEmployes().add(employee);
employee.setDepartment(department);
...
*/
} catch (SQLException e) {
try {
connection.rollback();
throw new RuntimeException(e);
} catch (SQLException e1) {
throw new RuntimeException(e1);
}
}finally{
try {
if (statement!=null) statement.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
/**
* Usage example
*
* @param a
* @throws ClassNotFoundException
* @throws SQLException
*/
public static void main(String[] a) throws ClassNotFoundException, SQLException{
Class.forName("org.postgresql.Driver");
Connection connection =
DriverManager.getConnection("jdbc:postgresql://localhost/example",
"postgres", "********");
DepartmentManager m =DepartmentManager.getInstance();
Department d = m.findDepartment("Research & Develoment", connection);
if (d == null){
d = m.createNewDepartment("Research & Develoment", connection);
}
m.assignEmployeeDepartment(3, d, connection);
}
}
As it was already said, in this example the JDBC connection is passed as an argument to every business method of the object managers.
This approach has the following disadvantages:
Let us re-write the manager class using our command pattern.
To support database connections we create the command base class:
/**
* Abstract command which works with the <code>java.sql.Connection</code>.
*
* @param <R> command result type
*/
public abstract class ConnectionCommand<R> implements Callable<R> {
/**
* Connection
*/
protected Connection connection;
/**
* Its supposed that processor will call this method
* before command execution (<code>call()</code>).
*
* @param connection opened connection to database
*/
void setConnection(Connection connection) {
this.connection = connection;
}
}
Then we create the subclass for the command that has to be executed within a single transaction:
/**
* {@link ConnectionCommand} subclass for the command that
* has to be executed in the single transaction.
*
* @param <R> command result type.
*/
public abstract class TransactionCommand<R> extends ConnectionCommand<R> {
}
ICommandProcessor
implementation:
/**
* Command processor implementation (Singleton)
*/
public class MyCommandProcessor implements ICommandProcessor {
/**
* connection to database
*/
protected Connection connection = null;
/**
* Level of nested commands (one command could be called from the other command)
*/
protected int nested = 0;
private static MyCommandProcessor instance;
private MyCommandProcessor(){}
/**
* @return processor instance
*/
public static MyCommandProcessor getInstance() {
return instance == null ? instance = new MyCommandProcessor() : instance;
}
/**
* Command processing implementation
*/
@SuppressWarnings("unchecked")
public <R> R processCommand(Callable<R> command) throws RuntimeException {
Connection connection = null;
Savepoint savepoint = null;
nested++;
try{
if (command instanceof ICommand){
((ICommand)command).setCommandProcessor(this);
}
if (command instanceof ConnectionCommand){
// command preparing
connection = this.getConnection();
((ConnectionCommand)command).setConnection(connection);
if (command instanceof TransactionCommand){
savepoint = connection.setSavepoint();
}
}
// call command
R result = command.call();
if (savepoint != null)
connection.releaseSavepoint(savepoint);
return result;
}catch(Throwable e){
try {
if (savepoint != null)
connection.rollback(savepoint);
} catch (SQLException e1) {
throw new RuntimeException(e1);
}
throw new RuntimeException(e);
}finally{
// clearing references
if (command instanceof ICommand){
((ICommand)command).setCommandProcessor(null);
}
if (command instanceof ConnectionCommand){
((ConnectionCommand)command).setConnection(null);
}
// closing connection for outer command
if ((nested--)==0) {
try {
connection.close();
} catch (SQLException e){
throw new RuntimeException(e);
}finally{
this.connection = null;
}
}
}
}
/**
* @return Connection to database
*/
protected Connection getConnection(){
if (connection == null){
try{
Class.forName("org.postgresql.Driver");
connection = DriverManager.getConnection("jdbc:postgresql://localhost/example", "postgres", "********");
connection.setAutoCommit(false);
}catch(Exception e){
throw new RuntimeException(e);
}
}return connection;
}
}
The new manager class is shown below:
/**
* "Department" object manager. Singleton.
*/
public class DepartmentManager{
private static DepartmentManager instance;
private DepartmentManager(){}
/**
* @return manager instance
*/
public static DepartmentManager getInstance() {
return instance == null? instance = new DepartmentManager() : instance;
}
/**
* Creates department with the specified name.
*
* @param name department name
*
* @return new department instance
*/
public Department createNewDepartment(final String name){
return MyCommandProcessor.getInstance().processCommand(
new TransactionCommand<Department>(){
public Department call() throws SQLException {
CallableStatement statement = connection.prepareCall(
"insert into department(name) values (?)");
statement.setString(0,name);
statement.execute();
Department result = new Department();
result.setName(name);
return result;
}
}
);
}
/**
* Searches department with the specified name
*
* @param name department name
*
* @return department instance or <code>null</code>
*/
public Department findDepartment(final String name){
return MyCommandProcessor.getInstance().processCommand(
new ConnectionCommand<Department>(){
public Department call() throws SQLException {
CallableStatement statement = connection.prepareCall(
"select * from department where name = ?");
statement.setString(1,name);
ResultSet set = statement.executeQuery();
if ( set.next() ){
set.close();
Department result = new Department();
result.setName(name);
// loading department employees
HashSet<Employee> employees = new HashSet<Employee>();
statement = connection.prepareCall(
"select id, firstName, lastName, phoneNumber " +
"from employee where department = ?");
statement.setString(1,name);
set = statement.executeQuery();
while (set.next()){
Employee employee = new Employee();
employee.setDepartment(result);
employee.setId(set.getInt(1));
employee.setFirstName(set.getString(2));
employee.setLastName(set.getString(3));
employee.setPhoneNumber(set.getString(4));
employees.add(employee);
}
result.setEmployees(employees);
return result;
}
return null;
}
}
);
}
/**
* Assigns employee to the department.
*
* @param employeeId employee identity
* @param department department
*/
public void assignEmployeeDepartment(final int employeeId,final Department department){
MyCommandProcessor.getInstance().processCommand(
new TransactionCommand<Void>(){
public Void call() throws SQLException {
CallableStatement statement = connection.prepareCall(
"update employee set department=? where id=?");
statement.setString(1,department.getName());
statement.setInt(2,employeeId);
statement.execute();
/* Correct instances in memory if necessary
..
Employee employee = EmployeeManager.findEmployee(employeeId);
Department oldDepartment = employee.getDepartment();
if (oldDepartment != null){
oldDepartment.getEmployes().remove(employee);
}
department.getEmployes().add(employee);
employee.setDepartment(department);
..
*/
return null;
}
}
);
}
/**
* Usage example
*
* @param arg
*/
public static void main(String[] arg){
DepartmentManager m = DepartmentManager.getInstance();
Department d = m.findDepartment("Research & Develoment");
if (d == null){
d = m.createNewDepartment("Research & Develoment");
}
m.assignEmployeeDepartment(3, d);
}
}
The Listener PatternThe Listener pattern is a mechanism of notification about changes in a system. It has two important components – event and event listener. An event is a unit of change. In other words, it describes the change. An event listener handles events. It receives notification about changes as a set of events that have taken place.
As is well-known, Java has a
mechanism for notification about events (see the java.awt.Event
and java.util.EventListener classes).
Our version does not essentially differ from the
Java event model, except that it is abstracted
from the AWT library and uses the latest Java
features.
The interface part of API includes two interfaces:
These interfaces correspond to the two components of the Listener pattern respectively: event and event listener. Necessary requirement to an application that uses the Listener pattern is that it should allow new listeners to be registered in the system
The IEvent
<S,T> interface
corresponds to the event component of the Listener
pattern. The interface has two formal type
parameters: the first parameter specifies an
object that is a source of an event (i.e. an
object that was acted upon and changed as a result
of the action), the second parameter is a type of
an event.
The IEventListener
interface corresponds to the event listener
component of the Listener pattern. IEventListener
contains one method: eventsPerformed(Collection<IEvent<S,T>>).
As stated above, an application
has to provide possibility to register event
listeners. Listeners are used to perform some
actions in response to changes in the system. Once
a change has happened, the system generates the
corresponding event(s) and notifies the registered
listeners by calling the IEventListener's
eventsPerformed(Collection<IEvent<S,T>>)
method. A collection of the generated events is
passed to the method as an argument.
The base implementation is a quite trivial one.
The constructor of the Event
class accepts two parameters: a reference to an
object that is the source of an event and the type
of an event. An Event is created by
the system before notifying registered listeners.
EventListener is an
abstract class. Its method eventsPerformed(Collection<IEvent<S,T>>)
iterates over the collection of event objects and
passes each of them to the eventPerformed(IEvent<S,T>)
method to handle it. The eventPerformed(IEvent<S,T>)
method is abstract in the EventListener
class.
Here is a very simple example of the event collection. At first we define the types of events:
/**
* Defines the types of events
*/
public enum MyType {
/**
* add event type
*/
added,
/**
* remove event type
*/
removed
}
Then we define the collection:
/**
* Collection which produces event with the type {@link MyType}
* on adding or removing element.
*
* @param <T> collection element type
*/
public class MyCollection<T> implements Collection<T>{
/**
* collection element container
*/
protected Collection<T> wrapped = new LinkedList<T>();
/**
* registered listeners
*/
protected Collection<IEventListener<T,MyType>> listeners =
new HashSet<IEventListener<T,MyType>>();
/**
* registers event listener
*
* @param listener to be registered
*
* @return <code>true</code>
*/
public boolean registerListener(IEventListener<T,MyType> listener){
return this.listeners.add(listener);
}
public boolean addAll(Collection<? extends T> c) {
boolean result = false;
LinkedList<IEvent<T,MyType>> events = new LinkedList<IEvent<T,MyType>>();
for (T t : c){
boolean changed = this.add(t,false);
if (changed) events.add(new Event<T,MyType>(t,MyType.added));
result |= changed;
}
if (result) this.fireEvents(events);
return result;
}
@SuppressWarnings("unchecked")
public boolean removeAll(Collection<?> c) {
boolean result = false;
LinkedList<IEvent<T,MyType>> events = new LinkedList<IEvent<T,MyType>>();
for (Object t : c){
boolean changed = this.remove(t,false);
if (changed) events.add(new Event<T,MyType>((T)t,MyType.removed));
result |= changed;
}
if (result) this.fireEvents(events);
return result;
}
public void clear() {
LinkedList<IEvent<T,MyType>> events = new LinkedList<IEvent<T,MyType>>();
for (T t : this.wrapped) events.add(new Event<T,MyType>(t,MyType.removed));
this.wrapped.clear();
this.fireEvents(events);
}
public boolean add(T o) {
return add(o,true);
}
public boolean remove(Object o) {
return remove(o,true);
}
/**
* Removes element from the collection
*
* @param o element to be removed
* @param fireEvents defines if the event should be produced or not
*
* @return <code>true</code> when element were removed with success
*/
@SuppressWarnings("unchecked")
protected boolean remove(Object o,boolean fireEvents) {
boolean result = this.wrapped.remove(o);
if (result && fireEvents) fireEvent(MyType.removed,(T)o);
return result;
}
/**
* Adds element to the collection
*
* @param o element to be added
* @param fireEvents defines if the event should be produced or not
*
* @return <code>true</code> when element were added with success
*/
protected boolean add(T o,boolean fireEvents) {
boolean result = this.wrapped.add(o);
if (result && fireEvents) fireEvent(MyType.added,o);
return result;
}
/**
* Generates an event and delivers them to the listeners
*
* @param type event type
* @param object event source
*/
@SuppressWarnings("unchecked")
protected void fireEvent(MyType type,T object){
IEvent<T,MyType> e = new Event<T,MyType>(object,type);
fireEvents(Arrays.asList(e));
}
/**
* Delivers events to the registered listeners
*
* @param events events to be delivered
*/
protected void fireEvents(Collection<IEvent<T,MyType>> events){
Collection<IEvent<T,MyType>> collection =
Collections.unmodifiableCollection(events);
for (IEventListener<T,MyType> listener: listeners)
listener.eventsPerformed(collection);
}
public int size() {
return this.wrapped.size();
}
public boolean isEmpty() {
return this.wrapped.isEmpty();
}
public boolean contains(Object o) {
return this.wrapped.contains(o);
}
public Iterator<T> iterator() {
return this.wrapped.iterator();
}
public Object[] toArray() {
return this.wrapped.toArray();
}
public <O> O[] toArray(O[] a) {
return this.wrapped.toArray(a);
}
public boolean containsAll(Collection<?> c) {
return this.wrapped.containsAll(c);
}
public boolean retainAll(Collection<?> c) {
throw new UnsupportedOperationException();
}
}
And finally we test how it works:
/**
* Example in action
*/
public class Sample {
/**
* @param args
*/
public static void main(String[] args) {
// Let's create collection
MyCollection<Integer> collection = new MyCollection<Integer>();
// Listener registration
collection.registerListener(new IEventListener<Integer,MyType>(){
public void eventsPerformed(Collection<IEvent<Integer, MyType>> events) {
System.out.println("Events performed");
for (IEvent<Integer,MyType> e : events){
System.out.println(e.getType()+"-"+e.getSource());
}
}
});
// add elements
collection.add(1);
collection.add(3);
// remove elements
collection.remove(3);
collection.removeAll(Arrays.asList(new Integer[]{1,2,3}));
// adds elements
collection.addAll(Arrays.asList(new Integer[]{1,2,3}));
// clears collection
collection.clear();
}
}
The Flags Container PatternThe mechanism of bit states (flags) have been known for a long time. For example, in the UNIX systems the file access attributes are stored in a bite. If the first bit (flag) is set to 1, the file is accessible to executing. If the second bit is set to 1, the file is accessible to writing. And If the third bit (flag) is set to 1, the file is accessible to reading. If all of those bits are set to 1 then the file is accessible for executing reading and writing. To check these attributes programmatically, the bitwise "AND" operation is used . Below you can find the example that shows the described technique:
/**
* The mechanism of bit states (flags) have been known
* for a long time. For example, in the UNIX systems
* the file access attributes are saved in a bite.
* If the first bit (flag) is set, the file is
* accessible to reading, the set second bit means
* that the file is accessible to writing. If both
* the first and second flags are set, the file is
* accessible for reading and writing. To check
* these attributes, the bitwise "AND" operation
* is used in a code.
*
*/
public class BitsExplanation {
/**
* Below you can see the example that shows the described technique.
*
* @param args
*/
public static void main(String[] args){
final int X = 1; // execute attribute
final int W = 1<<1; // write attribute
final int R = 1<<2; // read attribute
final int RW = R|W;
int state = RW; // read write attribute
System.out.println("Can execute = "+((state&X)==X));
System.out.println("Can read = "+((state&R)==R));
System.out.println("Can write = "+((state&W)==W));
System.out.println("Can read and write = "+((state&(RW))==(RW)));
System.out.println("Can read or write = "+((state&(RW))!=0));
// Remove write attribute
if ((state&W)==W) state ^= W;
System.out.println("Can execute = "+((state&X)==X));
System.out.println("Can read = "+((state&R)==R));
System.out.println("Can write = "+((state&W)==W));
System.out.println("Can read and write = "+((state&(RW))==(RW)));
System.out.println("Can read or write = "+((state&(RW))!=0));
}
}
The extensions of the Java standards provided in Java 1.5 make it possible to represent the state of flags in a more convenient form.
FlagsContainer – is
a flags container base class. Semantic meanings of
flags are supposed to be declared as constants in
descendant classes.
FlagsContainer
declares a flags protected member of int
type. The flags variable is used as a bit field.
In other words, the flags variable
stores flags state. Bitwise OR is used to set
flags in the bit field.
FlagsContainer
defines two constructors: an empty constructor
(without parameters) and a constructor that
accepts an int parameter that is used
to initialize the flags field.
FlagsContainer
contains the following methods:
getFlags() -
returns the current flags state;contains(int ...
flags) – checks if at least one of the defined
parameters conforms to the flags state. From the
logical point of view the parameter delimiter
(the comma) is analogous to the logical OR
operation.setup(int ...
flags) – sets the flags indicated in method
parameters. Only those bits that have been
physically set as a result of this operation will
be set in the returned value.remove(int ...
flags) – removes the flags indicated in method
parameters. Only those bits that have been
physically unset as a result of this operation
will be unset in the returned value.See how to re-write the class
shown before using the FlagsContainer
class:
/**
* File state
*/
public class FileState extends FlagsContainer {
/**
* Execute bit
*/
public static final int X = 1;
/**
* Write bit
*/
public static final int W = 1<<1;
/**
* Read bit
*/
public static final int R = 1<<2;
/**
* Read write bits
*/
public static final int RW = R|W;
/**
* Constructor
*/
public FileState() {}
/**
* Constructor with initial state
*
* @param flags initial state
*/
public FileState(int flags) {
super(flags);
}
/**
* Override to open method visibility
*/
@Override
public int remove(int... flags) {
return super.remove(flags);
}
/**
* Override to open method visibility
*/
@Override
public int setup(int... flags) {
return super.setup(flags);
}
}
And how to use it:
/**
* Example with {@link FileState}
*/
public class Sample {
/**
* @param args
*/
public static void main(String[] args) {
// read write attribute
FileState state = new FileState(FileState.RW);
System.out.println("Can execute = "+state.contains(FileState.X));
System.out.println("Can read = "+state.contains(FileState.R));
System.out.println("Can write = "+state.contains(FileState.W));
System.out.println("Can read and write = "+state.contains(FileState.RW));
System.out.println("Can read or write = "+state.contains(FileState.R,FileState.W));
// Remove write attribute
state.remove(FileState.W);
System.out.println("Can execute = "+state.contains(FileState.X));
System.out.println("Can read = "+state.contains(FileState.R));
System.out.println("Can write = "+state.contains(FileState.W));
System.out.println("Can read and write = "+state.contains(FileState.RW));
System.out.println("Can read or write = "+state.contains(FileState.R,FileState.W));
//An example of rollback changes in the state
int delta = 0;
try{
delta = state.setup(FileState.W);
//not safe operation
}catch(Throwable e){
// rollback the state
state.remove(delta);
}
}
}
The Manager PatternSeparating data and business
logic have been always considered as a good
practice. One of the most striking examples of
such an approach in Java environment is the EJB
technology. In EJB 2.0, on the client side data
are represented as an interface that extends the EJBObject
interface, and functionality is defined by some
manager interface that is derived from of the EJBHome
interface. Here is an example of EJB interfaces
from EJB 2.0:
/**
* "Employee" enterprise bean
*/
public interface Employee extends EJBObject{
/**
* @return employee identity
*/
public Integer getId();
/**
* @param id employee identity
*/
public void setId(Integer id);
/**
* @return employee last name
*/
public String getLastName();
/**
* @param lastName employee last name
*/
public void setLastName(String lastName);
/**
* @return employee first name
*/
public String getFirstName();
/**
* @param firstName employee first name
*/
public void setFirstName(String firstName);
/**
* @return employee phone number
*/
public String getPhoneNumber();
/**
* @param phoneNumber employee phone number
*/
public void setPhoneNumber(String phoneNumber);
}
/**
* "Employee" home interface (manager in EJB)
*/
public interface EmployeeHome extends EJBHome {
/**
* Creates employee
*
* @param id employee identity
* @param firstName employee first name
* @param lastName employee last name
* @param phoneNumber employee phone number
*
* @return new employee object
*
* @throws CreateException
* @throws RemoteException
*/
Employee create(Integer id,
String firstName,
String lastName,
String phoneNumber)
throws CreateException,RemoteException;
/**
* @param id employee identity
*
* @return Employee with the identity specified
*
* @throws FinderException
* @throws RemoteException
*/
Employee findByPrimaryKey(Integer id)
throws FinderException,RemoteException;
/**
* @param firstName employee first name
* @param lastName employee last name
*
* @return Collection with the employees with the specified first and last names.
*
* @throws FinderException
* @throws RemoteException
*/
@SuppressWarnings("unchecked")
Collection findByName(String firstName,String lastName)
throws FinderException,RemoteException;
/**
* @return Employee count
* @throws RemoteException
*/
public int getEmpoyeCount() throws RemoteException;
}
The EJBObject
interface declares the getEJBHome()
method that returns an object of EJBHome
type.
The requirement that user
interfaces have to be derived from EJBObject
and EJBHome was removed in the EJB
3.0 specification. So it became possible to
explicitly specify the manager type when defining
the getEJBHome() method. This
approach clarifies the association between a Java
bean and its manager.
Here we suggest a small (in comparison with the EJB technology) model for separating data from business logic but our model provides a more explicit association between an object and its manager.
The base interfaces:
We propose to explicitly define
an association between a data object and its
manager in formal type parameters of these
interfaces: IObject<M extends
IManager>, IManager<O extends
IObject>.
The org.fishbolt.common.manager.ext
package includes a number of interfaces that are
extensions of IManager. Each of them
specifies certain behavior:
ICreateManager –
creates new data object instancesIStoreManager -
stores an objectIFindManager –
searches an object using its unique keyICollectionManager
– provides a collection of objectsIRefreshManager –
refreshes an objectIRemoveManager –
removes an objectIGetManager –
extracts a value of the specified type from an
objectISetManager -
sets a value of the specified type to an objectIValidateManager
- validates an objectAs an example, consider two data
objects: Department and Employee.
/**
* "Employee" data object
*/
public class Employee implements IObject<EmployeeManager>{
private int id;
private String firstName;
private String lastName;
private String phoneNumber;
private Department department;
/**
* Constructor
*
* @param id employee identity
*/
public Employee(int id){
this.id = id;
}
/**
* @return employee's department
*/
public Department getDepartment() {
return department;
}
/**
* @param department employee's department
*/
public void setDepartment(Department department) {
this.department = department;
}
/**
* @return employee last name
*/
public String getLastName() {
return lastName;
}
/**
* @param lastName employee last name
*/
public void setLastName(String lastName) {
this.lastName = lastName;
}
/**
* @return employee first name
*/
public String getFirstName() {
return firstName;
}
/**
* @param firstName employee first name
*/
public void setFirstName(String firstName) {
this.firstName = firstName;
}
/**
* @return employee phone number
*/
public String getPhoneNumber() {
return phoneNumber;
}
/**
* @param phoneNumber employee phone number
*/
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
/**
* @return employee identity
*/
public int getId() {
return id;
}
/**
* @param id employee identity
*/
public void setId(int id) {
this.id = id;
}
/**
* @return The Manager of the employee
*/
public EmployeeManager getManager() {
return EmployeeManager.getInstance();
}
@Override
public String toString(){
return this.firstName+" "+this.lastName;
}
}
/**
* "Department" data object
*/
public class Department implements IObject<DepartmentManager>{
private String name;
private Set<Employee> employes = new LinkedHashSet<Employee>();
/**
* @return department employees
*/
public Set<Employee> getEmployes() {
return employes;
}
/**
* @param employes new department employees
*/
public void setEmployes(Set<Employee> employes) {
this.employes = employes;
}
/**
* @return department name
*/
public String getName() {
return name;
}
/**
* @param name department name
*/
public void setName(String name) {
this.name = name;
}
/**
* @return Department manager
*/
public DepartmentManager getManager() {
return DepartmentManager.getInstance();
}
@Override
public String toString(){
return name;
}
}
Here are Department's and
Employee's managers: DepartmentManager
and EmployeeManager. The common
functionality of these two managers has been moved
to the BaseManager base class:
/**
* Base Manager
*
* @param <O> data object type
* @param <P> identity type
*/
public abstract class BaseManager<O extends IObject<?>,P>
implements IFindManager<O,P>,
ICollectionManager<O>,
ICreateManager<O>,
IStoreManager<O>,
IRemoveManager<O> {
/**
* map of identity and object
*/
protected LinkedHashMap<P,O> objects = new LinkedHashMap<P,O>();
private Class<O> type;
/**
* Manager Constructor
* @param type managed object type
*/
protected BaseManager(Class<O> type){
this.type = type;
}
public O find(P pk) {
return this.objects.get(pk);
}
public void store(O object) {
this.objects.put(getIdentity(object),object);
}
public Collection<? extends O> getObjects() {
return objects.values();
}
public void remove(O object){
this.objects.remove(getIdentity(object));
}
public O create() {
try {
return type.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
/**
* Manager for Department object (Singleton)
*/
public class DepartmentManager extends BaseManager<Department,String> {
private static DepartmentManager instance;
private DepartmentManager(){
super(Department.class);
}
/**
* @return manager instance
*/
public static DepartmentManager getInstance(){
return instance == null ? instance = new DepartmentManager() : instance;
}
public String getIdentity(Department object) {
return object.getName();
}
/**
* Assigns employee to the department
*
* @param employee to assign
* @param department to assign to
*/
public void assignEmployeeDepartment(Employee employee,Department department){
Department old = employee.getDepartment();
if (old != null) old.getEmployes().remove(employee);
employee.setDepartment(department);
department.getEmployes().add(employee);
}
}
/**
* Employee manager (Singleton)
*/
public class EmployeeManager extends BaseManager<Employee,Integer> {
private static EmployeeManager instance;
private static int count;
private EmployeeManager() {
super(Employee.class);
}
/**
* @return manager instance
*/
public static EmployeeManager getInstance(){
return instance == null ? instance = new EmployeeManager() : instance;
}
public Integer getIdentity(Employee object) {
return object.getId();
}
@Override
public Employee create() {
Employee result = super.create();
result.setId(count++);
return result;
}
}
The Value Presenter PatternMost developers may know general principles of the Model-View-Controller (MVC) pattern. To provide complete MVC implementation on basis of a definite data structure and a set of program tools for designing user interface, developers have to solve a lot of difficult problems. Solutions for these problems can be especially hard to find if the underlying data model permits changes, including changes that can be made through the user interface.
We suggest solving first the problem of representing a unit of information. After that, further ways of the MVC pattern implementation become clearer.
In the context of data representation problem let us bring into use the term "representation type" Representation type is a type, which a value is reduced to for its subsequent representation in a user interface. Traditionally, a string (text) is one of the most wide-spread forms of data representation: numerical, date, and boolean types can be easily represented as a string.
Such an abstraction as representation type allows decreasing dependency of a code on a GUI library. With this approach it is possible to easily switch from one GUI library to another. So what we need is the code that is able to convert a value type to a presentation type. It is often the case that a user interface is used to get data from a user. To put this another way, most user interfaces allow data input. So another task appears: a task of converting a representation type to a value type.
When we tell about a unit of information, we mean a data object of a definite type. The number of possible values for a data type determines how an object of this type is represented in a user interface. Thus, depending on the number of values a user must be provided with either a kind of list (a combo-box, a set of checkboxes) to choose a value from or a text field to enter a value. On the basis of the number of possible values, data types can be classified as follows:
Below we describe a number of interfaces and base classes that convert different data types to the corresponding representation types and back. The proposed interfaces and classes can be effectively used for building graphical user interfaces.
IValuePresenter converts a value
type to a representation type and so provides a
representation for a value. The IValuePresenter
interface has two formal type parameters V
and P: a value type (a type of a data
unit) and a representation type (for example,
String).
The IValuePresenter
interface contains two methods:
getPresentation(V)
– returns a representation for the value
specified in the method parameter;getPresentationsSuite(Colleсtion<V>)
– returns a IPresentationsSuite for
the specified collection of values.The IPresentationsSuite
provides representations (a "suite" of
representations) for a set of values. The IPresentationsSuite
interface declares the following methods:
getPresentations() –
returns a list (java.util.List) of
representations for a set of values.
getValueIndex(V) – receives
a value as a parameter and returns an index
of its representation in the list;getValue(int) – returns a
value by its representation index in the list.The abstract ValuePresenter
class is a base implementation of the IValuePresenter
interface. The ValuePresenter's getPresentation(V)
method checks if the value specified in the
argument is null. If it is, the
method calls the getPresentation4Null()
method that returns representation for a null
value. Otherwise, the getPresentation4NotNull()
method that returns representation for a non-null
value is called. Both methods getPresentation4Null
and getPresentation4NotNull are
declared abstract in the ValuePresenter
class.
The BidirectValuePresenter,
BoundedValuePresenter, and CollectionValuePresenter
classes described below extend the ValuePresenter
class. These classes correspond each to one of the
data types from the classification considered
above (an infinite set of possible values, a
bounded set of values, a collection of values).
The classes help to design graphical user
interfaces in such a way that values can be not
only represented, but also changed by users via
their representations.
The BidirectValuePresenter
class is intended for data types that have an
infinite set of values. Values of such types are
usually represented as contents of input fields in
a user interface. BidirectValuePresenter
defines the getValue(P) method that
returns a value recognized from the representation (text for example) that was
entered by a user into a text field. If it fails
to recognize a value from its representation, the
method throws PresentationException.
An exception handling code could generate a
message that would inform a user about an attempt
to enter an incorrect value into the text field.
The BoundedValuePresenter
class is intended for data types that have a
bounded set of values. A bounded set of values is
usually represented in a user interface with a
list.
BoundedValuePresenter
defines the getPossiblePresentationsSuite()
method that returns an IPresentationsSuite
instance. As stated above, IPresentationsSuite
provides representations (a "suite" of
representations) for a set of values. IPresentationsSuite
makes it easy to represent a "suite" of values as
a list in a user interface.
As is well known, for working
with multiple objects in Java collections are used
(interfaces and classes of the Java Collections
Framework). The CollectionValuePresenter
class helps to represent collections in a user
interface.
A collection can be represented as a string, with, for example, a comma used to separate elements of the collection from one another. Lists are also often used to represent collections in a user interface. When provided with a list, a user can select elements so as to do something with the them.
Let us consider the case where a
user selects an element and then clicks a button
to remove it. So, there is a collection behind the
scenes and what is needed is ability to remove
elements from the collection. The CollectionValuePresenter's
getCollectionItemPresenter() method
returns IValuePresenter that
represents an element from the current collection.
If we call the getPresentationsSuite(ColleсtionV>)
method on the IValuePresenter
instance and pass our collection as an argument,
we will get IPresentationsSuite that
is useful in creating a list in a user interface
(See the section about IPresentationsSuite
above for details). IPresentationsSuite
provides an value by the index
of the selected representation element in the list. (In other
words, IPresentationsSuite gets a
value by its representation in the list.) To
remove this element from the collection, we can
use a method of the collection itself: the remove
method.
The task of adding elements to a
collection is analogous to the task of removing
elements. Elements are added to a collection
behind the scenes when a user adds new data via a
user interface. To add new data a user enters
something into a text field or selects elements in
a list (it may be a set of checkboxes or radio
buttons as well). A type of the data being added
can have an infinite or bounded set of possible
values. The getCollectionItemPresenter()
method returns a BidirectValuePresenter
or BoundedValuePresenter instance
depending on the type of data being added (both BidirectValuePresenter
and BoundedValuePresenter are derived
from ValuePresenter that implements IValuePresenter).
Each of these presenter classes supports
possibility to get a value from its representation
in a user interface (See the sections about BidirectValuePresenter
and BoundedValuePresenter
for details). To add a new element (i.e. a new
value) to the collection, we can use a method of
the collection itself: the add method.
The classes and interfaces
described above are abstracted from concrete
representation types (a concrete presentation type
is specified in a formal type parameter of a class
or interface). This section describes classes that
are intended for a concrete representation type: String.
Among these classes are StringPresenter,
StringBidirectPresenter, StringBoundedPresenter,
and StringCollectionPresenter. They
are the descendants of ValuePresenter, BidirectValuePresenter,
BoundedValuePresenter, and CollectionValuePresenter
respectively.
By default these classes form a
string representation for a data object by calling
the toString() method on the data
object. The null object is
represented as an empty string.
As is well-known, the Java
programming language's standard libraries include
classes that are intended for formatting strings.
Among them are the java.text.Format
class and its subclasses: java.text.DateFormat,
java.text.NumberFormat, etc.
This subsection describes the FormatPresenter,
FormatBidirectPresenter and NumberFormatBidirectPresenter
classes that provide formatted string
representations for data objects. Each of these
classes has a constructor that takes a java.text.Format
instance as an argument. This instance is used by
the classes to delegate responsibility for
formatting string representations.
The constructor declared in the NumberFormatBidirectPresenter
class has two parameters: java.text.NumberFormat
and java.lang.Class. The second – Class
– parameter stands for a data type. It must be a
subtype of java.lang.Number.
Many frequently used classes in
Java such as, for example, children of java.lang.Number
have a constructor with a String
parameter. These classes parse a String
passed as an argument to the constructor and
convert it to a value of a proper type (for
example, numerical type).
The ConstructorBidirectPresenter
class makes use of constructors with a String
parameter: it uses reflection to instantiate a
class via its constructor. ConstructorBidirectPresenter
passes a String that represents a
value to the constructor of the object being
created. So ConstructorBidirectPresenter
delegates the task of getting a value from its
string representation to the created instance. The
ConstructorBidirectPresenter's
constructor has a Class parameter
that specifies the type of the object to
instantiate.
Among the types that have a
bounded set of possible values are java.lang.Enum
and java.lang.Boolean. This
subsection describes two classes – BoundedEnumPresenter
and BoundedBooleanPresenter – that
represent enum and boolean
values respectively. Both classes extend StringBoundedPresenter.
The BoundedEnumPresenter's
constructor has two parameters: Class
and boolean. The first parameter – Class
– is supposed to provide an enumeration type in
its formal type parameter. The second parameter –
boolean – determines if a null
value must be included into the set of
representations for the possible values (See the
description of the getPossiblePresentationsSuite
method in BoundedValuePresenter the
class for details).
The BoundedBooleanPresenter's
constructor has a boolean parameter that
determines if a null value must be
included into the set of representations for the
possible values together with true
and false.
Very often representation tasks
include requirements to decorate a String that
represents a value in a user interface. For
example, it may be necessary to add the '%' symbol
after a number or to add the '$' symbol before a
real number. For doing such tasks the Decorator
class is provided.
The Decorator
constructor takes two String arguments: the first
argument is a prefix (a string that is appended to
the representation string at the beginning), the
second argument is a postfix (a string that is
appended to the representation string at the end).
If a prefix or a postfix is not needed, null
must be specified in the corresponding argument
when creating a Decorator.
To "decorate" the string
representation of some value, it is needed to
create a Decorator and call its getDecorated(IValuePresenter<V,String>)
method. This method returns the IValuePresenter
that "decorates" the string representation
provided by the other IValuePresenter
that is passed to the method as an argument.
Here is an example of IValuePresenter
in action. The Sample class shown below
contains a static registry of IValuePresenter
that is filled in the static initialization block.
The main method gives some job to the
IValuePresenter to demonstrate how
they work.
/**
* Enumeration type
*/
public enum MyEnum {
/**
* first value
*/
first,
/**
* second value
*/
second,
/**
* third value
*/
third;
};
/**
* Example
*/
@SuppressWarnings("unchecked")
public class Sample {
/**
* presenters registry
*/
private static HashMap<Class, IValuePresenter> presenters;
/**
* filling the presenters registry
*/
static {
// decorators
Decorator percent = new Decorator(null, "%", null);
Decorator dollar = new Decorator("$ ", null, null);
// presenters
presenters = new HashMap<Class, IValuePresenter>();
presenters.put(Boolean.class, new BoundedBooleanPresenter(false) {
@Override
protected String getPresentation4NotNull(Boolean value) {
return value ? "Yes" : "No";
}
});
presenters.put(Date.class, new FormatPresenter(DateFormat
.getDateInstance(DateFormat.FULL)));
presenters.put(BigDecimal.class, dollar
.getDecorated(new FormatBidirectPresenter(new DecimalFormat(
"#,##0.00"))));
presenters.put(MyEnum.class, new BoundedEnumPresenter(MyEnum.class,
false));
presenters.put(Integer.class, percent
.getDecorated(new ConstructorBidirectPresenter(Integer.class)));
}
/**
* Work with presentaions
*
* @param s
* @throws PresentationException
*/
public static void main(String[] s) throws PresentationException {
// prints presentations
printPresentaion(new Date());
printPresentaion(Boolean.FALSE);
printPresentaion(MyEnum.second);
printPresentaion(new Integer(1));
printPresentaion(BigDecimal.valueOf(1.1));
// prints values
printValue(Integer.class, "30%");
printValue(BigDecimal.class, "$ 30,00");
printValue(Integer.class, "30");
printValue(BigDecimal.class, "30,00");
printValue(BigDecimal.class, "sdfg");
}
// prints object presentaion
private static void printPresentaion(Object o) {
System.out.println("'"
+ presenters.get(o.getClass()).getPresentation(o)+"'");
}
// prints object by its string presentation
private static void printValue(Class valueType, String presentation)
throws PresentationException {
IValuePresenter p = presenters.get(valueType);
if (p instanceof BidirectValuePresenter) {
try{
System.out.println(((BidirectValuePresenter) p)
.getValue(presentation));
} catch (PresentationException e) {
System.err.println("Presentation '" +presentation+
"' is incorrect for type "+
valueType.getSimpleName());
}
} else {
throw new PresentationException(
"BidirectValuePresenter isn't accociated with type "
+ valueType.getName());
}
}
}