Paul Bininda – Blog
10Mar/14

New Box

This weekend I built myself a new Computer. My old one (a Dell XPS 720) was showing its age even though I had been upgrading it along the way.

Components

Here are the components I used. I bought them all from amazon.de.

Case

Corsair CC-9011023-WW Carbide 200R PC-Gehäuse (midi-ATX, 4x 3,5 HDD, 7x PCI-e, 1x USB 3.0) schwarz

 

Power Supply

Corsair RM Series 650Watt Voll Modular 80 PLUS Gold ATX/EPS Netzteil (CP-9020054-EU)

 

Motherboard

MSI 7816-001R Z87-G43 Intel Z87 Mainboard Sockel LGA 1150 (4x DDR3, Intel HD Graphics, 2x PCI-e, 6x SATA, ATX)

 

Processor

Intel Core i7 4770K Prozessor (3,5GHz, Sockel LGA1150, 8MB Cache) boxed

 

Main Memory

Crucial Ballistix Sport Arbeitsspeicher 16GB (1600MHz, 240-polig, CL9, 2x 8GB) DDR3-RAM Kit

 

Graphics Card

MSI N760 TF 4GD5/OC 4096MB GDDR5 PCI-E 256 bits DV

SSD

Samsung MZ-7TE500BW Serie 840 EVO Basic interne SSD-Festplatte (6,3 cm (2,5 Zoll) (500GB, 512MB Cache, SATA III) metallic silber

 

Hard Drive

Seagate Barracuda ST2000DM001 interne Festplatte 2TB (8,9 cm (3,5 Zoll), 7200rpm, 64MB Cache, SATA III)

 

Removable Disk Drive

Asus BW-16D1HT Silent interner Blu-Ray Brenner (16x BD-R (SL), 12x BD-R (DL), 16x DVD±R), Retail, BDXL, Sata, Schwarz

The New System

I can say that I am quite happy with the new system. Windows 8.1 installed without a problem. The drivers provided by MSI work quite well and the overclocking tools are very nice. All in all, the system is very quite even though the case is not sound isolated at all.

Filed under: Uncategorized No Comments
15Jun/12

Project Time Recorder – Powered by AngularJS


Our company SEKAS GmbH has a proprietary system for keeping track of employee work hours. The system has been in place since 1992. Its an Oracle Forms based system. Recently we decided for various reasons, that we wanted something new. One of the problems with the old system is that employees working at clients sites have no way to book times from remote.

The new system will be a browser based system with a backend running in our company. The browser UI will consist of two parts built with different technologies.

The management side allows maintenance of  core data like available projects and work packages, users, billing information etc. It also allows company management to generate reports and bills etc. This part of the UI will use a classical round trip approach and will only be available on company premises.

The UI for employees on the other hand will allow booking of work time and project/work package times and is based on AngularJS which is an ideal framework for the purpose. It will provide an offline mode which stores entered data in browser local storage and an online mode which keeps the data in sync with the server on premises.

The picture below shows my prototype for the employee UI.

The red rows in the left hand table are weekends and German hollidays. The golden row is the selected day, for which details are shown on the right.

The Start Stop and Break columns allow the user to enter the working times for a given day. Those times then have to be allotted to projects (or rather work packages which are organized into projects). Various validations highlight different kinds of errors (time > 24:00, illegal time format, Stop Time < Start Time etc.)

Over the next weeks I plan to post a few articles on the gritty details of how this all works and especially how AngularJS can be used effectively to build applications like this.

In the meantime, please play around with the prototype. The link will always take you to the current version. Feel free to View Source and have a look at the JavaScript. It's not minified. Also if you are interested in tests, have a look at the unit tests and the e2e tests.

26May/12

Naturbad Furth

Heute fahre ich rüber zum Naturbad Furth. Dort ist unsere Familie nun Mitglied. Ich fahre gleich mal da hin um unsere Ausweise machen zu lassen. Ich freue mich schon auf die Badesaison 2012. Hier sind auch gleich die Bilder für die Ausweise.

Passbilder (ZIP)

Filed under: Uncategorized No Comments
28Jun/11

My Part of the Bitcoin World Economy

A few months ago, I first heard about Bitcoin on the Security Now podcast by Steve Gibson and Leo Laporte. I was intrigued and downloaded the Bitcoin client. I noticed, how the exchange rate of Bitcoin vs. US Dollars kept climbing and got even more intrigued. So I opened an account at the Bitcoin exchange Mt. Gox and transferred €500 to it on May 31. It turned out to be $713.60, for which I started buying Bitcoins. I tried to catch the lows in the exchange rate development but didn’t really succeed. So in effect I bought around 68.28BTC. Luckily I finished buying before the exchange rate skyrocketed to $31 on June 7.

So what will I do with my Bitcoins now? Well I think I’ll keep them and dream a bit. Lets assume, the world at large embraces the advantages of Bitcoins over the current currencies we have and in a few years all currencies are traded into Bitcoins. Today the amount of $ (M2) floating around is around 10 000 Billion $, The amount of € (M2) is around 85 000 Billion €. Let’s assume the total amount of money in the world is somewhere around 40 000 Billion $.

The total amount of Bitcoins is limited by the system to asymptotically approach 21 Million BTC (it is currently around 6.4 Million BTC).

So, if the world economy completely converts to Bitcoin (21 Million available) and I still have my 68.28 BTC, I will have about 0.000325% of the global money supply. Translated to the current money supply that would come out to be around 130 Million $. Not too shabby.

Of course you could argue that this outcome isn’t very likely. I completely agree. However, I have invested only 500€ in this little dream and so far it is living up to its promise. Today my Bitcoins are worth around 800€ and I can’t wait to check again tomorrow.

 

 

Filed under: Bitcoin No Comments
6Oct/10

Motorola Fills up Android Property System

milestone-propsysFor almost half a year now, I have been having sporadic problems with my Mororola Milestone. After booting the phone, everything worked alright but after some usage, some features started to fail. Either WiFi connections didn’t work any more or 3G data connections stopped working or VPN failed to establish a connection. Also, some settings (for example: notification sound for SD card availability) failed to persist through reboots.

I finally found out why!

Android has a low level property system that lives in the init process. A good writeup of how it works can be found at

http://rxwen.blogspot.com/2010/01/android-property-system.html

The important part is that the property system only has space for 247 system properties. The properties are filled from the following files:

/default.prop
/system/build.prop
/system/default.prop
/data/local.prop

Additional properties are created by scripts with the setprop utility and by various processes (wifi manager, vpn manager etc.)

Since Android 2.1, Motorola has filled the above files with lots of properties so that the property service is almost filled up by the time the phone has finished booting. When opening a WiFi connection, additional properties have to be set. If no more properties are available, things start to fail.

I have commented out quite a few of the properties from above files to make some space and now everything works smoothly again. I am not sure yet, which properties are important and which not, once I have done some more research, I will post the changes I have made.

I hope this helps Milestone users with similar problems.

Update:

Here is my modified /system/build.prop:

# begin build properties
# autogenerated by buildinfo.sh
ro.build.id=SHOLS_U2_02.36.0
ro.build.display.id=SHOLS_U2_02.36.0
ro.build.version.incremental=8985865
ro.build.version.sdk=7
ro.build.version.codename=REL
ro.build.version.release=2.1-update1
##ro.build.svn=24
##ro.build.date=Thu Jun  3 12:47:57 CDT 2010
##ro.build.date.utc=1275587277
##ro.build.type=user
##ro.build.user=milestcm
##ro.build.host=il93lnxebld02
##ro.build.tags=rel-keys
ro.product.model=Milestone
ro.product.brand=MOTO_VFDE
ro.product.name=umts_sholes
ro.product.device=umts_sholes
ro.product.board=sholes
ro.product.cpu.abi=armeabi-v7a
ro.product.cpu.abi2=armeabi
ro.product.manufacturer=motorola
ro.product.locale.language=de
ro.product.locale.region=DE
ro.wifi.channels=
ro.board.platform=omap3
# ro.build.product is obsolete; use ro.product.device
ro.build.product=umts_sholes
# Do not try to parse ro.build.description or .fingerprint
ro.build.description=umts_sholes-user 2.1-update1 SHOLS_U2_02.36.0 8985865 rel-keys
ro.build.fingerprint=MOTO_VFDE/umts_sholes/umts_sholes/sholes:2.1-update1/SHOLS_U2_02.36.0/8985865:user/rel-keys
# end build properties
#
# system.prop for UMTS Sholes
#

rild.libpath=/system/lib/libril-moto-umts-1.so
rild.libargs=-d /dev/ttyS0
ro.sf.lcd_density=240
ro.default_usb_mode=0

# Default network type.
# 3 -> GSM/WCDMA (auto mode, according to PRL)
# AVAILABLE Application Settings menu
ro.telephony.default_network=3

wifi.interface = tiwlan0
# Time between scans in seconds. Keep it high to minimize battery drain.
# This only affects the case in which there are remembered access points,
# but none are in range.
wifi.supplicant_scan_interval = 15

# The OpenGL ES API level that is natively supported by this device.
# This is a 16.16 fixed point number
ro.opengles.version = 131072

# This is a high density device with more memory, so larger vm heaps for it.
dalvik.vm.heapsize=24m

#proximit sensor screen off delay
mot.proximity.delay=450

#proximit sensor disable touch distance 
mot.proximity.distance=60

#
# ADDITIONAL_BUILD_PROPERTIES
#
ro.product.multi_touch_enabled=true
ro.product.max_num_touch=2
ro.product.tfdt_string=com.motorola.android.app.tfdt.START
dalvik.vm.jniopts=warnonly
ro.com.motorola.smartsensor=true
ro.config.notification_sound=F1_New_SMS.ogg
ro.config.alarm_alert=Alarm_Classic.ogg
ro.telephony.call_ring.multiple=false
ro.telephony.call_ring.delay=3000
##ro.url.safetylegal=http://www.motorola.com/staticfiles/Support/legal/?model=A855
ro.setupwizard.enable_bypass=1
ro.media.enc.hprof.file.format=mp4
ro.media.enc.hprof.codec.vid=h264
ro.media.enc.hprof.codec.aud=aac
ro.media.enc.hprof.vid.width=720
ro.media.enc.hprof.vid.height=480
ro.media.enc.hprof.vid.fps=25
ro.media.enc.hprof.vid.bps=3000000
ro.media.enc.hprof.aud.bps=96000
ro.media.enc.hprof.aud.hz=16000
ro.media.enc.hprof.aud.ch=1
ro.media.enc.hprof.duration=60
##ro.media.enc.lprof.file.format=mp4
##ro.media.enc.lprof.codec.vid=m4v
##ro.media.enc.lprof.codec.aud=amrnb
##ro.media.enc.lprof.vid.width=320
##ro.media.enc.lprof.vid.height=240
##ro.media.enc.lprof.vid.fps=15
##ro.media.enc.lprof.vid.bps=200000
##ro.media.enc.lprof.aud.bps=12200
##ro.media.enc.lprof.aud.hz=8000
##ro.media.enc.lprof.aud.ch=1
##ro.media.enc.lprof.duration=30
ro.media.enc.file.format=3gp,mp4
ro.media.enc.vid.codec=h264,m4v,h263
ro.media.enc.aud.codec=aac,amrnb
ro.media.enc.vid.h264.width=176,720
ro.media.enc.vid.h264.height=144,480
ro.media.enc.vid.h264.bps=64000,8000000
ro.media.enc.vid.h264.fps=1,30
ro.media.enc.vid.h263.width=176,720
ro.media.enc.vid.h263.height=144,480
ro.media.enc.vid.h263.bps=64000,8000000
ro.media.enc.vid.h263.fps=1,30
ro.media.enc.vid.m4v.width=176,720
ro.media.enc.vid.m4v.height=144,480
ro.media.enc.vid.m4v.bps=64000,8000000
ro.media.enc.vid.m4v.fps=1,30
ro.media.enc.aud.amrnb.bps=5525,12200
ro.media.enc.aud.amrnb.hz=8000,8000
ro.media.enc.aud.amrnb.ch=1,1
ro.media.enc.aud.aac.bps=8192,96000
ro.media.enc.aud.aac.hz=16000,16000
ro.media.enc.aud.aac.ch=1,1
ro.media.dec.aud.wma.enabled=1
ro.media.dec.vid.wmv.enabled=1
ro.media.cam.preview.fps=0
ro.com.google.clientid=android-motorola
##ro.url.legal=http://www.google.com/intl/%s/mobile/android/basic/phone-legal.html
##ro.url.legal.android_privacy=http://www.google.com/intl/%s/mobile/android/basic/privacy.html
ro.config.vc_call_vol_steps=7
dalvik.vm.dexopt-flags=m=y
net.bt.name=Android
ro.config.sync=yes
dalvik.vm.stack-trace-file=/data/anr/traces.txt
ro.config.ringtone=Red_Beats.ogg
ro.com.google.gmsversion=2.1_r1
ro.com.google.clientidbase.am=android-vf-{country}
ro.com.google.clientidbase=android-hms-vf-{country}
ro.setupwizard.mode=OPTIONAL
ro.com.google.clientidbase.gmm=android-motorola
ro.com.google.clientidbase.yt=android-motorola 
ro.com.google.locationfeatures=1

 

The entries starting with ## have been commented out by me.

  • The commented ro.build.* entries contain information about the host on which the system was built (who cares?)
  • ro.url.safetylegal is an URL for some legal notice?
  • ro.media.enc.lprof.* are modified settings for video recording with low quality. (I only use high quality anyway.)
  • ro.url.legal und ro.url.legal.android_privacy again are URLs fore some safety/privacy policies?

And here is my modified /system/default.prop

ro.com.google.clientidbase.vs=android-motorola
ro.build.config.version=GAS_EMEA_USASHLS00VFDE_P035
ro.build.config.date=Mon Jul 12 20:51:05 -0500 2010
##ro.bidi.Hebrew=Off
ro.keyboard.type=qwertz
ro.bidi.enabled=false
##ro.bidi.Arabic=Off
persist.sys.timezone=Europe/Berlin
ro.fota.enable=1
sms.convert.char.for.latam=0
##ro.bidi.Russian=Off

bidi seems to stand for “bidirectional input”. Since ro.bidi.enabled is false, I assume that the other ro.bidi.* properties are irrelevant anyway.

After thinning out the property files, is have 245 properties after using WiFi, 3G-data and VPN. Two properties left in reserve 😉

Examples for properties that are created dynamically are:

[dhcp.tiwlan0.result]: [ok]
[init.svc.dhcpcd]: [running]
[dhcp.tiwlan0.pid]: [3986]
[dhcp.tiwlan0.reason]: [RENEW]
[dhcp.tiwlan0.dns1]: [192.168.1.6]
[dhcp.tiwlan0.dns2]: []
[dhcp.tiwlan0.dns3]: []
[dhcp.tiwlan0.dns4]: []
[dhcp.tiwlan0.ipaddress]: [192.168.1.61]
[dhcp.tiwlan0.gateway]: [192.168.1.6]
[dhcp.tiwlan0.mask]: [255.255.255.0]
[dhcp.tiwlan0.leasetime]: [14400]
[dhcp.tiwlan0.server]: [192.168.1.6]
[net.dns1]: [192.168.1.6]

 

If these properties can not be created when activating WiFi, WiFi does not work.

1Dec/09

Weak Listeners

Resource Deallocation in Java

Resource deallocation in Java is no problem since Java has automatic garbage collection.

Right! ...? ...?? ...Wrong!

Since Java has automatic garbage collection, resource deallocation is often taken care of for you,
but when it is not, it becomes a real pain.
Since the language relies so heavily on garbage collection, it seems the language
designers forgot to include good mechanisms for the cases where garbage collection does
not solve the problem (releasing external resources) or just does not happen automatically.

A common scenario for memory leaks in Java is Listener registration and de-registration.
When you want one component to react to events in another component you usually use a listener
pattern. The listening component registers itself as a callback client to an observable component.

Finding the right place to register the listener usually is not a problem.
Often it is done in the constructor of the listening component or in some setup
method.

Unregistering however is often not so obvious since there is no canonical tear down
mechanism in Java. You are often left with listeners staying registered in their
observables and therefore not eligable for garbage collection.

Netbeans Solution

The Netbeans developers have addressed this problem by providing a class called
WeakListener. It works by inserting a WeakListener object between the listener
and the observable. The listener is only referenced weakly (with a WeakReference)
and therefore is automatically collected, when other references to the listener
go away. The WeakListener class also unregisters itself automagically when it
notices that its concrete listener has been collected. Unregistering the WeakListener
is only possible, if the observable conforms to certain conventions.

This solution is great, when you have a given model and want to make sure, that
your listeners do not remain referenced indefinitely.

My Solution

My solution works the other way around. The observable has support for weak listeners
built in. It keeps its list of listeners in a WeakListener support class. Listeners
can register themselves in the usual way. They may unregister themselves explicitly
or just count on being disconnected automatically.

This solution is great, when you are designing a model (observable) and want to
make life easier for your clients (listeners).

Example

Observable

public class NumberModel {

    private WeakNumberChangedListeners listeners =
            new WeakNumberChangedListeners();

    private int theNumber = 0;
    private String name;

    public NumberModel(String name)
    {
        this.name = name;
    }

    public synchronized void inc()
    {
        int oldNumber = theNumber;
        theNumber++;
        listeners.fire (new NumberChangedEvent(this, oldNumber, theNumber));
    }

    public synchronized void dec()
    {
        int oldNumber = theNumber;
        theNumber--;
        listeners.fire (new NumberChangedEvent(this, oldNumber, theNumber));
    }

    public synchronized int getNumber ( )
    {
        return theNumber;
    }

    public String getName()
    {
        return name;
    }

    public void addNumberChangedLisener (NumberChangedListener listener)
    {
        listeners.add (listener);
    }

    public void removeNumberChangedListener (NumberChangedListener listener)
    {
        listeners.remove (listener);
    }
}

Listener Interface

public interface NumberChangedListener {

    void numberChanged (NumberChangedEvent event);

}

WeakListeners class

public class WeakNumberChangedListeners
        extends WeakListeners<NumberChangedListener, NumberChangedEvent>
{

    @Override
    protected void fireOne(NumberChangedListener listener, NumberChangedEvent event)
    {
        listener.numberChanged (event);
    }

}
}

