Engineering Software Development in Java Lecture Notes for ENCE 688R, Civil Information Systems Spring Semester, 2016 Mark Austin, Department of Civil and Enviromental Engineering, University of Maryland, College Park, Maryland 20742, U.S.A. c Copyright ⃝2012-2016 Mark A. Austin. All rights reserved. These notes may not be reproduced without expressed written permission of Mark Austin. Chapter 12 Modeling Abstractions and Software Design Patterns 12.1 Abstractions for Modeling System Structure By definition, structure is an arrangement of parts, or constituent particles, in a substance or body (Merriam Webster, 1981). The system structure viewpoint focuses on the internal subsystems and how they are connected. This viewpoint helps us to understand how the system will accomplish its purpose? To understand structure at the meta-model level, we need to: Identify the basic data types (or objects) that will exist within a domain and then devise mechanisms for describing connectivity and containment relations among these entities... Components, Attributes and Relationships When an engineer describes the structure of a system, the description must contain a list of components/parts, attributes, and relationships: 1. Components Components are the operating parts of a system consisting of input, process, and output. Each system component may assume a variety of values to describe a system state. 2. Attributes Attributes are the properties of the components in the system. Most often the attributes characterize the system. 3. Relationships Relationships are the links between the components and attributes. Most systems are assembled from a combination of components and subsystems. An important characteristic of a system is that its purpose is met by the properties of the system as “a whole,” and not just by the union of the components. 368 369 Chapter 12 For software, typical characteristics are data types associated with variables and methods. For systems hardware, characteristics can include things like size, weight, shape and location. In many of the traditional engineering disciplines, even in high-level representations of systems, geometric positioning – containment includes relationships like: Is object A inside object B? – and spatial layout of system objects is an important element of defining and evaluating system structure. Interconnect Design Methodology The interconnect design methodology assumes that complicated products and processes can be represented as hierarchies and networks of product and process blocks, i.e., Network Abstraction Hierarchy Abstraction A D E Connector Port B C Module A contains modules B and C Module D is connected to module E Figure 12.1. Hierarchy and network abstractions for SE development. Assigning properties and functions to blocks is relatively straightforward. Combining Hierarchy and Connectivity of Components. Figure 12.2 shows a simple system hierarchy decomposed into two levels, with a network of components (B and C) at level 2. A Level 1 B C Level 2 Port Relations Component Figure 12.2. Hierarchy and network abstractions for SE development. The system structure must satisfy the following constraints: 1. Within a hierarchy, each level is logically connected to the levels above and below it. 370 Engineering Software Development in Java 2. A port cannot be contained by more than one entity. 3. Links cannot cross levels in the hierarchy, 4. Port-to-port communications must have compatible data types (e.g., signal, energy, force). Data values at lower levels aggregate into the data values at higher levels. Evaluation mechanisms should provide the designer with critical feedback on the feasibility and correctness of the system architecture. System Assembly. Systems are assembled from parts, connectors, and ports with interface specifications: 1. Part Parts represent a set of instances that are aggregated within a containing classifier instance. Parts may be joined by attached connectors, and specify configurations of linked instances. 2. Connector Connectors specify a link (or an instance of an association) that enables communication between parts in a structure or with the surrounding environment. 3. Port Each part attaches to other parts at specific locations. These locations are called ports. Ports specify an interaction point between a classifier and its environment and/or between a classifier and other internal ports. 4. Interface Specification An interface specification defines precisely what a client of that interface/component can expect in terms of supplied operations and services (i.e., in terms of forces, material or energy or information protocols; minimum and maximum levels of component functionality), and operational pre- and post-conditions. Synthesis of Block Assemblies Creation of system architectures can be viewed via collections of modules and connectors, and rules for system assembly. Rules for System Assembly A B a b a b Small collection of modules and connectors. Synthesis A a b B Simple System Assembly Figure 12.3. Synthesis of system architectures supported by product descriptions on Web. 371 Chapter 12 More complicated sub-systems can be modeled graphs. Looking ahead, we need to be able to merge graph models and remove inconsistencies among viewpoints. Interface Contracts Complex objects have multiple interaction points (i.e., ports). Each port will be dedicated to a specific purpose and present and interface appropriate to that purpose – the pre- and post-conditions and satisfaction of the input requirements constitute a contract. Interface Contract Pre−conditions Object Post−conditions Figure 12.4. Role of pre- and post-conditions in contracts for object usage (Source: Newton A.R., ”Notes on Interface-Based Design,” EECS, UC Berkeley). Networks of interacting/communicating objects having a larger purpose/functionality can be synthesized by stitching together objects that have compatibly I/O requirements and pre- and post-conditions. 372 12.2 Engineering Software Development in Java Structural Design Patterns Definition and Benefits Definition. Experienced designers know that instead of always returning to first principles, routine design problems are best solved by adapting solutions to designs that have worked well for them in the past [1, 18]. A design pattern is simply a description of: 1. A problem that occurs over and over again, and 2. A core solution to that problem stated in such a way that it can be reused many times. In other words, a design pattern prescribes a [ problem, solution ] pair. The design pattern identifies the participating subsystems and parts, their roles and collaborations, and distribution of responsibilities. Design Patterns in Urban Design. For a wide range of domains, this approach to problem solving is popular because it encodes many years of professional experience in the how and why of design, and is time efficient. Design patterns crop up in many avenues of day-to-day life. For example, as illustrated in Figures 12.5 and 12.6 the layout of streets in planned communities follows patterns, in part, because ... ... it is known that these patterns lead to efficiencies in the use of resources such as energy, etc. [1]. Benefits. Gamma and co-workers [18] point out that patterns facilitate reuse – one persons pattern can be another persons fundamental building block. Software design patterns are particularly beneficial in the development of architectures for distributed systems (Buschmann, Henney, and Schmidt, 2007). Summary of Structural Design Patterns Structural design patterns provide ... ... effective ways to partition and to combine the elements of an application. Of course there are many ways that the elements of an application can be combined. This, in turn, results in an ensemble of structural design patterns. 1. Adapter Design Pattern. Acts as an intermediary between two classes, converting the interface of one class so that it can be used with the other. 2. Bridge Design Pattern. Divides a complex component into separate, but related, inheritance hierarchies. One hierarchy provides a functional abstraction; the second hierarchy provides the details of implementation. Organizing code in the way makes it easier to change either aspect of the component. 373 Chapter 12 Figure 12.5. Urban design. Rectangular pattern with superimposed diagonals. Figure 12.6. Urban design pattern. Suburbia. 374 Engineering Software Development in Java 3. Composite Design Pattern. This design pattern provides a flexible way to create hierarchical tree structures of arbitrary complexity, while enabling every element in the structure to operate with a uniform interface. 4. Decorator Design Pattern. This design pattern provides a way to flexibly add or remove component functionality without chaning its external appearance or function. 5. Facade Design Pattern. Provides a simplified interface to a group of subsystems or a complex subsystem. 6. Proxy Design Pattern. This design pattern provides a representative of another object, usually for reasons of functionality and performance, such as access, speed or security. In this chapter we will focus on only three structural patterns: (1) The Bridge Design Pattern, (2) The Adapter Design Pattern, and (3) The Composite Design Pattern. 375 Chapter 12 12.3 Bridge Design Pattern The bridge pattern ... ... decouples an abstraction from its implementation, so that the two can vary independently. In many applications, the separation of abstraction from implementation can vastly simplify the management of code where classes and what they do vary often. The class itself can be thought of as ... ... the implementation and what the class can do as ... ... the abstraction. In other words, the bridge pattern can also be thought of as two layers of abstraction. A Simple Example. A household switch controlling lights, ceiling fans, etc. is an example of a bridge. Figure 12.7. Schematic for the bridge design pattern. The purpose of the switch is to turn a device on or off. The actual switch can be implemented as a pull chain, simple two position switch, or a variety of dimmer switches. Check list 1. Decide if two orthogonal dimensions exist in the domain. These independent concepts could be: abstraction/platform, or domain/infrastructure, or front-end/back-end, or interface/implementation. 376 Engineering Software Development in Java 2. Design the separation of concerns: what does the client want, and what do the platforms provide. 3. Design a platform-oriented interface that is minimal, necessary, and sufficient. Its goal is to decouple the abstraction from the platform. 4. Define a derived class of that interface for each platform. 5. Create the abstraction base class that “has a” platform object and delegates the platform-oriented functionality to it. 6. Define specializations of the abstraction class if desired. Example 01. Working with Multiple APIs In this example, we develop code for drawing shapes through the use of multiple drawing application interfaces. << abstract >> Shape uses << interface >> DrawingAPI extends CircleShape implements DrawingAPI1 DrawingAPI2 Figure 12.8. Class hierarchy for bridge to multiple drawing application interfaces. As illustrated in Figure 12.8, CircleShape objects will be created as concrete extensions to an abstract Shape definition. Then, in turn, the abstract Shape class uses the DrawingAPI interface as a bridge to objects responsible for drawing circles (i.e., DrawingAPI1 and DrawingAPI2). DrawingAPI.java. The DrawingAPI interface defines a formal abstraction against which concrete implementations can be programmed. source code /* * ========================================================== * DrawingAPI.java: Drawing API interface ... * ========================================================== */ public interface DrawingAPI { public void drawCircle( double x, double y, double radius ); } Chapter 12 377 DrawingAPI1.java. Here is the first concrete implementation of a drawing application interface. source code /* * ====================================================================== * DrawingAPI1: Concrete implementation of the DrawingAPI. * ====================================================================== */ public class DrawingAPI1 implements DrawingAPI { public void drawCircle(double x, double y, double radius) { System.out.printf("*** In DrawingAPI1(): "); System.out.printf("Circle at (%4.1f:%4.1f): Radius = %4.1f\n", x, y, radius); } } To keep things simple, we print a message and leave. DrawingAPI2.java. And here is the second concrete implementation of a drawing application interface. source code /* * ====================================================================== * DrawingAPI2: Concrete implementation of the DrawingAPI. * ====================================================================== */ public class DrawingAPI2 implements DrawingAPI { public void drawCircle(double x, double y, double radius) { System.out.printf("*** In DrawingAPI2(): "); System.out.printf("Circle at (%4.1f:%4.1f): Radius = %4.1f\n", x, y, radius); } } Shape.java. The drawing API is used by a class hierarchy of shapes. source code /* * ================================================================= * Shape.java; Abstract definition of a shape ... * ================================================================= */ public abstract class Shape { protected DrawingAPI drawingAPI; protected Shape(DrawingAPI drawingAPI){ this.drawingAPI = drawingAPI; 378 Engineering Software Development in Java } public abstract void draw(); public abstract void resizeByPercentage(double pct); // low-level // high-level } The root of this hierarchy is the abstract class Shape. Notice that Shape refers indirectly to concrete implentations of the drawing API through the variable declaration: protected DrawingAPI drawingAPI; And although objects of types Shape will never be created, Shape contains a constructor method which will be called by sub-class constructor methods. CircleShape.java. Circle shape is a concrete implementation of a shape. source code /* * ======================================================================= * CircleShape.java: Create circle shapes with reference to DrawingAPI ... * ======================================================================= */ public class CircleShape extends Shape { private double x, y, radius; public CircleShape(double x, double y, double radius, DrawingAPI drawingAPI ) { super(drawingAPI); this.x = x; this.y = y; this.radius = radius; } // Low-level i.e. Implementation specific public void draw() { drawingAPI.drawCircle(x, y, radius); } // High-level i.e. Abstraction specific public void resizeByPercentage(double pct) { radius *= pct; } } RunBridgePattern.java. We exercise the bridge design pattern by creating three circle shape objects having a variety of drawing APIs. 379 Chapter 12 source code /* * ===================================================================== * RunBridgePattern.java: Exercise methods in the bridge design pattern. * ===================================================================== */ public class RunBridgePattern { public static void main(String[] args) { // Create an array of circle shapes .... Shape s01 = new CircleShape( 1, 2, 3, new DrawingAPI1() ); Shape s02 = new CircleShape( 5, 7, 5, new DrawingAPI2() ); Shape s03 = new CircleShape( 1, 4, 11, new DrawingAPI1() ); Shape[] shapes = new Shape[] { s01, s02, s03 }; // Traverse array: resize and draw each shape item ... for (Shape shape : shapes) { shape.resizeByPercentage( 2.5 ); shape.draw(); } } } After compiling the test program in the usual way, the program output is: prompt *** In *** In *** In prompt >> java RunBridgePattern DrawingAPI1(): Circle at ( 1.0: 2.0): Radius = 7.5 DrawingAPI2(): Circle at ( 5.0: 7.0): Radius = 12.5 DrawingAPI1(): Circle at ( 1.0: 4.0): Radius = 27.5 >> An alternative and potentially bettern implementation would separate the circle object creation from the specification of the drawing API. This would lead to code that is more dynamic – for example, Shape s01 = new CircleShape( 1, 2, Drawing dapi1 = new DrawingAPI1(); Drawing dapi2 = new DrawingAPI2(); 3, new DrawingAPI1() ); s01.setDrawingAPI( dapi1 ); Then at a later point in the program execution you could change the drawing API, e.g., s01.setDrawingAPI( dapi2 ); 380 Engineering Software Development in Java Example 02. Customized Formatting of Lists This bridge design pattern example is adapted from the text of Stelting and Maassen [39]. It shows how the functionality for an ordered list implemenation can be extended through the use of a list interface and multiple abstraction classes. Abstractions RunBridgePattern Implementation uses BaseList ListInterface extends NumberedList implements OrnamentedList OrderedListImpl uses uses uses Figure 12.9. Class hierarchy plus annotations for an ordered list implementation that presents multiple abstractions to the outside world. Figure 12.9 shows the relationship among the classes. The BaseList, NumberList and OrnamentedList classes present three different abstractions for an ordered list to outside test programs. To see how this will work in practice, suppose that the list contains the string “One” as an item. The output for each abstraction might be: One 1. One + One <-- BaseLine abstraction <-- Numerical abstraction <-- Ornamented abstraction Each abstraction will communicate with the ordered list implemenation via the list interface. BaseList.java. The baselist class provides general list capabilities. source code /* * ================================================================== * BaseList.java: Base List abstraction ... * ================================================================== */ public class BaseList { protected ListInterface implementor; public void setImplementor( ListInterface impl ){ implementor = impl; } Chapter 12 381 public void add( String item ){ implementor.addItem(item); } public void add(String item, int position ){ if ( implementor.supportsOrdering() ) { implementor.addItem(item, position); } } public void remove(String item){ implementor.removeItem(item); } public String get(int index){ return implementor.getItem(index); } public int count(){ return implementor.getNumberOfItems(); } } Notice that when an operation is requested (e.g., add(..)) of the list, it is actually delegated across the bridge to the ListImplementation object. NumberedList.java. This class creates a numbered list string representation for an item stored in the list. source code /* * ================================================================== * NumberedList.java: Return a string for a numbered list ... * ================================================================== */ public class NumberedList extends BaseList { public String get(int index){ return (index + 1) + ". " + super.get(index); } } OrnamentedList.java. An OrnamentedList expands on the BaseList model by adding a list character. source code /* * ================================================================== 382 Engineering Software Development in Java * OrnamentedList.java: Generate ornamented string representation. * ================================================================== */ public class OrnamentedList extends BaseList { private char itemType; public char getItemType(){ return itemType; } public void setItemType( char newItemType ){ if (newItemType > ’ ’){ itemType = newItemType; } } public String get(int index){ return itemType + " " + super.get(index); } } ListInterface.java. The list interface defines the methods that an implementation will need to provide. source code /* * ================================================================== * ListInterface.java: Interface to various implementations of a List * ================================================================== */ public interface ListInterface { public void addItem(String item); public void addItem(String item, int position); public void removeItem(String item); public int getNumberOfItems(); public String getItem(int index); public boolean supportsOrdering(); } OrderedListImpl.java. This class provides the underlying storage capability for the list, and can be flexibly paired with the three classes which provide the abstraction. source code /* * ================================================================== * OrderedListImpl.java: Implementation for an Ordered List... * ================================================================== */ import java.util.ArrayList; Chapter 12 383 public class OrderedListImpl implements ListInterface { private ArrayList items = new ArrayList(); public void addItem(String item){ if (!items.contains(item)){ items.add(item); } } public void addItem(String item, int position){ if (!items.contains(item)){ items.add(position, item); } } public void removeItem(String item){ if (items.contains(item)){ items.remove(items.indexOf(item)); } } public boolean supportsOrdering(){ return true; } public int getNumberOfItems(){ return items.size(); } public String getItem(int index){ if (index < items.size()){ return (String)items.get(index); } return null; } } In this particular implementation, the list items are stored in an ArrayList. RunBridgePattern.java. Our test program assembles a BaseList containing four String items – One, Two, Three and Four. It then passes a copy of the implementation reference to instances of OrnamentedList and NumberedList. A this point there is one list in memory with three refences to it. Finally, the test program prints the list’s contents in each of three abstraction styles. source code /* * ============================================================ * RunBridgePattern.java: Exercise methods in bridge pattern... * ============================================================ */ 384 Engineering Software Development in Java public class RunBridgePattern { public static void main(String [] arguments){ System.out.println("============================="); System.out.println("Run Bridge pattern "); System.out.println("============================="); // Create ordered list object ... ListInterface implementation = new OrderedListImpl(); // Create baseline object ... BaseList listOne = new BaseList(); // Assign ordered list implementation to base ... listOne.setImplementor(implementation); // Now add elements to the list ... listOne.add("One"); listOne.add("Two"); listOne.add("Three"); listOne.add("Four"); // Create an ornamented list object ... OrnamentedList listTwo = new OrnamentedList(); listTwo.setImplementor(implementation); listTwo.setItemType(’+’); // Create an numbered list object ... NumberedList listThree = new NumberedList(); listThree.setImplementor(implementation); System.out.println(); // Print contents of the base list ... System.out.println("Printing out first list (BaseList)"); for (int i = 0; i < listOne.count(); i++){ System.out.println("\t" + listOne.get(i)); } System.out.println(); // Print contents of the ornamented list ... System.out.println("Printing out second list (OrnamentedList)"); for (int i = 0; i < listTwo.count(); i++){ System.out.println("\t" + listTwo.get(i)); } System.out.println(); Chapter 12 385 // Print contents of the numbered list ... System.out.println("Printing our third list (NumberedList)"); for (int i = 0; i < listThree.count(); i++){ System.out.println("\t" + listThree.get(i)); } } } The program output is as follows: prompt >> prompt >> java RunBridgePattern ============================= Run Bridge pattern ============================= Printing out first list (BaseList) One Two Three Four Printing out second list (OrnamentedList) + One + Two + Three + Four Printing our third list (NumberedList) 1. One 2. Two 3. Three 4. Four prompt >> A Few Remarks 1. Adapters makes things work after they are designed. Bridges makes them work beforehand. 2. Bridge is designed up-front to let the abstraction and the implementation vary independently. Adapter is retrofitted to make unrelated classes work together. 3. Adapter is meant to change the interface of an existing object. 4. State, Strategy, Bridge (and to some degree Adapter) have similar solution structures. They all share elements of the handle/body idiom. They differ in intent - that is, they solve different problems. 386 12.4 Engineering Software Development in Java Adapter Design Pattern The Adapter pattern allows otherwise incompatible classes to work together by converting the interface of one class into an interface expected by the clients. Figure 12.10. Socket metaphor for implementation of the adapter design pattern. Socket wrenches provide an example of the Adapter. A socket attaches to a ratchet, provided that the size of the drive is the same. Typical drive sizes in the United States are 1/2 and 1/4. Obviously, a 1/2 drive ratchet will not fit into a 1/4 drive socket unless an adapter is used. A 1/2 to 1/4 adapter has a 1/2 female connection to fit on the 1/2 drive ratchet, and a 1/4 male connection to fit in the 1/4 drive socket. Source: http://sourcemaking.com/design patterns/adapter Example 03. Adapter Interface for Incompatible Objects In this example we will develop an adapter interface for dealing with the drawing of line and rectangular objects in a completely uniform way. Code Before Adapter: Source: http://sourcemaking.com/design patterns/adapter Suppose that in some legacy code, lines are defined by pairs of end points (e.g., (x1 , y1 ) and (x2 , y2 ) ), and rectangles are defined by a lower left-hand (x,y) coordinate point, plus dimensions for the rectangle width and height. Routines for drawing lines and rectangles might look like: public class LegacyLine { public void draw(int x1, int y1, int x2, int y2) { System.out.println("line from (" + x1 + ’,’ + y1 + ") to (" + x2 + ’,’ + y2 + ’)’); } } and Chapter 12 387 public class LegacyRectangle { public void draw(int x, int y, int w, int h) { System.out.println("rectangle at (" + x + ’,’ + y + ") with width " + w + " and height " + h); } } Because the interface between Line and Rectangle objects is incapatible, the user has to recover the type of each shape and manually supply the correct arguments, i.e., source code public class AdapterDemo { public static void main(String[] args) { // Create and array of legacy shapes ... Object[] shapes = { new LegacyLine(), new LegacyRectangle() }; // A begin and end point from a graphical editor int x1 = 10, y1 = 20, x2 = 30, y2 = 60; for (int i = 0; i < shapes.length; ++i) if (shapes[i].getClass().getName().equals("LegacyLine") == true) ((LegacyLine)shapes[i]).draw(x1, y1, x2, y2); else if (shapes[i].getClass().getName().equals("LegacyRectangle") == true) ((LegacyRectangle)shapes[i]).draw(Math.min(x1, x2), Math.min(y1, y2) , Math.abs(x2 - x1), Math.abs(y2 - y1)); } } In our bare-bones implementation, we decide which version of the draw() method should be called by directly relating it to the class name, e.g., if ( shapes[i].getClass().getName().equals("LegacyLine") == true ) .... While this approach to implementation will work, the lack of separation in the abstraction and implementation means that the code may need to be changed in numerous places if a new shape is added (e.g., LegacyTriangle.java, LegacySquare.java). 388 Engineering Software Development in Java Code After Adapter The adapter introduces and additional level of indirection to create a common interface mapping to legacy-specific peculiar interfaces. This results in the class hierarchy relationship shown in Figure 12.11. << interface >> Shape uses LegacyRectangle implements Line Rectangle LegacyLine uses Figure 12.11. Class hierarchy for the adapter interfaces to legacy code. The revised source code is as follows: LegacyLine.java source code /* * ========================================================== * LegacyLine.java: * ========================================================== */ public class LegacyLine { public void draw(int x1, int y1, int x2, int y2) { System.out.printf("Line from ( %2d, %2d ) to ( %2d, %2d )\n", x1, y1, x2, y2 ); } } LegacyRectangle.java source code /* * ========================================================== * LegacyRectangle.java: * ========================================================== */ public class LegacyRectangle { public void draw(int x, int y, int w, int h) { System.out.printf("Rectangle at ( %2d, %2d ): width = %2d : height = %2d\n", 389 Chapter 12 x, y, w, h ); } } Shape.java source code /* * ========================================================== * Shape.java: * ========================================================== */ public interface Shape { void draw(int x1, int y1, int x2, int y2); } Line.java source code /* * ========================================================== * Line.java: Adapter object to legacy line ... * ========================================================== */ public class Line implements Shape { private LegacyLine adaptee = new LegacyLine(); public void draw(int x1, int y1, int x2, int y2) { adaptee.draw(x1, y1, x2, y2); } } Rectangle.java source code /* * ========================================================== * Rectangle.java: Adapter to legacy rectangle format. * ========================================================== */ public class Rectangle implements Shape { 390 Engineering Software Development in Java private LegacyRectangle adaptee = new LegacyRectangle(); public void draw(int x1, int y1, int x2, int y2) { int width = Math.abs(x2 - x1); int height = Math.abs(y2 - y1); adaptee.draw(Math.min(x1, x2), Math.min(y1, y2), width, height ); } } RunAdapterPattern.java. The test program creates an array of references to shape objects, one line and one rectangle. Then instances of Line and Rectangle, refer indirectly to implementations for the legacy line and rectangle. source code /* * ========================================================== * RunAdapterPattern.java: * ========================================================== */ public class RunAdapterPattern { public static void main(String[] args) { // Create and array of references to shapes ... Shape[] shapes = { new Line(), new Rectangle() }; // Call routine to draw individual shapes ... int x1 = 10, y1 = 20; int x2 = 30, y2 = 60; for (int i = 0; i < shapes.length; ++i) shapes[i].draw(x1, y1, x2, y2); } } The program output is as follows: prompt >> java RunAdapterPattern Line from ( 10, 20 ) to ( 30, 60 ) Rectangle at ( 10, 20 ): width = 20 : height = 40 prompt >> 391 Chapter 12 Example 04. Table Adapter Model for Textual Requirements In this example we develop an adapter to map an array of requirements objects into a simple requirements table view. See Figure 12.12. Figure 12.12. Screendump of a simple requirements table view. Figure 12.13 shows the essential details of the class hierarchy needed to create the array-to-table adapter model. The abstract table model defines methods for accessing information about the table size, the contents within a particular cell, and the machinery for listening to and handling table events. The abbreviated details of AbstractTableModel are as follows: public abstract class AbstractTableModel implements TableModel, Serializable { public String getColumnName(int columnIndex) { ... } public int findColumn(String columnName) { ... } public void setValueAt(Object value, int rowIndex, int columnIndex) { .. } public abstract Object getValueAt(int row, int column); public abstract int getColumnCount(); public abstract int getRowCount(); } The TableModel interface specifies methods that need to be implemented in order for JTable to operate. Again, the abbreviate details are: import javax.swing.event.TableModelListener; public interface TableModel { public int getRowCount(); public int getColumnCount(); public String getColumnName(int columnIndex); public Class getColumnClass(int columnIndex); public boolean isCellEditable(int rowIndex, int columnIndex); public Object getValueAt(int rowIndex, int columnIndex); public void setValueAt(Object aValue, int rowIndex, int columnIndex); public void addTableModelListener(TableModelListener listener); public void removeTableModelListener(TableModelListener listener); } 392 Engineering Software Development in Java Array − to − Table Adapter AbstractTableModel JTable << interface >> TableModel RequirementsTableModel RunAdapterPattern Requirement Figure 12.13. Class hierarchy for requirements table program. Where it makes sense, default implementations for these methods can be provided in AbstractTableModel. Implementations for the remaining methods are provided by an adapter class that extends AbstractTableModel, i.e., in our application, this is RequirementsTableModel. The beauty of this setup is that it allows JTable to refer to adapter models simply in terms of the methods prescribed in the TableModel interface – JTable never actually needs to know about the working details of RequirementsTableModel. Requirement.java. This class defines methods and attributes for a single textual requirement and its value. source code /* * * * * * */ ================================================================== Requirement.java: This class defines a single textual requirement. Written by: Mark Austin March 2012 ================================================================== import java.lang.Math.*; public class Requirement { String sName; String sDescription; double dValue; // Constructor public Requirement() {} Chapter 12 public Requirement( String sName, String sDescription ) { this.sName = sName; this.sDescription = sDescription; this.dValue = 0.0; } // Set and get requirement name ... public void setName( String sName ) { this.sName = sName; } public String getName() { return this.sName; } // Set and get requirement description ... public void setDescription( String sDescription ) { this.sDescription = sDescription; } public String getDescription() { return this.sDescription; } // Set and get value of requirement ... public void setValue( double dValue ) { this.dValue = dValue; } public double getValue() { return this.dValue; } // Copy Requirement parameters to a string format ... public String toString() { return "Req(" + sName + ") is (" + sDescription + "): value = " + dValue; } // Exercise methods in class Circle ... public static void main( String [] args ) { System.out.println("Exercise methods in class Requirement"); System.out.println("====================================="); Requirement rA = new Requirement(); rA.setName ("1.0"); rA.setDescription("My first requirement"); rA.setValue( 3.0 ); System.out.println("Requirement" ); 393 394 Engineering Software Development in Java System.out.println( rA ); } } As a standalone program, this source code file generates the output: prompt >> java Requirement Exercise methods in class Requirement ===================================== Requirement Req(1.0) is (My first requirement): value = 3.0 prompt >> RequirementsTableModel.java. This is the interesting part of the array-to-table adapter. While AbstractTableModel talks about a table model in generic terms, RequirementsTableModel defines the size and contents of a table having three columns. source code /* * =================================================================== * RequirementsTableModel.java: Adapt a collection of requirements for display in a JTable format. * * March 2012 * Written By: Mark Austin * =================================================================== */ import javax.swing.table.*; public class RequirementsTableModel extends AbstractTableModel { protected Requirement[] requirements; protected String[] columnNames = new String[] { "Name", "Description", "Value" }; // Construct a table of requirements from an array of requirements. public RequirementsTableModel( Requirement[] requirements ) { this.requirements = requirements; } // Return the number of columns in this table. public int getColumnCount() { return columnNames.length; } // Return the name of the indicated column. public String getColumnName(int i) { return columnNames[i]; } Chapter 12 395 // Return the number of rows in this table. public int getRowCount() { return requirements.length; } // Return the value at the indicated row and column.. public Object getValueAt(int row, int col) { switch (col) { case 0: return requirements[row].getName(); case 1: return requirements[row].getDescription(); case 2: return new Double( requirements[row].getValue() ); default: return null; } } } The requirements adapter stores the requirements as an array of references to individual requirements. The methods getRowCount(..) and getValueAt(..) retrieve the contents of the requirements matrix. Notice that we do not define methods for the event listeners – these are defined in AbstractTableModel and inherited through the extension mechanism. RunAdapterPattern.java. The test program instantiates seven textual requirement objects, organizes their referenes into an array called ra, creates and object for the requirments table model, and finally, creates and displays a JTable within a frame. source code /* * =========================================================================== * RunAdapterPattern.java: Use a RequirementsTableModel adapter interface to link individual requirements to a JTable view. * * March 2012 * Written By: Mark Austin * =========================================================================== */ import java.awt.Component; import java.awt.Font; import import import import javax.swing.*; javax.swing.JFrame; javax.swing.JScrollPane; javax.swing.JTable; 396 Engineering Software Development in Java public class RunAdapterPattern { public static void main(String[] args) { // Initialize user interface fonts .... Font font = new Font("Dialog", Font.PLAIN, 18); UIManager.put("Table.font", font); UIManager.put("TableHeader.font", font); // Generate a small set of requirements ... Requirement Requirement Requirement Requirement Requirement Requirement Requirement r1 r2 r3 r4 r5 r6 r7 = = = = = = = new new new new new new new Requirement("1.1", Requirement("1.2", Requirement("1.3", Requirement("1.4", Requirement("1.5", Requirement("1.6", Requirement("1.7", "My "My "My "My "My "My "My first requirement" ); second requirement" ); third requirement" ); fourth requirement" ); fifth requirement" ); sixth requirement" ); seventh requirement" ); // Create a requirements table model ... Requirement ra [] = { r1, r2, r3, r4, r5, r6, r7 }; RequirementsTableModel rtm = new RequirementsTableModel( ra ); // Create and view table model. JTable table = new JTable( rtm ); table.setRowHeight(36); JScrollPane pane = new JScrollPane(table); pane.setPreferredSize( new java.awt.Dimension(800, 250) ); // Create and display frame .... JFrame frame = new JFrame("Requirements Table View"); frame.getContentPane().add( pane ); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.pack(); frame.setVisible(true); } } 397 Chapter 12 12.5 Composite Design Pattern Purpose. To develop a flexible way to create hierarchical tree structures of arbitrary complexity while enabling every element of the structure to operate with a uniform interface. Description. Composite allows a group of objects to be treated in the same way as a single instance of an object. The intent of composite is to compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions uniformly. Implementation. Implementations of this pattern employ component, node, and composite classes: 1. The base component provides the core model, defining all methods and/or variables to be used by all objects in the class Composite. 2. The node (or leaf) classes represent terminal behavior (i.e., parts of the composite that cannot contain other components). 3. Composite (or branch) classes can have other components added to them, thereby permitting extension of the Composite structure. Figure 12.14 shows the class diagram for the composite design pattern. <<interface>> 0 .. * Component void operation(); Node Composite Component components []; void operation(); void addComponent ( Component c); void removeComponent ( Component c ); void operation(); Figure 12.14. Composite class diagram (pg. 159 of Stelting). Implementation of the composite design pattern requires three elements: 1. Component. The component interface defines methods available to all parts of the tree structure. Normally component is implemented as an abstract class. Its subclass (Node) is a concrete class and is used to create collections or a tree structure. 2. Composite. This class is defined by the components it contains. It supports a dynamic group of Components, and so it has methods to add and remove Component objects from its collection. 398 Engineering Software Development in Java The Composite class also provides concrete implementions of operational methods defined in class Component. 3. Node. The Node class implements the Component interface and provides an implementation for each of the component’s operational methods. Notice that unlike the Composite, Node is a leaf – it does not contain any references to other components. Example 05. Composite Hierarchy for Furniture Placement Within buildings, rooms are spaces with an assigned function and a given set of characteristics. Within rooms, collections of furniture provide support for required functions. For example, an office is a space dedicated to work. A kitchen is a space dedicated to the preparation of food. Each item within a room, whether it be a desk or chair of benchtop for preparation of food, occupies space. Collectively, the building-room-item decomposition constitutes a nested hierarchy of spaces. A second insight from architectural design is that collections of items work together to support a functional need. For example, an office is not an office without a desk. And a desk is of little use unless there is a chair positioned in a way that allows for usage of the desk space. The natural way of encoding these needs is to say [23]: ... spaces can include one or more sub-spaces (children), and are governed by rules for how things should be configured. To see how these ideas might be implemented in practice, Figures 12.15 and 12.16 show plan- and treeviews for the nesting and placement of spaces and furniture in a simple office. Notice that in the office definition, we first define a workspace, and then position furniture within the workspace. Each space in the hierarchy can be augmented with information on its position, default size, range of permissible values, an abstractions for visualizing the space/artifact. For example, while the geometry of furniture can be complex, the shape of a given piece can be approximated by a tightly fitting bounding box. Abbreviated Coding the Composite Hierarchy Let’s now look at the structure of a composite hiearchy implementation for the workspace and furniture layout. Using the composite design pattern, we can draw or print the details of space and furniture placement by recursively visiting spaces, starting at a given room (root space). To keep things simple, we will print a string description of the furniture items and keep track of the layer level. Book keeping details of the coordinate transformations and rotations will be added in the next example. Here is a summary of the required functionality for this example: 1. Office and Workspace contain children and, as such, can be viewed as containers in the hierarchy. Will will simply print the layer level. 2. Desk and Chair a leaf nodes in the hierarchy – they do not have children. We will print the name of the furniture item. Chapter 12 Figure 12.15. Composite hierarchy for layout of spaces and components (Source: [23]). Figure 12.16. Tree hierarchy for layout of furniture in a simple office (Source: [23]). 399 400 Engineering Software Development in Java The composite hiearchy design pattern provides a uniform view of containers and leaf nodes through the PrintView interface, i.e., public interface PrintView { public void print(); } A stripped down version of the Chair object might look like: /* * =================================================== * Chair.java: Code to create and print chair objects. * =================================================== */ public class Chair implements PrintView { String sName; // Name of chair ... public Chair ( String sName ) { this.sName = sName; } public void print() { System.out.println("*** Chair: " + sName ); } } Table objects will follow a similar format. Now we can define the CompositeHierarchy for systematically traversing the tree structure of containers and leaf components, i.e., /* * ================================================================ * CompositeHierarchy.java: Hierarchy of composite/furniture items. * ================================================================ */ import java.util.ArrayList; public class CompositeHierarchy implements PrintView { private static int iCurrentLevel = 0; private String sName; // Collection of child printitems. private ArrayList<PrintView> children = new ArrayList<PrintView>(); // Constructor for composite hierarchy ... public CompositeHierarchy( String sName ) { this.sName = sName; } // Print the composite graphic. Chapter 12 401 public void print() { // Increment level ... iCurrentLevel = iCurrentLevel + 1; System.out.printf("*** Start level %1d : %s\n", iCurrentLevel, sName ); // Loop to print the children items .... for (PrintView printview : children) { printview.print(); } // Decrement level ... System.out.printf("*** Finish level %1d : %s\n", iCurrentLevel, sName ); iCurrentLevel = iCurrentLevel - 1; } // Add composite/furniture item to the composition. public void add( PrintView printitem ) { children.add( printitem ); } // Remove composite/furniture item the composition. public void remove( PrintView printitem ) { children.remove( printitem ); } } Test Program. The test program creates composite objects for the office and workspaces, chair and table objects, and then systematically assembles the composite hierarchy. /* * ============================================================================= * TestComposite.java: Create and print hierarchy of spaces and furniture items. * ============================================================================= */ public class TestComposite { public static void main(String[] args) { // Initialize three composite graphics CompositeHierarchy office = new CompositeHierarchy("Office"); CompositeHierarchy workspace = new CompositeHierarchy("Workspace"); // Create table and chairs ... Table desk = new Table("Office Desk"); Chair chair01 = new Chair("Office Chair"); 402 Engineering Software Development in Java Chair chair02 = new Chair("Customer Chair"); // Add chair and table to the workspace ... workspace.add( chair01 ); workspace.add( desk ); // Add customer chair and workspace to the office space ... office.add( chair02 ); office.add( workspace ); // Traverse composite hierarchy ... office.print(); } } systematically assembles the hierarchy of spaces and furniture shown in Figure . office Level 1 chair workspace chair Level 2 desk Level 3 Figure 12.17. Tree hierarchy of placement of spaces and furniture in a simple office. Here, office and workspace are composite hierarchy objects (i.e., think of them as containers) and chair and table are components that will be positioned as leafs in the component hierarchy tree. The input/output is as follows: print >> java TestComposite *** Start level 1 : Office *** Chair: Customer Chair *** Start level 2 : Workspace *** Chair: Office Chair *** Table: Office Desk *** Finish level 2 : Workspace *** Finish level 1 : Office print >> The composite object office is the root of the space hierarchy. Chapter 12 403 Example 06. Draw Composite Hierarchy of Furniture In this example, we assemble a composite hierarchy for a dining room that contains four eating areas, and then systematically traverse the composite hierarchy tree to draw the dining room exterior and contents. Figure 12.18 shows geometry of the dining room exterior and layout of tables and chairs in the dining room. Figure 12.18. Modeling the perimeter of a room and layout of furniture as a composite hierarchy. There a number of ways to set up this problem. One possibility is to ... ... specify the position and orientation of every table and chair object with respect to a single coordinate system. While the use of a single coordinate simplifies source code needed to draw the dining room layout, this approach completely ignores the local relationships between objects needed to implement required functionality. For example, the chairs need to be positioned relative to the table so that people can sit at the table – if at some point the table moves, then the chairs need to be moved as well. This suggests that a far better approach is to ... ... implement the system layout with a hierarchy of coordinate systems, and then devise code to manage the manipulation of coordinate systems during traversal of the composite hierarchy. 404 Engineering Software Development in Java The source code is arranged into four groups of files, defintion of leaf components (e.g., rectangular boxes), support for a composite hierarchy of graphical interfaces, drawing of the composite hierarchy, and the test program. -----------------------------------------------------Leaf components Composite Hierarchy ====================================================== Box.java Ellipse.java Polygon.java CompositeHierarchy.java Graphic.java -----------------------------------------------------Graphical Interface Test Program ====================================================== DisplayCompositePanel.java TestComposite.java DisplayComposite.java ====================================================== Figure 12.19 shows the relationship among classes and interfaces in the composite hierarchy. << interface >> Graphic void draw(); Ellipse Polygon Box CompositeHierarchy void draw(); void draw(); void draw(); void draw(); Figure 12.19. Composite hierarchy for modeling the perimeter of a room and layout of furniture. Drawing of components is facilitated through the definition of a graphic interface, /* * ========================================================== * Graphic.java: Graphic interface .... * ========================================================== */ import java.awt.*; import java.awt.geom.*; public interface Graphic { public void draw( Graphics2D g2 ); // Draw the graphic ... } Chapter 12 405 followed by an Ellipse class that implements the Graphic interface, i.e., /* * ================================================================================ * Ellipse.java: Draw ellipse component. This is a leaf in the component hierarchy. * ================================================================================ */ import import import import import import import import import java.awt.*; java.awt.BasicStroke; java.awt.Color; java.awt.Font; java.awt.Graphics; java.awt.Graphics2D; java.awt.geom.*; java.awt.Point; javax.swing.*; public class Ellipse implements Graphic { final static BasicStroke stroke = new BasicStroke(2.0f); String sName; int x, y; public Ellipse ( String sName, int x, int y ) { this.sName = sName; this.x = x; this.y = y; } public void draw( Graphics2D g2D ) { // Draw ellipse .... g2D.setStroke(stroke); g2D.draw(new Ellipse2D.Double(x, y, 20, 20)); // Add label to ellipse ... g2D.scale( 1.0, -1.0 ); g2D.drawString( sName, x, - 200); g2D.scale( 1.0, -1.0 ); } } The framework for creating a hierarchy of Graphic entities is defined as follows: /* * ================================================================ * CompositeHierarchy.java: * ================================================================ */ import java.lang.Math; import java.util.List; import java.util.ArrayList; 406 Engineering Software Development in Java import java.awt.geom.*; // Needed for affine transformation.... import java.awt.Graphics; import java.awt.Graphics2D; public class CompositeHierarchy implements Graphic { private static int iCurrentLevel = 0; private static double dCurrentXOffSet = 0.0; private static double dCurrentYOffSet = 0.0; private static double dCurrentRotation = 0.0; private static AffineTransform at = new AffineTransform(); // Coordinates and orientation of composite graphic ... private double private double private double xOffset; yOffset; rotation; // Collection of child graphics. private ArrayList<Graphic> children = new ArrayList<Graphic>(); // Constructor for composite hierarchy ... public CompositeHierarchy( double xOffset, double yOffset, double rotation ) { this.xOffset = xOffset; this.yOffset = yOffset; this.rotation = rotation; } // Set Affine Fransformation ... public void setAffineTransform ( AffineTransform at ) { this.at = at; } // Draw the composite graphic. public void draw( Graphics2D g2 ) { // Update parameters ... iCurrentLevel = iCurrentLevel + 1; dCurrentXOffSet = dCurrentXOffSet + xOffset; dCurrentYOffSet = dCurrentYOffSet + yOffset; dCurrentRotation = dCurrentRotation + rotation; at.translate( xOffset, yOffset ); at.rotate( rotation ); g2.setTransform( at ); // Draw children .... for (Graphic graphic : children) { graphic.draw( g2 ); } 407 Chapter 12 // Decrement level, x- and y- offsets and rotation.... iCurrentLevel = iCurrentLevel - 1; dCurrentXOffSet = dCurrentXOffSet - xOffset; dCurrentYOffSet = dCurrentYOffSet - yOffset; dCurrentRotation = dCurrentRotation - rotation; at.rotate( -rotation ); at.translate( -xOffset, -yOffset ); g2.setTransform( at ); } // Adds the graphic to the composition. public void add(Graphic graphic) { children.add(graphic); } //Removes the graphic from the composition. public void remove(Graphic graphic) { children.remove(graphic); } } Display Composite Hierarchy Visualization of the composite hierarchy is handled by two source code files, DisplayComposite.java nad DisplayCompositePanel.java, which, in turn, are called by the test program. JPanel TestComposite DisplayComposite DisplayCompositePanel Figure 12.20. Relationship among classes in display of the floor plan. The relationship among these files, and the point of extension to create a panel for display of the floor plan is shown in Figure 12.20. /* * ========================================================================= * DisplayComposite.java: ..... * ========================================================================= */ import java.awt.*; import java.awt.Color; import javax.swing.*; 408 Engineering Software Development in Java import import import import import import import java.awt.Font; java.awt.Graphics; java.awt.Graphics2D; java.awt.geom.*; java.awt.Point; java.awt.event.*; java.util.*; public class DisplayComposite { private DisplayCompositePanel canvas; private CompositeHierarchy base; public DisplayComposite( CompositeHierarchy base ) { this.base = base; } // Create graphics screen .... public void display() { canvas = new DisplayCompositePanel( base ); canvas.setBackground( Color.white ); // Create a scroll pane and add the panel to it. JScrollPane scrollCanvas = new JScrollPane( canvas, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); scrollCanvas.setPreferredSize( new java.awt.Dimension(800, 800) ); // Create buttons for panel along bottom of screen ... JPanel panel = new JPanel(); panel.setLayout( new BorderLayout() ); panel.add( scrollCanvas, BorderLayout.CENTER ); JFrame frame = new JFrame("Display Composite Floorplan"); frame.getContentPane().setLayout( new BorderLayout() ); frame.getContentPane().add( panel ); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); frame.setSize( 800, 800); frame.pack(); frame.setVisible(true); } public void border() { canvas.drawBorder(); } } and /* * * * ====================================================================== DisplayCompositePanel.java: A simple JPanel to display contents of the composite hierarchy. Chapter 12 * ====================================================================== */ import import import import import import import import import import import import import import import import import import import import java.awt.*; java.awt.BasicStroke; java.awt.Color; java.awt.Font; java.awt.Graphics; java.awt.Graphics2D; java.awt.geom.*; java.awt.Point; java.awt.event.*; java.awt.event.MouseEvent; java.awt.event.MouseListener; java.awt.event.MouseMotionListener; java.beans.PropertyChangeEvent; java.util.*; java.util.List; java.util.Hashtable; java.util.Enumeration; java.util.Iterator; java.util.Set; javax.swing.*; public class DisplayCompositePanel extends JPanel { private CompositeHierarchy base; private AffineTransform at; int iBorder = 10; int width, height; Graphics2D g2D; public DisplayCompositePanel( CompositeHierarchy base ) { this.base = base; } // Paint panel ... public void paint() { Graphics g = getGraphics(); super.paintComponent(g); paintComponent(g); } protected void paintComponent(java.awt.Graphics g) { Dimension d = getSize(); height = getSize().height; width = getSize().width; g2D = (Graphics2D) g.create(); // Create screen-to-base coordinate transformation ... at = new AffineTransform(); at.translate( iBorder, height - iBorder ); at.scale( 1, -1 ); g2D.setTransform( at ); 409 410 Engineering Software Development in Java // Draw border around bounding box ... drawBorder(); // Draw contents of composite hierarchy ... base.setAffineTransform( at ); base.draw( g2D ); } // Draw rectangle around exterior boundary ... protected int x1 int y1 int x2 int y2 void drawBorder() { = 0; = 0; = width - 2*iBorder; = height- 2*iBorder; g2D.setColor( Color.blue ); g2D.draw( new Line2D.Double( x1, y1, x2, y1 g2D.draw( new Line2D.Double( x1, y1, x1, y2 g2D.draw( new Line2D.Double( x1, y2, x2, y2 g2D.draw( new Line2D.Double( x2, y1, x2, y2 g2D.setColor( Color.blue ); g2D.fill(new Ellipse2D.Double( -5, -5, 10, )); )); )); )); 10)); } } Test Program. .... /* * ====================================================================== * TestComposite.java: Test program for drawing the exterior boundary and layout of furniture in a dining room. * * ====================================================================== */ import java.util.ArrayList; public class TestComposite { public static void main(String[] args) { // Initialize three composite graphics CompositeHierarchy base = new CompositeHierarchy( 0.0, 0.0, 0.0 ); CompositeHierarchy CompositeHierarchy CompositeHierarchy CompositeHierarchy eatingArea1 eatingArea2 eatingArea3 eatingArea4 = = = = new new new new CompositeHierarchy( 50.0, 350.0, -Math.PI/2.0 ); CompositeHierarchy( 300.0, 100.0, 0.0 ); CompositeHierarchy( 250.0, 480.0, Math.PI/2.0 ); CompositeHierarchy( 300.0, 350.0, 0.0 ); // Create polygon for room exterior ... ArrayList<Integer> x = new ArrayList(); 411 Chapter 12 ArrayList<Integer> y = new ArrayList(); x.add x.add x.add x.add x.add x.add ( ( ( ( ( ( 20 300 500 700 600 120 ); ); ); ); ); ); y.add y.add y.add y.add y.add y.add ( ( ( ( ( ( 120 700 600 650 250 20 ); ); ); ); ); ); Polygon room = new Polygon( x.add x.add x.add x.add x.add ( ( ( ( ( 20 300 500 700 600 ); ); ); ); ); y.add y.add y.add y.add y.add ( ( ( ( ( 700 600 650 250 20 ); ); ); ); ); 0, 0, x, y ); // Create table and chairs ... Box table = new Box chair1 = new Box chair2 = new Box chair3 = new Box chair4 = new Ellipse chair5 = Ellipse chair6 = Ellipse chair7 = Ellipse chair8 = Box( 0, 50, 175, 100); Box( 0, 0, 25, 25); Box( 50, 0, 25, 25); Box( 100, 0, 25, 25); Box( 150, 0, 25, 25); new Ellipse("c1", 0, 175); new Ellipse("c2", 50, 175); new Ellipse("c3", 100, 175); new Ellipse("c4", 150, 175); // Define layout of table and chairs .... CompositeHierarchy tableandchairs.add tableandchairs.add tableandchairs.add tableandchairs.add tableandchairs.add tableandchairs = new CompositeHierarchy( 0.0, 0.0, 0.0 ); (table); (chair1); tableandchairs.add (chair2); (chair3); tableandchairs.add (chair4); (chair5); tableandchairs.add (chair6); (chair7); tableandchairs.add (chair8); // Add table and chairs to eating areas .... eatingArea1.add( eatingArea2.add( eatingArea3.add( eatingArea4.add( tableandchairs tableandchairs tableandchairs tableandchairs ); ); ); ); // Compose the scene .... base.add(eatingArea1); base.add(eatingArea2); base.add(eatingArea3); base.add(eatingArea4); base.add(room); // Draw complete graphic starting at the base ... DisplayComposite display = new DisplayComposite( base ); display.display(); } } Points to note: 412 Eating area 1 Engineering Software Development in Java Eating area 4 Eating area 3 Eating area 2 Figure 12.21. Coordinate system hierarchy for display of furniture placement in a dining room. 1. We can add print statements to the composite hierarchy to track the systematic evolution of coordinate offsets and rotations that occur during traversal of the composite hierarchy. This gives: Script started on Thu Feb 23 10:09:43 2012 prompt >> java TestComposite Start : Level= 1 : (x,y,rot) offset = ( 0.0, 0.0, 0.00) AffineTransform[ [ 1.0, 0.0, 10.0 ], [ 0.0, -1.0, 786.0 ]] Start : Level= 2 : (x,y,rot) offset = ( 50.0, 350.0, -1.57) AffineTransform[ [-0.0, 1.0, 60.0 ], [ 1.0, 0.0, 436.0 ]] Start : Level= 3 : (x,y,rot) offset = ( 50.0, 350.0, -1.57) AffineTransform[ [-0.0, 1.0, 60.0 ], [ 1.0, 0.0, 436.0 ]] End Level: 3 End Level: 2 Start : Level= 2 : (x,y,rot) offset = ( 300.0, 100.0, 0.00) AffineTransform[ [ 1.0, 0.0, 310.0 ], [ 0.0, -1.0, 686.0 ]] 413 Chapter 12 Start : Level= 3 : (x,y,rot) offset = ( 300.0, 100.0, 0.00) AffineTransform[ [ 1.0, 0.0, 310.0 ], [ 0.0, -1.0, 686.0 ]] End Level: 3 End Level: 2 Start : Level= 2 : (x,y,rot) offset = ( 250.0, 480.0, AffineTransform[ [ 0.0, -1.0, 260.0 [-1.0, -0.0, 306.0 Start : Level= 3 : (x,y,rot) offset = ( 250.0, 480.0, AffineTransform[ [ 0.0, -1.0, 260.0 [-1.0, -0.0, 306.0 End Level: 3 End Level: 2 Start : Level= 2 : (x,y,rot) offset = ( 300.0, 350.0, AffineTransform[ [ 1.0, 0.0, 310.0 [ 0.0, -1.0, 436.0 Start : Level= 3 : (x,y,rot) offset = ( 300.0, 350.0, AffineTransform[ [ 1.0, 0.0, 310.0 [ 0.0, -1.0, 436.0 End Level: 3 End Level: 2 End Level: 1 prompt >> Script done on Thu Feb 23 10:09:58 2012 2. 3. 4. 1.57) ], ]] 1.57) ], ]] 0.00) ], ]] 0.00) ], ]] 414 12.6 Engineering Software Development in Java Data-Flow Processing with Graphs of Executable Components In this section we present interface specifications and code implementations for a general purpose, executable, data processing component. The component is assembled from wires and ports. Data processing is enabled by a metacomponent infrastructure that keeps track of the working components, their input and output ports, and connectivity relationships between ports (implemented with wires). Arithmetic Component Processors Network Assembly and Process Execution Manager Input data Block 1 Addition Block 3 Multiplication Input data Output result Block 2 Addition Figure 12.22. Component network of arithmetic processors. For example, if BC is a basic component, .... [java] [java] [java] [java] BC(Block BC(Block BC(Block *** i = 1: ADD) Inputs: 1.0 2.0 Outputs: 3.0 2: ADD) Inputs: 3.0 4.0 Outputs: 7.0 3: MULT) Inputs: 3.0 7.0 Outputs: 21.0 1 Answer: y = 21.00 When the network description is enclosed within a looping construct, repeated evaluations of the arithmetic network are possible, e.g., Loop 1 [java] [java] [java] [java] BC(Block 1: BC(Block 2: BC(Block 3: Answer: y = ADD) Inputs: 2.0 3.0 Outputs: 5.0 ADD) Inputs: 4.0 5.0 Outputs: 9.0 MULT) Inputs: 5.0 9.0 Outputs: 45.0 45.00 Loop 2 [java] [java] [java] [java] BC(Block 1: BC(Block 2: BC(Block 3: Answer: y = ADD) Inputs: 3.0 4.0 Outputs: 7.0 ADD) Inputs: 5.0 6.0 Outputs: 11.0 MULT) Inputs: 7.0 11.0 Outputs: 77.0 77.00 The network process manager is responsible for incrementally stepping the data processing components through computational steps. For example, in Figure 12.22, one step of computation is required to compute the sums in blocks 1 and 2. A second computational step computes the multiplication in block 3, leading to the output result. Source Code From a software standpoint, this application is interesting because ... 415 Chapter 12 ... it is completely defined by collections of interfaces for the components, ports, wires and processing elements. The source code is an assembly of 13 files. To simplify the explanation of the interface definitions and implementation, we will the bundle source code into interfaces and implementations for ports, wires, and components. Part1: Port Implementation Port Interfaces ======================================================== PortImpl.java InputPortImpl.java OutputPortImpl.java Port.java InputPort.java OutputPort.java Part2: Component Implementations Component Interfaces ======================================================== BaseComponent.java Component.java ComponentEngine.java Part3: Wire Implementation Wire Interface ======================================================== WireImpl.java Wire.java Part4: Management of Data Processing ======================================================== MetaComponentSimple.java ======================================================== We will see that the system architecture and processing/function capability can be completely defined by ... ... relationships among the port, component, and wire interfaces. which, in turn, need to reflected in ... ... implementations for the component, port, wire and computational engine classes. Network of Arithmetic Processors To demonstrate these capabilities, a network of arithmetic block processors is defined by the three files: Part5: Application Implementation Use of Interfaces ============================================================== ArithmeticComponentEngine.java ComponentEngine.java ArithmenticOps.java TestNetwork.java ============================================================== 416 Engineering Software Development in Java ArithmeticOps.java is a simple implementation for basic arithmetic operations. ArithmeticComponentEngine.java is the processing engine that retrieves data values from the incoming ports and calls the arithmetic operations processor. TestNetwork.java is the code that assembles the component network of arithmetic processors shown in Figure 12.22. Part 1. Port Interface Hierarchy and Implementation Port.java, InputPort.java and OutputPort.java. Input and output ports are defined through a twolevel hierarchy of interface definitions. Implementations Interfaces implements PortImpl OutputPortImpl InputPortImpl << interface >> Port << interface >> InputPort implements implements Figure 12.23. Class and interface architecture for ports. The bundled source code is as follows: source code /** * ================================================================= * Port.java, InputPort.java and OutputPort.java ... * ================================================================= */ package component; public interface Port<T> { public Component<T> getParent(); public void setName(String sName); } // Set the name and value of the input port ... public interface InputPort<T> extends Port<T> { public void setValue(T value); } // Get the value of the output port << interface >> OutputPort 417 Chapter 12 public interface OutputPort<T> extends Port<T> { public T getValue(); } Points to note are as follows: 1. The port interface specifies a method for retrieving the component to which a port belongs (i.e., getParent()) and a simple method for setting the name of the port. Notice that the declaration doesn’t talk about a specific component implementations – instead it ... ... simply refers to the parent component indirectly through the Component interface implemented by specific components. 2. The input and output port interfaces extend (or build upon) the port interfaces. Input ports set data values and output ports get data values. 3. The use of java generics implies that all three port interfaces will be implemented with a single data type T (e.g., Boolean is true/false values are propogated through the network; Double if numerical values are propogated through the network). Port Implementation. source code /** * * * * * * * */ =============================================================== PortImpl.java: A base for building port implementations. It can function either as an input or output port, or both. @see com.wickedcooljava.sci.component.InputPortImpl @see com.wickedcooljava.sci.component.OutputPortImpl =============================================================== package component; public class PortImpl<T> implements Port<T> { private boolean initialized = false; private String sName; private Component<T> parent; private T value; public PortImpl(Component<T> par) { parent = par; } public Component<T> getParent() { return parent; } 418 Engineering Software Development in Java public void setName( String sName ) { this.sName = sName; } public void setValue(T val) { this.initialized = true; value = val; } public T getValue() { return value; } public boolean getInitialized() { return this.initialized; } public String toString() { return "\n" + sName + ".value=" + value; } } The methods getParent() and setName() are provided in PortImpl to satisfy the contract specified in Port.java. Notice that we also provide methods for setValue() and getValue() – these could have been provided in InputPort and OutputPort. The method getInitialized() is used by component engines to ensure that data values are initialized before proceeding with a computation. Input and Output Port Implementations. Implementations of InputPort and OutputPort use PortImpl as a base. Thus, the bundled source code is as follows: source code package component; public class InputPortImpl<T> extends PortImpl<T> implements InputPort<T> { public InputPortImpl( Component<T> parent ) { super(parent); } } public class OutputPortImpl<T> extends PortImpl<T> implements OutputPort<T> { public OutputPortImpl( Component<T> parent ) { super(parent); } } 419 Chapter 12 Since all of the functionality for setting and getting data values has been provided in PortImpl.java, all ImportPortImpl and OutputPortImpl have to do is provide constructor methods that pass a reference to the parent component to the constructor method in PortImpl.java. Part 2. Component Interface and Base Component Implementation Component.java. A component has an specific number of input and output ports that connect to other components, and performs some process that converts inputs into outputs. Input Ports Function Output Ports Figure 12.24. Architecture of a data processing component. Here is the component interface definition: source code /** * ================================================================================= * Component.java: A generic interface for components that process any type of data. * ================================================================================= */ package component; public interface Component<T> { // set component name ... public void setName( String sName ); // get the number of input ports public int getInputSize(); // get the number of output ports public int getOutputSize(); // get the nth input port public InputPort<T> getInputPort(int index); // get the nth output port public OutputPort<T> getOutputPort(int index); // perform the component’s processing public void process(); } 420 Engineering Software Development in Java Base Component Implementation The base component implementation provides a pass-through implementation of the Component interface, i.e., source code /** * ==================================================================== * BaseComponent.java: Basic implementation of the Component interface. * ==================================================================== */ package component; public class BaseComponent<T> implements Component<T> { protected String sName; protected int inSize, outSize; protected InputPortImpl<T>[] inports; protected OutputPortImpl<T>[] outports; // the function performed by this component protected ComponentEngine<T> function; /** * * @param inputs Number of input ports * @param outputs Number of output ports * @param f The function to perform the processing */ public BaseComponent(int inputs, int outputs, ComponentEngine<T> f ) { inSize = inputs; outSize = outputs; function = f; inports = new InputPortImpl[inSize]; for (int i = 0; i < inSize; i++) { inports[i] = new InputPortImpl<T>(this); } outports = new OutputPortImpl[outSize]; for (int i = 0; i < outSize; i++) { outports[i] = new OutputPortImpl<T>(this); } } public void setName ( String sName ) { this.sName = sName; for (int i = 0; i < inSize; i++) { inports[i].setName("BC(" + sName + ").in(" + i + "]"); } 421 Chapter 12 for (int i = 0; i < outSize; i++) { outports[i].setName("BC(" + sName + ").out[" + i + "]"); } } public int getInputSize() { return inSize; } public int getOutputSize() { return outSize; } public InputPort<T> getInputPort(int index) { return inports[index]; } public OutputPort<T> getOutputPort(int index) { return outports[index]; } /** * Delegate to the engine to do the processing. */ public void process() { function.process(inports, outports); } // Detailed string representation .... public String toString() { StringBuffer buf = new StringBuffer(); buf.append("BC(" + sName + ") "); buf.append(" Inputs: "); for (PortImpl port : inports) { buf.append(port.getValue()); buf.append(" "); } buf.append(" Outputs: "); for (OutputPort port : outports) { buf.append(port.getValue()); buf.append(" "); } return buf.toString(); } } plus provision for evaluation of arithmetic functions through the use of ComponentEngine interfaces, i.e., 422 Engineering Software Development in Java protected ComponentEngine<T> function; Details on the component engine interface are provided in the next section. Part 3. Wire Interface and Implementation A wire connects an output port to one or more input ports. Input Ports Output Port Source port Target ports Figure 12.25. Wire model: one-to-many connectivity between a source port and target ports. Here is a schematic of the implementation for the wire interface: Implementation Interface WireImpl implements << interface >> Wire Figure 12.26. Implementation of the wire interface. Wire.java. The wire interface contains methods for retrieving the source port, the number of target ports, a specific target port, and a method for propogating data from the source port to one or more target ports. source code /** * ========================================================== * Wire.java: Interface for a wire ... * ========================================================== */ package component; public interface Wire<T> { public OutputPort<T> getSourcePort(); Chapter 12 423 public int getNumberOfTargetPorts(); public InputPort<T> getTargetPort(int index); public void propagateSignal(); } Wire Implementation The wire implementation uses arraylists to support the one-to-many relationship between source and destination ports. source code /** * * * * * * * */ =========================================================================== WireImpl.java: A basic implementation of a Wire. Note. The number of target ports can grow dynamically, but for performance reasons it will use a single target object when the first one is added. As more targets are added, an ArrayList is used and is populated with them. =========================================================================== package component; import java.util.ArrayList; public class WireImpl<T> implements Wire<T> { private OutputPort<T> source; // lazy target array private ArrayList<InputPort<T>> targetList; private InputPort<T> target; private int count = 0; public WireImpl(OutputPort<T> src) { source = src; } public OutputPort<T> getSourcePort() { return source; } public int getNumberOfTargetPorts() { return count; } public InputPort<T> getTargetPort(int index) { if (index >= count || index < 0) { throw new IndexOutOfBoundsException(); } if (target != null) { 424 Engineering Software Development in Java return target; } return targetList.get(index); } public void addTargetPort(InputPort<T> tgt) { if (targetList == null) { if (target == null) { target = tgt; count++; } else { targetList = new ArrayList<InputPort<T>>(); targetList.add(target); target = null; } } if (targetList != null) { if (!targetList.contains(tgt)) { targetList.add(tgt); count++; } } } public void propagateSignal() { T value = source.getValue(); if (target == null) { if (targetList != null) { for (InputPort<T> tgt : targetList) { tgt.setValue(value); } } } else { target.setValue(value); } } } Chapter 12 425 Part 4. Simple Implementation of a MetaComponent In this section we provide a JGraphT-enabled implementation for assembly of components into a network of processes, and systematic evaluation (process execution) of arthmetic expressions. The implementation is “simple” in the sense that MetaComponentSimple does not implement the Component interface – as such, this implementation cannot be inserted into a hierarchy of arithmetic processors. source code /** * * * * * * * * */ ====================================================================== MetaComponentSimple.java: This is a simple version of a MetaComponent. It uses jgrapht to maintain the connections between child components. Original Code: Wicked Cool Java book. Modified by: Mark Austin October 2009 ====================================================================== package component; import org.jgraph.*; import org.jgraph.graph.*; import org.jgrapht.*; import org.jgrapht.ext.*; import org.jgrapht.graph.*; import org.jgrapht.ListenableGraph; import org.jgrapht.ext.JGraphModelAdapter; import org.jgrapht.graph.ListenableDirectedGraph; // Setup default edge import org.jgrapht.graph.DefaultEdge; public class MetaComponentSimple<T> { // the graph that maintains child components private ListenableDirectedGraph graph; public MetaComponentSimple() { graph = new ListenableDirectedGraph<T,DefaultEdge>(DefaultEdge.class); } // =================================================================== // Connect an output port to an input port. // =================================================================== public void connect( OutputPort<T> out, InputPort<T> in ) { Component<T> source = out.getParent(); 426 Engineering Software Development in Java Component<T> target = in.getParent(); // 1: Add parent components to graph if (graph.containsVertex(source) != true) { graph.addVertex(source); } if (graph.containsVertex(target) != true) { graph.addVertex(target); } // 2: Add ports to graph if (graph.containsVertex(in) != true ) { graph.addVertex(in); } if (graph.containsVertex(out) != true) { graph.addVertex(out); } // 3: Add an edge from out parent to output port graph.addEdge(source, out); // 4: Add an edge from output port to input port graph.addEdge(out, in); // 5: add an edge from input port to target component graph.addEdge(in, target); } // // // // =================================================================== Perform the processing by processing each of the subcomponents and propagating signals from outputs to inputs. =================================================================== public void process() { processSubComponents(); propagateSignals(); } // // // // =================================================================== For all connected sub componets, propagate signals from all outputs to all inputs. =================================================================== private void propagateSignals() { // Walk along edges and propogate output port values to // input port values .... 427 Chapter 12 for (Object item : graph.edgeSet()) { DefaultEdge edge = (DefaultEdge) item; Object source = graph.getEdgeSource( edge ); Object target = graph.getEdgeTarget( edge ); if (source instanceof OutputPort) { OutputPort<T> out = (OutputPort<T>) source; InputPort<T> in = (InputPort<T>) target; in.setValue( out.getValue() ); } } } // =================================================================== // Process all subcomponents, by calling the process methods for each. // =================================================================== private void processSubComponents() { for (Object item : graph.vertexSet()) { if (item instanceof Component) { ((Component<T>) item).process(); } } } // =================================================================== // Returns the graph used by this MetaComponent // =================================================================== public Graph getGraph() { return graph; } } A few points to note: 1. The connect() method is responsible for assembly of the JGraphT model. Vertices are added to the graph for the parent components (i.e., in.getParent()) and out.getParent()) and the input/output ports connected to wires. Input ports that are not connected to a wire are not part of the graph model. 2. The process() method systematically executes functionality for all of the subcomponents (i.e., component fuctionalities in the graph), and then propagates data values across the wires that are connected to ports. Example 07. Network of Arithmetic Block Processors ComponentEngine.java. This class defines an interface for the processing engine of a component. The purpose of the component engine is to convert inputs into outputs. 428 Engineering Software Development in Java source code package component; public interface ComponentEngine<T> { public void process( PortImpl<T>[] in, PortImpl<T>[] out ); } ArithmeticOps.java. A simple implementation for arithmetic operations. source code /** * ================================================================= * ArithmeticOps.java: A simple arithmetic operations implementation * ================================================================= */ package testnetwork; public class ArithmeticOps { private String name; private ArithmeticOps ops; // Details of arithmetic operation .... public public public public static static static static final final final final ArithmeticOps ADD = new ArithmeticOps MULTIPLY = new ArithmeticOps SUBTRACT = new ArithmeticOps DIVIDE = new ArithmeticOps("ADD"); ArithmeticOps("MULTIPLY"); ArithmeticOps("SUBTRACT"); ArithmeticOps("DIVIDE"); // ======================================================================= // Create arithmetic operations object with the specified number of args.. // ======================================================================= public ArithmeticOps(int inputs, int outputs) { this.name = null; } public ArithmeticOps( String name ) { this.name = name; } public void setOpsType ( final ArithmeticOps ops ) { this.ops = ops; } public void setName ( String name ) { this.name = name; } public String getName() public ArithmeticOps getOps() { return this.name; } { return this.ops; } Chapter 12 429 public double computeValue ( double arg1, double arg2 ) { double value = 0.0; if ( ArithmeticOps.ADD == this.getOps() ) { value = arg1 + arg2; } else if ( ArithmeticOps.SUBTRACT == this.getOps() ) { value = arg1 - arg2; } else if ( ArithmeticOps.MULTIPLY == this.getOps() ) { value = arg1 * arg2; } else if ( ArithmeticOps.DIVIDE == this.getOps() ) { value = arg1 / arg2; } else { System.out.println("*** Arithmetic Operation Type not defined .. "); } return value; } public String toString() { String s = "ArithmeticOps: type = " + this.name + "\n"; return s; } } ArithmeticOpsEngine.java. This class defines a component engine for arithmetic operations. It implements the ComponentEngine interface. source code /** * ============================================================================= * ArithmeticComponentEngine.java: A component engine for computing arithmetic operations. * * ============================================================================= */ package testnetwork; import component.*; public class ArithmeticComponentEngine implements ComponentEngine<Double> { private ArithmeticOps tt; public ArithmeticComponentEngine( ArithmeticOps aops ) { tt = aops; } public void process( PortImpl<Double>[] in, PortImpl<Double>[] out ) { PortImpl<Double> arg1 = in[0]; PortImpl<Double> arg2 = in[1]; if( arg1.getInitialized() == true && arg2.getInitialized() == true ) { 430 Engineering Software Development in Java double value = tt.computeValue ( arg1.getValue().doubleValue(), arg2.getValue().doubleValue() ); PortImpl<Double> arg3 = out[0]; arg3.setValue ( value ); } } } TestNetwork.java. Here is the code to assemble the network of computational blocks, initialize the port data, and propogate the computations through the network of computational blocks. source code /* * * * * * * */ ========================================================================== TestNetwork.java: Exercise simple metacomponent model in a network of arithmetic operations... Modified by: Mark Austin October 2009 ========================================================================== package testnetwork; import component.*; public class TestNetwork { // ============================================== // Create an Add (ADD) component .... // ============================================== public static Component<Double> createAddComponent(int inputs) { ArithmeticOps add = new ArithmeticOps (inputs, 1); add.setOpsType ( ArithmeticOps.ADD ); add.setName ( "ADD" ); ArithmeticComponentEngine processor = new ArithmeticComponentEngine(add); BaseComponent<Double> bc = new BaseComponent<Double>(inputs, 1, processor); return ( bc ); } // ============================================== // Create a Multiply (MULTIPLY) component .... // ============================================== public static Component<Double> createMultiplyComponent(int inputs) { ArithmeticOps multiply = new ArithmeticOps (inputs, 1); multiply.setOpsType ( ArithmeticOps.MULTIPLY ); 431 Chapter 12 multiply.setName ( "MULT" ); ArithmeticComponentEngine processor = new ArithmeticComponentEngine(multiply); BaseComponent<Double> bc = new BaseComponent<Double>(inputs, 1, processor); return ( bc ); } // ============================================== // Assemble and Exercise component assembly .... // ============================================== public static void main(String[] args) { // Create network manager .... MetaComponentSimple<Double> manager = new MetaComponentSimple<Double>(); // Create Add and Multiply processing components ... Component<Double> add1 add1.setName("Block 1: Component<Double> add2 add2.setName("Block 2: = createAddComponent(2); ADD"); = createAddComponent(2); ADD"); Component<Double> mult1 = createMultiplyComponent(2); mult1.setName("Block 3: MULT"); // Get input ports ..... InputPort<Double> InputPort<Double> InputPort<Double> InputPort<Double> a b c d = = = = add1.getInputPort(0); add1.getInputPort(1); add2.getInputPort(0); add2.getInputPort(1); // Set the input values a.setValue( b.setValue( c.setValue( d.setValue( new new new new Double(1.0) Double(2.0) Double(3.0) Double(4.0) ); ); ); ); // Assemble graph of processing components ... manager.connect( add1.getOutputPort(0), mult1.getInputPort(0) ); manager.connect( add2.getOutputPort(0), mult1.getInputPort(1) ); // Print details of components at beginning of propogation ... System.out.println( ""); System.out.println( "Part 1: Initial Condition of Base Components ... "); System.out.println( "------------------------------------------------ "); System.out.println( add1.toString() ); 432 Engineering Software Development in Java System.out.println( add2.toString() ); System.out.println( mult1.toString() ); // Print details of meta-component graph ... System.out.println( ""); System.out.println( "Part 2: Meta-Component Graph ... "); System.out.println( "----------------------------------- "); System.out.println( manager.getGraph().toString() ); // Propogate signals through component wires ... System.out.println( ""); System.out.println( "Part 3: Propogate signals: Steps 1 and 2... "); System.out.println( "------------------------------------------- "); manager.process(); manager.process(); // Print details of meta-component graph ... System.out.println( ""); System.out.println( "Part 4: Meta-Component Graph ... "); System.out.println( "----------------------------------- "); System.out.println( manager.getGraph().toString() ); System.out.println( add1.toString() ); System.out.println( add2.toString() ); System.out.println( mult1.toString() ); System.out.println( ""); System.out.println( "Part 5: Result .... "); System.out.println( "----------------------------------- "); OutputPort<Double> y = mult1.getOutputPort(0); System.out.println( "Answer: y = " + y.getValue().doubleValue() ); } } The (slightly edited) program input/output is as follows: prompt >> ant run09 Part 1: Initial Condition of Base Components ... -----------------------------------------------BC(Block 1: ADD) Inputs: 1.0 2.0 Outputs: null BC(Block 2: ADD) Inputs: 3.0 4.0 Outputs: null BC(Block 3: MULT) Inputs: null null Outputs: null Part 2: Meta-Component Graph ... ----------------------------------( [ Chapter 12 BC(Block 1: ADD) Inputs: 1.0 2.0 Outputs: null, BC(Block 3: MULT) Inputs: null null Outputs: null , BC(Block 3: MULT).in(0].value=null, BC(Block 1: ADD).out[0].value=null, BC(Block 2: ADD) Inputs: 3.0 4.0 Outputs: null , BC(Block 3: MULT).in(1].value=null, BC(Block 2: ADD).out[0].value=null ] , [ ( BC(Block 1: ADD) Inputs: 1.0 2.0 Outputs: null, BC(Block 1: ADD).out[0].value=null ), ( BC(Block 1: ADD).out[0].value=null, BC(Block 3: MULT).in(0].value=null ), ( BC(Block 3: MULT).in(0].value=null, BC(Block 3: MULT) Inputs: null null Outputs: null ), ( BC(Block 2: ADD) Inputs: 3.0 4.0 Outputs: null, BC(Block 2: ADD).out[0].value=null ), ( BC(Block 2: ADD).out[0].value=null, BC(Block 3: MULT).in(1].value=null ), ( BC(Block 3: MULT).in(1].value=null, BC(Block 3: MULT) Inputs: null null Outputs: null ) ] ) Part 3: Propogate signals: Steps 1 and 2... ------------------------------------------Part 4: Meta-Component Graph ... ----------------------------------( [ BC(Block 1: ADD) Inputs: 1.0 2.0 Outputs: 3.0 , BC(Block 3: MULT) Inputs: 3.0 7.0 Outputs: 21.0 , BC(Block 3: MULT).in(0].value=3.0, BC(Block 1: ADD).out[0].value=3.0, BC(Block 2: ADD) Inputs: 3.0 4.0 Outputs: 7.0 , BC(Block 3: MULT).in(1].value=7.0, BC(Block 2: ADD).out[0].value=7.0 ] , [ ( BC(Block 1: ADD) Inputs: 1.0 2.0 Outputs: 3.0, BC(Block 1: ADD).out[0].value=3.0), ( BC(Block 1: ADD).out[0].value=3.0, BC(Block 3: MULT).in(0].value=3.0), ( BC(Block 3: MULT).in(0].value=3.0, BC(Block 3: MULT) Inputs: 3.0 7.0 Outputs: 21.0 ), ( BC(Block 2: ADD) Inputs: 3.0 4.0 Outputs: 7.0, BC(Block 2: ADD).out[0].value=7.0 ), ( BC(Block 2: ADD).out[0].value=7.0, BC(Block 3: MULT).in(1].value=7.0 ), ( BC(Block 3: MULT).in(1].value=7.0, BC(Block 3: MULT) Inputs: 3.0 7.0 Outputs: 21.0 ) ] ) BC(Block 1: ADD) Inputs: 1.0 2.0 Outputs: 3.0 BC(Block 2: ADD) Inputs: 3.0 4.0 Outputs: 7.0 BC(Block 3: MULT) Inputs: 3.0 7.0 Outputs: 21.0 Part 5: Result .... 433 434 Engineering Software Development in Java Assembly of components and ports Block 1 Block 3 Block 2 Two−step execution of arithmetic processors Step 2 Step 1 Figure 12.27. Graph structure for the network of arithmetic processors. ----------------------------------Answer: y = 21.0 prompt >> Key points to note: 1. The system architecture is modeled with the seven-node graph structure shown in Figure 12.27. Three nodes are parent components, i.e., BC(Block 1: ADD) Inputs: 1.0 2.0 BC(Block 2: ADD) Inputs: 3.0 4.0 BC(Block 3: MULT) Inputs: null null Outputs: null Outputs: null Outputs: null The remaining four nodes are input and output ports, i.e., BC(Block BC(Block BC(Block BC(Block 1: 2: 3: 3: ADD).out[0].value=null, ADD).out[0].value=null, MULT).in(0].value=null, MULT).in(1].value=null, Four graph edges correspond to links between the input/output ports and their parent components, i.e., 435 Chapter 12 ( BC(Block BC(Block ( BC(Block BC(Block ( BC(Block BC(Block ( BC(Block BC(Block 1: 1: 2: 2: 3: 3: 3: 3: ADD) Inputs: 1.0 2.0 Outputs: null, ADD).out[0].value=null ), ADD) Inputs: 3.0 4.0 Outputs: null, ADD).out[0].value=null ), MULT).in(0].value=null, MULT) Inputs: null null Outputs: null ), MULT).in(1].value=null, MULT) Inputs: null null Outputs: null ) The remain two edges connect blocks 1 and 2 to block 3, i.e., ( BC(Block 1: ADD).out[0].value=null, BC(Block 3: MULT).in(0].value=null ), ( BC(Block 2: ADD).out[0].value=null, BC(Block 3: MULT).in(1].value=null ), 2. At the beginning of the data processing, the contents of blocks 1 through 3 are: BC(Block 1: ADD) BC(Block 2: ADD) BC(Block 3: MULT) Inputs: 1.0 2.0 Inputs: 3.0 4.0 Inputs: null null Outputs: null Outputs: null Outputs: null And at the end they are: BC(Block 1: ADD) BC(Block 2: ADD) BC(Block 3: MULT) Inputs: 1.0 2.0 Inputs: 3.0 4.0 Inputs: 3.0 7.0 Outputs: 3.0 Outputs: 7.0 Outputs: 21.0 The first call to manager.process() systematically evaluates the block functionality for all of the JGraphT nodes which implement the Component interface. For our simple application, blocks 1 and 2 have data values on their input ports – the corresponding addition operations are computed and propogated across the wire to the input ports for block 3. Block 3 has functionality, but during step one neither of the input ports have been initialized with data, so nothing happens. During the second call to manager.process(), the arithmetic operations for blocks 1 and 2 are repeated – obviously very inefficient – and the arithmetic operation for block 3 is computed. 436 Engineering Software Development in Java Example 08. Hierarchy of Components for Data-Flow Processing Now, let us implement a base component: source code /** * ================================================================================== * MetaComponent.java * ================================================================================== */ package component; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; public class MetaComponent<T> implements Component<T> { private String sName; public void setName ( String sName ) { this.sName = sName; } public interface MetaVisitor<T> { public void visit(Component<T> component); } private private private private private HashMap<OutputPort<T>,WireImpl<T>> sourceWireMap; ArrayList<Wire<T>> wires; HashSet<Component<T>> subComponents; ArrayList<InputPort<T>> externalInputs = new ArrayList<InputPort<T>>(); ArrayList<OutputPort<T>> externalOutputs = new ArrayList<OutputPort<T>>(); public void accept(MetaVisitor<T> v) { for (Component<T> item : subComponents) { v.visit(item); } } public int getInputSize() { return externalInputs.size(); } public int getOutputSize() { return externalOutputs.size(); } public InputPort<T> getInputPort(int index) { return externalInputs.get(index); } public OutputPort<T> getOutputPort(int index) { return externalOutputs.get(index); } 437 Chapter 12 public MetaComponent() { subComponents = new HashSet<Component<T>>(); wires = new ArrayList<Wire<T>>(); sourceWireMap = new HashMap<OutputPort<T>,WireImpl<T>>(); } public void addConnection(OutputPort<T> src, InputPort<T>... targets) { for (InputPort<T> target : targets) { addConnection(src, target); } } public void addConnection(OutputPort<T> src, InputPort<T> target) { // Add ports’ parent components to my subcomponent list subComponents.add(src.getParent()); subComponents.add(target.getParent()); WireImpl<T> srcWire = sourceWireMap.get(src); if (srcWire == null) { // make a new wire srcWire = new WireImpl<T>(src); sourceWireMap.put(src, srcWire); } // TODO: Remove any old wire connections using the target? // add the target port to the wire srcWire.addTargetPort(target); } private void propagateSignals() { for (Wire<T> w : wires) { w.propagateSignal(); } } private void processSubComponents() { for (Component<T> item : subComponents) { item.process(); } } public void process() { processSubComponents(); propagateSignals(); } public void processRepeat(int count) { if (count <= 0) { return; 438 Engineering Software Development in Java } for (int i=0; i<count; i++) { process(); } } public void addExternalPort(InputPort<T> port) { subComponents.add(port.getParent()); externalInputs.add(port); } public void addExternalPort(OutputPort<T> port) { subComponents.add(port.getParent()); externalOutputs.add(port); } } and build an application ... source code TBD ...