FAQ for Developers

FAQ for Developers

This document provides pointers in the code to Qualyzer developers. Because the code changes more quickly and more often than this document, it should not be seen as the tablets of law.

Contents

Where is ...

The RTF Parser

The RTF parser is implemented in the qualyzer.editors.RTFDocumentProvider2 class. The parsing (open) starts in method setDocumentContent(). The conversion from the editor to RTF (save) starts in method doSave().

The model

All classes representing the Qualyzer model elements (e.g., Transcript, Memo, Participant) are in the package qualyzer.model.

All model classes have a fPersistenceID long field that contains the primary key from the database. Changes in the model are reported to listeners. It is currently possible to listen to changes in instances of: Project, Code, Investigator, Participant, Transcript, and Memo. Each of these classes have a corresponding listener (e.g., ParticipantListener).

Listener management is performed by qualyzer.model.ListenerManager. The single instance of the ListenerManager is held by the qualyzer.model.Facade class.

Model objects are created, saved, and deleted through the qualyzer.model.Facade instance.

Creation and modification of model objects are validated with the classes in qualyzer.model.validation. For example, a modification of an investigator is validated by the qualyzer.model.validation.InvestigatorValidator class.

The persistence layer (DB)

We use Hibernate to persist model objects to a relational DB. We embed the H2 database with Qualyzer. This is a relational database management system written in Java and running in the same virtual machine as Qualyzer.

The database data file is saved in the directory QualyzerWorkspace/project/.db.

Four classes are responsible for the persistence:

  • qualyzer.model.HibernateDBManager - This class establishes a connection to the database and enables the creation of an Hibernate session.
  • qualyzer.model.PersistenceManager - This class provides top-level services on the DB such as retrieving the project object for a project. This class is a singleton and can be accessed from anywhere.
  • qualyzer.util.HibernateUtil - This class provides common operations on the DB such as saving and refreshing an object without throwing any exception.
  • qualyzer.model.Facade - This class provides all the operations to create, save, and delete model objects.

The version number

The version number is declared by the constant QualyzerActivator.CURRENT_VERSION. The version of each project is saved in the file QualyzerWorkspace/project/.project.

Properties in the .project file such as the version number are read and written using FileUtil.getProjectProperty and FileUtil.setProjectProperty.

The GUI

Most of the menus and shortcuts are defined in the plugin.xml file.

The editors such as the Transcript Editor and their actions such as ItalicAction are located in the qualyzer.editors package.

The dialogs and the wizards are in the qualyzer.dialogs and qualyzer.wizards package respectively.

The "action" corresponding to a menu selection or a keyboard shortcut are mostly located in the qualyzer.handlers package.

The project explorer, i.e., the navigation tree located on the left of Qualyzer, is located in the qualyzer.projectExplorer package.

How is it done?

Logging

See the Logging page for an in-depth presentation of our logging infrastructure.

Update of the DB schema between two versions

When Qualyzer is started (QualyzerActivator.start()), we compare the CURRENT_VERSION constant with the PROJECT_VERSION property of each project. If the two values differ, we update the schema of the project's database (Facade.updateProject). Hibernate then automatically update the schema of the database to add, remove, and modify tables and columns.

Since we added the automatic update of the schema, we only modified the model once (addition of one field). There is only so much that can be automatically done when updating a schema so if the model changes significantly between two versions, extensive testing will be required. By significant changes, we mean the addition of non-optional fields, changes in the relations between two model classes, etc.

Internationalization

We follow the following process:

  • Each String value that needs to be translated contains a key that usually looks like class.msgKey. For example, the string that tells whether a database upgrade went well contains the value "QualyzerActivator.upgradeWell".
  • Each line that contains a String literal that should not be translated must end with "$NON-NLS-1$"
  • Each package that contains String objects that needs to be translated must contain a file named "messages.properties".
  • Each package that contains String objects that needs to be translated must contain a final class named "Messages".
  • To retrieve the translated value of a String (e.g., to display on the GUI), call Messages.getString(key).

Bold/Underline/Italic in the transcript editor