WeakListeners.java

package com.bininda.weaklistener;

import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.WeakHashMap;

/**
 * A list of listeners that are only referenced weakly.
 * Listeners are registered using the add method. They may be deregistered
 * using the remove method, however if the listener is garbage collected, because
 * it is not hard referenced any more, it is automatically removed.
 * The method fireOne must be overwritten in a derived class.
 * @param I The listener interface
 * @param E The event type
 * @author bininda
 */
public abstract class WeakListeners<I, E> {

    /**
     * Weak map of references to listeners.
     * WeakHashMap has the nice feature of automatically getting rid of
     * stale entries.
     */
    private Set<I> wSet = Collections.newSetFromMap(new WeakHashMap<I, Boolean>());

    /**
     * Create an empty weak listener list.
     */
    public WeakListeners()
    {

    }

    /**
     * Add a new listener to the list of registered listeners.
     * If the listener has already been added, the only effect is diagnostic
     * output.
     * @param listener The new listener
     */
    public synchronized void add (I listener)
    {
        wSet.add (listener);
    }

    /**
     * Remove a registered listener from the list.
     * If the listener has already been removed, output diagnostics.
     * Note that automatic removal can not happen as long as someone still has
     * a hard reference to the listener and is therefore able to call this method.
     * @param listener
     */
    public synchronized void remove (I listener)
    {
        wSet.remove (listener);
    }

