The Source for Java Technology Collaboration
User: Password:



Evan Summers's Blog

August 2006 Archives


Swing and Roundabouts 1M: Emission DTs

Posted by evanx on August 31, 2006 at 09:12 AM | Permalink | Comments (0)

(*)

Preface

This article might be debunked over time at this permalink on java.net CVS. Actually corrections have probably already been made to that copy, during a post-publication review (or two), so maybe click through to there rather than continue here.

Cos it's so much easier to right-click and commit a subdirectory from Netbeans, than to edit weblogs using blogowarez. Not to mention uploading accompanying files one-by-one... So there's another great use for Netbeans/CVS - to edit and publish blog entries :)

Introduction
"I can't believe it! Reading and writing actually paid off!" Homer Simpson

Swing and Roundabouts 1P: Epoxy DTs looked at using dynamic proxies to avoid boiler-plate code to build GUI helper classes whose methods should all run in the EDT, even if they are invoked from a background thread. For example, a long running task in a SwingWorker thread might require confirmation dialogs, GUI focus changes and such, which should be invoked in the EDT.

Another option is eventbus.dev.java.net by Michael Bushe, which uses the subscribe/publish model, eg. one can publish events to invoke subscribers inside the EDT. This is more loosely coupled than Swing listeners, since those need to be attached to specific components.

Since this seems to be an interesting approach that I haven't used before, let's implement a simplistic subscribe/publish mechanism, and use it for some dialogs and what-not, to see what it's all about.

Wherez the warez?

If you want the warez before the hows, here is the demo...

(*) (QuiteBusy, Java5, 450k, unsandboxed)

Which looks something like this...

(*)

Which looks, and is, very ordinary, except that the dialogs, console and status bar, are called into action using a message bus, for loose-coupling and what-not.

The code is currently in the quitebusy and quitebusydemo packages on vellum.dev.java.net, but will move to aptfoundation.dev.java.net at some stage, and thereafter maybe to quitebusy.dev.java.net one day. You can check out the vellum project, and run the same main class as in the JNLP, ie. quitebusydemo.common.QMessageBusDemo.

Message Bus Interfaces

Our main QMessageBus interface below exposes methods to subscribe and unsubscribe services for specific message types, and of course also methods to publish messages.

public interface QMessageBus<Response> {
    public void subscribe(Class messageClass, QSubscriber subscriber);
    public void subscribeWeakly(Class messageClass, QSubscriber subscriber);
    public void subscribeResponder(Class messageClass, QResponder subscriber) throws Throwable;
    public void unsubscribe(Class messageClass, QSubscriber subscriber);
    public void publish(QMessage message) throws Throwable;
    public void publishBackground(QMessage message);
    public Response getResponse(QMessage message) throws Throwable;
    public void publishException(Throwable exception, String text);
    public void publishInfo(Level level, String text);
}

In a minute we'll present an implementation where publishBackground() uses a background SwingWorker thread outside the EDT, and publish() uses invokeAndWait() on the EDT.

We parameterise the message bus implementation with a Response type, rather than let getResponse() return an Object, out of habit more than anything else. It makes most sense to default the Response to Object, so as not to limit our options.

Our QMessage interface is just a tag as follows.

public interface QMessage {
}

Our QSubscriber interface is as follows.

public interface QSubscriber<Message> {
    public void receive(Message message) throws Exception;
}

where a subscriber receives a message, and might throw an exception.

When subscribing a service (to receive messages published via the bus), we can opt to subscribe "weakly" using subscribeWeakly(), in which case we will use Java's "weak references." These don't prevent garbage collection (when there are only weak references to an object). This is handy for subscribers that might come and go.

Our QResponder interface below is similar to QSubscriber above, except that our "responder" can return an object.

public interface QResponder<Message, Response> {
    public Response receive(Message message) throws Exception;
}

We subscribe a "responder" service for a given message type using subscribeResponder(). In this case, we can use the getResponse() method to wait for the response, and return the response object. For example, we might publish a message for a "confirmation dialog" service to display, where it returns a Boolean object. This is essentially a loosely-coupled (and thinly-veiled) method invocation. So it makes sense to have only a single responder for a given message type.

Incidently, in a future article, we might try to push our luck with a distributed message bus, perhaps using RESTful messages, eg. QBasicRestMessage with QRestMessageType of POST, GET, PUT and DELETE, and the URI and what-not. Those might be serialised into XML, and sent over HTTP to remote QRestResource responders. Sounds like too much fun not to try!

Message Bus Implementation Singleton

We implement a QDefaultMessageBus as follows.

In order to support a "logging aspect," we use a proxy object for the messageBus singleton. This delegates the method invocation to our NLoggingInvoker, for logging methods, their arguments, and return value (if there is one). Actually this is just an exercise to reuse our dynamic proxy classes introduced in Epoxy DTs :)

Rather than getResponse() return an generic object, we use generics to limit our options to a Boolean response type, at least for our messageBus singleton below.

We have two subscriber maps, one for weak references to subscribers, and another for strong references. And we have a responder map too.

public class QDefaultMessageBus<Response> implements QMessageBus<Response> {
    public static final QMessageBus<Boolean> messageBusProxy = (QMessageBus)
        NProxyFactory.createProxy(new QDefaultMessageBus(), new NLoggingInvoker());
    
    public static final QDefaultMessageBus<Boolean> messageBus = new QDefaultMessageBus();
    
    QMessageBusConfiguration configuration = new QMessageBusConfiguration();
    QPublisher<Response> publisher = new QEdtPublisher();
    QSubscriberMap weakSubscriberMap = new QSubscriberMap(QWeakSubscriberList.class);
    QSubscriberMap defaultSubscriberMap = new QSubscriberMap(QDefaultSubscriberList.class);
    Map<Class, QResponder> responderMap = Collections.synchronizedMap(new HashMap());
    
    protected QDefaultMessageBus() {
    }
    
    public synchronized void subscribe(Class messageClass, QSubscriber subscriber) {
        QSimpleList subscriberList = defaultSubscriberMap.getSubscriberList(messageClass);
        if (subscriberList.contains(subscriber)) {
            logger.warning(messageClass, subscriber);
        } else {
            subscriberList.add(subscriber);
        }
    }
    
    public synchronized void subscribeWeakly(Class messageClass, QSubscriber subscriber) {
        QSimpleList subscriberList = weakSubscriberMap.getSubscriberList(messageClass);
        if (subscriberList.contains(subscriber)) {
            logger.warning(messageClass, subscriber);
        } else {
            subscriberList.add(subscriber);
        }
    }
    
    public synchronized void subscribeResponder(Class messageClass, QResponder responder) 
        throws Throwable {
        if (responderMap.get(messageClass) != null) {
            throw new NRuntimeException(
                configuration.subscribeResponderExclusiveError, messageClass);
        } else {
            responderMap.put(messageClass, responder);
        }
    }
    
