This website uses cookies for visitor traffic analysis. By using the website, you agree with storing the cookies on your computer.More information

Category: Development

Eclipse Oxygen.2 with ANT and JRE6

The support for ANT and JRE6 with Eclipse Oxygen.2 (December 2017).

The plugin was created for:

Version: Oxygen.2 Release (4.7.2)
Build id: 20171218-0600

Don't forget the -clean start (read the original article for more details)!

One more thing you should do:

Oxygen.1 and Oxygen.2 contain ANT 1.10.1. This version requires Java 8. In order to use Java 6, you have to download ANT 1.9.x. Simply download ANT and set the ANT Home to your version, via

Window >> Preferences >> ANT >> Runtime >> Ant Home...

If you try to use the bundled ANT with JRE 6, it will fail with an Exception.

Download the plugin from here. It works for us - no warranty!

More Details: Eclipse Oxygen.1 with ANT and JRE6

VisionX CSS Styling feature

The upcoming release of VisionX will support CSS styling. We'll show you a first impression with a short video:

Vaadin AddOn for VisionX

The editor supports autocompletion and syntax highlighting:

VisionX CSS Editor

VisionX CSS Editor

We did integrat the RSyntaxTextArea for this. If you want to know how this works, check the documentation.

JVx and PostgreSQL, supporting Savepoints

Transactions are an integral mechanism of databases, without them we could hardly ensure data consistency. Well, we could of course, but it would be a lot more work. JDBC has of course support for transactions, but it also supports so called "Savepoints", which we will have a look at today.

What are transactions?

Normally, I'd assume that everyone of you knows what transactions are and what they are used for, but today I will quickly rehash it to make sure. Transactions enable us to define groups of statements to be executed on a database with all either succeeding or all failing. Imagine the following statements which might be executed on a database as one action:

1: Insert record into A
2: Update record of B
3: Update another record of B
4: Insert record into C

This might happen in the background if you click a button or do something similar. This example is straightforward but if we start thinking about possible problems we will soon realize that there is a lot of potential for things going wrong. For example if statement #3 would fail we would miss records in the tables B and C. Overall, our data would be in an inconsistent state after that. To make sure that it does not happen, we can define these statements as a single transaction.

Transaction
  |-Insert record into A
  |-Update record of B
  |-Update another record of B
  |-Insert record into C
  \-Commit

If one of these statements fail, all changes done by previous statements can be undone and the database will return to the state before we started manipulating it. That is great, because we can now guarantee that even though an error has happened, the data will remain in a consistent state.

We could even extend that with additional application logic, for example we insert three records and then we check if the values of all records add up to a certain threshold, if yes, we simply undo the changes. Or if we notice that a constraint has been violated (though, pretty much all databases support constraint definitions in one way or the other). Additionally, transactions are completely isolated from all other connected clients. That means that if we start a transaction and insert a record into table A, all other clients will not see this record until we commit our changes. So the data is never in an inconsistent state, not even temporarily or transiently.

What are Savepoints?

Savepoints are sub-transactions within another transaction. It allows to undo parts of an ongoing transaction.

Another example:

Transaction
  |-Insert record into A
  |-Insert child record into B
  |-Insert child record into B
  |-Insert another record into A
  |-Insert child record into B
  |-Insert child record into B
  |-Insert another record into A
  |-Insert child record into B
  |-Insert child record into B
  \-Commit

This is a little bit more fabricated and complicated, assume we want to insert three master records with two detail records each. The following conditions apply:

  • If a master record fails to insert, nothing should be inserted.
  • If a detail record fails to insert, the corresponding master record should also not be inserted.

This is hard to do with a simple transaction, but is quite easy when it comes to using savepoints:

Transaction
  |-Savepoint 1
  |   |-Insert record into A
  |   |-Insert child record into B
  |   \-Insert child record into B
  |-Savepoint 2
  |   |-Insert another record into A
  |   |-Insert child record into B
  |   \-Insert child record into B
  |-Savepoint 3
  |   |-Insert another record into A
  |   |-Insert child record into B
  |   \-Insert child record into B
  \-Commit

We create a savepoint before the insert of the master record, if the insert of the master record fails we rollback the transaction, if the insert of a detail record fails we rollback to the savepoint we created earlier. This allows quite complicated and nested transactions, especially because there is no defined limit to how deep savepoints can be nested.

Error behavior

Of course it can always happen that a statement fails for one reason or another, so it is important to know how the database behaves once an error occurred. We built ourselves another simple example:

Transaction
  |-Insert record into A
  |-Insert record into B (this fails)
  |-Insert record into C
  \-Commit

We insert three records, and the second one fails to insert. How should the database behave in such a situation? Turns out that this differs between different database systems.

Silent/Automatic restore to a previous state

Many databases perform a simple "silent/automatic restore to a previous state", all changes done (if any) of the current statement are undone and the transaction can be treated like the failing statement never happened. With the example above, and assuming that we do not cancel the transaction on an error, the records would be inserted into A and C.

In our tests Oracle, MySQL/MariaDB, H2 and SQLite were all behaving this way.

Requiring manual recovery

PostgresSQL requires to perform a "manual recovery" from a failed statement. So that once an error occurred during a transaction, the user has to revert to a (manually) set savepoint or rollback the complete transaction. We will go into details on that later.

Reverting everything and happily continuing

MS SQL on the other hand has a quite different approach. When an error occurs during a transaction, all changes are (automatically) rolled back but the transaction can still be used. So in our example, only the record in C would be inserted.

PostgreSQL JDBC and Savepoints

Back to PostgresSQL and how it requires manual recovery. When a statement fails within a transaction in PostgreSQL, the transaction enters the aborted state and one can then see an error like the one below if further statements are issued:

Current transaction is aborted, commands ignored until end of transaction block.

What that means is simple that the connection/server is still waiting on input on what to do with the transaction. There are three possible ways to recover from there:

  1. Rollback the complete transaction
  2. Commit, which will be transformed into a rollback at this point
  3. Rollback to a savepoint

But these actions must be initiated by the user before the transaction can be further used (or not, if it is being rolled back completely). If one wants to emulate the behavior of other databases in PostgreSQL, every statement that is executed within a transaction has to be "wrapped" with a savepoint, in pseudo-code:

begin transaction
    savepoint
    try
        execute statement
        release savepoint
    catch
        rollback to savepoint
   
    ...
commit

Even though that seems tedious, that is not the case. If you have such a requirement you already have central point through which all statements pass before being executed, so this can be implemented easily.

JVx and Savepoints

In our case it is DBAccess, our main datasource. Because every database interaction has to pass through DBAccess (in one way or the other), we could easily implement such emulation at a low-level and it is automatically available to all users. To be exact, DBAccess has received internal support to wrap all statements in savepoints when configured to do so. This configuration possibility is protected and is currently only used by the PostgresSQL DBAccess extension. It does exactly what it says on the tin and is only active when enabled and automatic commits have been turned off. So this change does have no effect on any other database but PostgreSQL.

We already have plans to extend this basic savepoint support with a public API which allows users of JVx to utilize this new functionality. One of the ideas that we are currently discussing is to provide the ability to create named savepoints. A simple mockup of that idea:

  1. // Switch off auto commit to use transactions.
  2. dbAccess.setAutoCommit(false);
  3.  
  4. // Insert some data.
  5. dbStorage.insert(aRecord);
  6. dbStorage.insert(anotherRecord);
  7.  
  8. // This part is optional.
  9. try
  10. {
  11.     dbAccess.setSavepoint("NAME");
  12.    
  13.     dbStorage.insert(yetAnotherRecord);
  14.     dbStorage.update(someOtherRecord);
  15. }
  16. catch (DataSourceException dse)
  17. {
  18.     log.error(dse);
  19.     dbAccess.rollbackTo("NAME");
  20. }
  21.  
  22. dbAccess.commit();

