Java Code Style

Last modified by Thomas Mortagne on 2023/12/06 15:53

The XWiki project is following a specific coding style for Java code. We're using Checkstyle (checkstyle.xml) to ensure compliance of the code. Our build (the Maven one) is configured to fail on violations. This is part of the automated checks, see there for ways to skip if necessary at times. However the decision to follow this code style and enforce it was only made long after the beginning of the project and not all the code base has been moved to this new code style. Hence:

  • We're only enforcing the code style in the code that has been moved to the new code style. The checked files are defined in xwiki/core/pom.xml (bottom of file).
  • We're asking new code to follow the new style and then once a Java file is compliant, to edit xwiki/core/pom.xml and add it there so that we cannot regress...

For examples of "clean" class see the following example and its unit tests:

Configuring your IDE to use the XWiki code style

Eclipse

Download codestyle-eclipse-java.xml.

After this, select Window > Preferences, and open up the configuration for Java > Code Style > Code Formatter. Click on the button labeled Import... and select the file you downloaded. Give the style a name, and click OK.

To reformat a file, press Ctrl+Shift+F while inside that file. To format only a portion of the file, select it and press the same key combination.

Download codetemplates-eclipse.xml.

After this, select Window > Preferences, and open up the configuration for Java > Code Style > Code Templates. Click on the button labeled Import... and select the file you downloaded. You can enable "Automatically add comments for new methods and types" if you want.

To generate a javadoc, press Meta+Shift+J while on the element you want to document.

IntelliJ IDEA

  • Set up IntelliJ IDEA code styles:
  • Download codestyle-idea.xml.
  • Go to IntelliJ's File > Settings and to Editor > Code Style and select Import Scheme > IntelliJ IDEA Code Style XML as shown on

    intellij-idea-import-styles.png

  • Set up File Templates (used when creating a new Java class, Interface, etc):

    Download idea-fileTemplates-xwiki.tar.gz.

    Close IntelliJ IDEA. Ungzip and untar the file in the configuration directory (or copy unzipped "fileTemplates" directory in the following location):

    • For Mac: in ~/Library/Application Support/JetBrains/<IDEA VERSION>
    • For Linux: in /.config/JetBrains/<IDEA VERSION>
    • For Windows: in C:\Users\<username>\AppData\Roaming\JetBrains\<IDEA VERSION>
  • If codestyle is not imported automatically, go to Other Settings > Default Settings > Java codestyle > Set from (XML) > select file downloaded above.
  • Restart Intellij IDEA.

It's recommended to add the CheckStyle and SolarLint plugins in your IntelliJ IDEA instance.

Interface best practices

Do not use 'public' in interfaces

Public is always implied in interfaces. Do not write:

public interface Page
{
   public String getParentSpaceKey();
}

But instead, write

public interface Page
{
    String getParentSpaceKey();
}

Make sure your code is backward compatible

Adding a new method to a public interface is a breaking change and your build will fail with error:

  Method was added to an Interface.

But it's possible to make a new interface method backward compatible by providing a default implementation (using the default keyword as in):

@Unstable
default MethodType myNewMethod()
{
 return myOtherMethod();
}

Javadoc Best Practices

We are following the Oracle Javadoc coding conventions. Please make sure you're familiar with them when you write javadoc.

Write useful comments

Do not repeat the name of the method or any useless information. For example, if you have:

/**
 * @return the id
 */

public String getId()
{
   return this.id;
}

Instead, write:

/**
 * @return the attachment id (the id is the filename of the XWikiAttachment object used to construct this Attachment object)
 */

public String getId()
{
   return this.id;
}

Do not duplicate Javadoc

If you inherit from an interface/class then you shouldn't copy the Javadoc from the super type. Instead you should reference it or add more fine-tuned explanations. For example if getSomething() is the implementation of a method defined in an inherited Something interface or parent class, you shouldn't write:

/**
 * Do something blah blah.
 */