    public synchronized void unsubscribe(Class messageClass, QSubscriber subscriber) {
        defaultSubscriberMap.getSubscriberList(messageClass).remove(subscriber);
        weakSubscriberMap.getSubscriberList(messageClass).remove(subscriber);
        responderMap.remove(messageClass);
    }
    ...
}

We now implement methods to publish messages, eg. via invokeAndWait() in the EDT, or alternatively in the background, eg. using a SwingWorker thread, ie. outside of the EDT. If our task will take any length of time to execute, then we must use publishBackground() to avoid blocking the EDT, otherwise the GUI will appear to be "hung" while the long task is executing.

Services that receive messages via publishBackground() must themselves use publish() to invoke any GUI services eg. popping up dialogs and such, to ensure that those run in the EDT. Otherwise they could use EDT-safe helper classes, as in the prequels Event DTs and Epoxy DTs.

    
    public void publishBackground(QMessage message) {
        try {
            publish(message, true, true);
        } catch (Throwable e) {
            throw new NWrappedRuntimeException(e, null, message);
        }
    }
    
    public void publish(QMessage message) throws Throwable {
        publish(message, false, true);
    }
    
    protected boolean publish(QMessage message, boolean background, boolean strict) 
        throws Throwable {
        if (message == null) {
            throw new NullPointerException(configuration.nullMessageError);
        }
        Class messageClass = message.getClass();
        if (!hasSubscriber(messageClass)) {
            if (strict) {
                throw new NRuntimeException(
                    configuration.publishSubscriberListEmptyError, messageClass);
            }
            return false;
        }
        QResponder responder = responderMap.get(messageClass);
        if (responder != null) {
            publisher.publishResponder(responder, message, background);
        }
        publish(defaultSubscriberMap, message, background);
        publish(weakSubscriberMap, message, background);
        return true;
    }
    
    protected boolean hasSubscriber(Class messageClass) {
        if (responderMap.containsKey(messageClass)) return true;
        if (hasSubscriber(defaultSubscriberMap, messageClass)) return true;
        if (hasSubscriber(weakSubscriberMap, messageClass)) return true;
        return false;
    }
    
    protected boolean hasSubscriber(QSubscriberMap subscriberMap, Class messageClass) {
        List<QSubscriber> subscriberList = subscriberMap.getSubscriberList(messageClass).getList();
        return (subscriberList.size() > 0);
    }

In our private publish() message above, if we find that there are no subscribers for our message, and we are being strict, then we complain, bitterly.

We will introduce QSubscriberMap shortly, where this is essentially an alias as follows.

public interface QSubscriberMap extends Map<Class, QSubscriberList> {
}

ie. a Map of message types to the List of subscribers for that message type.

QSubscriberList is essentially an alias as follows, with the addition of a getList() method to provide a copy of its list of subscribers, for thread-safety.

public interface QSubscriberList extends List<QSubscriber> {
    public List<QSubscriber> getList(); // get thread-safe copy
}

We reuse the publish() method below for each QSubscriberMap, namely weakSubscriberMap and defaultSubscriberMap.

    protected void publish(QSubscriberMap subscriberMap, QMessage message, boolean background) 
        throws Throwable {
        Class messageClass = message.getClass();
        List<QSubscriber> subscriberList = subscriberMap.getSubscriberList(messageClass).getList();
        for (QSubscriber subscriber : subscriberList)  {
            publisher.publish(subscriber, message, background);
        }
    }

We use getList() to get a thread-safe copy of the subscriber list, because while we might be waiting for the subcriber to process the message, another thread might invoke the subcribe() and/or unsubscribe() methods for the same message type, which would modify the list.

We delegate to a QPublisher helper as below, to publish the message to a subscriber or responder.

public interface QPublisher<Response> {
    public void publish(
        QSubscriber subscriber, QMessage message, boolean background) 
        throws Throwable;

    public Response publishResponder(
        QResponder responder, QMessage message, boolean background) 
        throws Throwable;
}

Further below we present the QEdtPublisher implementation of the above interface. This invokes the subscriber's receive() method in the EDT using invokeAndWait(), or otherwise in a background SwingWorker thread.

Publishing to Responsive Subscribers

We publish to a responder as follows.

    public Response getResponse(QMessage message) 
        throws Throwable {
        return publishResponder(message, false);
    }
    
    protected Response publishResponder(QMessage message, boolean background) 
        throws Throwable {
        Class messageClass = message.getClass();
        QResponder responder = responderMap.get(messageClass);
        if (responder == null) {
            throw new NRuntimeException(
                configuration.publishResponderNoResponderError, 
                messageClass);
        }
        return publisher.publishResponder(responder, message, background);
    }

where we wait for a response, and return a response object.

So clearly our publishing methods cannot be synchronized, because they might take a while a execute, eg. if background is false.

Spanglish

Incidently, we "externalise" our error messages and such, into a configuration object as follows. Then we can readily inject externalised resources into this object, eg. from a resource bundle.

public class QMessageBusConfiguration {    
    @ResourceAnnotation()
    String nullMessageError = "Cannot publish a null message";                
    
    @ResourceAnnotation()
    String subscribeResponderExclusiveError = "Not exclusive responder";
    
    @ResourceAnnotation()
    String publishSubscriberListEmptyError = "No subscribers for message";            
    
    @ResourceAnnotation()
    String publishResponderNoSubscriberError = "No responder for message";        

    public QMessageBusConfiguration() {
       resourceBundleHelper.configure(this);
    }
}

Subscriber Map

QSubscriberMap maintains a map of QSubscriberList, which manages a list of subscribers, for a given message type.

QSubscriberMap is implemented as follows.

public class QSubscriberMap extends QValueMap<Class, QSubscriberList> {    

    public QSubscriberMap(Class subscriberListClass) {
        super(subscriberListClass);
    }
    
    public QSubscriberList getSubscriberList(Class messageClass) {
        return super.getValue(messageClass);
    }
}

where we extend QValueMap presented below.

public class QValueMap<Key, Value> extends HashMap<Key, Value> {
    Class valueClass;
    
    public QValueMap(Class valueClass) {
        super();
        this.valueClass = valueClass;
    }

    public Value getValue(Key key) {
        Value value = super.get(key);
        if (value == null) {
            try {
                value = (Value) valueClass.newInstance();
                super.put(key, value);
            } catch (Throwable e) {
                throw new NWrappedRuntimeException(e, null, valueClass);
            }
        }
        return value;
    }
}

where we create a value instance on demand in getValue() using valueClass.newInstance().

Default Subscriber List

QSubscriberList below is just an alias for a basic list.

public interface QSubscriberList extends QSimpleList<QSubscriber> {
}

where QSimpleList is defined as follows.

public interface QSimpleList<Element> {    
    public void add(Element element);
    public void remove(Element element);
    public boolean contains(Element element);
    public int size();
    public List<Element> getList();
}