The content of the Transcript and Memo editors is displayed using two information:

  1. The textual content obtained from a RTF file.
  2. An org.eclipse.jface.text.source.AnnotationModel that contains a set of org.eclipse.jface.text.source.Annotation objects associated with a org.eclipse.jface.text.Position. An Annotation is just an object containing a style (e.g., BOLD).

The class qualyzer.editors.RTFDecorationSupport is responsible for mapping an annotation (e.g., "BOLD") to a visual style (e.g., SWT.BOLD).

The class qualyzer.editors.RTFDocumentProvider2 is responsible for parsing a RTF file to extract the textual content and associate appropriate annotations with parts of the text. For example, the RTF control command \b will be translated to an Annotation of style "BOLD", which will ultimately be converted to the visual style SWT.BOLD.

Highlighting of codes in the transcript editor

We refer to the part of the text that is associated with a code as a "fragment". For each fragment in a document, we generate a qualyzer.editors.FragmentAnnotation associated with the position of the fragment.

The color and the style of the highlighted fragments are declared in qualyzer.editors.RTFDecorationSupport.setAnnotationPreference().

Import and Export of projects

The GUI uses the Import and Export wizard extensions of Eclipse. See the classes ProjectImportWizard and ProjectExportWizard.

The Import wizard delegates most of the work to Eclipse: the user selects a zip file and Eclipse imports all the projects contained in the zip file to the workspace. Then, in the performFinish() method, Qualyzer loops through all projects and refreshes the project explorer while deleting the non-Qualyzer projects (e.g., it is possible to package a Java project with a Qualyzer project in the same zip file. The Java project will be deleted from the workspace).

The Export wizard also delegates most of the work to Eclipse: the ProjectExportWizardPage just tells to Eclipse to automatically include all files in the project. This is the only custom code written for this feature.

Synchronization between views

We use the Listener/Observer pattern to synchronize the various views in Qualyzer. For example, if a fragment is coded in a transcript, the code editor is refreshed to update the code count. See the section Where is The Model for more information about the model and its listeners.

Each view or editor that needs to be updated when the model changes needs to implement the appropriate listener class and needs to register to the ListenerManager. Each view/editor registered as a listener must unregister itself when it is disposed (usually in the dispose() method).

A view or editor never explicitly notifies other listeners: when the model is updated through the Facade, the Facade notifies the appropriate listeners.

For an example of view synchronization, see the ColorEditorPage class.

Split into Plug-ins, Features, and Products

Qualyzer is a RCP application and the code is split into many modules called plug-ins and features:

  • ca.mcgill.cs.swevo.qualyzer: This plug-in contains all the code of Qualyzer, the splash screen and the intro files (displayed for first-time users).
  • ca.mcgill.cs.swevo.qualyzer.dependencies: This feature indicates which plug-ins outside of the Eclipse ecosystem are required by Qualyzer. For example, the org.hibernate plug-in is referenced here. Each dependency is packaged in its own plug-in, and is in the source repository.
  • ca.mcgill.cs.swevo.qualyzer.feature: This feature refers to the qualyzer plug-in. It also contains a product definition file, qualyzer.product, that describes the main features composing Qualyzer.
  • ca.mcgill.cs.swevo.qualyzer.logging: This feature packages three logging libraries (log4j, slf4j, and Apache commons logging) and contains the configuration for these logging frameworks in the src folder. slf4j is used by Hibernate (*Bart: I think*).
  • ca.mcgill.cs.swevo.qualyzer.rcp_feature: This feature refers to all the Eclipse plug-ins that are needed to run Qualyzer. It also indicates that the org.eclipse.rcp feature should be included. When a new release is built, this feature ensures that all the necessary Eclipse plug-ins are added in the release.
  • com.h2database: This plug-in packages the database library we use to persist the model.
  • net.javazoom.BasicPLayer: This plug-in packages the music player we use to play mp3 and wave files (audio recording of an interview).
  • net.sf.colorer: This plug-in packages the colorer editor. The colorer editor is a mix of Java files and native files. This is the text editor backend we use because it provides automatic word wrap at the end of each line, a feature currently not supported by Eclipse.
  • org.hibernate: This plug-in packages the hibernate library we use to automatically map our model to a relational database.
  • qualyzer.bitbucket.org: This project found in our source repository contains the source of the qualyzer.org website. This project is not distributed with Qualyzer.