    /**
     * Fire one event to one registered listener.
     * @param listener The listener to fire to
     * @param event The event to pass to the listener.
     */
    abstract protected void fireOne(I listener, E event);

    /**
     * Fire an event to all registered listeners.
     * If diagnostics are enabled, listeners that have been collected automatically
     * are printed out.
     * @param event The event to fire to the listeners.
     */
    public synchronized void fire(E event)
    {
        {
            List<I> lcp = new LinkedList<I>(wSet);
            for (I l: lcp)
            {
                try
                {
                    fireOne (l, event);
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                }
            }
        }
    }

}

8Oct/09

Is the USPTO violating Patents it granted itself?

I was reading a news story about the patents US-Patent 5,838,906 and US-Patent 7,599,985. The patents are about:

Distributed hypermedia method and system for automatically invoking external application providing interaction and display of embedded objects within a hypermedia document

As I understand the patents, they are (among other things) about browser plug-in. If you follow the links  to the patents (either US-Patent 5,838,906 or US-Patent 7,599,985), you come to pages on the United States Patent and Trademark Office. At the top of the page there are a couple of red buttons, one of them is labeled “Images”. If you click on that button, you come to a page that requires an Apple QuickTime plug-in.

Apple of course is one of the companies being sued by Eolas Technologies for violating the patent.