Just to it keep it simple.

We implement QDefaultSubscriberList as follows.

public class QDefaultSubscriberList implements QSubscriberList {
    List<QSubscriber> subscriberList = new ArrayList();
    
    protected QDefaultSubscriberList() {
    }
    
    public synchronized void add(QSubscriber subscriber) {
        subscriberList.add(subscriber);
    }
    
    public synchronized void remove(QSubscriber subscriber) {
        subscriberList.remove(subscriber);
    }
    
    public synchronized boolean contains(QSubscriber subscriber) {
        return subscriberList.contains(subscriber);
    }
    
    public synchronized int size() {
        return subscriberList.size();
    }

    public synchronized List<QSubscriber> getList() {
        return new ArrayList(subscriberList);
    }
}

where we make it thread-safe using synchronized methods.

The getList() method returns a (thread-safe) copy of the list, which we can use to iterate through our subscribers, eg. in a for loop.

Since we have a single responder per message type, we create a regular thread-safe map as follows.

    Map<Class, QResponder> responderMap = Collections.synchronizedMap(new HashMap());

where the key is the message type.

Weak Reference Subscriber List

In addition to the above default subscriber list, we also implement a subscriber list that uses weak references, ie. Java's WeakReference wrapper. In this case, our weak reference to the subscriber will not prevent it being garbage-collected, if there are no other (strong) references to it in our application.

Actually QWeakSubscriberList is just an alias as follows.

public class QWeakSubscriberList extends QWeakList<QSubscriber> {    
}

where QWeakList is implemented as follows.

public class QWeakList<Element> implements QSimpleList<Element> {    
    List<WeakReference> referenceList = new ArrayList();

    public QWeakList() {
    }
    
    public synchronized boolean contains(Element element) {
        for (WeakReference reference : referenceList) {
            if (reference.get() == element) {
                return true;
            }
        }
        return false;
    }
    
    public synchronized void add(Element element) {
        referenceList.add(new WeakReference(element));
    }
    
    public synchronized void remove(Element element) {
        for (WeakReference reference : referenceList) {
            if (reference.get() == element) {
                referenceList.remove(reference);
                break;
            }
        }
    }

    public synchronized List<Element> getList() {
        List<Element> elementList = new ArrayList();
        WeakReference removeReference = null;
        for (WeakReference reference : referenceList) {
            Element element = (Element) reference.get();
            if (element == null) {
                removeReference = reference;
            } else {
                elementList.add(element);
            }
        }
        if (removeReference != null) {
            referenceList.remove(removeReference);
        }
        return elementList;
    }

    public int size() {
        return referenceList.size();
    }
}

In the getList() method, we do some incremental house-keeping, ie. removing a garbage-collected reference, if we find one. We know that if weakReference.get() returns null, then that subscriber has been garbage-collected.

If we publish messages to this list more often than we unsubscribe, which we should do, then it's fine to remove only one garbage-collected reference in getList() at a time. Nonetheless, this is clearly a case of premature optimisation (which we hear is a terrible practice). In particular, we avoid creating a List for possible elements to remove, when there is typically at most one, and usually none.

Receiving messages

QSubscriber is an interface for receiving messages.

public interface QSubscriber<Message> {
    public void receive(Message message) throws Throwable;
}

Our QResponder interface is similar, except that it returns a response, as follows.

public interface QResponder<Message, Response> {
    public Response receive(Message message) throws Throwable;
}

We allow services to throw an exception. In this case, our message bus needs an exception handler. We will implement this in a minute, as a dialog popup service that subscribes to the message bus, to receive exception messages :)

Publishing messages

Our QDefaultMessageBus implementation presented further above delegates to an QPublisher helper to publish messages to subscribers and responders.

public interface QPublisher<Response> {
    public void publish(
        QSubscriber subscriber, QMessage message, boolean background) 
        throws Throwable;
    public Response publishResponder(
        QResponder responder, QMessage message, boolean background) 
        throws Throwable;
}

where QMessage is just a tag as follows.

public interface QMessage {
}

We can request the message bus to invoke the service in the background, or otherwise wait for the execution to complete, eg. using invokeAndWait() on the EDT. In the case of publishResponder(), we can return a response. If publishResponder() is invoked in the background, then it will return null immediately, and it's response is not available in this case.

Publishing to the EDT

We implement an QEdtPublisher, which is cognisant of the EDT. If background is true, we invoke the service in a background SwingWorker thread (which is not the EDT). Otherwise we invokeAndWait() on the EDT.

import static quitebusy.common.QDefaultMessageBus.*; // for messageBus singleton
import static aptfoundation.common.context.NContext.*; // for singletons, eg. logger
...
public class QEdtPublisher<Response> implements QPublisher<Response> {
    
    public void publish(QSubscriber subscriber, QMessage message, boolean background) 
        throws Throwable {
        logger.entering(subscriber, message, background);
        final FutureTask task = new FutureTask(createCallable(subscriber, message));
        if (!background) invokeAndWait(task);
        doInBackground(task);
    }
    
    public Response publishResponder(
        QResponder responder, QMessage message, boolean background) 
        throws Throwable {
        logger.entering(responder, message, background);
        final FutureTask task = new FutureTask(createCallable(responder, message));
        if (!background) return (Response) invokeAndWait(task);
        doInBackground(task);
        return null;
    }
       
    protected Object invokeAndWait(final FutureTask task) throws Throwable {
        logger.entering();
        if (!SwingUtilities.isEventDispatchThread()) {
            SwingUtilities.invokeAndWait(task);
        } else {
            task.run();
        }
        try {
            return task.get();
        } catch (InterruptedException ie) {
            throw ie;
        } catch (ExecutionException ee) {
            throw ee.getCause();
        }
    }
    
    protected void doInBackground(final FutureTask task) {
        logger.entering();
        SwingWorker worker = new SwingWorker() {
            protected Object doInBackground() throws Throwable {
                try {
                    task.run();
                    task.get();
                } catch (Throwable e) {
                    e.printStackTrace();
                    messageBus.publishException(e, null);
                }
                return null;
            }
        };
        worker.execute();
    }
    
    protected Callable createCallable(
        final QSubscriber subscriber, final QMessage message) {
        return new Callable() {
            public Object call() throws Throwable {
                subscriber.receive(message);
                return null;
            }
        };
    }
    
    protected Callable createCallable(
        final QResponder subscriber, final QMessage message) {
        return new Callable() {
            public Object call() throws Throwable {
                return subscriber.receive(message);
            }
        };
    }    
}

In publishResponder(), if we are gonna wait for a response, ie. via invokeAndWait(), rather than doInBackground(), then we can return a response value from our service, eg. a Boolean object from a confirmation dialog service. Otherwise we return null.

In our background SwingWorker, in the event of an error, we publish an exception message to the message bus. We will go into this further below.