As said, we are currently in the process of discussing such possibilities but definitely want to provide such an API at one point.

Conclusion

As it turns out, the "special" behavior of PostgreSQL isn't as special as it seems to be. It is a design decision that was taken and that is understandable. Changing this behavior now, 20+ years in, is out of the question as it would require a substantial effort to make sure that this behavior is backwards compatible. The gains from such a change on the other hand would be very little, as it is a quite specialized case in which this behavior matters and the "fix" is rather easy.

DokuWiki XMLRPC with Java

DokuWiki

is a simple to use and highly versatile Open Source wiki software that doesn't require a database.

It's a really nice software for your documentation. The default theme isn't super modern, but it works. There are many custom themes available and styling is simple.

The really cool thing is the remote API of DokuWiki. It has a XML based RPC interface. Here are some details.

The documentation looks promise. There's a Java example :)
But the last update of the Java client was 2016 :(

After downloading and testing the client, it was clear that it doesn't work. It had a problem with cookie management. I tried to fix the problem in the base lib - aXMLRPC but it was tricky. The problem was the standard Http URL connection. After replacing it with Apache HttpClient, the dokujclient was working without problems.
I didn't try to find the root cause of the problem because it wasn't worth the time. It was faster to replace the communication part.

Here's a link to our patch and all required libraries (pre-built).

Here's a short snippet

DokuJClient client = new DokuJClient(url);
client.login(username, password);

client.putPage(pNamespace + ":" + pPageName, sText);

FileSearch fs = new FileSearch();
fs.search(dirImages, true);

for (String file : fs.getFoundFiles())
{
    client.putAttachment(pNamespace + ":" + pPageName + ":" + FileUtil.getName(file),
                         file, true);
}

client.logoff();

We use the remote interface for automatic article updates. It works great!

EPlug 1.2.7

We're happy to announce the release of EPlug 1.2.7. This minor update comes with a big performance improvement!

Major performance improvement

The most notable and important feature of this release is the major speed improvement when checking files. We've revisited how the build pipeline works and could determine quite a few bottlenecks which did not only encumber the build process, but also the live checks of databooks. After fixing these bottlenecks the databook live checks are blazing fast and one does not notice any more that these are active.

While reviewing the code, we also saw the possibility for a few more tweaks which we will incorporate into the next major release, to squeeze even more performance out of this.

Improved DataBook View

The DataBook view has also received some much needed attention. There is now a toolbar button which allows to sort the columns by their name (instead of their order as returned by the datasource). Additionally the view does no longer flicker when refreshing and will also clear itself when the last editor closes.

Other fixes

  1. There is no longer a sleeping EPlug job in the Progress View.
  2. The EPlug preferences are now only available on Java projects.
  3. If there is no screen lifecycle object, the session object is now used.
  4. Removed raw HTML in tooltips.

How to get it?

Simply update EPlug via Eclipse!

JVx 2.7 is available

We're happy to announce that JVx 2.7 is available. It's a bigger bugfix release with a small number of new features.

What's new?

  • Tibero Database Support
  • boolean support

    Our DBAccess implementation now supports boolean as parameter.

  • SimpleJavaSource

    It's now possible to create java objects from java source code, see our REST interface

  • Struct support

    Struct support for Oracle DB in procedures and functions.

  • Session timeouts

    We fixed the problem of session timeouts with long up/downloads.

  • WorkScreen

    It's now possible to open a work-screen with additional parameters. A new (optional) Parameter class is available.

  • Mac OS

    We fixed the problems with internal frame borders.

  • Many improvements

The full changelog is available here.

Start with JVx

VisionX, JVx and native Vaadin

If you create an application with VisionX, it's always a JVx based application. You get all advantages of JVx and its GUI independency, but sometimes the GUI indepency is not important and you want to use native GUI controls in your JVx application because JVx doesn't contain the GUI control or you need a commercial control. This is a very simple use-case and it's not a problem to mix JVx components with native components. We have different examples, with different GUI technologies, for this use-case.

Here are some links:

But we don't have a link for our Vaadin implementation. But no problem, here it is!

Simply create a new application with VisionX and a dummy screen like this one:

Simple screen

Simple screen

The screen contains a simple table and two editors. Nothing special. Now we want to add a custom Vaadin component in the empty space. It doesn't matter which component you use. Every vaadin component or AddOn component can be used. JVx doesn't do specific things, it's only an UI abstraction layer.

So, lets add an Accordion to the screen:

Integrated Accordion

Integrated Accordion

The Accordion component is a standard Vaadin component, simply added to the screen. But more... Do you see the "Show Vaadin Notification" Button? This is a standard JVx component. So we mix native vaadin components with standard JVx components and get the full power of both. One advantage of the JVx components is that the automatic translation works without additional hacks, or what about JVx' layouts, event handling, ...

Interested in the source code?

No worries, it's super simple to understand :)

Lets have a look at the custom code:

UIButton butNotification = new UIButton("Show Vaadin Notification");
butNotification.eventAction().addListener(new IActionListener()
{
        public void action(UIActionEvent arg0) throws Throwable
        {
                Notification noti = new Notification("Message",
                                                     "Description", Type.WARNING_MESSAGE);
                noti.setDelayMsec(2000);
                noti.show(Page.getCurrent());
        }
});

UIFormLayout flJVxPanel = new UIFormLayout();

UIPanel panJVxPanel = new UIPanel();
panJVxPanel.setLayout(flJVxPanel);

panJVxPanel.add(butNotification);

if (getApplication().getLauncher().isWebEnvironment())
{
        Accordion acc = new Accordion();
        acc.setHeight(100.0f, Unit.PERCENTAGE);

        for (int i = 1; i < 8; i++)
        {

                if (i >= 2)
                {
                        final Label label = new Label("Welcome sheet!", ContentMode.HTML);
                        label.setWidth(100.0f, Unit.PERCENTAGE);

                        final VerticalLayout layout = new VerticalLayout(label);
                        layout.setMargin(true);

                        acc.addTab(layout, "Tab " + i);
                }
                else
                {
                        groupPanelOverview.add(panJVxPanel);

                        acc.addTab(((Component)panJVxPanel.getResource()), "Tab " + i);
                }
        }

        groupPanelOverview.add(new UICustomComponent(acc), formLayout1.getConstraints(0, 2, -1, -1));
}

The Accordion source code was copied from Vaadin Sampler:

sample = new Accordion();
sample.setHeight(100.0f, Unit.PERCENTAGE);
 
for (int i = 1; i < 8; i++) {
    final Label label = new Label(TabSheetSample.getLoremContent(), ContentMode.HTML);
    label.setWidth(100.0f, Unit.PERCENTAGE);
 
    final VerticalLayout layout = new VerticalLayout(label);
    layout.setMargin(true);
 
    sample.addTab(layout, "Tab " + i);
}

So, what are the most interesting parts in our code?

First, we add the JVx panel to another JVx panel. This is important to get support for translation. If it's not important for your, simply ignore the line:

groupPanelOverview.add(panJVxPanel);

The groupPanelOverview is a simple UIGroupPanel with UIFormLayout:

UIFormLayout formLayout1 = new UIFormLayout();

UIGroupPanel groupPanelOverview = new UIGroupPanel();

groupPanelOverview.setText("Overview");
groupPanelOverview.setLayout(formLayout1);

groupPanelOverview.add(labelName, formLayout1.getConstraints(0, 0));
groupPanelOverview.add(editOverviewName, formLayout1.getConstraints(1, 0));
groupPanelOverview.add(labelDescription, formLayout1.getConstraints(0, 1));
groupPanelOverview.add(editOverviewDescription, formLayout1.getConstraints(1, 1, -1, 1));

Our JVx button will be added to the native vaadin Accordion with following code:

acc.addTab(((Component)panJVxPanel.getResource()), "Tab " + i);

We don't add the JVx component itself, we use the wrapped resource. This is a simple vaadin component: com.vaadin.ui.Button

And finally, we add the Accordion as custom component to our JVx group panel:

groupPanelOverview.add(new UICustomComponent(acc), formLayout1.getConstraints(0, 2, -1, -1));

This code:

if (getApplication().getLauncher().isWebEnvironment())

is important for VisionX because the vaadin components aren't available in Swing, so we use this check for the supported environment.

So far, we mixed native vaadin components with JVx components. It's super easy to use, isn't it?

But it's also possible to use CSS for JVx components:

Css style for Button

Css style for Button

The button got the friendly style, which is defined in Vaadin CSS. Check some examples.

To add the friendly style class to the JVx button, simple add:

Style.addStyleNames(butNotification, "friendly");

This example is using predefined CSS from vaadin. It's also possible to set custom styles in your own css file. Simple follow this instructions.

This article covered the integration of native vaadin components into an existing JVx application with all advantages of vaadin.

Let's experiment, reducing code in screens and lifecycle objects.

Let's do a small experiment, and see if we can reduce the code required in screens and lifecycle objects to a minimum.

But be advised, this blog post is meant as a food for thoughts and less as a practical manual on doing things. So we will explore different possibilities which might not be practical.

Starting point

We will start with a small screen which has been automatically generated by VisionX, but has (manually) been stripped of all comments, documentation, imports and has some formatting modifications to make it easier readable in this post.

  1. public class PeopleWorkScreen extends DataSourceWorkScreen
  2. {
  3.     private UIEditor editPeopleFirstName = new UIEditor();
  4.     private UIEditor editPeopleLastName = new UIEditor();
  5.     private UIEditor editPeopleDateOfBirth = new UIEditor();
  6.     private UIEditor editPeopleOccupation = new UIEditor();
  7.     private UILabel labelFirstName = new UILabel();
  8.     private UILabel labelLastName = new UILabel();
  9.     private UILabel labelDateofBirth = new UILabel();
  10.     private UILabel labelOccupation = new UILabel();
  11.     private UIFormLayout formLayout1 = new UIFormLayout();
  12.     private UIGroupPanel groupPanelPeople = new UIGroupPanel();
  13.     private NavigationTable tablePeople = new NavigationTable();
  14.     private UIPanel splitPanelMainFirst = new UIPanel();
  15.     private UIPanel splitPanelMainSecond = new UIPanel();
  16.     private UISplitPanel splitPanelMain = new UISplitPanel();
  17.     private UIBorderLayout borderLayout1 = new UIBorderLayout();
  18.     private UIBorderLayout borderLayout2 = new UIBorderLayout();
  19.     private UIBorderLayout borderLayout3 = new UIBorderLayout();
  20.     private RemoteDataBook rdbPeople = new RemoteDataBook();
  21.    
  22.     public PeopleWorkScreen(
  23.             IWorkScreenApplication pApplication,
  24.             AbstractConnection pConnection,
  25.             Map pParameter) throws Throwable
  26.     {
  27.         super(pApplication, pConnection, pParameter);
  28.        
  29.         initializeModel();
  30.         initializeUI();
  31.     }
  32.    
  33.     private void initializeModel() throws Throwable
  34.     {
  35.         rdbPeople.setName("people");
  36.         rdbPeople.setDataSource(getDataSource());
  37.         rdbPeople.open();
  38.     }
  39.    
  40.     private void initializeUI() throws Throwable
  41.     {
  42.         tablePeople.setMaximumSize(new UIDimension(450, 350));
  43.         tablePeople.setDataBook(rdbPeople);
  44.         tablePeople.setAutoResize(false);
  45.        
  46.         labelFirstName.setText("First Name");
  47.        
  48.         labelLastName.setText("Last Name");
  49.        
  50.         labelDateofBirth.setText("Date of Birth");
  51.        
  52.         labelOccupation.setText("Occupation");
  53.        
  54.         editPeopleFirstName.setDataRow(rdbPeople);
  55.         editPeopleFirstName.setColumnName("FIRST_NAME");
  56.        
  57.         editPeopleLastName.setDataRow(rdbPeople);
  58.         editPeopleLastName.setColumnName("LAST_NAME");
  59.        
  60.         editPeopleDateOfBirth.setDataRow(rdbPeople);
  61.         editPeopleDateOfBirth.setColumnName("DATE_OF_BIRTH");
  62.        
  63.         editPeopleOccupation.setDataRow(rdbPeople);
  64.         editPeopleOccupation.setColumnName("OCCUPATION");
  65.        
  66.         splitPanelMainFirst.setLayout(borderLayout2);
  67.         splitPanelMainFirst.add(tablePeople, UIBorderLayout.CENTER);
  68.        
  69.         groupPanelPeople.setText("People");
  70.         groupPanelPeople.setLayout(formLayout1);
  71.         groupPanelPeople.add(labelFirstName, formLayout1.getConstraints(0, 0));
  72.         groupPanelPeople.add(editPeopleFirstName, formLayout1.getConstraints(1, 0));
  73.         groupPanelPeople.add(labelLastName, formLayout1.getConstraints(2, 0));
  74.         groupPanelPeople.add(editPeopleLastName, formLayout1.getConstraints(3, 0));
  75.         groupPanelPeople.add(labelDateofBirth, formLayout1.getConstraints(0, 1));
  76.         groupPanelPeople.add(editPeopleDateOfBirth, formLayout1.getConstraints(1, 1));
  77.         groupPanelPeople.add(labelOccupation, formLayout1.getConstraints(2, 1));
  78.         groupPanelPeople.add(editPeopleOccupation, formLayout1.getConstraints(3, 1));
  79.        
  80.         splitPanelMainSecond.setLayout(borderLayout3);
  81.         splitPanelMainSecond.add(groupPanelPeople, UIBorderLayout.CENTER);
  82.        
  83.         splitPanelMain.add(splitPanelMainFirst, UISplitPanel.FIRST_COMPONENT);
  84.         splitPanelMain.add(splitPanelMainSecond, UISplitPanel.SECOND_COMPONENT);
  85.        
  86.         setLayout(borderLayout1);
  87.         add(splitPanelMain, UIBorderLayout.CENTER);
  88.     }
  89. }

This is the PeopleWorkScreen class. It really doesn't do much except containing a Split panel, having on the left a table and on the right a few editors. That does look quite manageable, also it does look as one would expect in the GUI.

The screen we are going to use as demonstration.

But it is a very good starting point for our experiments. As an additional note, we will lose VisionX support with all the changes we will make, unfortunately, that can't be circumvented I'm afraid.

Annotations for editors

The first thought one has when it comes to reducing code is to use Annotations to deliver important information. We can do this here, too, to remove some setup from the editors and instead add it to a more central place. So we will create a new Annotation which is holding the required information for the editors, which are the name of the databook and the name of the column.

  1. @Retention(RUNTIME)
  2. @Target(FIELD)
  3. public @interface DataBound
  4. {
  5.     public String dataBookName();
  6.     public String columnName();
  7. }
  1. public class PeopleWorkScreen extends DataSourceWorkScreen
  2. {
  3.     @DataBound(dataBookName = "people", columnName = "FIRST_NAME")
  4.     private UIEditor editPeopleFirstName = new UIEditor();
  5.     @DataBound(dataBookName = "people", columnName = "LAST_NAME")
  6.     private UIEditor editPeopleLastName = new UIEditor();
  7.     @DataBound(dataBookName = "people", columnName = "DATE_OF_BIRTH")
  8.     private UIEditor editPeopleDateOfBirth = new UIEditor();
  9.     @DataBound(dataBookName = "people", columnName = "OCCUPATION")
  10.     private UIEditor editPeopleOccupation = new UIEditor();
  11.     private UILabel labelFirstName = new UILabel();
  12.     private UILabel labelLastName = new UILabel();
  13.     private UILabel labelDateofBirth = new UILabel();
  14.     private UILabel labelOccupation = new UILabel();
  15.     private UIFormLayout formLayout1 = new UIFormLayout();
  16.     private UIGroupPanel groupPanelPeople = new UIGroupPanel();
  17.     private NavigationTable tablePeople = new NavigationTable();
  18.     private UIPanel splitPanelMainFirst = new UIPanel();
  19.     private UIPanel splitPanelMainSecond = new UIPanel();
  20.     private UISplitPanel splitPanelMain = new UISplitPanel();
  21.     private UIBorderLayout borderLayout1 = new UIBorderLayout();
  22.     private UIBorderLayout borderLayout2 = new UIBorderLayout();
  23.     private UIBorderLayout borderLayout3 = new UIBorderLayout();
  24.     private RemoteDataBook rdbPeople = new RemoteDataBook();
  25.    
  26.     public PeopleWorkScreen(
  27.             IWorkScreenApplication pApplication,
  28.             AbstractConnection pConnection,
  29.             Map pParameter) throws Throwable
  30.     {
  31.         super(pApplication, pConnection, pParameter);
  32.        
  33.         initializeModel();
  34.         initializeUI();
  35.     }
  36.    
  37.     private void initializeModel() throws Throwable
  38.     {
  39.         rdbPeople.setName("people");
  40.         rdbPeople.setDataSource(getDataSource());
  41.         rdbPeople.open();
  42.     }
  43.    
  44.     private void initializeUI() throws Throwable
  45.     {
  46.         tablePeople.setMaximumSize(new UIDimension(450, 350));
  47.         tablePeople.setDataBook(rdbPeople);
  48.         tablePeople.setAutoResize(false);
  49.        
  50.         labelFirstName.setText("First Name");
  51.        
  52.         labelLastName.setText("Last Name");
  53.        
  54.         labelDateofBirth.setText("Date of Birth");
  55.        
  56.         labelOccupation.setText("Occupation");
  57.        
  58.         splitPanelMainFirst.setLayout(borderLayout2);
  59.         splitPanelMainFirst.add(tablePeople, UIBorderLayout.CENTER);
  60.        
  61.         groupPanelPeople.setText("People");
  62.         groupPanelPeople.setLayout(formLayout1);
  63.         groupPanelPeople.add(labelFirstName, formLayout1.getConstraints(0, 0));
  64.         groupPanelPeople.add(editPeopleFirstName, formLayout1.getConstraints(1, 0));
  65.         groupPanelPeople.add(labelLastName, formLayout1.getConstraints(2, 0));
  66.         groupPanelPeople.add(editPeopleLastName, formLayout1.getConstraints(3, 0));
  67.         groupPanelPeople.add(labelDateofBirth, formLayout1.getConstraints(0, 1));
  68.         groupPanelPeople.add(editPeopleDateOfBirth, formLayout1.getConstraints(1, 1));
  69.         groupPanelPeople.add(labelOccupation, formLayout1.getConstraints(2, 1));
  70.         groupPanelPeople.add(editPeopleOccupation, formLayout1.getConstraints(3, 1));
  71.        
  72.         splitPanelMainSecond.setLayout(borderLayout3);
  73.         splitPanelMainSecond.add(groupPanelPeople, UIBorderLayout.CENTER);
  74.        
  75.         splitPanelMain.add(splitPanelMainFirst, UISplitPanel.FIRST_COMPONENT);
  76.         splitPanelMain.add(splitPanelMainSecond, UISplitPanel.SECOND_COMPONENT);
  77.        
  78.         setLayout(borderLayout1);
  79.         add(splitPanelMain, UIBorderLayout.CENTER);
  80.     }
  81. }

As one can see, we've annotated the fields with our new Annotation and removed the setup lines from the initializeUI() function. Of course, that alone does nothing, we must add the processing of the annotations somewhere. The best place would be in the application when the workscreen is opened.

  1. public class AnnotationAwareApplication extends ProjX
  2. {
  3.     public AnnotationAwareApplication(UILauncher pLauncher) throws Throwable
  4.     {
  5.         super(pLauncher);
  6.     }
  7.    
  8.     @Override
  9.     public synchronized IWorkScreen openWorkScreen(
  10.             String pClassName,
  11.             Modality pModality,
  12.             AbstractConnection pConnection,
  13.             Map pParameters,
  14.             boolean pSingleInstance) throws Throwable
  15.     {
  16.         DataSourceWorkScreen workScreen = (DataSourceWorkScreen)super.openWorkScreen(
  17.                 pClassName,
  18.                 pModality,
  19.                 pConnection,
  20.                 pParameters,
  21.                 pSingleInstance);
  22.        
  23.         for (Field field : workScreen.getClass().getDeclaredFields())
  24.         {
  25.             DataBound dataBound = field.getAnnotation(DataBound.class);
  26.            
  27.             if (dataBound != null && IEditor.class.isAssignableFrom(field.getType()))
  28.             {
  29.                 IDataBook dataBook = workScreen.getDataSource().
  30.                                         getDataBook(dataBound.dataBookName());
  31.                 String columnName = dataBound.columnName();
  32.                
  33.                 field.setAccessible(true);
  34.                
  35.                 IEditor editor = (IEditor)field.get(workScreen);
  36.                
  37.                 editor.setDataRow(dataBook);
  38.                 editor.setColumnName(columnName);
  39.             }
  40.         }
  41.        
  42.         return workScreen;
  43.     }
  44.    
  45. }

Easy enough, it removes some lines from the screen and the logic added inside the application is straightforward.

The upside is that we now have the information of the data binding right there in the field declaration, the downside is that it doesn't save us that much. In theory the gain is only 1 line per editor. We could do better than that.

Automatic editors

Annotations are interesting, but don't fit well here. We could do better when we extend the UIEditor itself and fit it with the necessary logic to be able to find its data binding on its own. That means that it would need to go upward at some point and find its parent workscreen to retrieve the datasource (which holds all the databooks). Walking upwards in the hierarchy is straightforward, the question is when we should do that? The best point in time would be when addNotify() is being called, because at that point the GUI is being created, so we are very, very likely inside the initializeUI() function of the workscreen or at a later point.

  1. public class AutomaticEditor extends UIEditor
  2. {
  3.     private String dataBookName = null;
  4.     private String columnName = null;
  5.    
  6.     public AutomaticEditor(String pDataBookName, String pColumnName)
  7.     {
  8.         super();
  9.        
  10.         dataBookName = pDataBookName;
  11.         columnName = pColumnName;
  12.     }
  13.  
  14.     @Override
  15.     public void addNotify()
  16.     {
  17.         DataSourceWorkScreen workScreen = getParentWorkScreen();
  18.        
  19.         try
  20.         {
  21.             setDataRow(workScreen.getDataSource().getDataBook(dataBookName));
  22.             setColumnName(columnName);
  23.         }
  24.         catch (ModelException e)
  25.         {
  26.             ExceptionHandler.raise(e);
  27.         }
  28.        
  29.         super.addNotify();
  30.     }
  31.    
  32.     private DataSourceWorkScreen getParentWorkScreen()
  33.     {
  34.         IContainer parent = getParent();
  35.        
  36.         while (parent != null && !(parent instanceof DataSourceWorkScreen))
  37.         {
  38.             parent = parent.getParent();
  39.         }
  40.        
  41.         return (DataSourceWorkScreen)parent;
  42.     }
  43. }
  1. public class PeopleWorkScreen extends DataSourceWorkScreen
  2. {
  3.     private UIEditor editPeopleFirstName=new AutomaticEditor("people","FIRST_NAME");
  4.     private UIEditor editPeopleLastName = new AutomaticEditor("people", "LAST_NAME");
  5.     private UIEditor editPeopleDoB = new AutomaticEditor("people", "DATE_OF_BIRTH");
  6.     private UIEditor editPeopleOccupation=new AutomaticEditor("people","OCCUPATION");
  7.     private UILabel labelFirstName = new UILabel();
  8.     private UILabel labelLastName = new UILabel();
  9.     private UILabel labelDateofBirth = new UILabel();
  10.     private UILabel labelOccupation = new UILabel();
  11.     private UIFormLayout formLayout1 = new UIFormLayout();
  12.     private UIGroupPanel groupPanelPeople = new UIGroupPanel();
  13.     private NavigationTable tablePeople = new NavigationTable();
  14.     private UIPanel splitPanelMainFirst = new UIPanel();
  15.     private UIPanel splitPanelMainSecond = new UIPanel();
  16.     private UISplitPanel splitPanelMain = new UISplitPanel();
  17.     private UIBorderLayout borderLayout1 = new UIBorderLayout();
  18.     private UIBorderLayout borderLayout2 = new UIBorderLayout();
  19.     private UIBorderLayout borderLayout3 = new UIBorderLayout();
  20.     private RemoteDataBook rdbPeople = new RemoteDataBook();
  21.    
  22.     public PeopleWorkScreen(
  23.             IWorkScreenApplication pApplication,
  24.             AbstractConnection pConnection,
  25.             Map pParameter) throws Throwable
  26.     {
  27.         super(pApplication, pConnection, pParameter);
  28.        
  29.         initializeModel();
  30.         initializeUI();
  31.     }
  32.    
  33.     private void initializeModel() throws Throwable
  34.     {
  35.         rdbPeople.setName("people");
  36.         rdbPeople.setDataSource(getDataSource());
  37.         rdbPeople.open();
  38.     }
  39.    
  40.     private void initializeUI() throws Throwable
  41.     {
  42.         tablePeople.setMaximumSize(new UIDimension(450, 350));
  43.         tablePeople.setDataBook(rdbPeople);
  44.         tablePeople.setAutoResize(false);
  45.        
  46.         labelFirstName.setText("First Name");
  47.        
  48.         labelLastName.setText("Last Name");
  49.        
  50.         labelDateofBirth.setText("Date of Birth");
  51.        
  52.         labelOccupation.setText("Occupation");
  53.        
  54.         splitPanelMainFirst.setLayout(borderLayout2);
  55.         splitPanelMainFirst.add(tablePeople, UIBorderLayout.CENTER);
  56.        
  57.         groupPanelPeople.setText("People");
  58.         groupPanelPeople.setLayout(formLayout1);
  59.         groupPanelPeople.add(labelFirstName, formLayout1.getConstraints(0, 0));
  60.         groupPanelPeople.add(editPeopleFirstName, formLayout1.getConstraints(1, 0));
  61.         groupPanelPeople.add(labelLastName, formLayout1.getConstraints(2, 0));
  62.         groupPanelPeople.add(editPeopleLastName, formLayout1.getConstraints(3, 0));
  63.         groupPanelPeople.add(labelDateofBirth, formLayout1.getConstraints(0, 1));
  64.         groupPanelPeople.add(editPeopleDoB, formLayout1.getConstraints(1, 1));
  65.         groupPanelPeople.add(labelOccupation, formLayout1.getConstraints(2, 1));
  66.         groupPanelPeople.add(editPeopleOccupation, formLayout1.getConstraints(3, 1));
  67.        
  68.         splitPanelMainSecond.setLayout(borderLayout3);
  69.         splitPanelMainSecond.add(groupPanelPeople, UIBorderLayout.CENTER);
  70.        
  71.         splitPanelMain.add(splitPanelMainFirst, UISplitPanel.FIRST_COMPONENT);
  72.         splitPanelMain.add(splitPanelMainSecond, UISplitPanel.SECOND_COMPONENT);
  73.        
  74.         setLayout(borderLayout1);
  75.         add(splitPanelMain, UIBorderLayout.CENTER);
  76.     }
  77. }

That removes the complete setup of the editor from the code, with the exception of the constructor, and instead the editor itself manages its own setup, neat. So I think we can't lose any more of the editor associated code at this point, the only further possibility would be to associate a "default" databook with the workscreen so that we can scrap the databook name from the constructor. Or, we could scan the fields of the screen and build the editors based on their name, but that is a rather fragile approach.

Inline fields

While we are at it and we will lose VisionX support anyway, we can inline the fields directly into the initializeUI() function to shed more lines.

  1. public class PeopleWorkScreen extends DataSourceWorkScreen
  2. {
  3.     private RemoteDataBook rdbPeople = new RemoteDataBook();
  4.    
  5.     public PeopleWorkScreen(
  6.             IWorkScreenApplication pApplication,
  7.             AbstractConnection pConnection,
  8.             Map pParameter) throws Throwable
  9.     {
  10.         super(pApplication, pConnection, pParameter);
  11.        
  12.         initializeModel();
  13.         initializeUI();
  14.     }
  15.    
  16.     private void initializeModel() throws Throwable
  17.     {
  18.         rdbPeople.setName("people");
  19.         rdbPeople.setDataSource(getDataSource());
  20.         rdbPeople.open();
  21.     }
  22.    
  23.     private void initializeUI() throws Throwable
  24.     {
  25.         NavigationTable tablePeople = new NavigationTable();
  26.         tablePeople.setMaximumSize(new UIDimension(450, 350));
  27.         tablePeople.setDataBook(rdbPeople);
  28.         tablePeople.setAutoResize(false);
  29.        
  30.         UIPanel splitPanelMainFirst = new UIPanel();
  31.         splitPanelMainFirst.setLayout(new UIBorderLayout());
  32.         splitPanelMainFirst.add(tablePeople, UIBorderLayout.CENTER);
  33.        
  34.         UIFormLayout formLayout1 = new UIFormLayout();
  35.        
  36.         UIGroupPanel groupPanelPeople = new UIGroupPanel();
  37.         groupPanelPeople.setText("People");
  38.         groupPanelPeople.setLayout(formLayout1);
  39.         groupPanelPeople.add(new UILabel("First Name"),
  40.                              formLayout1.getConstraints(0, 0));
  41.         groupPanelPeople.add(new AutomaticEditor("people", "FIRST_NAME"),
  42.                              formLayout1.getConstraints(1, 0));
  43.         groupPanelPeople.add(new UILabel("Last Name"),
  44.                              formLayout1.getConstraints(2, 0));
  45.         groupPanelPeople.add(new AutomaticEditor("people", "LAST_NAME"),
  46.                              formLayout1.getConstraints(3, 0));
  47.         groupPanelPeople.add(new UILabel("Date of Birth"),
  48.                              formLayout1.getConstraints(0, 1));
  49.         groupPanelPeople.add(new AutomaticEditor("people", "DATE_OF_BIRTH"),
  50.                              formLayout1.getConstraints(1, 1));
  51.         groupPanelPeople.add(new UILabel("Occupation"),
  52.                              formLayout1.getConstraints(2, 1));
  53.         groupPanelPeople.add(new AutomaticEditor("people", "OCCUPATION"),
  54.                              formLayout1.getConstraints(3, 1));
  55.        
  56.         UIPanel splitPanelMainSecond = new UIPanel();
  57.         splitPanelMainSecond.setLayout(new UIBorderLayout());
  58.         splitPanelMainSecond.add(groupPanelPeople, UIBorderLayout.CENTER);
  59.        
  60.         UISplitPanel splitPanelMain = new UISplitPanel();
  61.         splitPanelMain.add(splitPanelMainFirst, UISplitPanel.FIRST_COMPONENT);
  62.         splitPanelMain.add(splitPanelMainSecond, UISplitPanel.SECOND_COMPONENT);
  63.        
  64.         setLayout(new UIBorderLayout());
  65.         add(splitPanelMain, UIBorderLayout.CENTER);
  66.     }
  67. }

That makes the code harder to read but in the end it saves a lot of lines.

Automatic databooks

Now the only part of the screen which we can now reduce is the model part. This is a little more complicated, though. First, if we create a databook on the fly it's a little bit more complicated to get it customized, so we have to assume that we can use the databooks "as they are", with all the necessary setup being done on the server side (which is ideal anyway). Second, ideally we could let the connection create the databooks as they are needed, that is a little bit more complicated and for this example we will add that logic to the screen instead. So the PeopleWorkScreen gains the method getDataBook(String) which gets or creates a databook for the given name and returns it.

  1. public class PeopleWorkScreen extends DataSourceWorkScreen
  2. {
  3.     public PeopleWorkScreen(
  4.             IWorkScreenApplication pApplication,
  5.             AbstractConnection pConnection,
  6.             Map pParameter) throws Throwable
  7.     {
  8.         super(pApplication, pConnection, pParameter);
  9.        
  10.         initializeUI();
  11.     }
  12.    
  13.     private void initializeUI() throws Throwable
  14.     {
  15.         NavigationTable tablePeople = new NavigationTable();
  16.         tablePeople.setMaximumSize(new UIDimension(450, 350));
  17.         tablePeople.setDataBook(getDataBook("people"));
  18.         tablePeople.setAutoResize(false);
  19.        
  20.         UIPanel splitPanelMainFirst = new UIPanel();
  21.         splitPanelMainFirst.setLayout(new UIBorderLayout());
  22.         splitPanelMainFirst.add(tablePeople, UIBorderLayout.CENTER);
  23.        
  24.         UIFormLayout formLayout1 = new UIFormLayout();
  25.        
  26.         UIGroupPanel groupPanelPeople = new UIGroupPanel();
  27.         groupPanelPeople.setText("People");
  28.         groupPanelPeople.setLayout(formLayout1);
  29.         groupPanelPeople.add(new UILabel("First Name"),
  30.                              formLayout1.getConstraints(0, 0));
  31.         groupPanelPeople.add(new AutomaticEditor("people", "FIRST_NAME"),
  32.                              formLayout1.getConstraints(1, 0));
  33.         groupPanelPeople.add(new UILabel("Last Name"),
  34.                              formLayout1.getConstraints(2, 0));
  35.         groupPanelPeople.add(new AutomaticEditor("people", "LAST_NAME"),
  36.                              formLayout1.getConstraints(3, 0));
  37.         groupPanelPeople.add(new UILabel("Date of Birth"),
  38.                              formLayout1.getConstraints(0, 1));
  39.         groupPanelPeople.add(new AutomaticEditor("people", "DATE_OF_BIRTH"),
  40.                              formLayout1.getConstraints(1, 1));
  41.         groupPanelPeople.add(new UILabel("Occupation"),
  42.                              formLayout1.getConstraints(2, 1));
  43.         groupPanelPeople.add(new AutomaticEditor("people", "OCCUPATION"),
  44.                              formLayout1.getConstraints(3, 1));
  45.        
  46.         UIPanel splitPanelMainSecond = new UIPanel();
  47.         splitPanelMainSecond.setLayout(new UIBorderLayout());
  48.         splitPanelMainSecond.add(groupPanelPeople, UIBorderLayout.CENTER);
  49.        
  50.         UISplitPanel splitPanelMain = new UISplitPanel();
  51.         splitPanelMain.add(splitPanelMainFirst, UISplitPanel.FIRST_COMPONENT);
  52.         splitPanelMain.add(splitPanelMainSecond, UISplitPanel.SECOND_COMPONENT);
  53.        
  54.         setLayout(new UIBorderLayout());
  55.         add(splitPanelMain, UIBorderLayout.CENTER);
  56.     }
  57.    
  58.     public IDataBook getDataBook(String pDataBookName) throws ModelException
  59.     {
  60.         IDataBook dataBook = getDataSource().getDataBook(pDataBookName);
  61.        
  62.         if (dataBook == null)
  63.         {
  64.             dataBook = new RemoteDataBook();
  65.             dataBook.setName(pDataBookName);
  66.             dataBook.setDataSource(getDataSource());
  67.             dataBook.open();
  68.         }
  69.        
  70.         return dataBook;
  71.     }
  72. }

and the changed editor

  1. public class AutomaticEditor extends UIEditor
  2. {
  3.     private String dataBookName = null;
  4.     private String columnName = null;
  5.    
  6.     public AutomaticEditor(String pDataBookName, String pColumnName)
  7.     {
  8.         super();
  9.        
  10.         dataBookName = pDataBookName;
  11.         columnName = pColumnName;
  12.     }
  13.  
  14.     @Override
  15.     public void addNotify()
  16.     {
  17.         PeopleWorkScreen workScreen = getParentWorkScreen();
  18.        
  19.         try
  20.         {
  21.             setDataRow(workScreen.getDataBook(dataBookName));
  22.             setColumnName(columnName);
  23.         }
  24.         catch (ModelException e)
  25.         {
  26.             ExceptionHandler.raise(e);
  27.         }
  28.        
  29.         super.addNotify();
  30.     }
  31.    
  32.     private PeopleWorkScreen getParentWorkScreen()
  33.     {
  34.         IContainer parent = getParent();
  35.        
  36.         while (parent != null && !(parent instanceof PeopleWorkScreen))
  37.         {
  38.             parent = parent.getParent();
  39.         }
  40.        
  41.         return (PeopleWorkScreen)parent;
  42.     }
  43. }

This introduces a quite unhealthy coupling between the PeopleWorkScreen and the AutomaticEditor, we can live with that for this example, but in a real application we'd have to correctly structure these objects. For example by introducing a workscreen base class, or by actually extending the datasource to provide this functionality.

If we ingore the method we just introduced in the PeopleWorkScreen, we actually managed to reduce the screen class a great deal and removed code which can be automated. That means that, at least theoretically, the likelihood of errors as we write the code has been lowered and it has become easier to write error free code. We can also now see that the screen class has become quite minimal, there really isn't anything left that we could restructure or remove.

Lifecycle objects

Now let us jump to the server and have a look at the associated lifecycle object.

  1. public class People extends Session
  2. {
  3.     public DBStorage getPeople() throws Exception
  4.     {
  5.         DBStorage dbsPeople = (DBStorage)get("people");
  6.         if (dbsPeople == null)
  7.         {
  8.             dbsPeople = new DBStorage();
  9.             dbsPeople.setWritebackTable("PEOPLE");
  10.             dbsPeople.setDBAccess(getDBAccess());
  11.             dbsPeople.open();
  12.  
  13.             put("people", dbsPeople);
  14.         }
  15.         return dbsPeople;
  16.     }  
  17. }

There isn't much here that we can do, but a few small things might make it easier to write in the future. Again, we will be losing VisionX support if we edit this "too much", but we are far beyond that point anyway.

Reducing error potential

What can happen easily with managing storages is that one does copy and paste code and misses to edit the key, under which the storage is stored, correctly. That can happen quickly especially if there is a complex object hierarchy in place. So what we can do is separating the storing logic from the creation logic.

  1. public class People extends Session
  2. {
  3.     public DBStorage getPeople() throws Exception
  4.     {
  5.         return getOrCreateStorage("people", (storage) ->
  6.         {
  7.             storage.setWritebackTable("PEOPLE");
  8.         });
  9.     }
  10.    
  11.     protected DBStorage getOrCreateStorage(String pName, Consumer pStorageConfigurer)
  12.                                           throws Exception
  13.     {
  14.         DBStorage storage = (DBStorage)get(pName);
  15.         if (storage == null)
  16.         {
  17.             storage = new DBStorage();
  18.             storage.setDBAccess(getDBAccess());
  19.            
  20.             pStorageConfigurer.accept(storage);
  21.            
  22.             if (!storage.isOpen())
  23.             {
  24.                 storage.open();
  25.             }
  26.  
  27.             put(pName, storage);
  28.         }
  29.        
  30.         return storage;
  31.     }
  32. }

This has the upside that it would reduce the error potential greatly, especially with a lot of storages, and it would allow us to add additional safety checks, for example if a storage with that name is already existing and would be overridden. But it has the downside that with each access a new lambda function is created, that might or might not be important for your use case.

If we try to mitigate this side effect we will quickly reach certain limits, for example if we change the process to a registry based approach we will find that we've again introduced the very thing we wanted to remove. The storage configurer must be registered at the registry with a name and the storage must be received with a name, so we'd be back at square one, actually.

A more dynamic approach

To get rid of this duplication of the name we could create a registry with the configurers and use a generic approach to retrieving it. The problem here is that that is not possible at compile time, so what we need would actually be a system that catches non-matched calls for storages in the client/server connection and redirects it to our generic method. That is unfortunately not trivial and I will only outline this approach here now.

  1. public class People extends Session
  2. {
  3.     private Map<String, Consumer> registeredStorages = new HashMap();
  4.    
  5.     public People()
  6.     {
  7.         super();
  8.        
  9.         registerStorage("people", (storage) ->
  10.         {
  11.             storage.setWritebackTable("PEOPLE");
  12.         });
  13.     }
  14.    
  15.     protected DBStorage getOrCreateStorage(String pName) throws Exception
  16.     {
  17.         DBStorage storage = (DBStorage)get(pName);
  18.         if (storage == null)
  19.         {
  20.             storage = new DBStorage();
  21.             storage.setDBAccess(getDBAccess());
  22.            
  23.             registeredStorages.get(pName).accept(storage);
  24.            
  25.             if (!storage.isOpen())
  26.             {
  27.                 storage.open();
  28.             }
  29.            
  30.             put(pName, storage);
  31.         }
  32.        
  33.         return storage;
  34.     }
  35.    
  36.     protected void registerStorage(String pName, Consumer pStorageConfigurer)
  37.     {
  38.         registeredStorages.put(pName, pStorageConfigurer);
  39.     }
  40. }

Now all that is missing would be that the connection is calling our getOrCreateStorage(String) method with the name of the requested object. Of course there is also a lot of error checking missing here, but that is not relevant for our example.

And again, if we remove the additional code we added, because it should be contained inside a base class, we have managed to reduce the code on the server side significantly. From here going any further becomes complicated, for example, again, one might to work with Annotations but that won't save us anything here anymore.

Conclusion

From time to time it is necessary to let the thoughts drift and think about different things, for example how to reduce the code inside of classes which are automatically generated and managed. Once I had a nice discussion with someone online on how to do things differently in their library. We had a little back and forth on what could be done and in the end we agreed that it should stay the way it was because none of the approaches we came up with had a significant advantage over the current state. In the end they said that, even though nothing came of it, it was an important discussion to have because from time to time one had to engage in such stimulated, technical and theoretical discussions, and I agree completely with that. It is important to be open to new ideas and consider different approaches even when they don't end up taken.

JVx Reference, the FormLayout

Let's talk about the FormLayout, and why the anchor system makes it much more flexible than just a simple grid.

Basics

JVx comes with 5 layouts by default:

  • null/none/manual
  • BorderLayout
  • FlowLayout
  • GridLayout
  • FormLayout

From these five the first four are easily explained, only the FormLayout needs some more information because it might not be as easy to grasp in the first moment than the others.

The FormLayout uses a dependent anchor system. An Anchor in this context is a position inside the layout which is calculated from parent anchors and either the size of the component or a fixed value. So we can say there are two different types of Anchors inside the FormLayout which we are concerned about:

  • AutoSize-Anchors, its position is calculated from the component assigned to it.
  • Fixed-Anchors, its position is fixed.

Additionally, there are three special cases of Fixed-Anchors:

  • Border-Anchors, which surround the FormLayout at its border.
  • Margin-Anchors, which are inset from the border by the defined value.
  • Gap-Anchors, which are added to create a gap between components.

When it comes to calculating the position of an anchor, the position of the parent anchor is determined and then the value of the current anchor is added (which is either the size of a component or a fixed value). Simplified and in pseudo-code it can expressed like this:

  1. public int getPosition(Anchor pAnchor)
  2. {
  3.     int parentPosition = 0;
  4.    
  5.     if (pAnchor.getParent() != null)
  6.     {
  7.         parentPosition = getPosition(pAnchor.getParent());
  8.     }
  9.  
  10.     if (pAnchor.isAutoSize())
  11.     {
  12.         return parentPosition + pAnchor.getComponent().getWidth();
  13.     }
  14.     else
  15.     {
  16.         return parentPosition + pAnchor.getValue();
  17.     }
  18. }

With this knowledge, we are nearly done with completely understanding the FormLayout.

Creating constraints

Now, the second important part after the basics is knowing how the constraints are created. For example this:

  1. panel.add(component, layout.getConstraints(0, 0));

FormLayout with one added component.

With the coordinates of 0,0, no new anchors are created but instead the component is attached to the top and left margin anchor. Two new AutoSize-Anchors (horizontally and vertically) are created and attached to the component.

If we now add a second component in the same row:

  1. panel.add(component, layout.getConstraints(0, 0));
  2. panel.add(component, layout.getConstraints(1, 0));

FormLayout with two added components.

Because we are still on row 0 the component is attached to the top margin anchor and the previous AutoSize-Anchor for this row. Then, a new Gap-Anchor will be created which is attached to the trailing AutoSize-Anchor of the previous component.

We can of course also add items to the right and bottom:

  1. panel.add(component, layout.getConstraints(0, 0));
  2. panel.add(component, layout.getConstraints(1, 0));
  3. panel.add(component, layout.getConstraints(-1, -1));

FormLayout with three added components, one in the bottom right corner.

What happens is the same as when adding a component at the coordinates 0,0, except that the reference is the lower right corner. The component is attached to the bottom and right margin anchors, with trialing AutoSize-Anchors.

Last but not least, we can add components which span between anchors:

  1. panel.add(component, layout.getConstraints(0, 0));
  2. panel.add(component, layout.getConstraints(1, 0));
  3. panel.add(component, layout.getConstraints(-1, -1));
  4. panel.add(component, layout.getConstraints(2, 1, -2, -2));

FormLayout with four added components, one stretched.

Again, the same logic applies as previously, with the notable exception that new Gap-Anchors are created for all four sides. That includes variants which span over anchors:

  1. panel.add(component, layout.getConstraints(0, 0));
  2. panel.add(component, layout.getConstraints(1, 0));
  3. panel.add(component, layout.getConstraints(0, 1, 2, 1));

FormLayout with three added components, one of them spans multiple anchors.

The component is horizontally attached to the left Margin-Anchor and additionally to the AutoSize-Anchor of the second column. The AutoSize- and Gap-Anchor of the first column are not ignored, they are not relevant to this case.

At this point it is important to note that spanning and stretched components are disregarded for the preferred size calculation of the layout. So whenever you span or stretch a component, it is not taken into account when the preferred size of the layout is calculated, which can lead to unexpected results.

Interactive demo

Sometimes, however, it might not be obvious what anchors are created and how they are used. For this we have created a simple interactive demonstration application which allows to inspect the created anchors of a layout, the JVx FormLayout Visualization.

FormLayout Visualization Demo

On the left is the possibility to show and hide anchors together with the information about the currently highlighted anchor. On the right is a Lua scripting area which allows you to quickly and easily rebuild and test layouts. It utilizes the JVx-Lua bridge from a previous blog post and so any changes to the code are directly applied.

The most simple usage: Flow-like

Enough of the internals, let's talk use-cases. The most simple use-case for the FormLayout can be a container which flows its contents in a line until a certain number of items is reach, at which it breaks into a new line:

  1. layout.setNewlineCount(3);
  2.  
  3. panel.add(component);
  4. panel.add(component);
  5. panel.add(component);
  6. panel.add(component);
  7. panel.add(component);
  8. panel.add(component);
  9. panel.add(component);

FormLayout with a flow layout

It does not require any interaction from us except adding components. In this case, when three components have been added, the next one will be added to the next line and so on. This is quite useful when all you want to do is display components in a fixed horizontal grid.

The obvious usage: Grid-like

The FormLayout can also be used to align components in a grid, and actually layout them in a grid-like fashion:

  1. panel.add(component, layout.getConstraints(0, 0));
  2. panel.add(component, layout.getConstraints(1, 0));
  3. panel.add(component, layout.getConstraints(2, 0, -2, 0));
  4. panel.add(component, layout.getConstraints(-1, 0));
  5.  
  6. panel.add(component, layout.getConstraints(0, 1, 2, 1));
  7. panel.add(component, layout.getConstraints(3, 1, -1, 1));
  8.  
  9. panel.add(component, layout.getConstraints(0, 2, -2, -1));
  10. panel.add(component, layout.getConstraints(-1, 2, -1, -1));

FormLayout with a grid layout

With the main difference being that the columns and rows are sized according to the components in it and not given a fixed slice of the width of the panel.

The advanced usage: Anchor Configuration

Additionally, the FormLayout offers the possibility to manually set the anchor positions, for example when it is necessary to give the first elements a certain size:

  1. panel.add(component, layout.getConstraints(0, 0));
  2. panel.add(component, layout.getConstraints(1, 0));
  3. panel.add(component, layout.getConstraints(2, 0));
  4.  
  5. panel.add(component, layout.getConstraints(0, 1));
  6. panel.add(component, layout.getConstraints(1, 1));
  7. panel.add(component, layout.getConstraints(2, 1));
  8.  
  9. panel.add(component, layout.getConstraints(0, 2));
  10. panel.add(component, layout.getConstraints(1, 2));
  11. panel.add(component, layout.getConstraints(2, 2));
  12.  
  13. layout.setAnchorConfiguration("r0=64,r1=8,r2=128,b1=32");

Together with the ability to span components, this allows to create complex and rich layouts.

FormLayout with a set anchor configuration

Conclusion

The JVx FormLayout allows to quickly and easily create complex, good looking and working layouts which are still flexible enough for the cases when a component is swapped, removed or added. It can be used in many different circumstances and is still easy enough to use to make sure that even beginners are able to create a basic layout within seconds.

JVx REST interface update

Our generic REST interface of JVx got an update.

The REST services are a powerful feature of JVx and built-in. Usually you would implement your own REST services, but JVx has the concept of lifecycle objects and the powerful action mechanism. It would be ugly to implement another layer on top of JVx just for REST services. This is a framework feature.

We had this powerful feature for a long time and it is still in use for different use-cases:

AngularJS 4 with VisionX and JVx REST services
AngularJS with JVx in action

Our REST interface just works and you are able to create microservices very fast and efficient.

But the interface has a problem with some kind of parameters because they are also generic. Suppose you have the following method:

public IFileHandle createReport(ICondition pFilter, SortDefinition pSort)

The result type (IFileHandle) isn't a problem because JVx will send the bytes in the REST response, but the parameters: pFilter and pSort are specific types and not base types like String, Array, int, float.

It wasn't possible to call such methods without wrapper methods like:

public IFileHandle createReport(String pFilter, String pSort)
{
   return createReport(createCondition(pFilter), createSort(pSort));
}

Still not hard to solve, but parsing the filter and sort definition weren't trivial tasks. In fact, it was annoying.

We now have support for such parameters and much more. It's not super modern but an awesome solution!

Assume you have following code in one of your lifecycle objects:

public DBStorage getAdrData() throws Throwable
{
        DBStorage dbs = (DBStorage)get("adrData");
       
        if (dbs == null)
        {
                dbs = new DBStorage();

                dbs.setDBAccess(getDataSource());
                dbs.setWritebackTable("ADDRESS");
                dbs.open();
               
                put("adrData", dbs);
        }
       
        return dbs;
}

The DBStorage class offers a public method:

public IFileHandle createCSV(String pFileName, String[] pColumnNames, String[] pLabels,
                             ICondition pFilter, SortDefinition pSort) throws Exception

This method creates a CSV report (address data in this example). It has some parameters for the expected filename, optional column names which should be used for the report, optional labels for the column names, the filter condition and sort definition.

To call this method as REST service, simply send a post request with following information:

e.g. URL:

http://localhost/jvx/services/rest/demo/Address/object/adrData/createCSV

(demo is the application name, Address is the lifecycle object name, adrData is the object name)

Body:

["test.rtf", null, null,
 "{j:new LessEquals('NR', 10)}",
 "{j:new SortDefinition(new String[] {'NR', 'STAIR'}, new boolean[] {true, false}}"]);

The body contains Java Code :) (in the JSON string).

W00t?

JVx got support for executing simple Java code. We introduced the new utility class SimpleJavaSource. It's a backport from our commercial product VisionX. The SimpleJavaSource is able to execute the given parameters and creates real Java objects. It doesn't support conditions or loops, so it's really simple - but powerful!

With our new SimpleJavaSource and the support for parameter Code, it'll be possible to call most methods without additional wrapper methods.

The SimpleJavaSource class is now included in JVx and ready to use. It's simple but powerful:

SimpleJavaSource ssj = new SimpleJavaSource();
ssj.addImport("javax.rad.model.condition.*");

String sEqualsCode = "new Equals('ID', 0).or(new Like('NAME', '%john%'))";

ICondition cond = (ICondition)ssj.execute(sEqualsCode);

The new features will be available with our next JVx release and are already available via nightly builds.