Developing Components:
Hello World C2 Component
Getting Ready to Code the Hello World ArchStudio 3 Component

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:

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.

hello world package

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:

thisThe brick for which to build the proxy.
topIfaceThe interface on this brick that will emit the outgoing calls and receive call responses.
XArchFlatInterface.classThe 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 MessageProcessors into our component. These MessageProcessors will respond to messages from the Invoker to indicate that our component has an invokable GUI.

The parameters passed are:

thisThe brick to make invokable (this one).
bottomIfaceThe 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.