ArchStudio 3 includes several components that are good examples of how to build a good ArchStudio 3 component. Perhaps the most comprehensive of these is currently ArchEdit, but others are emerging and will be documented here.
We are going to walk through the development of the equivalent of an ArchStudio 3 "Hello, World" component. This component and its source are included in the ArchStudio 3 build for teaching purposes.
The purpose of this component will be to provide an invokable interface that displays the URLs of the open architecture descriptions in xArchADT, similar to what the File Manager/Invoker component does.
Our component will:
- Be a "legacy" C2 component that can be welded into the ArchStudio 3 architecture.
- Respond to requests from the Invoker and advertise its invokability to the Invoker.
- Create and manage the GUI that will be updated whenever the list of open files changes.
First, we will choose a short name and a long name for our component. The short name will correspond to the Java package name, and the long name will be used everywhere else. Simply, we choose:
Short Name: helloworld
Long Name: HelloWorld
Now, we need to create a new Java package for our ArchStudio 3
component. This package should be a subpackage of archstudio.comp
. The
package name should be one lowercase word and will be the canonical
short name for your component. In our case, we will create a package
called archstudio.comp.helloworld
.

ArchStudio 3 components anc connectors are traditional or "legacy" C2 bricks. That is, they all have exactly two interfaces--the 'Top' and 'Bottom' interfaces. Requests are emitted from the top interface of a brick, while notifications are emitted from the bottom. Likewise, notifications are received on the top interface of a brick and requests are received on the bottom.
c2.fw, the architecture framework underlying ArchStudio 3, includes several
base classes to support building "legacy" C2 components. We will use one of
these as the base class for HelloWorldComponent
.
Building the HelloWorld Component
ArchStudio 3 components may be composed of as many Java classes as are necessary
to implement their functionality. However, each component must have a distinguished
class that implements the c2.fw.Component
interface, which will serve
as the entrypoint into the component. Our HelloWorldComponent
is
simple enough that we will be able to implement the component using only one class.
We will call this class HelloWorldC2Component
, and its code will be in
a file called HelloWorldC2Component.java
in the
archstudio/comp/helloworld/
directory.
To start building a component, first we need to have some standard Java header declarations, declaring the package of our component (which we have already chosen, above) and importing some packages that provide useful functionality. The header will look like this:
package archstudio.comp.helloworld; //Includes classes that will help to make our component //"Invokable" from the ArchStudio File Manager/Invoker import archstudio.invoke.*; //The c2.fw framework import c2.fw.*; //Support for "legacy" C2 components import c2.legacy.*; import c2.legacy.comp.*; //Includes classes that allow our component to //make Event-based Procedure Calls (EPCs) import c2.pcwrap.*; //Imported to support xArchADT use import edu.uci.ics.xarchutils.*; //Standard Java imports import java.awt.*; import java.awt.event.*; import java.util.*; import javax.swing.*; import javax.swing.event.*; |
Next, we will provide the opening declaration for our class, and some
useful constants. Our class will implement the c2.fw.Brick
interface.
This interface includes all the functions needed for a component to be welded into
a C2 architecture. However, most of these functions are boilerplate (especially
for "legacy" C2 components with a fixed number of interfaces). So, c2.fw includes
some base classes that implement these boilerplate functions for us. The one we
will extend is called AbstractC2DelegateBrick
. Most all "legacy"
C2 components should extend this class, unless it is necessary to extend
another class.
Because our component will also be invoked by the Invoker, it will
implement the archstudio.invoke.InvokableBrick
interface, which
will be covered later.
public class HelloWorldC2Component extends AbstractC2DelegateBrick implements InvokableBrick{ public static final String PRODUCT_NAME = "Hello World Component"; public static final String PRODUCT_VERSION = "1.0"; |
The EBI Wrapper Mechanism
Now is a good time to read about the EBI wrapper mechanism and how it works. Return here when you're finished.
HelloWorldComponent
's Constructor
Now that you have a good understanding of the EBI wrapper mechanism, we can walk through
the constructor of the HelloWorldComponent
.
//This XArchFlatInterface is an EPC interface implemented //in another component, in this case xArchADT. Because // this component uses c2.fw's EBI wrapper mechanism, //we can call functions in this interface directly and //all the communication gets translated from procedure //calls (PC) to EPC. protected XArchFlatInterface xarch; ... public HelloWorldC2Component(Identifier id){ super(id); this.addMessageProcessor(new StateChangeMessageProcessor()); xarch = (XArchFlatInterface)EBIWrapperUtils.addExternalService(this, topIface, XArchFlatInterface.class); InvokeUtils.deployInvokableService(this, bottomIface, "HelloWorld", "A demonstration component for ArchStudio 3."); } |
The first line of the constructor calls the superclass constructor, passing
an Identifier. Identifiers are objects that are used to identify many things
in c2.fw, including components. Because there may be more than one
HelloWorldComponent
in an architecture, they will be distinguished
by their Identifiers.
The next line adds a new message processor to our component. Message processors
are objects that implement a handle(...)
method, and get a chance
to handle any messages that arrives for a component. This particular message
processor handles state change messages coming from the XArchADT component, determining
when and if HelloWorldComponent
should update its GUI. This will be
discussed in detail later.
The next line creates a local proxy to an object implementing the
XArchFlatInterface
, which resides in another component (namely xArchADT).
Later, we will call functions on this xarch
object to get the list
of open architecture descriptions.
The parameters passed are:
this | The brick for which to build the proxy. |
topIface | The interface on this brick that will emit the outgoing calls and receive call responses. |
XArchFlatInterface.class | The Java interface class implemented by the object in the remote component. |
The final line makes a call to the InvokeUtils
object. Similar to some
calls to EBIWrapperUtils
, this call plugs some additional MessageProcessor
s
into our component. These MessageProcessor
s will respond to messages from
the Invoker to indicate that our component has an invokable GUI.
The parameters passed are:
this | The brick to make invokable (this one). |
bottomIface | The interface on this brick that will receive messages
from the Invoker. Because these are request messages, they will arrive on the
bottom interface of HelloWorldComponent . |
"HelloWorld" | The name of the service we're providing, which will appear in the menu of the Invoker. |
"A demonstration..." | A description of the service we're providing. |
Swing Stuff
HelloWorldComponent
includes an inner class that extends JFrame
.
This class implements the GUI functionality for the component. Most of it is pretty normal
Swing code.
class HelloWorldFrame extends JFrame{ protected JList urlList; public HelloWorldFrame(){ super(PRODUCT_NAME + " " + PRODUCT_VERSION); init(); } //This is pretty standard Swing GUI stuff in Java. private void init(){ Toolkit tk = getToolkit(); Dimension screenSize = tk.getScreenSize(); double xSize = (200); double ySize = (100); double xPos = (screenSize.getWidth() * 0.70); double yPos = (screenSize.getHeight() * 0.70); urlList = new JList(new DefaultListModel()); JPanel mainPanel = new JPanel(); mainPanel.setLayout(new BorderLayout()); mainPanel.add("North", new JLabel("Open URLs:")); mainPanel.add("Center", urlList); this.getContentPane().add(new JScrollPane(mainPanel)); setVisible(true); setSize((int)xSize, (int)ySize); setLocation((int)xPos, (int)yPos); setVisible(true); paint(getGraphics()); this.addWindowListener(new HelloWorldWindowAdapter()); //Go get the initial list of URLs. updateOpenURLs(); } class HelloWorldWindowAdapter extends WindowAdapter{ public void windowClosing(WindowEvent e){ destroy(); dispose(); setVisible(false); hwFrame = null; } } public void updateOpenURLs(){ DefaultListModel lm = (DefaultListModel)urlList.getModel(); lm.removeAllElements(); //This innocuous call gets translated by the local proxy //(XArchFlatInterface xarch) into an EPC and sent off, via an event //to xArchADT. The result is marshalled into an event and returned //here. All this magic happens under the covers because of the //services provided by EBIWrapperComponent. String[] urls = xarch.getOpenXArchURLs(); for(int i = 0; i < urls.length; i++){ lm.addElement(urls[i]); } repaint(); } } |
The part of the program in red above is the only interesting part of this code, because it makes an EPC to the xArchADT component, located elsewhere in the architecture. As you can see, it doesn't look any different from a regular procedure call because of the EBI wrapper mechanism we set up in the constructor.
Making the Component Invokable
As you'll recall, HelloWorldComponent
is an InvokableBrick
.
An InvokableBrick
implements one additional invoke(...)
method. The code for it is shown here.
//This gets called automatically when we get an invoke message from //the invoker. public void invoke(InvokeMessage m){ //Let's open a new window. newWindow(); } public void newWindow(){ //This makes sure we only have one active window open. if(hwFrame == null){ hwFrame = new HelloWorldFrame(); } else{ hwFrame.requestFocus(); } } |
In the above invoke(...)
method, we get an InvokeMessage
passed
to us that contains additional details about how we may be invoked, such as architecture
URLs that were selected in the file manager at the time. These are unimportant to
HelloWorldComponent
, so we can safely ignore them.
Handling State Changes in xArchADT
Programming an event-based component is somewhat different from programming a regular
object-oriented class. The component should respond properly to asynchronous events
coming from sources of change that affect it. For us, the only source of change
to be concerned about is xArchADT, which emits a state-change message every time
the list of files it manages changes. We have to monitor these messages and
update our list of open files whenever we get one. This is done by the
StateChangeMessage Processor
referred to in the constructor, above.
class StateChangeMessageProcessor implements MessageProcessor{ //This message processor handles state change messages //coming in from our top interface. We're looking for //state change messages that have, as their 0th parameter, //an XArchFileEvent public void handle(Message m){ if(m instanceof NamedPropertyMessage){ NamedPropertyMessage npm = (NamedPropertyMessage)m; try{ //If we get a state change message from above, it may have come from //xArchADT. We need to pay attention to this message if it indicates //a file was opened or closed. if(npm.getBooleanParameter("stateChangeMessage")){ //The first parameter of the state change message, if it does //indicate a change in the open-file-list, will be an XArchFileEvent if(npm.getParameter("paramValue0") instanceof XArchFileEvent){ XArchFileEvent evt = (XArchFileEvent)npm.getParameter("paramValue0"); //If it was, then we'll cheat a little bit, and instead of modifying our //list based on the event, we'll just re-ask xArchADT to give //us an updated list of all the open files. if(hwFrame != null){ hwFrame.updateOpenURLs(); return; } else{ return; } } } return; } catch(Exception e){ return; } } } } |
That's it!
Combining all the code fragments in this exercise gives the full code for HelloWorldComponent. It is listed here in full for convenience:
package archstudio.comp.helloworld; //Includes classes that will help to make our component //"Invokable" from the ArchStudio File Manager/Invoker import archstudio.invoke.*; //The c2.fw framework import c2.fw.*; //Support for "legacy" C2 components import c2.legacy.*; import c2.legacy.comp.*; //Includes classes that allow our component to //make Event-based Procedure Calls (EPCs) import c2.pcwrap.*; //Imported to support xArchADT use import edu.uci.ics.xarchutils.*; //Standard Java imports import java.awt.*; import java.awt.event.*; import java.util.*; import javax.swing.*; import javax.swing.event.*; //HelloWorldC2Component is a "legacy" C2 component that will be //inserted in the architecture. This means it has only 2 interfaces: //TOP and BOTTOM. A lot of boilerplate functionality for such //components is implemented in AbstractC2DelegateBrick, so we extend //that class when making our new C2 component. public class HelloWorldC2Component extends AbstractC2DelegateBrick implements InvokableBrick{ public static final String PRODUCT_NAME = "Hello World Component"; public static final String PRODUCT_VERSION = "1.0"; //This XArchFlatInterface is an EPC interface //implemented in another component, in this case //xArchADT. Because this component uses c2.fw's //EBI wrapper mechanism, we can call functions // in this interface directly and all the //communication gets translated from procedure //calls (PC) to EPC. protected XArchFlatInterface xarch; //The main application window protected HelloWorldFrame hwFrame = null; public HelloWorldC2Component(Identifier id){ super(id); this.addMessageProcessor(new StateChangeMessageProcessor()); xarch = (XArchFlatInterface)EBIWrapperUtils.addExternalService(this, topIface, XArchFlatInterface.class); InvokeUtils.deployInvokableService(this, bottomIface, "HelloWorld", "A demonstration component for ArchStudio 3."); } //This gets called automatically when we get an invoke message from //the invoker. public void invoke(InvokeMessage m){ //Let's open a new window. newWindow(); } public void newWindow(){ //This makes sure we only have one active window open. if(hwFrame == null){ hwFrame = new HelloWorldFrame(); } else{ hwFrame.requestFocus(); } } class StateChangeMessageProcessor implements MessageProcessor{ //This message processor handles state change messages //coming in from our top interface. We're looking for //state change messages that have, as their 0th parameter, //an XArchFileEvent public void handle(Message m){ if(m instanceof NamedPropertyMessage){ NamedPropertyMessage npm = (NamedPropertyMessage)m; try{ //If we get a state change message from above, it may have come from //xArchADT. We need to pay attention to this message if it indicates //a file was opened or closed. if(npm.getBooleanParameter("stateChangeMessage")){ //The first parameter of the state change message, if it does //indicate a change in the open-file-list, will be an XArchFileEvent if(npm.getParameter("paramValue0") instanceof XArchFileEvent){ XArchFileEvent evt = (XArchFileEvent)npm.getParameter("paramValue0"); //If it was, then we'll cheat a little bit, and instead of modifying our //list based on the event, we'll just re-ask xArchADT to give us an updated //list of all the open files. if(hwFrame != null){ hwFrame.updateOpenURLs(); return; } else{ return; } } } return; } catch(Exception e){ return; } } } } class HelloWorldFrame extends JFrame{ protected JList urlList; public HelloWorldFrame(){ super(PRODUCT_NAME + " " + PRODUCT_VERSION); init(); } //This is pretty standard Swing GUI stuff in Java. private void init(){ Toolkit tk = getToolkit(); Dimension screenSize = tk.getScreenSize(); double xSize = (200); double ySize = (100); double xPos = (screenSize.getWidth() * 0.70); double yPos = (screenSize.getHeight() * 0.70); urlList = new JList(new DefaultListModel()); JPanel mainPanel = new JPanel(); mainPanel.setLayout(new BorderLayout()); mainPanel.add("North", new JLabel("Open URLs:")); mainPanel.add("Center", urlList); this.getContentPane().add(new JScrollPane(mainPanel)); setVisible(true); setSize((int)xSize, (int)ySize); setLocation((int)xPos, (int)yPos); setVisible(true); paint(getGraphics()); this.addWindowListener(new HelloWorldWindowAdapter()); //Go get the initial list of URLs. updateOpenURLs(); } class HelloWorldWindowAdapter extends WindowAdapter{ public void windowClosing(WindowEvent e){ destroy(); dispose(); setVisible(false); hwFrame = null; } } public void updateOpenURLs(){ DefaultListModel lm = (DefaultListModel)urlList.getModel(); lm.removeAllElements(); //This innocuous call gets translated by the local proxy //(XArchFlatInterface xarch) into an EPC and sent off, via an event //to xArchADT. The result is marshalled into an event and returned //here. All this magic happens under the covers because of the //services provided by EBIWrapperComponent. String[] urls = xarch.getOpenXArchURLs(); for(int i = 0; i > urls.length; i++){ lm.addElement(urls[i]); } repaint(); } } } |
Questions? Comments?
Questions or comments about this tutorial should be sent to Eric Dashofy.