I found this quite amusing.

17Sep/09

Image Preview in JFileChooser

I just put together a little code to show an image preview in the JFileChooser. It shows a scaled image on the right side of the file chooser. The image is scaled to completely fit inside the box to the right.

ImageFileChooser
The file chooser does not filter files. It allows the user to select any file but if it can be opened as an image, the preview is shown.
I created a component based on JPanel for the preview which overrides the paintComponent method:

package x.y.z.util;

import java.awt.Graphics;
import java.awt.Image;
import javax.swing.JPanel;

public class ImagePanel extends JPanel
{

    private Image image;
    private Image scaledCache;

    public ImagePanel()
    {
        setBorder(javax.swing.BorderFactory.createEtchedBorder());
    }

    public void setImage (Image image)
    {
        this.image = image;
        scaledCache = null;
        repaint();
    }

    private Image getScaled()
    {
        int iw = image.getWidth(this);
        int ih = image.getHeight(this);
        int pw = getWidth();
        int ph = getHeight();
        double scale;
        if (1.0 * pw / iw < 1.0 * ph / ih)
        {
            scale = 1.0 * pw / iw;
        }
        else
        {
            scale = 1.0 * ph / ih;
        }
        int scaledw = (int) (iw * scale);
        int scaledh = (int) (ih * scale);
        if (scaledCache != null)
        {
            if (scaledCache.getWidth(this) == scaledw &&
                    scaledCache.getHeight(this) == scaledh)
            {
                return scaledCache;
            }
        }
        scaledCache = image.getScaledInstance(scaledw, scaledh, Image.SCALE_DEFAULT);
        return scaledCache;
    }

