Wiki source code of Java Code Style

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

Show last authors
1 {{box cssClass="floatinginfobox" title="**Contents**"}}
2 {{toc/}}
3 {{/box}}
4
5 The XWiki project is following a specific coding style for Java code. We're using [[Checkstyle>>http://checkstyle.sourceforge.net/]] ([[checkstyle.xml>>https://raw.github.com/xwiki/xwiki-commons/master/xwiki-commons-tools/xwiki-commons-tool-verification-resources/src/main/resources/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>>https://dev.xwiki.org/xwiki/bin/view/Community/Building/#HAutomaticChecks]], 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:
6
7 * 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).
8 * 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...
9
10 For examples of "clean" class see the following example and its unit tests:
11
12 * DefaultObservationManager.java ([[main sources>>https://github.com/xwiki/xwiki-commons/blob/master/xwiki-commons-core/xwiki-commons-observation/xwiki-commons-observation-local/src/main/java/org/xwiki/observation/internal/DefaultObservationManager.java]]|[[tests>>https://github.com/xwiki/xwiki-commons/blob/master/xwiki-commons-core/xwiki-commons-observation/xwiki-commons-observation-local/src/test/java/org/xwiki/observation/ObservationManagerTest.java]])
13
14 = Configuring your IDE to use the XWiki code style =
15
16 == Eclipse ==
17
18 Download [[codestyle-eclipse-java.xml>>https://raw.github.com/xwiki/xwiki-commons/master/xwiki-commons-tools/xwiki-commons-tool-verification-resources/src/main/resources/codestyle-eclipse-java.xml]].
19
20 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.
21
22 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.
23
24 Download [[codetemplates-eclipse.xml>>https://raw.github.com/xwiki/xwiki-commons/master/xwiki-commons-tools/xwiki-commons-tool-verification-resources/src/main/resources/codetemplates-eclipse.xml]].
25
26 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.
27
28 To generate a javadoc, press ##Meta+Shift+J## while on the element you want to document.
29
30 == IntelliJ IDEA ==
31
32 * Set up IntelliJ IDEA code styles:
33 * Download [[codestyle-idea.xml>>https://raw.github.com/xwiki/xwiki-commons/master/xwiki-commons-tools/xwiki-commons-tool-verification-resources/src/main/resources/codestyle-idea.xml]].
34 * Go to IntelliJ's ##File > Settings## and to ##Editor > Code Style## and select ##Import Scheme > IntelliJ IDEA Code Style XML## as shown on(((
35 {{image reference="intellij-idea-import-styles.png" width="350px"/}}
36 )))
37 * Set up File Templates (used when creating a new Java class, Interface, etc):(((
38 Download [[idea-fileTemplates-xwiki.tar.gz>>attach:idea-fileTemplates-xwiki.tar.gz]].
39
40 Close IntelliJ IDEA. Ungzip and untar the file in the [[configuration directory>>https://www.jetbrains.com/help/idea/directories-used-by-the-ide-to-store-settings-caches-plugins-and-logs.html#config-directory]] (or copy unzipped "fileTemplates" directory in the following location):
41
42 * For Mac: in ##~~/Library/Application Support/JetBrains/<IDEA VERSION>##
43 * For Linux: in ##/.config/JetBrains/<IDEA VERSION>##
44 * For Windows: in ##C:\Users\<username>\AppData\Roaming\JetBrains\<IDEA VERSION>##
45 )))
46 * If codestyle is not imported automatically, go to ##Other Settings > Default Settings > Java codestyle > Set from (XML) > select file downloaded above.##
47 * Restart Intellij IDEA.
48
49 {{info}}
50 It's recommended to add the CheckStyle and SolarLint plugins in your IntelliJ IDEA instance.
51 {{/info}}
52
53 = Interface best practices =
54
55 == Do not use 'public' in interfaces ==
56
57 Public is always implied in interfaces. Do not write:
58
59 {{code}}
60 public interface Page
61 {
62 public String getParentSpaceKey();
63 }
64 {{/code}}
65
66 But instead, write
67
68 {{code}}
69 public interface Page
70 {
71 String getParentSpaceKey();
72 }
73 {{/code}}
74
75 == Make sure your code is backward compatible ==
76
77 Adding a new method to a public interface is a breaking change and your build will fail with error:
78
79 {{error}}
80 Method was added to an Interface.
81 {{/error}}
82
83 But it's possible to make a new interface method backward compatible by providing a default implementation (using the ##default## keyword as in):
84
85 {{code}}
86 @Unstable
87 default MethodType myNewMethod()
88 {
89 return myOtherMethod();
90 }
91 {{/code}}
92
93 = Javadoc Best Practices =
94
95 We are following the [[Oracle Javadoc coding conventions>>http://www.oracle.com/technetwork/java/javase/documentation/index-137868.html#styleguide]]. Please make sure you're familiar with them when you write javadoc.
96
97 == Write useful comments ==
98
99 Do not repeat the name of the method or any useless information. For example, if you have:
100
101 {{code language="java"}}
102 /**
103 * @return the id
104 */
105 public String getId()
106 {
107 return this.id;
108 }
109 {{/code}}
110
111 Instead, write:
112
113 {{code language="java"}}
114 /**
115 * @return the attachment id (the id is the filename of the XWikiAttachment object used to construct this Attachment object)
116 */
117 public String getId()
118 {
119 return this.id;
120 }
121 {{/code}}
122
123 == Do not duplicate Javadoc ==
124
125 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:
126
127 {{code language="java"}}
128 /**
129 * Do something blah blah.
130 */
131 public void doSomething()
132 {
133
134 [...]
135 {{/code}}
136
137 Instead, write either the following:
138
139 {{code language="java"}}
140 /**
141 * {@inheritDoc}
142 *
143 * <p>Optionally add here javadoc additional to the one inherited from the parent javadoc.</p>
144 */
145 @Override
146 public void doSomething()
147 {
148 [...]
149 {{/code}}
150
151 or (it you don't have anything else to add in the javadoc):
152
153 {{code language="java"}}
154 @Override
155 public void doSomething()
156 {
157 [...]
158 {{/code}}
159
160 {{warning}}
161 Don't forget the {{{@Override}}} annotation!
162 {{/warning}}
163
164 == Do not duplicate method comments with parameters comments ==
165
166 Instead of writing:
167
168 {{code language="java"}}
169 /**
170 * Returns the key of the space to which this page belongs to.
171 *
172 * @return - Parent space's key as String.
173 */
174 public String getParentSpaceKey();
175 {{/code}}
176
177 Write:
178
179 {{code language="java"}}
180 /**
181 * @return the key of the space to which this page belongs to. For example "Main".
182 */
183 public String getParentSpaceKey();
184 {{/code}}
185
186 == Use version and since javadoc tags ==
187
188 For example:
189
190 {{code language="java"}}
191 /**
192 * Something, blah blah...
193 *
194 * @version $Id$
195 * @since 16.0.0RC1
196 */
197 {{/code}}
198
199 {{warning}}
200 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.
201 {{/warning}}
202
203 == Use one @since per version ==
204
205 When introducing a class or a new method on several branches, multiple {{code language="java"}}@since{{/code}} annotations must be used to mention the different versions in which this element has been added.
206
207 For example:
208
209 {{code language="java"}}
210 [...]
211 * @since 7.4.5
212 * @since 8.2.2
213 * @since 16.0.0RC1
214 */
215 {{/code}}
216
217 (and not {{code language="java"}}@since 7.4.5, 8.2.2, 16.0.0RC1{{/code}})
218
219 == Use the right format for examples ==
220
221 If you need to use example and have what you past be not modified, use the ##{@code}}## construct, as in:
222
223 {{code language="java"}}
224 * <pre>{@code
225 * <plugin>
226 * <groupId>org.xwiki.platform</groupId>
227 * <artifactId>xwiki-platform-tool-provision-plugin</artifactId>
228 * <version>...version...</version>
229 * <configuration>
230 * <username>Admin</username>
231 * <password>admin</password>
232 * <extensionIds>
233 * <extensionId>
234 * <id>org.xwiki.contrib.markdown:syntax-markdown-markdown12</id>
235 * <version>8.5.1</version>
236 * </extensionId>
237 * </extensionIds>
238 * </configuration>
239 * <executions>
240 * <execution>
241 * <id>install</id>
242 * <goals>
243 * <goal>install</goal>
244 * </goals>
245 * </execution>
246 * </executions>
247 * </plugin>
248 * }</pre>
249 {{/code}}
250
251 {{error}}
252 There's currently a [[bug in Checkstyle>>https://github.com/checkstyle/checkstyle/issues/5482]] that forces us to escape the ##<## as in:
253
254 {{code language="none"}}
255 * <pre><code>
256 * &#60;plugin&#62;
257 * &#60;groupId&#62;org.xwiki.platform&#60;/groupId&#62;
258 * &#60;artifactId&#62;xwiki-platform-tool-provision-plugin&#60;/artifactId&#62;
259 * &#60;version&#62;...version...&#60;/version&#62;
260 * &#60;configuration&#62;
261 * &#60;username&#62;Admin&#60;/username&#62;
262 * &#60;password&#62;admin&#60;/password&#62;
263 * &#60;extensionIds&#62;
264 * &#60;extensionId&#62;
265 * &#60;id&#62;org.xwiki.contrib.markdown:syntax-markdown-markdown12&#60;/id&#62;
266 * &#60;version&#62;8.5.1&#60;/version&#62;
267 * &#60;/extensionId&#62;
268 * &#60;/extensionIds&#62;
269 * &#60;/configuration&#62;
270 * &#60;executions&#62;
271 * &#60;execution&#62;
272 * &#60;id&#62;install&#60;/id&#62;
273 * &#60;goals&#62;
274 * &#60;goal&#62;install&#60;/goal&#62;
275 * &#60;/goals&#62;
276 * &#60;/execution&#62;
277 * &#60;/executions&#62;
278 * &#60;/plugin&#62;
279 * </code></pre>
280 {{/code}}
281 {{/error}}
282
283 = Trailing Whitespace =
284
285 Trailing whitespace is prohibited except for one case.
286 In empty lines in a javadoc comment, a single trailing space character is acceptable but not required.
287
288 {{code language="java"}}
289 /**
290 * The Constructor.
291 *
292 * $param something...
293 */
294 {{/code}}
295
296 The trailing whitespace in the center line in that comment is permissible. See [[this proposal>>http://lists.xwiki.org/pipermail/devs/2010-October/020439.html]] for more information.
297
298 = Class/Interface names =
299
300 * Prefix class names with ##Abstract## for abstract classes
301 * Class names should start with an uppercase letter
302 * The interface name should be as short and expressive as possible with no technical prefix or suffix. For example "Parser".
303 ** 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).
304 * Classes implementing interfaces should extend the interface name by prefixing it with a characteristic of the implementation. For example "XWikiParser".
305 ** As a consequence implementation classes shouldn't be suffixed with "Impl", "Implementation", etc.
306 * Default implementation classes where there's only one implementation provided by XWiki should be prefixed with "Default". As in "DefaultParser".
307
308 = Members and fields names =
309
310 * All methods and fields names should be camelCase, starting with a lower letter (##someProperty##, ##getSomeProperty()##)
311 * The names should be understandable and short, avoiding abbreviations (##parentDocument## instead of ##pdoc##, for example)
312 * Constants should all be uppercase, with underscores as word separators, and no prefix letter (##WIKI_PAGE_CREATOR## instead of ##WIKIPAGECREATOR##)
313 * 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"##)
314
315 = Package names =
316
317 * All code that is not located in the oldcore module should use ##org.xwiki##.
318 * The package name for code using the component-based architecture must be of the format ##org.xwiki.(module name).*##. For example ##org.xwiki.rendering##.
319 * 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.##**
320 * [[Script Services>>extensions:Extension.Script Module]] 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.
321
322 = Logging Best Practices =
323
324 * Use SLF4J. Specifically, if your code is in a Component it must get a Logger using the following construct:(((
325 {{code language="java"}}
326 import org.slf4j.Logger;
327 ...
328 @Inject
329 private Logger logger;
330 {{/code}}
331
332 If not inside a Component, a Logger can be retrieve through:
333
334 {{code language="java"}}
335 import org.slf4j.Logger;
336 ...
337 private static final Logger LOGGER = LoggerFactory.getLogger(MyClass.class);
338 {{/code}}
339 )))
340 * You should use the severity according to the following rules:
341 ** **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).
342 ** **debug**: Logs that are informational but that shouldn't be printed by default. Logging configuration needs to be updated to show these logs.
343 ** **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:(((
344 {{code language="java"}}
345 LOGGER.warn("Failed to determine if the index exists: [{}]. Trying to recreate the index..", e.getMessage());
346 {{/code}}
347
348 write instead:
349
350 {{code language="java"}}
351 LOGGER.warn("Failed to determine if the index exists: [{}]. Trying to recreate the index..", ExceptionUtils.getRootCauseMessage(e));
352 {{/code}}
353
354 These logs are shown by default (with the default XWiki logging configuration).
355 )))
356 ** **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).
357 * 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>>http://www.slf4j.org/faq.html#logging_performance]]. For example:(((
358 {{code language="java"}}
359 this.logger.debug("Test message with [{}] and [{}]", param1, param2);
360 {{/code}}
361
362 Do NOT write:
363
364 {{code language="java"}}
365 this.logger.debug("Test message with [" + param1 + "] and [" + param2 + "]", param1, param2);
366 {{/code}}
367 )))
368 * Always log as much information as possible to make it easier to understand what’s going on.
369 * 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.
370
371 = Imports =
372
373 * imports should be added individually for each class/interface
374 * individual imports should be grouped together and separated by blank lines following this model:(((
375 {{code language="java"}}
376 import java.*
377
378 import javax.*
379
380 import org.*
381
382 import com.*
383
384 import <any other imports>
385
386 import static <any static imports>
387 {{/code}}
388 )))
389 * 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>>https://github.com/xwiki/xwiki-commons/blob/master/xwiki-commons-tools/xwiki-commons-tool-verification-resources/src/main/resources/eclipse.importorder]].
390
391 = Equals/HashCode and ToString implementations =
392
393 We've decided to standardize on using Apache Commons Lang [[HashCodeBuilder>>http://commons.apache.org/proper/commons-lang//apidocs/org/apache/commons/lang3/builder/HashCodeBuilder.html]], [[EqualsBuilder>>http://commons.apache.org/proper/commons-lang//apidocs/org/apache/commons/lang3/builder/EqualsBuilder.html]] and [[ToStringBuilder>>http://commons.apache.org/proper/commons-lang//apidocs/org/apache/commons/lang3/builder/ToStringBuilder.html]].
394
395 For example:
396
397 {{code language="java"}}
398 ...
399 @Override
400 public boolean equals(Object object)
401 {
402 if (object == null) {
403 return false;
404 }
405 if (object == this) {
406 return true;
407 }
408 if (object.getClass() != getClass()) {
409 return false;
410 }
411 WikiBotListenerData rhs = (WikiBotListenerData) object;
412 return new EqualsBuilder()
413 .appendSuper(super.equals(object))
414 .append(getReference(), rhs.getReference())
415 .isEquals();
416 }
417
418 @Override
419 public int hashCode()
420 {
421 return new HashCodeBuilder(3, 17)
422 .appendSuper(super.hashCode())
423 .append(getReference())
424 .toHashCode();
425 }
426 ...
427 {{/code}}
428
429 XWiki provides a custom ##ToStringBuilder## implementation named ##XWikiToStringBuilder## that uses a custom XWiki's ##toString## style (see the [[Text Module>>extensions:Extension.Text Module]] for information).
430
431 For example:
432
433 {{code language="java"}}
434 ...
435 @Override
436 public String toString()
437 {
438 ToStringBuilder builder = new XWikiToStringBuilder(this);
439 builder = builder.append("Typed", isTyped())
440 .append("Type", getType().getScheme());
441
442 if (getReference() != null) {
443 builder = builder.append("Reference", getReference());
444 }
445
446 if (!getBaseReferences().isEmpty()) {
447 builder = builder.append("Base References", getBaseReferences());
448 }
449
450 Map<String, String> params = getParameters();
451 if (!params.isEmpty()) {
452 builder = builder.append("Parameters", params);
453 }
454
455 return builder.toString();
456 }
457 ...
458 {{/code}}
459
460 This example would generate the following:
461
462 {{code language="java"}}
463 ...
464 ResourceReference reference = new ResourceReference("reference", ResourceType.DOCUMENT);
465 Assert.assertEquals("Typed = [true] Type = [doc] Reference = [reference]", reference.toString());
466
467 reference.addBaseReference("baseref1");
468 reference.addBaseReference("baseref2");
469 Assert.assertEquals("Typed = [true] Type = [doc] Reference = [reference] "
470 + "Base References = [[baseref1], [baseref2]]", reference.toString());
471
472 reference.setParameter("name1", "value1");
473 reference.setParameter("name2", "value2");
474 Assert.assertEquals("Typed = [true] Type = [doc] Reference = [reference] "
475 + "Base References = [[baseref1], [baseref2]] "
476 + "Parameters = [[name1] = [value1], [name2] = [value2]]", reference.toString());
477 ...
478 {{/code}}
479
480 = Test classes =
481
482 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.
483
484 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).
485
486 = Script Services =
487
488 See [[Best practices for the Script Module>>extensions:Extension.Script Module#HBestPractices]].
489
490 = Deprecation =
491
492 {{version since="14.0RC1"}}
493 * Always use both the ##@Deprecated## annotation and the ##@deprecated## javadoc tag.
494 * In the ##@deprecated## javadoc tag, always specify WHY it’s deprecated and WHAT should be used instead.
495 * 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>>Community.DevelopmentPractices#HBackwardCompatibility]] and the default value is ##false##.
496 * 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.
497 * 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:(((
498 {{code language="java"}}
499 @Deprecated(since = "15.5RC1,14.10.12")
500 {{/code}}
501 )))
502
503 Example:
504
505 {{code language="java"}}
506 public class Worker
507 {
508 /**
509 * Calculate period between versions.
510 *
511 * @param machine the instance
512 * @return the computed time
513 * @deprecated This method is no longer acceptable to compute time between versions because... Use {@link Utils#calculatePeriod(Machine)} instead.
514 */
515 @Deprecated(since = "4.5")
516 public int calculate(Machine machine)
517 {
518 return machine.exportVersions().size() * 10;
519 }
520 }
521 {{/code}}
522 {{/version}}

Get Connected