Dialogue

So now let's implement some dialog support. First we need a dialog message to publish.

public class QDialogMessage implements QStatusMessage {    
    Component parentComponent = QApplicationContext.getInstance().getFrame(); 
    int option = JOptionPane.OK_OPTION;
    String title;

    public QDialogMessage(String text) {
        super(text);
    }

    ... // getters and setters, eg. getParentComponent(), getOption()
}

where the QStatusMessage superclass is as follows.

public class QStatusMessage implements QMessage {    
    String text;
    
    public QStatusMessage() {
    }
    
    public QStatusMessage(String text) {
        this.text = text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public String getText() {
        return text;
    }
}

Now we can implement a dialog service to receive the above message type.

public class QDialogService implements QSubscriber<QDialogMessage> {     
    public static final QDialogService dialogService = new QDialogService();
    
    private QDialogService() {    
    }    

    public void receive(QDialogMessage message) throws Throwable {
        JOptionPane.showMessageDialog(
            message.getParentComponent(), 
            message.getText());
    }    
}

where we create a singleton, so that it is impossible to create and subscribe more than one dialog service by mistake, in which case, multiple dialogs would popup at the same time.

(*)

Two-way Dialogue

We implement a QConfirmationDialogService as follows, to handle confirmation dialogs, so it is a responder service, returning a Boolean response.

public class QConfirmationDialogService 
        implements QResponder<QConfirmationMessage, Boolean> { 
    ...    
    public Boolean receive(QConfirmationMessage message) throws Throwable {
        int result = JOptionPane.showConfirmDialog(
            message.getParentComponent(), 
            message.getText(), 
            null, 
            message.getOption());
        return result == message.getConfirmationOption();
    }    
}

where QConfirmationMessage extends QDialogMessage as follows.

public class QConfirmationMessage extends QDialogMessage {
    int option = JOptionPane.OK_CANCEL_OPTION;
    int confirmationOption = JOptionPane.OK_OPTION;
    
    public QConfirmationMessage(String message) {
        super(message);
        setOption(option);
    }
    
    public int getConfirmationOption() {
        return confirmationOption;
    }
}

where our confirmation message has an affirmative confirmationOption set to JOptionPane.OK_OPTION. We might also want to add support for YES_NO_OPTION, as an alternative.

Kicking the tyres

Let's see if it works, by subscribing an instance of QDialogService to the bus, and then publishing a QDialogMessage.

public class QMessageBusDemo {    
    ...

    protected void subscribe() {   
        messageBus.subscribe(QDialogMessage.class, QDialogService.dialogService);
        ...      
    }

    public void testMessageDialog() {
        messageBus.publish(new QDialogMessage("Hello, Emission DT!"));
    }
}

Here again is Web Start demo of this Quite Busy warez.

(*) (QuiteBusy, Java5, 450k, unsandboxed)

Which looks something like this...

(*)

Continue Reading...



In Love with Java5

Posted by evanx on August 30, 2006 at 04:34 AM | Permalink | Comments (6)

love_shopping.jpg Rolling your own Java

In Shai's "Java5 language changes were a bad idea", he suggests just that.

A coupla of years ago, before i starting tooling up (starting with IntelliJ, then using Eclipse, and nowadays Netbeans), i used vi. You're right RMS, it's not a sin to use vi, it's a penance - in order to find salvation with Java IDEs.

So there were a few things i really wanted in the language, for my personal convenience. So i created a bash script with some greps, that did the following to my Java2 code on that project.