    @Override
    public void paintComponent(Graphics g)
    {
        if (g != null)
        {
            Graphics scratch = g.create();
            scratch.setColor(getBackground());
            scratch.fillRect(0, 0, getWidth(), getHeight());
            if (image != null)
            {
                Image scaled = getScaled();
                scratch.drawImage(scaled, getWidth() / 2 - scaled.getWidth(this) / 2,
                        getHeight() / 2 - scaled.getHeight(this) / 2, this);
            }
        }
    }
}

Then I created a the class ImageFileChooser derived from JFileChooser:

package x.y.z.util;

import java.awt.Dimension;
import java.awt.Image;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import javax.swing.ImageIcon;
import javax.swing.JFileChooser;

public class ImageFileChooser extends JFileChooser {

    public ImageFileChooser()
    {
        final ImagePanel preview = new ImagePanel();
        preview.setPreferredSize(new Dimension (150, 150));
        setAccessory(preview);
        addPropertyChangeListener(new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent e)
            {
                String propertyName = e.getPropertyName();
                if (propertyName.equals(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY))
                {
                    File selection = (File) e.getNewValue();
                    String name;
                    if (selection == null)
                    {
                        return;
                    }
                    else
                    {
                        name = selection.getAbsolutePath();
                    }
                    ImageIcon icon = new ImageIcon(name);
                    Image newImage = icon.getImage();
                    preview.setImage (newImage);
                }
            }
        });
    }
}

Works great.

Tagged as: , No Comments
17Sep/09

Netbeans Debugger Problems

My colleague Chris Andritzky keeps getting bitten by problems of the Netbeans Debugger. In a multi threaded application, threads are stopped by breakpoints but the debugger does not realize it.
It seems there are two bugs behind this, one in JDK6 and one in the Netbeans Debugger.
The bug is described in Issue 167776. The JDK6 bug is supposedly fixed in Update 16. The Netbeans bug will be fixed in Netbeans 6.8. Chris is going to try Netbeans 6.8 Milestone 1.

Update:
Chris tried updating to Update 16 first. This didn't help. Updating to Netbeans 6.8 Milestone 1 solved the problem though.

27Aug/09

Subdocument in Netbeans RCP

In my current Netbeans RCP project, I manage entities called configurations which are persisted in a separate component. Each configuration is defined by an XML file and a bunch of additional resource files. The configuration itself (the XML file) can be edited graphically.

A new requirement came up which essentially defined a new type of additional resource files. These resource files contain Lua scripts and have to be editable in the configuration tool.