public void doSomething()
{

[...]

Instead, write either the following:

/**
 * {@inheritDoc}
 *
 * <p>Optionally add here javadoc additional to the one inherited from the parent javadoc.</p>
 */

@Override
public void doSomething()
{
[...]

or (it you don't have anything else to add in the javadoc):

@Override
public void doSomething()
{
[...]

Don't forget the @Override annotation!

Do not duplicate method comments with parameters comments

Instead of writing:

/**
 * Returns the key of the space to which this page belongs to.
 *
 * @return - Parent space's key as String.
 */

public String getParentSpaceKey();

Write:

/**
 * @return the key of the space to which this page belongs to. For example "Main".
 */

public String getParentSpaceKey();

Use version and since javadoc tags

For example:

/**
 * Something, blah blah...
 *
 * @version $Id$
 * @since 16.0.0RC1
 */

Do not use author javadoc tags! We don't have code ownership in XWiki. Everyone can modify any portion of code and all committers support all code.

Use one @since per version

When introducing a class or a new method on several branches, multiple @since annotations must be used to mention the different versions in which this element has been added.

For example:

[...]
* @since 7.4.5
* @since 8.2.2
* @since 16.0.0RC1
*/

(and not @since 7.4.5, 8.2.2, 16.0.0RC1)

Use the right format for examples

If you need to use example and have what you past be not modified, use the {@code}} construct, as in:

 * <pre>{@code
* <plugin>
*   <groupId>org.xwiki.platform</groupId>
*   <artifactId>xwiki-platform-tool-provision-plugin</artifactId>
*   <version>...version...</version>
*   <configuration>
*     <username>Admin</username>
*     <password>admin</password>
*     <extensionIds>
*       <extensionId>
*         <id>org.xwiki.contrib.markdown:syntax-markdown-markdown12</id>
*         <version>8.5.1</version>
*       </extensionId>
*     </extensionIds>
*   </configuration>
*   <executions>
*     <execution>
*       <id>install</id>
*       <goals>
*         <goal>install</goal>
*       </goals>
*     </execution>
*   </executions>
* </plugin>
* }</pre>

There's currently a bug in Checkstyle that forces us to escape the < as in:

* <pre><code>
 * &#60;plugin&#62;
 *   &#60;groupId&#62;org.xwiki.platform&#60;/groupId&#62;
 *   &#60;artifactId&#62;xwiki-platform-tool-provision-plugin&#60;/artifactId&#62;
 *   &#60;version&#62;...version...&#60;/version&#62;
 *   &#60;configuration&#62;
 *     &#60;username&#62;Admin&#60;/username&#62;
 *     &#60;password&#62;admin&#60;/password&#62;
 *     &#60;extensionIds&#62;
 *       &#60;extensionId&#62;
 *         &#60;id&#62;org.xwiki.contrib.markdown:syntax-markdown-markdown12&#60;/id&#62;
 *         &#60;version&#62;8.5.1&#60;/version&#62;
 *       &#60;/extensionId&#62;
 *     &#60;/extensionIds&#62;
 *   &#60;/configuration&#62;
 *   &#60;executions&#62;
 *     &#60;execution&#62;
 *       &#60;id&#62;install&#60;/id&#62;
 *       &#60;goals&#62;
 *         &#60;goal&#62;install&#60;/goal&#62;
 *       &#60;/goals&#62;
 *     &#60;/execution&#62;
 *   &#60;/executions&#62;
 * &#60;/plugin&#62;
 * </code></pre>

Trailing Whitespace

Trailing whitespace is prohibited except for one case.
In empty lines in a javadoc comment, a single trailing space character is acceptable but not required.

/**
 * The Constructor.
 *
 * $param something...
 */

The trailing whitespace in the center line in that comment is permissible. See this proposal for more information.

Class/Interface names

  • Prefix class names with Abstract for abstract classes
  • Class names should start with an uppercase letter
  • The interface name should be as short and expressive as possible with no technical prefix or suffix. For example "Parser".
    • As a consequence interfaces shouldn't be prefixed with "I" (as in "IParser") or suffixed with "Interface" (as in "ParserInterface"), nor suffixed with "IF" (as in "ParserIF).
  • Classes implementing interfaces should extend the interface name by prefixing it with a characteristic of the implementation. For example "XWikiParser".
    • As a consequence implementation classes shouldn't be suffixed with "Impl", "Implementation", etc.
  • Default implementation classes where there's only one implementation provided by XWiki should be prefixed with "Default". As in "DefaultParser".

Members and fields names

  • All methods and fields names should be camelCase, starting with a lower letter (someProperty, getSomeProperty())
  • The names should be understandable and short, avoiding abbreviations (parentDocument instead of pdoc, for example)
  • Constants should all be uppercase, with underscores as word separators, and no prefix letter (WIKI_PAGE_CREATOR instead of WIKIPAGECREATOR)
  • Constants should be public/private static final in classes (public static final String CONTEXT_KEY = "theKey") and without any modifiers in interfaces, since public, static and final are implied and enforced (String PREFERENCES_DOCUMENT_NAME = "XWiki.XWikiPreferences")

Package names

  • All code that is not located in the oldcore module should use org.xwiki.
  • The package name for code using the component-based architecture must be of the format org.xwiki.(module name).*. For example org.xwiki.rendering.
  • Non user-public code must be located in an internal package just after the module name. For example: org.xwiki.rendering.internal.parser.. General rule is org.xwiki.(module name).internal.
  • Script Services component implementations should be located in a script package and in a non-internal package. This is because they are considered API and we wish to have our backward-compatibility tool report any breakage (and also so that they are included in the generated Javadoc). If you still need to expose a script service in an internal package the class name should end with InternalScriptService to not fail the build rule.

Logging Best Practices

  • Use SLF4J. Specifically, if your code is in a Component it must get a Logger using the following construct:
    import org.slf4j.Logger;
    ...
    @Inject
    private Logger logger;

    If not inside a Component, a Logger can be retrieve through:

    import org.slf4j.Logger;
    ...
    private static final Logger LOGGER = LoggerFactory.getLogger(MyClass.class);
  • You should use the severity according to the following rules:
    • info: To be used when it’s absolutely necessary for the user to see the message in the logs. Usually only used at startup and we want to limit what the user sees to the absolute necessary to avoid swamping him. These logs are shown by default (with the default XWiki logging configuration).
    • debug: Logs that are informational but that shouldn't be printed by default. Logging configuration needs to be updated to show these logs.
    • warning: To be used when an error happens but it doesn’t compromise the stability and general working of an XWiki instance. A warning just shows the user that something has gone wrong and it should provide him with as much information as possible to solve the issue. Do not print a stack trace when you output a warning since stack traces fill the logs and should be reserved for errors. In the view of users stack traces are synonymous with errors. We want it to be easy for admins to visually check the log files and see important problems (i.e. errors). In order to display the root cause without displaying a full stack trace, use org.apache.commons.lang3.exception.ExceptionUtils. Instead of writing:
      LOGGER.warn("Failed to determine if the index exists: [{}]. Trying to recreate the index..", e.getMessage()); 

      write instead:

      LOGGER.warn("Failed to determine if the index exists: [{}]. Trying to recreate the index..", ExceptionUtils.getRootCauseMessage(e)); 

      These logs are shown by default (with the default XWiki logging configuration).

    • error: To be used when there’s an important problem that compromises the stability of the XWiki instance or that prevent an important system from working and that should not have happened. Always pass a stack trace when logging errors since it’s something that shouldn’t have happened an a developer will need to debug the problem to fix it. These logs are shown by default (with the default XWiki logging configuration).
  • If you need to perform computations for passing parameters to the logging calls you should instead use the appropriate SLF4J signature to not incur performance penalty. For example:
    this.logger.debug("Test message with [{}] and [{}]", param1, param2);

    Do NOT write:

    this.logger.debug("Test message with [" + param1 + "] and [" + param2 + "]", param1, param2); 
  • Always log as much information as possible to make it easier to understand what’s going on. 
  • Surround parameters with [] in order to separate visually the text from the parameters and also clearly notice when leading/trailing spaces are located in parameters. 

Imports

  • imports should be added individually for each class/interface
  • individual imports should be grouped together and separated by blank lines following this model:
    import java.*

    import javax.*

    import org.*

    import com.*

    import <any other imports>

    import static <any static imports>
  • The above code style settings for IntelliJ IDEA will automatically follow these rules, so you usually do not have to take care (Be careful that the default configuration for IntelliJ IDEA is not appropriate). For Eclipse you should import eclipse.importorder.

Equals/HashCode and ToString implementations

We've decided to standardize on using Apache Commons Lang HashCodeBuilder, EqualsBuilder and ToStringBuilder.

For example:

...
   @Override
   public boolean equals(Object object)
   {
       if (object == null) {
           return false;
       }
       if (object == this) {
           return true;
       }
       if (object.getClass() != getClass()) {
           return false;
       }
        WikiBotListenerData rhs = (WikiBotListenerData) object;
       return new EqualsBuilder()
           .appendSuper(super.equals(object))
           .append(getReference(), rhs.getReference())
           .isEquals();
   }

   @Override
   public int hashCode()
   {
       return new HashCodeBuilder(3, 17)
           .appendSuper(super.hashCode())
           .append(getReference())
           .toHashCode();
   }
...

XWiki provides a custom ToStringBuilder implementation named XWikiToStringBuilder that uses a custom XWiki's toString style (see the Text Module for information).

For example:

...
   @Override
   public String toString()
   {
        ToStringBuilder builder = new XWikiToStringBuilder(this);
        builder = builder.append("Typed", isTyped())
           .append("Type", getType().getScheme());

       if (getReference() != null) {
            builder = builder.append("Reference", getReference());
       }

       if (!getBaseReferences().isEmpty()) {
            builder = builder.append("Base References", getBaseReferences());
       }

        Map<String, String> params = getParameters();
       if (!params.isEmpty()) {
            builder = builder.append("Parameters", params);
       }

       return builder.toString();
   }
...

This example would generate the following:

...
ResourceReference reference = new ResourceReference("reference", ResourceType.DOCUMENT);
Assert.assertEquals("Typed = [true] Type = [doc] Reference = [reference]", reference.toString());

reference.addBaseReference("baseref1");
reference.addBaseReference("baseref2");
Assert.assertEquals("Typed = [true] Type = [doc] Reference = [reference] "
   + "Base References = [[baseref1], [baseref2]]", reference.toString());

reference.setParameter("name1", "value1");
reference.setParameter("name2", "value2");
Assert.assertEquals("Typed = [true] Type = [doc] Reference = [reference] "
   + "Base References = [[baseref1], [baseref2]] "
   + "Parameters = [[name1] = [value1], [name2] = [value2]]", reference.toString());
...

Test classes

We often write sometimes complex tools in test classes but those should never be used in main code. While Maven accept it technically it's not the case of Eclipse for example.

If they really are needed then it's a sign that they should probably move to main or that the test tool manipulating those classes should itself have its classes locate in test (see xwiki-platform-test-page for an example for this use case).

Script Services

See Best practices for the Script Module.

Deprecation

XWiki 14.0+

  • Always use both the @Deprecated annotation and the @deprecated javadoc tag.
  • In the @deprecated javadoc tag, always specify WHY it’s deprecated and WHAT should be used instead.
  • In the @Deprecated annotation always use the since parameter to specify WHEN it's been deprecated, and don't specify forRemoval we don't break APIs in XWiki and the default value is false.
  • Don't specify the since versions in the @deprecated javadoc tag as it would be a duplication from the info in the @Deprecated annotation, and the javadoc tool displays the content of the @Deprecated annotation in the javadoc.
  • If the deprecation is done in several branches, the since parameter should use a comma-separated list of all versions in which the deprecation has been done. For example:
    @Deprecated(since = "15.5RC1,14.10.12")

Example:

public class Worker
{
   /**
     * Calculate period between versions.
     *
     * @param machine the instance
     * @return the computed time
     * @deprecated This method is no longer acceptable to compute time between versions because... Use {@link Utils#calculatePeriod(Machine)} instead.
     */

   @Deprecated(since = "4.5")
   public int calculate(Machine machine)
   {
       return machine.exportVersions().size() * 10;
   }
}

Tags:
   

Get Connected