  • where i found curly brackets, i inserted new Object[], and where i found an underscore following a comma, i converted to a string, eg. logger.info({"the int value is", _i}) ended up as logger.info(new Object[] {"the int value is", ""+ i}), ie. varargs and autoboxing.
  • less trivially, where i found foreach, i inserted for (Iterator it...).
  • where i found util.putTheDogInside(), i changed that to Util.getInstance().putTheDogInside() (because i don't believe in a class full of static methods). With Java5 you can achieve the same with static importing, innit.

Maybe i did some more stuff, but i can't remember. Anyways, as you can see, i was wanting Java5 very much!

Love is... Java5

Once i started using Java5, i actually found it impossible to revert to Java2. And believe me i tried. I know it's a career limiting move, but that doesn't change a thing. I just couldn't imagine living without my new found love.

I must say, i would like a type alias for generic types, because they tend to get very hairy, very quickly. (See this bug.) One can achieve this at least with interfaces, by extending them emptily. Similarly with classes, although then we have to duplicate the constructors.

For some or other reason, as much as i love enums and annotations, i wish they were more like regular objects, which can be extended and what-not. I wish the annotation declaration was a notation to create a new object of the given type, and set its properties. And at times, i've wished that the enum notation was short-hand notation for object construction, that could be reused for other wierd and wonderful things. One is never satisfied with enough, innit!

Look, No Strings!

At the maximal risk of being boring and repetitive (because i am being repetitive, mostly to avoid doing some constructive work right now), in addition to type aliases, what i would love to see in the language, before anything else like closures or whatever, is for references to fields, methods and properties to be first class citizens of the language, so as to be toolable (eg. prompting, autocompletion and not least, refactoring). So one could have code like

setNameFromField(saveButton, MyClass%saveButton);
createListenerFromMethod(saveButton, MyClass#saveActionPerformed);
bindEnablerFromPropertyDescriptor(saveButton, MyClass:saveButtonEnabled); 

where MyClass%saveButton gets at the Field named "saveButton", MyClass#saveActionPerformed, the Method named "saveActionPerformed," and MyClass:saveButtonEnabled, the PropertyDescriptor named "saveButtonEnabled" ie. there are methods isSaveButtonEnabled() and setSaveButtonEnabled(boolean) in MyClass.

Into Tools

toolbox_200.jpg I, myself and me personally, would be happy enough if just Netbeans supported the above as a short-hand notation, ie. in its editor, and refactorings. Because i use Netbeans. I wish i had a clue, and the time to write such a plugin. Actually i do (have the time), so i will :) as soon as i get some clues, if not before ;)

Which does raise the point, that perhaps conveniences should be built into tools (first), rather than into the language itself. If for no other reason, than to see what traction they can accrue, if any, before incorporating them into the language, or at least into other tools. I guess the risk is that the language might become "fragmented" between the (three) different IDEs, if they were to support differing short-hand notations. Having said that, there is no stopping people customising their own favourite IDE, for their own personal convenience and productivity, as i would wish to do, if i had a clue how.

No Name Brand

An alternative to the above, is binding by convention, eg. if one has a component saveButton, then we look for an event listener method and property with "conventional" names, eg. "saveButtonActionPerformed" and "saveButtonEnabled." But that's hardly toolable, eg. no IDE verification. Neither is using string references to refer to the method and property.

Mmmm, Annotations

Arguably the best approach is using annotations, and testing classes to reduce fragility, by inspecting the annotations, and verifying them using reflection. In particular, we should test that names in the annotations are valid, eg. if we have an annotation with enabledMethod="isSaveButtonEnabled", then the method isSaveButtonEnabled() should actually exist, and not have been renamed in a refactoring operation. Of course, since we don't have IDE verification (or auto-completion), it's easy to misspell in the first case, D'oh!

Post Scripts

Ps. On the non-Java personal side, i'm sitting in my house in Cape Town, for the first time in 5 months, preparing to move out of it. As the Russians say, "Moving house is an equivalent disaster to your house burning down, twice!"

I've been in Johannesburg staying with my brother, mostly. Leaving the gym monday, where we've gone routinely together everyday, and compared notes on the various bugs, features and figures of the instructors and other hot babes in the class, and knowing we wouldn't be doing that anymore, was shoulder dropping.

So I'm gonna miss not seeing my brother everyday, and my family regularly. Especially since my trip to France with my mom is postponed until Spring. I'm gonna try not to miss my ex-girlfriend, bless her.

vineyards_crop.jpg

I guess i'm gonna miss this house, particularly the space - my new place is smaller, but nicer. I'm not gonna miss the windy weather here (on Cape Point) - which hinders one's cycling plans. Yes I'm gonna love my new place, which is in the "wine lands," ie. inland a bit, a place called Durbanville actually. When i'm not Sideways at a wine farm, or cycling past vineyards and "cows in the field," you'll find me... right here! And that's even when i'm at my friend's coffee shop in the mall across the road, where i'll be pulling 3Gs on a regular basis. Mmmm, bottomless coffee, mmmm, wireless Java.

Swing and Roundabouts 1P: Epoxy DTs

Posted by evanx on August 18, 2006 at 12:36 PM | Permalink | Comments (3)

Some background

See the prequel Event DTs.

http://weblogs.java.net/blog/evanx/archive/empty_glue_tube_crop2.jpg Also see Alexander Potochkin's "Debugging Swing - is it really difficult?" and "Debugging Swing, the summary #1"   for a discussion on approaches to EDT issues.

Using dynamic proxies to run methods in the EDT
"Ssh, I'm trying to fix this camera. Easy, easy. No, I'll need a bigger drill." Homer Simpson

The Spin framework uses dynamic proxies to make code EDT-safe. So we gonna try to do the same.

Say we have a some methods that need to run in the EDT, as follows.

public class NDialogHelper {
    public int showConfirmDialog(Component parentComponent, Object message, String title, int option) {
        return JOptionPane.showConfirmDialog(parentComponent, message, title, option);
    }
    ...
}

But we also want to run these methods from code running in the background, ie. outside the EDT. For example an actionPerformed() method might start a SwingWorker thread to run a long task outside the EDT, so as not to block the EDT. This is important, so that the application does not appear to be hung while this long task is executing, ie. the user can still click on buttons and move windows around and such, since the Swing EDT is free to respond to these events.

In order to use dynamic proxies (to intercept method invocations to our target object), we refactor out an interface corresponding to our class, and our class becomes an implementation of that interface.

public interface NDialogHelper {
    public int showConfirmDialog(Component parentComponent, Object message, String title, int option); 
    public void showMessageDialog(Component parentComponent, Object message, String title, int option);    
    public boolean showConfirmDialog(Object message);
    public boolean showConfirmDialog(String format, Object ... args);
    public void showMessageDialog(Object message);
    public void showMessageDialog(String format, Object ... args);
    public void showExceptionDialog(Throwable exception, String message, Object ... args);
    ...
}

where the implementation class is as follows.

public class NDialogHelperImpl implements NDialogHelper {    
    public static final NDialogHelper dialogHelper = (NDialogHelper) 
        NProxyFactory.createProxy(new NDialogHelperImpl(), new NEdtInvoker());

    private NDialogHelperImpl() {        
    }
    
    public int showConfirmDialog(Component parentComponent, Object message, String title, int option) {
        return JOptionPane.showConfirmDialog(parentComponent, message, title, option);
    }

    public boolean showConfirmDialog(Object message) {
        return showConfirmDialog(null, message, null, JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION;
    }
    ...
}

We expose this implementation to the application via a proxy object, namely the static dialogHelper singleton constructed above, using an NProxyFactory as below. The constructor above is private, since we only access the target via this proxy, using the NDialogHelper interface.

public class NProxyFactory {    
    public static Object createProxy(Object target, NMethodInvoker invoker) {
        NProxyInvocationHandler invocationHandler = 
                new NProxyInvocationHandler(target, invoker);
        Object proxyObject = Proxy.newProxyInstance(
                target.getClass().getClassLoader(), 
                target.getClass().getInterfaces(), 
                invocationHandler);
        return proxyObject;
    }
}

which is nothing out of the ordinary.

Our createProxy() method takes an NMethodInvoker parameter, which is an interface as follows.

public interface NMethodInvoker {
    public Object invoke(Object target, Method method, Object[] args) throws Exception;
}

Our NProxyInvocationHandler will delegate method invocation to the given NMethodInvoker, as we'll see in a minute.

Our NEdtInvoker is implemented as follows.

public class NEdtInvoker implements NMethodInvoker {
    
    public Object invoke(Object target, Method method, Object[] args) throws Exception {
        Callable callable = createCallable(target, method, args);
        return callEdt(callable);
    }
    
    protected Callable createCallable(final Object target, final Method method, final Object[] args) {
        return new Callable() {
            public Object call() throws Exception {
                return method.invoke(target, args);
            }
        };
    }

    protected Object call(Callable callable) {
        return call(callable, false);
    }
    
    protected Object callEdt(Callable callable) {
        return call(callable, true);
    }

    protected Object call(Callable callable, boolean inEdt) {
        final FutureTask task = new FutureTask(callable);
        try {
            if (inEdt && !SwingUtilities.isEventDispatchThread()) {
                SwingUtilities.invokeAndWait(task);
            } else {
                task.run();
            }
            return task.get();
        } catch (InterruptedException ie) {
            throw new NWrappedRuntimeException(ie, null);
        } catch (InvocationTargetException ite) {
            throw new NWrappedRuntimeException(ite.getCause(), null);
        } catch (ExecutionException ee) {
            throw new NWrappedRuntimeException(ee.getCause(), null);
        }        
    }

    public void runBackground(final Runnable runnable) {
        SwingWorker worker = new SwingWorker() {
            protected Object doInBackground() throws Exception {
                try {
                    runnable.run();
                } catch (Exception e) {
                    dialogHelper.showExceptionDialog(e, null);
                }
                return null;
            }
        };
        worker.execute();
    }    
}

where for callEdt(), we check if isEventDispatchThread(), and if not then we invokeAndWait().

If we are gonna have classes with only some methods we wanna run inside the EDT, then we could annotate those methods, and implement the above invoke() method as follows.