How to ...

Test a dialog, a wizard, a view, or an editor

One the main problem of testing dialogs and wizards is that they are modal, which means that once they request the user input, it is very difficult to programmatically provide this input. We thus created our own Testing Framework.

The main idea is that for each command (e.g., creating a new investigator), the handler associated with the command implements the ITestableHandler and uses a few low-level patterns described in the testing framework wiki page. For example, after opening a dialog, we always pass it to a IDialogTester instance.

Add a new model class

To add a new model class that needs to be persisted (e.g., a Reviewer), follow these steps:

  1. Create a class in the package qualyzer.model.
  2. The class must be annotated with @javax.persistence.Entity
  3. This class must have a field named private Long fPersistenceId.
  4. Each field that needs to be persisted needs a corresponding getter and setter (even the fPersistenceId). Look at existing model classes for examples.
  5. Consult the mapping section of the Hibernate documentation for more information on how to annotated the getters. Most getters don't need to be annotated. Referring to other model elements require careful thoughts. Look at the existing model classes for examples on how to do it.
  6. Add a reference to the new class in qualyzer.model.HibernateDBManager.HibernateDBManager(...).
  7. Start Qualyzer with an existing project. Test that the automated schema conversion does not corrupt the project.

Create a new release/build

We have a detailed procedure to build a new release. We currently support Windows, MacOS and Linux. Support for Mac is difficult because Apple no longer ships Java by default (it must be downloaded by the user) and the graphic libraries vary depending on the version of the OS.

We follow three steps to build a new release:

  1. Using the Eclipse Product / Overview / Export wizard, we build a set of releases for all the platforms. This can be launched from any OS as long as you have installed the Delta pack (described in our detailed procedure).
  2. We further package the windows build into an installation file (e.g., qualyzer.msi). This is donw using WarSetup.
  3. Finally, we upload all the files to SourceForge.

Making a release is costly because we try to manually test all the packages before uploading them (we do a quick manual testing to see that we can at least open Qualyzer and create a transcript). In the past, the main problem we encountered was that the colorer editor (used by the transcript and memo editors) could not be loaded.

Tricky Modules

This section describes modules that should be very carefully modified because we consider them brittle or relying on some black magic. Eventually, the list of tricky modules should be shortened by relying on extensive testing, but some modules are inherently tricky and no amount of testing will make it easy to modify them.

The Code Editor

This editor is tricky from a usability perspective. Any change to this view must be carefully considered to avoid feature creep. Most researchers have their own way of looking at the data so it could be very easy to add 100 columns to the tables and the trees in this view.

The only tricky implementation detail is how the hierarchy of codes is saved and loaded: take a look at qualyzer.providers.TreeModel to see how this is done.

The RTF Parser

The RTF parser is tricky because the specification is surprisingly complex and text editors implement it in a very different way. With time, we hope to build a collection of RTF documents that we can automatically test against to make sure that the parser is not broken after a change.

The performance of the RTF parser is also important because it is not uncommon to open large documents (several megs). For example, the use of string literals concatenation is strickly forbidden ("abc" + "def") and StringBuilder should always be used.

The transcript/memo editor

These editors are tricky because they rely on the colorer editor to provide automated word wrapping. The directory structure of the net.sf.colorer plug-in is very sensitive and should not be changed.

Because the way annotations work in Eclipse, it is extremely difficult to make overlapping annotations or to leave an annotation "open" (e.g., all the text that the user will enter will be in bold). These are features requested by some users and as of now, we are not sure how to implement them.

Relations in the Persistence Layer

The Hibernate framework makes it easy to automatically map an object model to a relational database, but relations between model classes can be particularly difficult to configure. It seems there is always a parameter that needs to be added somewhere. Carefully testing on both ends of the relation is required when adding a relation. And plan for a few hours, just in case.

Updated

Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.