Developer guide
Source Code Management
See the Git page.
Building the code
Requirements
- Java JDK 8 or newer
- Apache Ant
Building from the source package distribution
See the instructions in BUILD.txt.
Building from the Github checkout
Checkout the code from Github and simply run the following to compile the code locally
ant -Dbasedir=`pwd` -f ./src/packaging/ant/build_enduser.xml
Copyright
- We have a single moral entity named “The Jajuk Team” that owns the copyright on all sources, please do not set your name in the file headers (BTW, make sure to use the provided code generation template, see bellow).
- Note that your credits are still available from various members lists, from the SCM system anyway and from this page.
Developers mailing Lists
- Jajuk developer mailing list is the project main communication stream. Note that the Reply-to is directed to the original sender, not the list so please use the “Reply all” button from you e-mail client to reply to the list.
Java version
- Jajuk has to run on every JVM >= 1.8
- Prefer using Java 8+ classes and features (like collection for instead of iterators or, autoboxing, enums). Using generics is mandatory.
IDE
We recommend the Eclipse platform. All following documentation is applicable to Eclipse only. We use basically Eclipse built-in formater configuration with :
- Line length = 100 (instead of 80 by default)
- Tabs = 4 spaces.
- Indentation : 2 spaces.
- No blank line inside a block.
- Formatter profile should be “Unmanaged” (this profile means you use the SCM conf file). To change the profile and share the changes, the only way we know is to hand-edit the .settings/
and commit/push it. Only use UTF-8 and Unix-flavor new file carriage return for your source (Project -> Properties -> Text file encoding : UTF-8). Check these setting by selecting the project in the Package explorer -> Properties.
To launch jajuk from Eclipse, create a run configuration with :
- Name :
jajuk
- Main class :
org.jajuk.Main
- Program arguments :
-test
- VM arguments are currently:
-Xms20M -Xmx512M -XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=10 -Djava.library.path=lib
Code conventions
Eclipse formatter and templates
- Defaults templates are provided as Eclipse preference with the project. They are mainly Eclipse defaults templates with the Jajuk header for new files
- Defaults code formatter configuration is provided as well with the Eclipse project preferences. It is the Eclipse default template with two spaces indentation and lines of 100 caracters max.
- Don’t use one line statement without braces: use
if (){..one line..}
, notif (...)
on a single line - Note that the release packager applies source and import formatting over all the code at each release to make sure the formatting is always applied
String externalization
- All application labels have to be externalized (see Translator Guide) with key names :
.<number from 0> . - For common labels like OK, Cancel… we use generic externalized labels (see
jajuk.properties
). Please keep number of generic labels to minimum and when creating new generic labels, keep in mind that some language (French, German…) are gender-specific in opposition to English. - Do not reuse existing label ids but always increment a new number for new strings because removed labels can still be present in out-of-date langpacks. For the same reason, leave a comment in the English langpack if your remove the last label of a class (to avoid another developer to use the same ID). For exemple if a class
Foo
contains 15 labelsFoo.0
,Foo.1
…Foo.14
and if you have to removeFoo.14
, leave a comment #Do not useFoo.14
, continue toFoo.15
. - Make sure to cleanup useless labels to reduce translators work.
- Debug logs are not externalized string but raw English strings.
XML
- Avoid to use PCDATA values, use attributes instead : it’s faster to parse and easier to code
- Use single quotes, not double quotes.
Comments
- For formatting reasons, use
/* */
for long comments in the methods, not several//
Dead code
Please remove any dead code, SCM helps to recover any previous code anyway. Tip : install the http://www.ucdetector.org eclipse plugin.
Class headers
- Please use the standard Jajuk header (see Eclipse templates configuration). Please do not use your own copyright but keep “The Jajuk Team” as owner.
- Make sure to use the template provided as Eclipse project preference
Code quality
Please keep Eclipse standard level of warnings (imports, static context…) and make sure to cleanup imports (Control-Alt-o). Check the Sonar analysis, you may want to install the local Sonar plugin as well.
GUI threading conventions
- Every widget creation or change has to be done inside the EDT (Event Dispatching Thread). For more details, check this page. This is currently endorsed through the Substance look and feel.
- Tasks that takes half a second or more to run (for a large collection) have to be done outside the EDT.
- When an operation requires long operations (done outside the EDT) and GUI updates afterwards (like downloading a picture and then displaying it), you have to use a the SwingWorker class.
- For views that provides a populate mechanism, use the Jajuk “TwoStepsDisplayable” pattern that abstracts the SwingWorker and makes code easier :
- Your view shall implement the
TwoStepsDisplayable
interface and providelongCall()
andshortCall()
methods for actions done respectively outside and inside the EDT. - Use the
UtilGUI.populate(TwoStepsDisplayable)
method to populate the view and run the underlying SwingWorker.
- Your view shall implement the
Documentation
- Please document your features in English on this website.
- You may want to add new tips of the day in
jajuk_properties
file.
Layout Manager
- Jajuk official layout manager is the excellent MigLayout. Please read the quick start guide and the cheat sheet.
- Please use MigLayout in priority for every layout need, even simple cases as the resulting code is extremely simple to write, read and maintain. There are very few cases where you have to use others layout managers (like an Horizontal alignment between two items) or special cases (FlowLayout in CatalogView for ie)
- An excellent Cheat Sheet is available.
- Please read the Tutorial at http://www.miglayout.com/
- MigLayout is really powerful and simple. Even if it supports cell-kind layout management (like grid bag or TableLayout), Please try avoid to use them when possible (what happens when you want to add a widgets among existing elements in the future ?) but use the regular
wrap
mode instead (tell explicitly that you reached the end of a row),split
andspan
. - In 99% of cases you don’t need any inner panel
- MigLayout saves a lot of swing code like
insets
,setMaxSize
, set horizontal/vertical gaps with Box etc… All of these information have to be set as MigLayout string constraints mainly within its constructor.
Validation
- To validation fields in forms, we use Simple-validation, that’s pretty good and non-intrusive.
Wizards
- Use QDWizard to write complex wizards (see
DJWizard
as a example) - Note that QDWizard contains its own error messages and validation scheme, we normally don’t have to use an external validation lib when writing a wizard.
Unit Testing
- Jajuk uses JUnit unit testing which is well integrated into Eclipse (Development IDE), Jenkins (Continuous integration server) and Sonar (Code Quality Checking tool)
General Guidelines
The following guidelines should be observed when committing code changes:
- Each class
src/main/java/...
should have its corresponding test case in the same Java package undersrc/test/java/...
- When implementing new features, please also create corresponding unit test cases or even better, follow Test Driven Development principles.
- When changing existing classes, run related unit tests before checking in.
- It is not mandatory to run all tests manually on your machine, however please take a look at the Jenkins build server after the next automated build and test run (usually done overnight) to ensure that no other test is broken now.
- Fix any unit test broken by your commit ASAP to avoid lengthy periods of broken Jenkins builds!
Utility classes/Helper methods
There is one class available as part of the current tests that provides basic helper methods: JUnitHelpers
. This class can be used for a number of small tasks that are often necessary when writing Unit Tests, it provides the following:
createSessionDirectory()
: Creates a temporary working directory for Jajuk which can then be used for Workspace related tests.testPrivateConstructor()
: In some cases we add a private constuctor to classes that only have static members to prevent this class from being instantiated (which makes no sense at all). In order to get coverage for this constructor, we have a small helper:
EqualsTest()/CompareToTest()/ToStringTest()/CloneTest()/HashCodeTest()/EnumTest()
: These are helpers for testing the standard methods that are available in many classes, e.g. EqualsTest will verify many general “rules” on equalness that are specified by the Java language definition and which can have side effects if not implemented correctly.clearSwingUtilitiesQueue()/waitForThreadToFinish()/waitForAllWorkToFinishAndCleanup()
: Helpers when testing Swing-related code. They allow to clean out the queue of asynchronous events and also to wait for threads and other things that are started. This avoid hard to trace errors in tests when things are started in the background by some classes.getFile()
: creates and registers a music-file.getTrack()
: creates and registers a music-track.MockPlayer
: A helper class that implements the Player interface.
Additionally we provide a class JajukTestCase
which calls the waitForAllWorkToFinishAndCleanup()
. This is useful when testing classes that always require cleanup before starting tests.
Headless mode
- In order to have Unit tests running on Continuous Integration platforms like Jenkins, all unit tests must run without UI interaction and without accessing any of the UI functionality from AWT/Swing. Naturally this prevents some things from being tested via unit tests, but on the other hand it ensures that all tests execute without popping up a message box and waiting endlessly for the user to press a button, something that is not useful for unit tests at all.
- In order to run the unit tests locally with the same setting to disable UI support in Java, please add the following setting to the run-configuration of Eclipse or any tool used to execute unit tests:
-Djava.awt.headless=true
As result of this, any place where UI is used will throw an “HeadlessException” that we need to ignore for tests, i.e. the test should still succeed, even if the HeadlessException is thrown.
Development hits and available features
Guava
We use Guava, the excellent toolbox from Google. Don’t hesitate to use features from Guava (Jajuk 1.9 uses Guava R11) to reduce the number of code lines and increase the code quality. Check Guava javadoc.
Using managers
Managers are the only way to access (list or change) items (Track, File, Type, Directory, Playlist, PlaylistFile, Device, Artist, album and Genre). For example, listing devices is done this way:
Note that getXXXs()
methods return copies that can be changed safely.
Anonymizer
- For any private data (track name, user info…), use double braces in logs. Example for Cover URL search:
- All strings between ```` are automatically replaced by
***
in quality agent. - Use this method to get a list of tracks for a given item like an artist or an album :
TrackManager.getAssociatedTracks()
Utilities features
- The
org.jajuk.utils.Util*
classes contain many useful methods. Please check it to avoid reinventing the wheel but don’t hesitate to add new features by yourself.
Files filters
- Jajuk provides a powerful
JajukFileFilter
class to filter files. Example: to keep only audio files or directories:
- Jajuk provides several predefined filters:
Audio
,NotAudio
,Playlist
,Directory
,KnownType
,AnyFile
than can be used in conjunction using aAND
or anOR
between them.
Collection filters
- Jajuk uses Jakarta Commons-Collections features to filter collections. Filtering is done by using decorated iterators using Predicates. Example:
allows to iterate only on items of given age.
- New predicates should be centralized into the
JajukPredicates
class
Accessing to configuration files
- The Jajuk workspace (the directory where Jajuk stores all configuration files and indexes) is variable (by default ~/.jajuk in production and ~/.jajuk_test_
in developement mode - ie using the -test option). This is why you have to access configuration files this way:
and not trying to build the file path on your own
Design patterns
Singleton [GOF]
- Some Jajuk classes (mainly in
org.jajuk.base
) are singletons, most of the time for serious reasons. - Please avoid making a class a singleton if you have no good reason for that
Observer [GOF]
- Jajuk provides a full featured observer pattern implementation (supports details, is fully asynchronous, has a cache, an overflow controller, etc.)
- To register to events, a view has to implement Observer interface and provide a list of observed events this way:
- To handle events, look at this sample :
- To notify an event :
- If you want to make sure that a observer is updated before others, implements
HighPriorityObserver
instead ofObserver
(of course, you can set only a single high priority observer per subject).
D-Bus
See these archives
Howtos
How to get tracks for a given artist, album, year or genre ?
How to get playable files for any item (logical or physical)?
How to filter a list of items ?
How to get the number of items (track, files…) ?
How to add a new option ?
- Add the option name in
org.jajuk.util.Const
:
- don’t forget to set a default value in
org.jajuk.util.Conf
:
- Use
setProperty()
method to set it in the program andgetBoolean()
,getProperty()
,getInt()
… methods to retrieve it. Options are automatically saved to theconf.properties
file at shutdown and loaded at startup.
How are managed window startup sizes and positions ?
- Default size is set using this algorithm:
begin width = screen width if width > 1400 width = 1200 //deal with dual heads else width = screen width - 120 height = screen height if height > 1200 height = 1000 //deal with dual heads else height = screen height - 120 end
- Then size is set at each startup using:
begin if we find a forced position in conf.properties (jajuk.frame.forced_position entry) use this position/size (used to allow XGL users to force a position) else if window manager buggy Apply (60,60,screen width - 120, screen height - 120) else if stored position (jajuk.window_position) contains "max" (maximalize) if window manager supports expand expand (the window manager then deal with task bars) else Apply (60,60,screen width - 120, screen height - 120) else int x = configurated x int y = configurated y int width = configurated width int height = configurated height if x < 0 or x > screen width x = 60 if y < 0 or y > screen height y = 60 if width <= 0 or width > screen width width = window width - 120 if height <= 0 or height > screen height height = screen height - 120 Apply (x,y,width,height) end
How to manage mouse events and right clicks ?
- Do not override directly
MouseAdapter
to manage mouse clicks but useJajukMouseAdapter
class instead (see its detailed javadoc for reasons, handling right click on every OS is a bit complicated) - Note that what makes all this a little complicated is the fact that most items can throw at the same time popup on right click and realize an action when the left click is recognized.
How to use the font manager ?
- Please always use the
FontManager
class to deal with fonts for code factorization and performance - If you don’t find a required font in
JajukFont
enum, please create a new one (set its properties in theregisterFonts()
method) - Fonts are accessed using :
How to format dates ?
- Use
UtilString.getLocaleDateFormatter()
andUtilString.getAdditionDateFormatter()
date formatters. The first method return the default date formatter for the current user and is intended for human display purpose. The second is used to store dates to be stored, it is the format used to store the date when tracks have been discovered (YYYYMMDD format)
How to get good random value ?
- Use only this method to get random value (use the Mersenne twister algorithm):
Traps
Date format not thread safe
- Keep in mind that several threads cannot use the same
DateFormat
object (can throwsOutOfBoundsException
), this applies to locale and addition formatters provided by jajuk (see how to format dates)
canWrite() method
- Do not use the
java.io.File.canWrite()
method. Under Windows, it may return always false. Prefer to catchIOException
.
SteppedComboBox fonts
- Do not use custom font for stepped comboboxes : the stepping doesn’t work anymore for unknown reason
Reading events properties
- If an event contains an non-string property to read, use
get(<property>)
to retrieve it. When usinggetProperty()
method, theProperty
class returnsnull
.
Trailing zeros
Do not use trailing 0
in integers like in :
but instead :
because 005
!= 5
(octal)
Char issue
Avoid using chars in strings building because a char + int + string => (int) + string. Example:
How to avoid memory leaks ?
- Use jconsole and/or visualvm to track class with high generations value (generations value is the number of concurrent generations alive). Use snapshots to compare generations values.
- Avoid using static inner classes : once loaded, they are never unloaded and are kept on non-heap space.
- Avoid using too much singletons pattern and static attributes
- Always clear collections after use if these collections are classes attributes.