    public Object invoke(Object target, Method method, Object[] args) throws Exception {
        Callable callable = createCallable(target, method, args);
        if (method.getAnnotation(InEdtAnnotation.class) == null) return call(callable);
        return callEdt(callable);
    }

which we might implement in an alternative NMethodInvoker, eg. NAnnotatedEdtInvoker, which might extend NEdtInvoker to override the invoke() method as above.

Our InvocationHandler below delegates to the given NMethodInvoker, eg. NEdtInvoker.

public class NProxyInvocationHandler implements InvocationHandler {
    protected Object target;
    protected NMethodInvoker invoker;
    
    public NProxyInvocationHandler(Object target, NMethodInvoker invoker) {
        super();
        this.target = target;
        this.invoker = invoker;
    }
    
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Class declaringClass = method.getDeclaringClass();
        if (declaringClass == Object.class) return invokeObject(proxy, method, args);
        Method targetMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes());
        return invoker.invoke(target, targetMethod, args);
    }
    ...
}

which knows only about NMethodInvoker and doesn't have any DTs, ie. is reusable beyond our NEdtInvoker.

OK, let's see if this works, by popping up a dialog, from a background (non-EDT) SwingWorker thread.

http://weblogs.java.net/blog/evanx/archive/epoxydt.png

Supoib!

Using EventBus for EDT-safety

An alternative approach is made possible by eventbus.dev.java.net. In this case, our dialog displayer would be implemented as a service, which subscribes on the EventBus to "dialog display events." For example, we might implement MessageDialogEvent, which contains the string to display in the dialog.

The dialog displayer would be invoked by EventBus in the EDT. So our application can then just publish events to be displayed onto the event bus, no matter whether we are in the EDT or not.

This is quite elegant, so i think i'll write me a simplistic EventBus, to ensure i have a Quite Eventful weekend. I'll blog it into Swing and Roundabouts 1M: Emission DTs.

Conclusion
"I hope I didn't brain my damage." Homer Simpson

So this is one approach to building some EDT-safe helper classes, which we can use from code running in SwingWorker background threads. We avoid the isEventDispatchThread() and invokeAndWait() boiler-plate code, using dynamic proxies. The price we pay is having to maintain an interface for the implementation.

The code can be found lurking in vellum.dev.java.net for now.

Coming Up

Swing and Roundabouts 5: Quite Gooey will present some reusable helper classes (into which the DTs of this blog are bundled), for building quick GUIs, with some tooling, beans binding by convention, validation and configuration via annotation and resource bundles.

Before that, Plumber's Hack 2: Quite Writer will take quite a trip through the warez i wrote to write these blog articles.

Please help! I'm working on Trip and Tick 4, about writing a Netbeans module to generate code in the editor, ie. manipulate java classes, and create new java classes, and such, along the lines of the following Web Start tool. If anyone has any pointers for me, eg. to sample module source code that does such manipulation and generation of java classes in Netbeans, please drop me a line (evanx at dev.java.net) - thanks!

http://weblogs.java.net/blog/evanx/archive/webstart.small.gif (BeanieGenie, Java5, 320k, unsandboxed)

One Laptop Per Teacher

Posted by evanx on August 16, 2006 at 04:37 AM | Permalink | Comments (4)

In Barbara Kurshan's "Philanthropy and GELC", she posted the recent Forbes interview with Scott McNealy. This got me to musing about computers in education...

redrose2_200.jpg In 2001/2002 i was involved with a a social responsibility programme, linuxlab.org.za, relating to opensource advocacy for schools in South Africa. (I found the old website on the internet archive.)

The Dalai Lama gave a talk in Cape Town's Botanical Gardens to thousands of people, including me. He said, be charitable because it makes you feel better - ie. do it for yourself! And boy, does it ever feel good, and isn't it the most rewarding thing you can do, to feel that you are really making a difference, not to your employer's bottom line, but to people's lives. By just writing and emailing and advocating technologies close to your heart, like Linux :)

One of the great things about it, is that i had something to talk to girls about at parties! Previously all i could talk about was computers, which is eye-glazing material for everyone not involved in computers. But now i could talk passionately about schools, and education, and NGOs - and suddenly everyone thought i was so interesting, and not just one of those computer geeks, woohoo! I fooled them with my electrified fooling machine (running Linux)!

Recycled PCs and Linux thin-clients

In 2001, I wrote a paper entitled "Champagne at beer prices" about building affordable computer labs using recycled PCs donated by corporates, as Linux thin-clients. Cos those old PII machines with 32Mb RAM just aint no good for Windows XP, man. So I networked with colleagues in NGO's involved with computers in schools, advocating the opensource option.

These days, just add one new dual-core Linux box with 4 gigs of RAM, hang 50 recycled PCs off it, and mesh it wirelessly with all the other schools around you.

On the Ground

We deployed one Linux lab ourselves in 2002, which was successful, if at nothing else, at teaching us about some of the challenges of "computers in education." We had it relatively easy because we chose a school that had a passionate "champion," that is, a teacher at the school that believed passionately in computers in education, and had the support of the principal in this.

He taught his students to use the computers as a tool, to assist them in their regular education. For example, he taught how to use OpenOffice for writing up school projects, and how to use a spreadsheet to assist with some specific subject-related exercises, and such.

alexandershs2_crop_623.jpg

One problem with Linux and opensource, as opposed to Windows, has been that the best offline resource is Encarta. In fact, from many an educator's perspective, the best reason to have computers in schools is Encarta. That is, useful content. When your school is offline, it's Encarta or bust.

Most teachers are scared of computers (and VCRs and such too). They don't wanna touch them with a barge pole. And by the way, most kids are better taught by teachers, without any interference by computers. Hey, excuse me for referring to "learners" as "kids," and "educators" as "teachers," i'm out of practice at the correct lingo.

Maths, Science, History and such, are better learnt by kids than Computer Studies. In my case, i reckon Latin made programming seem easy to me, taught me to problem solve, and was better learnt at school than "Computer Studies," which i wasn't taught. I didn't study Computer Science either, d'oh!

Maximising Limited Resources

Since only a small percentage of South African learners have access to computers at home, computer skills should be taught at school. The problem is, only a small percentage of South African schools currently have computer labs.

In order to urgently maximise the impact of our limited resources, we need to prioritise. And so it's not realistic for all primary schools to get computer labs, or for high schools to offer computer time to all students concurrently, in the near future.

Personally i'm an advocate of no computer classes until the final years of schooling. Students in their final years, should of course enjoy access to a computer lab, and training on using computers as a tool. That is, how to Google for information, use email, use Wikipedia, and such. These are what you could call "life skills," ie. everyone needs these skills in their lives, professional and otherwise.

Having said that, i believe that all learners who are interested in "playing" with computers, should be given access to the computer lab as an "extra mural" activity, and be mentored by older students and/or teachers. Therein be future players in our IT industry.

