How to implement something ("Best practices")

Do not repeat bad code from (hopefully) old parts of JPhotoTagger! For example, in most cases it's better to use Actions instead ActionListeners. Use the org.openide.util.Lookup to provide e.g. selected content together with it's support in JPhotoTaggers's Lib Project. You can see usage examples in the code of some modules, e.g. in Display Files Without Metadata or Module RepositoryFileBrowser.

Do not add code to the Program project. There should be only bugs fixed, code refactored or the functionality enhanced (only if not possible via the Java Service Provider Interface).

Start a new project for adding features. Refactorings are always welcome, if they will improve the design.

Don't hesitate to fix bugs in code written by others. Write unit tests for every method, NetBeans automatically generates test classes and method skeletons.

Use JPhotoTagger's API and Domain Projects (Java Service Provider Interface in general)

Do not use implementations, use interfaces. We are using the Java Service Provider Interface (SPI) to get implementations. With that lines of code you get an implementation:

MyInterface implementation = Lookup.getDefault().lookup(MyInterface.class);

As an example, the currently used database implements Repository interfaces. To use a different database and/or the JPA instead of JDBC, only the project implementing these interfaces has to be replaced and no other code has to be changed. Ideally the user does not recognize any change.

Another example: JPhotoTagger has limited capabilities to generate thumbnails of different camera image file formats or other media formats such as videos. You can add an implementation of ThumbnailCreator in a separate project, e.g. for your special camera RAW format, and it will be automatically used (if no thumbnail could be created by the default implementation).

Do not create files in META-INF/services for your service providers. This is very error prone on Move/Rename refactorings. Use the @ServiceProvider annotation instead, grep the source code for usage examples.

Extending JPhotoTagger

Extensions processing files

If the thumbnails panel shall offer a menu entry for processing selected files, implement in a separate project the interface FileProcessorPlugin. Examples may be a HTML Gallery generator or FTP upload. Existing example implementations are the projects Copy Filenames To Clipboard (easy and a good starting point to learn) or Flickr Upload.

Complex extensions

A complex extension implements Module. Modules will be initialized during the application startup without user interaction (file processors will be invoked only if the user calls them). Examples are FileEventHooks or RepositoryFileBrowser.

Thumbnails for a specific file format not supported by JPotoTagger

Implement org.jphototagger.domain.thumbnails.ThumbnailCreator. JPhotoTagger will find it through the Java Service Provider Interface.

EXIF for a specific file format not supported by JPotoTagger

Implement org.jphototagger.domain.metadata.exif.ExifReader. JPhotoTagger will find it through the Java Service Provider Interface.

EXIF Maker Notes

Implement org.jphototagger.domain.metadata.exif.ExifMakerNoteTags. JPhotoTagger will find it through the Java Service Provider Interface. In Opposite to an ExifReader, an ExifMakerNoteTags instance gets the already read Maker Notes byte array and has to use the specification of the maker to decode them.

Remarks

You can add menu entries, panels etc. to the application window, get thumbnails etc. – take a look into the API project. For querying or modifying a repository (part of a database), have also a look into the Domain project.

Events

JPhotoTagger publishes many events through EventBus. Events are defined in the API and Domain projects. If you are interested into an event, e.g. if the user selects an image, you can do something like that:

public final class MyImageProcessor {

    public MyImageProcessor() {
        // Registering to EventBus
        AnnotationProcessor.process(this);
    }

    @EventSubscriber(eventClass = ThumbnailsSelectionChangedEvent.class)
    public void thumbnailsSelectionChanged(ThumbnailsSelectionChangedEvent event) {
        List<File> selectedFiles = event.getSelectedFiles();
        for (File selectedFile : selectedFiles) {
            processFile(selectedFile);
        }
    }
}

Processes taking some time should create a separate thread and do their tasks in this thread since the listeners will be notified in the Event Dispatch Thread and as long as they do something, the GUI is not accessable.

Persist Preferences (Settings)

Preferences can be persisted and restored through org.jphototagger.api.preferences.Preferences:

private void persistPreferences() {
    Preferences prefs = Lookup.getDefault().lookup(Preferences.class);
    prefs.setString("MyModule.PreviousDirectory", directory.getAbsolutePath());
    prefs.setBoolean("MyModule.DeleteAlways", deleteAlways);
    prefs.setLocation("MyModule.MyDialogLocation", dialog);
}

private void restorePreferences() {
    Preferences prefs = Lookup.getDefault().lookup(Preferences.class);
    String previousDirectory = prefs.getString("MyModule.PreviousDirectory");
    boolean deleteAlways = prefs.getBoolean("MyModule.DeleteAlways");
    prefs.applyLocation("MyModule.MyDialogLocation", dialog);
}

If you want react immediately to changes in user settings, subscribe to the Eventbus (see obove), you will get a PreferencesChangedEvent with the old and new value.

Commit and push changes

Use Mercurial's commit and push command to share your code. I don't want spent time to integrate patches sent by e mail.

Author: Elmar Baumann
Write e-Mail
Status of this document: 2012-03-02