The Microsoft Option

In 2002, as the noise levels of opensource in schools was steadily increasing, Microsoft (South Africa) announced that some Microsoft software would be supplied free to all schools in South Africa. Incidently, the Microsoftie involved in that subsequently joined Novell, to promote Linux :)

If some of the noise we were making about opensource in schools contributed to this policy of free software for schools, then i see that as a success, even though it pulled a rug from under our feet. People would argue that exposing learners to propretiary software, rather than opensource software, is wrong. Well, they should give a whole lot more credit to kids, and people in general. Because people aren't so sheepish, that they won't switch when it suits them to do so.

If schools are getting free software, albeit closed-source, this is gonna mean more computers at more schools (and/or more money for schools to spend on sports and stuff), which means more computers for downloading opensource software, and i'm all for that.

The problem is that it turns out that the offer only applies to upgrades, so schools still to have fork out for Microsoft licenses for new deployments.

olpc-mit-laptop200.jpg One Laptop Per Child

These days we have the OLPC project. We read that Jon "Maddog" Hall (of Linux International) suggested that using recycled PCs is a good approach, because it creates local employment. It also addresses ecological issues as well.

Anyway, we all support the OLPC. I'm sure in 10 and 20 years time we'll be reading countless stories of IT luminaries whose first computer was an OLPC, running Linux, with wireless mesh networking :)

Focus on the Teachers

There are other aspects of computers in schools, namely for administration, teacher assistance, and curriculum delivery. Teacher assistance is an interesting one, where you deliver relevant teaching material (eg. curriculum, exercises, and such) to the teacher. So the teacher can login, and get a helping hand as to what to teach that day.

In South Africa, we have one of the highest HIV infection rates in the world (over 40% in some provinces, i believe), and this is affecting teachers as well of course. That is to say, the shortage of teachers is getting worse. Because teachers too, are dying of AIDS. As a result, we have to accelerate the training of new teachers, and of course provide new teachers with every assistance.

I believe that before our schools offer "computer studies" to learners, we should focus on universal "teacher training." That is, training teachers to use ICTs (Information and Communication Technologies) as a tool to assist them in their duties, eg. via content delivery to teachers. Because it's all about the content, and not about the computers.

I believe that universities and tertiary institutions (which are subsidised by government grants) should be tasked with the responsibility of teacher training, content development for teachers, and ICT deployment and curriculum delivery, eg. via GSM or wifi to low-cost laptops, PDAs and such.

So I would have suggested an "OLPT" programme ie. One Laptop Per Teacher, before an OLPC one. Actually, many teachers might feel more comfortable with a PDA which feels more like a cellphone, than a computer. So these teachers' devices would be used to deliver curriculum and assistance to teachers. To help our teachers to teach our children.

Here's a human story. Machines cannot teach humans! Only humans can teach humans. Humans can teach themselves some stuff of course, that is, when we get older eg. to high school level. Then we can use computers to discover content, and learn stuff. Problem is, given the choice, we might just use computers for games, which are way more fun than homework! Give me Doom over Geography any day!

Computers for learners

HP_thin_client_100.jpg The next step after a "PTA" (Portable Teacher's Assistant), be it a GSM-enabled PDA or wifi-enabled OLPT, should be providing computer access and skills to mature learners. One approach is a large lab of thin-clients, possibly with one or two thin-clients in classrooms, eg. on the teacher's desk, for the class to use Google, Wikipedia and such.

Alternatively, there is the OLPC model. The idea of giving each learner a personal device sounds great. This can used to replace text books in class, and go further than text books eg. interactive exercises and searchable content. I believe this is what education technologists mean by "curriculum delivery." Whether this is an OLPC laptop, or handheld PDA, doesn't matter.

Textbooks are expensive, and getting the right text books in the right quantities out to tens of thousands of schools, in time, is a challenging logistical problem. And rural schools often come up short. Not least because they have no integrated administration systems, and in many cases, no phones, sometimes no electricity even.

So i think that OLPC is an awesome project. I do confess an ulterior interest, that is, an "IT vocational" one. A child that has a programmable OLPC, has a good chance of getting interested in computers, and might well go into the IT industry later. Anyway, in most careers and jobs, proficiency with computers is required. So every child that is given an OLPC, will be glad of it, now and forever.

There is a "chicken and egg" debate over computers in schools. Educationalists say they is no point in "dumping" computers in schools without content eg. localised interactive text books. Technologists argue that schools must have computers first, and then content can be developed to leverage that platform.

The educators are right are of course. But as an IT vocationalist, i want computers in schools to teach basic computer skills to learners before they leave school, and to give those budding geeks a playground to blossom in.

Hardware cost of computer labs

Considering it might take a while to really give every child a laptop, I guess education departments and schools and NGOs are still gonna be building computer labs for a while.

What has promise is multi-headed PCs, eg. with four video cards and USB keyboards. That can get the cost per seat to below $150. But it's quite a specialised configuration, albeit using commodity PC components.

Some thin clients setups are already available for below $200, including monitors. At some stage in the near future, they should be available to school programmes in volume below $150. However, one has to add to this the cost of the server, eg. $50 per client.

school-girl2_200_alt.jpg One should note though that the hardware cost is only a small part of the picture. There are many other costs to factor in, eg. furniture and security, training and support, content and connectivity.

Connectivity

A problem with Google and Wikipedia (as opposed to Encarta), is that you gotta be online. This was a much bigger problem-with-no-solution a few years ago, eg. in the case of rural schools, and poorer urban schools. (Connectivity costs in South Africa are 10 times higher than other developing countries, they say.)

These days the solutions are emerging. Rural schools should have sponsored and subsidised GSM connectivity, and urban schools should use wireless meshing. Cities in South Africa are planning WiMAX metropolitan networks, and i imagine in future all urban schools will be connected this way.

Connectivity is also crucially important to enable remote technical support. Otherwise the support cost of labs becomes prohibitive.

IT Vocationing

Actually I'm a "vocational IT advocate." I believe that at least all high school children should have access to computers, like i did (as alluded to in My Desktop OS: Windows XP). Because it changed my life. And there are thousands of school children that, if had they access to computers, would become dedicated professionals in our industry.

If more school computer labs existed and had afternoon access, there would be many more a child happily immersing himself or herself in a bright new digital world, and for a while forgetting their problems at home, the poverty and such. They would be empowering themselves for a future where there is no poverty in their family, but rather, riches, such as a great job, a lovely house, their kids at good schools, and playing in safe streets. So every year that goes by, that school kids have no access to computers whatsoever, is a mournful thing.

I've read that the most effective action towards achieving the UN Millenium Development Goals, is promoting the education of girls in developing countries. Because girls become mothers, responsible for education and primary health care. Through projects like OLPC, hopefully very many will be responsible for primary data care as well!

Photo credits: flickr.com/people/crizk

Plumber's Hack 1: Highlighting Sourcy

Posted by evanx on August 09, 2006 at 01:07 AM | Permalink | Comments (5)

Grid Bag Grease introduced a Gbc.java convenience class, and a Sourcy sample app that used it. Here is the formal introduction to that Quite Sourcy thing.

colors.png So I want my code samples in my blog to be highlighted. Because it's easier to read, but more importantly, it just looks better! :) Netbeans' "Print to HTML" produces an HTML file, complete with CSS styles. So i wrote this Quite Sourcy thing to convert that into an HTML snippet to cut and paste into a blog entry.

It just trivially post-processes the output from Netbeans "Print to HTML" into a non-CSS format, using some "search and replace" commands. In particular, it changes span tags from referring to a CSS style eg. class="java-keywords", to using an embedded style attribute, as follows.

    private String process(String line) {
        line = replace(line, "java-keywords", "color: #000099; font-weight: bold");
        line = replace(line, "java-string-literal", "color: #99006b");
        line = replace(line, "java-layer-method", "font-weight: bold");
        line = replace(line, "java-numeric-literals", "color: #780000");
        return line;                
    }

    private String replace(String line, String className, String style) {
        String replacePattern = "class=\"" + className + "\"";
        String replaceWith = "style='" + style + "'";
        line = stringHelper.replaceAll(line, replacePattern, replaceWith);                
        return line;       
    }

Here is the Quite Sourcy launcher.

http://weblogs.java.net/blog/evanx/archive/webstart.small.gif (Sourcy, Java5, 195k, unsandboxed)

You'll get the following screen.

sourcyShot.png

You can try to cut and paste the contents of the HTML output file produced using Netbeans 5 "Print to HTML" into the Source tab, then press Process. The output will be written to the Output tab, and from there you can cut and paste it into your blog.

The source code for Quite Sourcy is in vellum.dev.java.net. Soon, its dependent aptfoundation classes will move across to aptfoundation.dev.java.net, and Sourcy will move to quitewriter.dev.java.net.

package_settings.png The next article is this series is Plumber's Hack 2: Quite Writer about some softwarez i'm writing to help me write technical articles. As you might expect, it does automatic syntax highlighting of code samples.

Swing and Roundabouts 5: Quite Gooey will present the pack of simple foundation and helper classes used for building the above Web Start demo, and more. I've been having far too much fun lately implementing some Quite Gooey helpers with beans binding by convention, and validation and configuration by annotation, and so this is shaping up to be a helluva long article, packed full of Web Start demos :)

Hey, in case you missed it, you can check in with Trip and Tick 3 on using cvs.exe to kick off a java.net/Netbeans project. If you don't already have your own sandpit on java.net, then c'mon, it's playtime! And if you haven't played around with Netbeans' Matisse GUI builder, then you are in for quite a treat :)

Credits: Icons by everaldo.com.

Trip and Tick 3: Setting up a new java.net/Netbeans project using CVS.exe

Posted by evanx on August 03, 2006 at 09:55 AM | Permalink | Comments (0)

lor2_100.jpg Prequels: This Trip and Tick series kicked off with Checking out a java.net project and continued with JooJ up your project page with a Web Start demo.

I dunno if it's just me, but the few times i've tried to setup a new java.net project, that is to get my Netbeans project/source and the java.net CVS married and checked in, i seem to struggle for hours. But no more! Today we gonna make it happen in 5 minutes or die trying, goddammit!

The trick is, we gonna add a command-line CVS client to our arsenal to beat Netbeans to the punch, if it comes to that, which it does. I downloaded a cvs.exe from ftp.gnu.org via cvs.nongnu.org.

So if you got any code of your own lying around, c'mon, let's do this! First request a Java project (it's quick and easy), which will get created right away, but only be visible by yourself until it's been approved. Incidently, this might take anything from a few days to a few weeks. Ample time to tell All Staff the good news that the crown jewels are going opensource ;) Incidently, i wrote an article on opensource licenses, called CDDLing up with Sun, to explain to myself the diffferences between GPL, MPL, ASL, and CDDL, so...

javaNetCreateProjectSmall.png

We use our command-line cvs client to checkout the new java.net project into our CVS projects directory. In this example, the java.net project is vellum (as in vellum.dev.java.net) so substitute vellum with your own project name. And USER with your java.net username.

  cd /projects
  cvs -d :pserver:USER@cvs.dev.java.net:/cvs co vellum
  dir vellum

Since it's a new project (on java.net), all it will contain is a www directory. We need to make a src directory, which we do, and we need to add that to the CVS, which we do, using our cvs client.

  cd /projects/vellum
  mkdir src
  cvs add src  

Our cvs client will look at the CVS/Repository file to get the repository, so we don't need to specify it as before ie. -d :pserver:USER@cvs.dev.java.net:/cvs.

Now if we look at the CVS/Entries file, it will reflect both the www and src directories.

cvsEntries.png

And we can browse the CVS source on our java.net project page to see that it's there too. Supoib!

Now that we have a source directory, we can create a Netbeans project, and specify this source directory.

newproject-src.png

My project properties are as follows, where as you can see, the CVS/source for the project is off a projects subdirectory, and netbeans project files are in a separate nbprojects directory, because i like to keep these separate.

vellum-src.png

When things go wrong, we might use cvs remove  to remove stuff from the CVS (after you have deleted it locally). Also, we can use cvs update  to recover stuff from the java.net CVS that we've deleted locally (and haven't yet done a cvs remove  on).

When things go really wrong, just start from scratch by removing your project directory eg. /projects/vellum, and checking it out again...

Remember, that whenever you create a new file or directory (in the command-line) you have to do cvs add on that file to get it into the CVS. And whenever you delete something, you gotta do a cvs remove  on it to get it out of the CVS. That's what's great about IDEs, that they keep track of new and deleted (and renamed) files, and handle all this CVS drudgery for you :)

the_one_ring2_crop.jpg Anyway, we just use the command-line cvs for the bare minimum, that is to add our empty src directory to CVS before creating the Netbeans project.

Now we need to copy our source into this src directory, assuming we have some source for the new project already. If the source is "tainted" by CVS directories from before, then we first make it "pristine" by searching for any old "CVS" directories and deleting them all. For example in Windows Explorer, we search for all files containing "CVS" in the file name, Ctrl-A to select them all, and Delete.

Then we can drop our "CVS-free" source tree into our src folder, make sure the project compiles, and then get our IDE to do a "cvs commit" for us, to add all these "new" files it finds to our CVS java.net repository. But as a rule do a "cvs update" before committing. Then Netbeans knows for sure that all these new files aren't in the CVS repository yet.

Our Netbeans and java.net projects are now forged into "one ring to rule them all" :) And we can forget about cvs.exe... till next time.



Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds