Bug 553769: [Doclet] add support for Java9 Doclet API

  added support to call the doclet from java9.
  However links are currently only generated correctly JavaDoc files
  created with Java9 or older. Afterwards the format of the HTML files
  changes, resulting in invalid links to documents.

Change-Id: Iaedb5eded7b332965d9d9520a59d4b409f3f0d5c
diff --git a/developers/org.eclipse.ease.helpgenerator/pom.xml b/developers/org.eclipse.ease.helpgenerator/pom.xml
index ca632b4..c11db3f 100644
--- a/developers/org.eclipse.ease.helpgenerator/pom.xml
+++ b/developers/org.eclipse.ease.helpgenerator/pom.xml
@@ -39,18 +39,30 @@
 
 	<dependencies>
 		<dependency>
-			<groupId>com.sun</groupId>
-			<artifactId>tools</artifactId>
-			<scope>system</scope>
-			<version>1.0</version>
-			<systemPath>${java.home}/../lib/tools.jar</systemPath>
-		</dependency>
-
-		<dependency>
 			<groupId>junit</groupId>
 			<artifactId>junit</artifactId>
 			<version>4.12</version>
 			<scope>test</scope>
 		</dependency>
 	</dependencies>
+
+	<profiles>
+		<profile>
+			<id>default-jdk</id>
+			<activation>
+				<file>
+					<exists>${java.home}/../lib/tools.jar</exists>
+				</file>
+			</activation>
+			<dependencies>
+				<dependency>
+					<groupId>com.sun</groupId>
+					<artifactId>tools</artifactId>
+					<scope>system</scope>
+					<version>1.0</version>
+					<systemPath>${java.home}/../lib/tools.jar</systemPath>
+				</dependency>
+			</dependencies>
+		</profile>
+	</profiles>
 </project>
\ No newline at end of file
diff --git a/developers/org.eclipse.ease.helpgenerator/resources/expected_module_org.eclipse.ease.helpgenerator.testproject.module1.html b/developers/org.eclipse.ease.helpgenerator/resources/expected_module_org.eclipse.ease.helpgenerator.testproject.module1.html
new file mode 100644
index 0000000..6f05767
--- /dev/null
+++ b/developers/org.eclipse.ease.helpgenerator/resources/expected_module_org.eclipse.ease.helpgenerator.testproject.module1.html
@@ -0,0 +1,260 @@
+<html>
+<head>
+	<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+	<link rel="stylesheet" type="text/css" href="../../org.eclipse.ease.help/help/css/modules_reference.css" />
+</head>
+<body>
+	<div class="module" title="Valid Test Module Module">
+		<h1>Valid Test Module Module</h1>
+		<p class="description">This is a test module.</p>
+
+	</div>
+
+	<h2>Constants</h2>
+	<table class="constants">
+		<tr>
+			<th>Constant</th>
+			<th>Description</th>
+		</tr>
+		<tr>
+ <td><a id="DEFINED_IN_BASE_CLASS">DEFINED_IN_BASE_CLASS</a></td>
+ <td class="description" data-field="DEFINED_IN_BASE_CLASS">Constant defined in the base class.</td>
+ </tr>
+		<tr>
+ <td><a id="INTERFACE_CONSTANT">INTERFACE_CONSTANT</a></td>
+ <td class="description" data-field="INTERFACE_CONSTANT">Constant defined in InterfaceA.</td>
+ </tr>
+		<tr>
+ <td><a id="OLD_VALUE" class="deprecatedText">OLD_VALUE</a></td>
+ <td>Some old stuff. <div class="warning"><b>Deprecation warning:</b> we do not need this anymore</div></td>
+ </tr>
+		<tr>
+ <td><a id="UNIVERSAL_TRUTH">UNIVERSAL_TRUTH</a></td>
+ <td class="description" data-field="UNIVERSAL_TRUTH">The answer to all your questions. This is really it.</td>
+ </tr>
+	</table>
+
+
+	<h2>Method Overview</h2>
+	<table class="functions">
+		<tr>
+			<th>Method</th>
+			<th>Description</th>
+		</tr>
+		<tr>
+			<td><a href="#abstractMethodToBeOverridden">abstractMethodToBeOverridden</a>()</td>
+			<td>Abstract method with documentation in base class.</td>
+		</tr>
+		<tr>
+			<td><a href="#baseParameters">baseParameters</a>()</td>
+			<td>Method with parameters.</td>
+		</tr>
+		<tr>
+			<td><a href="#interfaceAMethod">interfaceAMethod</a>()</td>
+			<td>Method documented in InterfaceA only.</td>
+		</tr>
+		<tr>
+			<td><a href="#interfaceBMethod">interfaceBMethod</a>()</td>
+			<td>Method B documented in the interface only.</td>
+		</tr>
+		<tr>
+			<td><a href="#methodFromBaseClass">methodFromBaseClass</a>()</td>
+			<td>This method is only defined in the base class.</td>
+		</tr>
+		<tr>
+			<td><a href="#methodToBeOverridden">methodToBeOverridden</a>()</td>
+			<td>Method with documentation in base class.</td>
+		</tr>
+		<tr>
+			<td><a href="#optionalParameters">optionalParameters</a>()</td>
+			<td>Method with optional parameters.</td>
+		</tr>
+		<tr>
+			<td><a href="#pleaseThrow">pleaseThrow</a>()</td>
+			<td>Method that always throws.</td>
+		</tr>
+		<tr>
+			<td><a href="#thisIsAMethodWithALongName">shortName</a>()</td>
+			<td>Alias for <a href="#thisIsAMethodWithALongName">thisIsAMethodWithALongName()</a>.</td>
+		</tr>
+		<tr>
+			<td><a href="#simple">simple</a>()</td>
+			<td>Simple method documentation.</td>
+		</tr>
+		<tr>
+			<td><a href="#thisIsAMethodWithALongName">thisIsAMethodWithALongName</a>()</td>
+			<td>Method with method name alias.</td>
+		</tr>
+		<tr>
+			<td><a href="#thisMethodHasGenericParameters">thisMethodHasGenericParameters</a>()</td>
+			<td>Method that uses generic parameters.</td>
+		</tr>
+	</table>
+
+
+	<h2>Methods</h2>
+	<div class="command" data-method="abstractMethodToBeOverridden">
+		<h3><a id="abstractMethodToBeOverridden">abstractMethodToBeOverridden</a></h3>
+		<p class="synopsis">void abstractMethodToBeOverridden()</p>
+
+		<p class="description">Abstract method with documentation in base class.</p>
+
+
+
+
+
+	</div>
+	<div class="command" data-method="baseParameters">
+		<h3><a id="baseParameters">baseParameters</a></h3>
+		<p class="synopsis">int baseParameters(int a, long b, <a href='https://docs.oracle.com/javase/8/docs/api/java/lang/String.html'>String</a> data)</p>
+
+		<p class="description">Method with parameters.</p>
+
+		<dl class="parameters">
+			<dt>a</dt>
+			<dd class="description" data-parameter="a">integer parameter</dd>
+			<dt>b</dt>
+			<dd class="description" data-parameter="b">parameter of type long</dd>
+			<dt>data</dt>
+			<dd class="description" data-parameter="data">java string</dd>
+		</dl>
+
+		<p class="return">always 0</p>
+
+
+
+	</div>
+	<div class="command" data-method="interfaceAMethod">
+		<h3><a id="interfaceAMethod">interfaceAMethod</a></h3>
+		<p class="synopsis">void interfaceAMethod()</p>
+
+		<p class="description">Method documented in InterfaceA only.</p>
+
+
+
+
+
+	</div>
+	<div class="command" data-method="interfaceBMethod">
+		<h3><a id="interfaceBMethod">interfaceBMethod</a></h3>
+		<p class="synopsis">void interfaceBMethod()</p>
+
+		<p class="description">Method B documented in the interface only.</p>
+
+
+
+
+
+	</div>
+	<div class="command" data-method="methodFromBaseClass">
+		<h3><a id="methodFromBaseClass">methodFromBaseClass</a></h3>
+		<p class="synopsis"><a href='https://docs.oracle.com/javase/8/docs/api/java/lang/String.html'>String</a> methodFromBaseClass()</p>
+
+		<p class="description">This method is only defined in the base class.</p>
+
+
+		<p class="return">nothing of importance</p>
+
+
+
+	</div>
+	<div class="command" data-method="methodToBeOverridden">
+		<h3><a id="methodToBeOverridden">methodToBeOverridden</a></h3>
+		<p class="synopsis">void methodToBeOverridden()</p>
+
+		<p class="description">Method with documentation in base class. Method body is implemented in the derived class.</p>
+
+
+
+
+
+	</div>
+	<div class="command" data-method="optionalParameters">
+		<h3><a id="optionalParameters">optionalParameters</a></h3>
+		<p class="synopsis"><a href='https://docs.oracle.com/javase/8/docs/api/java/lang/String.html'>String</a> optionalParameters(<a href='https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html'>Thread</a> mandatory, <i>[int optionalDefaultsTo1]</i>, <i>[<a href='https://docs.oracle.com/javase/8/docs/api/java/lang/String.html'>String</a> optionalDefaultsToNull]</i>)</p>
+
+		<p class="description">Method with optional parameters.</p>
+
+		<dl class="parameters">
+			<dt>mandatory</dt>
+			<dd class="description" data-parameter="mandatory">Mandatory parameter,</dd>
+			<dt>optionalDefaultsTo1</dt>
+			<dd class="description" data-parameter="optionalDefaultsTo1">simple integer parameter<span class="optional"><b>Optional:</b> defaults to &lt;<i>1</i>&gt;.</span></dd>
+			<dt>optionalDefaultsToNull</dt>
+			<dd class="description" data-parameter="optionalDefaultsToNull">second optional parameter<span class="optional"><b>Optional:</b> defaults to &lt;<i>null</i>&gt;.</span></dd>
+		</dl>
+
+		<p class="return">result of the function</p>
+
+
+		<dl class="examples">
+			<dt>optionalParameters(new Thread(), 22, "nothing")</dt>
+			<dd class="description">first way to call this method           </dd>
+			<dt>optionalParameters(new Thread())</dt>
+			<dd class="description">using default values for parameter 2 and 3           </dd>
+		</dl>
+
+	</div>
+	<div class="command" data-method="pleaseThrow">
+		<h3><a id="pleaseThrow">pleaseThrow</a></h3>
+		<p class="synopsis">void pleaseThrow() <a href='https://docs.oracle.com/javase/8/docs/api/java/io/IOException.html'>IOException</a></p>
+
+		<p class="description">Method that always throws.</p>
+
+
+
+		<dl class="exceptions">
+			<dt><a href='https://docs.oracle.com/javase/8/docs/api/java/io/IOException.html'>IOException</a></dt>
+			<dd class="description" data-exception="java.io.IOException">in any case           </dd>
+		</dl>
+
+
+	</div>
+	<div class="command" data-method="simple">
+		<h3><a id="simple">simple</a></h3>
+		<p class="synopsis">void simple()</p>
+
+		<p class="description">Simple method documentation.</p>
+
+
+
+
+
+	</div>
+	<div class="command" data-method="thisIsAMethodWithALongName">
+		<h3><a id="thisIsAMethodWithALongName">thisIsAMethodWithALongName</a></h3>
+		<p class="synopsis"><a href='https://docs.oracle.com/javase/8/docs/api/java/lang/String.html'>String</a> thisIsAMethodWithALongName()</p>
+
+		<p class="description">Method with method name alias. The documentation to this method is long to make sure it is handled with a line break in the source file. Further it
+ contains some valid HTML tags that need to be processed correctly by the doclet.
+ <p>
+ This is a separate paragraph.
+ </p></p>
+		<p class="synonyms"><em>Alias:</em>
+ shortName()</p>
+
+
+		<p class="return">always <code>null</code></p>
+
+
+
+	</div>
+	<div class="command" data-method="thisMethodHasGenericParameters">
+		<h3><a id="thisMethodHasGenericParameters">thisMethodHasGenericParameters</a></h3>
+		<p class="synopsis"><a href='https://docs.oracle.com/javase/8/docs/api/java/lang/String.html'>String</a> thisMethodHasGenericParameters(<a href='https://docs.oracle.com/javase/8/docs/api/java/util/function/Function.html'>Function</a> myFunctionParameter)</p>
+
+		<p class="description">Method that uses generic parameters.</p>
+
+		<dl class="parameters">
+			<dt>myFunctionParameter</dt>
+			<dd class="description" data-parameter="myFunctionParameter">A function to execute and whose return value will be returned</dd>
+		</dl>
+
+		<p class="return">result of the function</p>
+
+
+
+	</div>
+
+</body>
+</html>
diff --git a/developers/org.eclipse.ease.helpgenerator/resources/org.eclipse.ease.help/help/css/modules_reference.css b/developers/org.eclipse.ease.helpgenerator/resources/org.eclipse.ease.help/help/css/modules_reference.css
new file mode 100644
index 0000000..e12c941
--- /dev/null
+++ b/developers/org.eclipse.ease.helpgenerator/resources/org.eclipse.ease.help/help/css/modules_reference.css
@@ -0,0 +1,93 @@
+body				{ margin-left: 50px; }
+
+a:visited			{ color: blue; }
+h1					{ margin-left: -30px; padding: 10px 20px 10px 20px;
+						text-transform: uppercase; font-family: "Arial";
+ 						background-color: #443685; color: white;
+ 						border: 1px solid #443685; border-radius: 10px; 
+    				}
+h2					{ margin-left: -30px; margin-top: 50px; padding: 5px 0px 5px 10px;
+						font-family: "Arial";
+						background-color: #f7941e;  color: white;
+						border: 1px solid #f7941e; border-radius: 5px;
+					}
+h3					{ margin-top: 50px; padding: 5px 0px 5px 10px;
+						font-family: "Arial";
+						background-color: #f0f0ff; 
+						border-top: 1px solid black; border-bottom: 1px solid black;
+					}
+table				{ border-spacing: 0px; border-collapse: collapse; }
+th					{ background-color: #000000; color: #FFFFFF;
+						text-align: left; vertical-align: top;
+						font-weight: normal; padding: 5px 6px 5px 6px;
+						font-weight: bold; border: 1px solid white; font-family: "Arial";
+					}
+td					{ padding: 5px 10px 5px 20px; border: 1px solid white; vertical-align: top; }
+tr:nth-child(even)	{ background-color: #eeeeee; }
+tr:nth-child(odd)	{ background-color: #cccccc; }
+
+h3 a:after			{ content: "()"}
+.functions a		{ text-decoration: none; }
+.functions a:hover	{ text-decoration: underline; }
+
+ul.dependency		{ list-style-image:url(/help/topic/org.eclipse.ease.help/help/images/module.png); }
+.dependency a		{ text-decoration: none; }
+.dependency a:hover	{ text-decoration: underline; }
+
+.command			{ margin-left: 50px; }
+.command h3			{ margin-left: -50px; }
+.deprecated			{ background-color: #fbfbd5;
+						padding: 0px  0px 20px 20px;
+ 						border: 1px solid #f1d657; border-radius: 5px;
+					}
+.deprecatedText		{ text-decoration: line-through; }
+
+.description  		{   }
+.synopsis  			{ font-style: normal; color: #660000; font-family: "Arial"; font-weight: bold}
+.synonyms  			{  }
+.synonyms em		{ font-weight: bold; font-style: normal; margin-right: 20px; }
+
+dl.parameters::before	{ content: "Parameters:"; font-weight: bold; display: block;}
+dl.parameters dt	{  margin-left: 30px; }
+dl.parameters dd::before	{   content: "... ";}
+dl.parameters dd	{   margin: 0px 0px 5px 60px;}
+.return::before  			{ content: "Returns:"; font-weight: bold; display: block; margin-left: -30px; }
+.return  			{ margin-left: 30px; }
+
+.optional {
+	display: block;
+	margin-left: 1.4em;
+}
+
+dl.exceptions::before	{ content: "Exceptions:"; font-weight: bold; display: block;}
+dl.exceptions dt	{  margin-left: 30px; }
+dl.exceptions dd::before	{   content: "... ";}
+dl.exceptions dd	{   margin-left: 60px;}
+
+dl.examples::before	{ content: "Examples:"; font-weight: bold; display: block;}
+dl.examples dt	{  margin-left: 30px; 
+    unicode-bidi: embed;
+    font-family: monospace;
+    white-space: pre;
+	  border: 1px solid #dddddd;
+	  border-radius: 3px;
+	  background: #f5f5f5;
+	  padding: 3px 5px 3px 5px;
+}
+dl.examples dd::before	{   content: "... ";}
+dl.examples dd	{   margin: 3px 0px 10px 60px; }
+
+
+.warning			{ background-image:url("../images/warning.png"); background-repeat: no-repeat;
+						padding-left: 30px;
+					}
+					
+.example  			{ margin-left: 130px;  }
+.example em  		{ font-weight: bold; font-style: normal; margin-right: 20px; margin-left: -100px; }
+
+.example code  		{ margin-right: 20px; padding: 8 15 8 15; margin-bottom: 10px; 
+						border: 1px solid #cccccc; border-radius: 5px;
+						background-color:  #f2f2f2;
+						display: inline-block;
+						min-width: 500px;
+					}
diff --git a/developers/org.eclipse.ease.helpgenerator/resources/org.eclipse.ease.helpgenerator.testproject/src/org/eclipse/ease/helpgenerator/testproject/valid/AbstractClassA.java b/developers/org.eclipse.ease.helpgenerator/resources/org.eclipse.ease.helpgenerator.testproject/src/org/eclipse/ease/helpgenerator/testproject/valid/AbstractClassA.java
new file mode 100644
index 0000000..11d1050
--- /dev/null
+++ b/developers/org.eclipse.ease.helpgenerator/resources/org.eclipse.ease.helpgenerator.testproject/src/org/eclipse/ease/helpgenerator/testproject/valid/AbstractClassA.java
@@ -0,0 +1,46 @@
+package org.eclipse.ease.helpgenerator.testproject.valid;
+
+import java.io.IOException;
+import java.util.function.Function;
+
+import org.eclipse.ease.modules.ScriptParameter;
+import org.eclipse.ease.modules.WrapToScript;
+
+import com.sun.jdi.InterfaceType;
+
+/**
+ * This is a test module.
+ */
+public abstract class AbstractClassA implements InterfaceA {
+
+	/** Constant defined in the base class. */
+	@WrapToScript
+	public static final String DEFINED_IN_BASE_CLASS = 1;
+
+	/**
+	 * This method is only defined in the base class.
+	 * 
+	 * @return nothing of importance
+	 */
+	@WrapToScript
+	public String methodFromBaseClass() {
+		return null;
+	}
+
+	/**
+	 * Method with documentation in base class. Method body is implemented in the derived class.
+	 */
+	@WrapToScript
+	public void methodToBeOverridden() {
+	}
+
+	/**
+	 * Abstract method with documentation in base class.
+	 */
+	@WrapToScript
+	public abstract void abstractMethodToBeOverridden();
+
+	@WrapToScript
+	public void interfaceAMethod() {
+	}
+}
diff --git a/developers/org.eclipse.ease.helpgenerator/resources/org.eclipse.ease.helpgenerator.testproject/src/org/eclipse/ease/helpgenerator/testproject/valid/InterfaceA.java b/developers/org.eclipse.ease.helpgenerator/resources/org.eclipse.ease.helpgenerator.testproject/src/org/eclipse/ease/helpgenerator/testproject/valid/InterfaceA.java
new file mode 100644
index 0000000..9ee2f07
--- /dev/null
+++ b/developers/org.eclipse.ease.helpgenerator/resources/org.eclipse.ease.helpgenerator.testproject/src/org/eclipse/ease/helpgenerator/testproject/valid/InterfaceA.java
@@ -0,0 +1,15 @@
+package org.eclipse.ease.helpgenerator.testproject.valid;
+
+import org.eclipse.ease.modules.WrapToScript;
+
+public interface InterfaceA {
+	
+	/** Constant defined in InterfaceA. */ 
+	@WrapToScript
+	static int INTERFACE_CONSTANT = 22;
+	
+	/**
+	 * Method documented in InterfaceA only.
+	 */
+	void interfaceAMethod();
+}
diff --git a/developers/org.eclipse.ease.helpgenerator/resources/org.eclipse.ease.helpgenerator.testproject/src/org/eclipse/ease/helpgenerator/testproject/valid/InterfaceB.java b/developers/org.eclipse.ease.helpgenerator/resources/org.eclipse.ease.helpgenerator.testproject/src/org/eclipse/ease/helpgenerator/testproject/valid/InterfaceB.java
new file mode 100644
index 0000000..d97dd29
--- /dev/null
+++ b/developers/org.eclipse.ease.helpgenerator/resources/org.eclipse.ease.helpgenerator.testproject/src/org/eclipse/ease/helpgenerator/testproject/valid/InterfaceB.java
@@ -0,0 +1,9 @@
+package org.eclipse.ease.helpgenerator.testproject.valid;
+
+public interface InterfaceB {
+
+	/**
+	 * Method B documented in the interface only.
+	 */
+	void interfaceBMethod();
+}
diff --git a/developers/org.eclipse.ease.helpgenerator/resources/org.eclipse.ease.helpgenerator.testproject/src/org/eclipse/ease/helpgenerator/testproject/valid/ValidModule.java b/developers/org.eclipse.ease.helpgenerator/resources/org.eclipse.ease.helpgenerator.testproject/src/org/eclipse/ease/helpgenerator/testproject/valid/ValidModule.java
index 5ac0467..c5042b6 100644
--- a/developers/org.eclipse.ease.helpgenerator/resources/org.eclipse.ease.helpgenerator.testproject/src/org/eclipse/ease/helpgenerator/testproject/valid/ValidModule.java
+++ b/developers/org.eclipse.ease.helpgenerator/resources/org.eclipse.ease.helpgenerator.testproject/src/org/eclipse/ease/helpgenerator/testproject/valid/ValidModule.java
@@ -3,17 +3,29 @@
 import java.io.IOException;
 import java.util.function.Function;
 
+import org.eclipse.ease.modules.ScriptParameter;
 import org.eclipse.ease.modules.WrapToScript;
 
 /**
  * This is a test module.
  */
-public class ValidModule {
+public class ValidModule extends AbstractClassA implements InterfaceB {
 
-	/** The answer to all your questions. */
+	/**
+	 * The answer to all your questions. This is really it.
+	 */
 	@WrapToScript
 	public static final int UNIVERSAL_TRUTH = 42;
-	
+
+	/**
+	 * Some old stuff.
+	 * 
+	 * @deprecated we do not need this anymore
+	 */
+	@Deprecated
+	@WrapToScript
+	public static final String OLD_VALUE = "not needed anymore";
+
 	/**
 	 * Simple method documentation.
 	 */
@@ -38,7 +50,11 @@
 	}
 
 	/**
-	 * Method with method name alias.
+	 * Method with method name alias. The documentation to this method is long to make sure it is handled with a line break in the source file. Further it
+	 * contains some valid HTML tags that need to be processed correctly by the doclet.
+	 * <p>
+	 * This is a separate paragraph.
+	 * </p>
 	 * 
 	 * @return always <code>null</code>
 	 */
@@ -46,10 +62,12 @@
 	public String thisIsAMethodWithALongName() {
 		return null;
 	}
-	
+
 	/**
 	 * Method that always throws.
-	 * @throws IOException in any case
+	 * 
+	 * @throws IOException
+	 *             in any case
 	 */
 	@WrapToScript
 	public void pleaseThrow() throws IOException {
@@ -57,19 +75,26 @@
 	}
 
 	/**
-	 * Simple method that converts a long to string.
+	 * Method with optional parameters.
 	 *
-	 * @param myLongNumber
-	 *            A function to execute and whose return value will be returned
+	 * @param mandatory
+	 *            Mandatory parameter,
+	 * @param optionalDefaultsTo1
+	 *            simple integer parameter
+	 * @param optionalDefaultsToNull
+	 *            second optional parameter
 	 * @return result of the function
+	 * @scriptExample optionalParameters(new Thread(), 22, "nothing") first way to call this method
+	 * @scriptExample optionalParameters(new Thread()) ... using default values for parameter 2 and 3
 	 */
 	@WrapToScript
-	public String thisMethodHasNoParameters(long myLongNumber) {
-		return String.valueOf(myLongNumber);
+	public String optionalParameters(Thread mandatory, @ScriptParameter(defaultValue = "1") int optionalDefaultsTo1,
+			@ScriptParameter(defaultValue = ScriptParameter.NULL) String optionalDefaultsToNull) {
+		return null;
 	}
 
 	/**
-	 * Method that uses generic parameters. For a simpler case, use {@link #thisMethodHasNoParameters(long)}, just to link to it from this javadoc.
+	 * Method that uses generic parameters.
 	 *
 	 * @param myFunctionParameter
 	 *            A function to execute and whose return value will be returned
@@ -79,4 +104,16 @@
 	public String thisMethodHasGenericParameters(Function<Long, String> myFunctionParameter) {
 		return myFunctionParameter.apply(100L);
 	}
+
+	@WrapToScript
+	public void methodToBeOverridden() {
+	}
+
+	@WrapToScript
+	public void abstractMethodToBeOverridden() {
+	}
+
+	@WrapToScript
+	public void interfaceBMethod() {
+	}
 }
diff --git a/developers/org.eclipse.ease.helpgenerator/src/META-INF/MANIFEST.MF b/developers/org.eclipse.ease.helpgenerator/src/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..09f03f1
--- /dev/null
+++ b/developers/org.eclipse.ease.helpgenerator/src/META-INF/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Main-Class: org.eclipse.ease.helpgenerator.ModuleDoclet
+
diff --git a/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/AbstractModuleDoclet.java b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/AbstractModuleDoclet.java
new file mode 100644
index 0000000..f31a055
--- /dev/null
+++ b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/AbstractModuleDoclet.java
@@ -0,0 +1,400 @@
+/*******************************************************************************
+ * Copyright (c) 2019 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.helpgenerator;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.StringReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+import java.util.Set;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+import java.util.stream.Collectors;
+
+import org.eclipse.ease.helpgenerator.model.AbstractClassModel;
+import org.eclipse.ease.helpgenerator.model.Category;
+import org.eclipse.ease.helpgenerator.model.ModuleDefinition;
+
+public abstract class AbstractModuleDoclet {
+
+	protected static ModuleDefinition findModule(String className, Set<ModuleDefinition> modules) {
+		for (final ModuleDefinition module : modules) {
+			if (className.equals(module.getClassName()))
+				return module;
+		}
+
+		return null;
+	}
+
+	public static String createHTMLFileName(final String moduleID) {
+		return "module_" + escape(moduleID) + ".html";
+	}
+
+	private static String escape(final String data) {
+		return data.replace(' ', '_').toLowerCase();
+	}
+
+	private final Map<String, List<String>> fParameters = new HashMap<>();
+	private IReporter fReporter;
+
+	private final LinkProvider fLinkProvider = new LinkProvider();
+
+	public boolean run() {
+		setReporter(createReporter());
+
+		try {
+			for (final ModuleDefinition module : getModules()) {
+				final AbstractClassModel classModel = getClassModel(module);
+				if (classModel != null)
+					createHTMLFile(module, classModel);
+				else
+					getReporter().report(IReporter.WARNING, "No class definition found for module \"" + module.getName() + "\"");
+			}
+
+			if (hasModuleDefinitions()) {
+				// create module TOC files
+				final Set<String> tocFiles = createModuleTOCFiles();
+
+				// update plugin.xml
+				updatePluginXML(tocFiles);
+
+				// update MANIFEST.MF
+				updateManifest();
+
+				// update build.properties
+				updateBuildProperties();
+			}
+
+		} catch (final Exception e) {
+			getReporter().report(IReporter.ERROR, e.getMessage());
+			e.printStackTrace();
+			return false;
+		}
+
+		return !getReporter().hasErrors();
+	}
+
+	private boolean hasModuleDefinitions() throws Exception {
+		return !getModules().isEmpty() || !getCategories().isEmpty();
+	}
+
+	public void setParameter(String option, List<String> arguments) {
+		if (ModuleDoclet.OPTION_LINK.equals(option))
+			registerLinks(arguments.get(0));
+
+		else if (ModuleDoclet.OPTION_LINK_OFFLINE.equals(option))
+			registerOfflineLinks(arguments.get(0), arguments.get(1) + "/package-list");
+
+		else
+			fParameters.put(option, arguments);
+	}
+
+	private void setReporter(IReporter reporter) {
+		fReporter = reporter;
+	}
+
+	public IReporter getReporter() {
+		return fReporter;
+	}
+
+	public Set<ModuleDefinition> getModules() throws Exception {
+		final Set<ModuleDefinition> registeredModules = new HashSet<>();
+
+		final IMemento document = getPluginDefinition();
+
+		for (final IMemento extensionNode : document.getChildren("extension")) {
+			if ("org.eclipse.ease.modules".equals(extensionNode.getString("point"))) {
+				for (final IMemento instanceNode : extensionNode.getChildren("module"))
+					registeredModules.add(new ModuleDefinition(instanceNode));
+			}
+		}
+
+		return registeredModules;
+	}
+
+	public Set<Category> getCategories() {
+		final Set<Category> categories = new HashSet<>();
+
+		try {
+			final IMemento document = getPluginDefinition();
+
+			for (final IMemento extensionNode : document.getChildren("extension")) {
+				if ("org.eclipse.ease.modules".equals(extensionNode.getString("point"))) {
+					for (final IMemento instanceNode : extensionNode.getChildren("category"))
+						categories.add(new Category(instanceNode));
+				}
+			}
+		} catch (final Exception e) {
+		}
+
+		return categories;
+	}
+
+	public IMemento getPluginDefinition() throws Exception {
+		final InputStreamReader reader = new InputStreamReader(new FileInputStream(getPluginOrFragmentFile()));
+		final IMemento root = XMLMemento.createReadRoot(reader);
+		reader.close();
+
+		return root;
+	}
+
+	public File getProjectFile(String path, boolean shallExist) throws IOException {
+		final File pluginDefinition = getRootFolder().toPath().resolve(path).toFile();
+		if ((pluginDefinition.exists()) || !shallExist)
+			return pluginDefinition;
+
+		throw new IOException(path + " not found");
+	}
+
+	public File getPluginOrFragmentFile() throws IOException {
+		try {
+			return getProjectFile("plugin.xml", true);
+		} catch (final Exception e) {
+		}
+
+		return getProjectFile("fragment.xml", true);
+	}
+
+	public File getBuildPropertiesFile() throws IOException {
+		return getProjectFile("build.properties", true);
+	}
+
+	public File getManifestFile() throws IOException {
+		return getProjectFile("META-INF/MANIFEST.MF", true);
+	}
+
+	public File getRootFolder() throws IOException {
+		final List<String> parameter = fParameters.get(ModuleDoclet.OPTION_PROJECT_ROOT);
+		if ((parameter == null) || (parameter.size() != 1))
+			throw new IOException("Root folder not found, use " + ModuleDoclet.OPTION_PROJECT_ROOT + " parameter");
+
+		final File rootFolder = new File(parameter.get(0));
+
+		if (!rootFolder.exists())
+			throw new FileNotFoundException("Root folder \"" + parameter.get(0) + "\"does not exist");
+
+		if (!rootFolder.isDirectory())
+			throw new IOException("Root folder is not a directory");
+
+		return rootFolder;
+	}
+
+	public boolean failOnMissingDocs() {
+		final List<String> parameter = fParameters.get(ModuleDoclet.OPTION_FAIL_ON_MISSING_DOCS);
+		if ((parameter == null) || (parameter.size() != 1))
+			return true;
+
+		return Boolean.parseBoolean(parameter.get(0));
+	}
+
+	public boolean failOnHtmlErrors() {
+		final List<String> parameter = fParameters.get(ModuleDoclet.OPTION_FAIL_ON_HTML_ERRORS);
+		if ((parameter == null) || (parameter.size() != 1))
+			return true;
+
+		return Boolean.parseBoolean(parameter.get(0));
+	}
+
+	/**
+	 * Verifies that the HTML content is well formed and correct. This guarantees that the code can be displayed in help hovers and code completion proposals.
+	 *
+	 * @throws Exception
+	 *             when content is not well formed
+	 */
+	public void verifyXmlContent(String content, String className) {
+		try {
+			// try to read content into an XMLMemento
+			XMLMemento.createReadRoot(new StringReader(content));
+		} catch (final Exception e) {
+			getReporter().reportInvalidHtml("invalid file content for " + className + ":\t" + e.getMessage());
+
+		}
+	}
+
+	public void createHTMLFile(ModuleDefinition module, final AbstractClassModel classModel) throws IOException {
+		fLinkProvider.setClassModel(classModel);
+
+		final HtmlWriter htmlWriter = new HtmlWriter(module, classModel, fLinkProvider, getReporter());
+		final String content = htmlWriter.createContents();
+
+		verifyXmlContent(content, classModel.getClassName());
+
+		if (!Files.exists(getProjectFile("help", false).toPath()))
+			Files.createDirectory(getProjectFile("help", false).toPath());
+
+		Files.write(getHtmlHelpFilePath(module.getId()), content.getBytes());
+	}
+
+	private Path getHtmlHelpFilePath(String moduleID) throws IOException {
+		return getProjectFile("help/" + "module_" + escape(moduleID) + ".html", false).toPath();
+	}
+
+	public void updateBuildProperties() throws IOException {
+		final File buildFile = getBuildPropertiesFile();
+
+		final Properties properties = new Properties();
+		properties.load(new FileInputStream(buildFile));
+		final String property = properties.getProperty("bin.includes");
+		if (!property.contains("help/")) {
+			if (property.trim().isEmpty())
+				properties.setProperty("bin.includes", "help/");
+			else
+				properties.setProperty("bin.includes", "help/," + property.trim());
+
+			final FileOutputStream out = new FileOutputStream(buildFile);
+			properties.store(out, "");
+			out.close();
+		}
+	}
+
+	public void updateManifest() throws IOException {
+		final File manifestFile = getManifestFile();
+
+		final Manifest manifest = new Manifest();
+		manifest.read(new FileInputStream(manifestFile));
+
+		final Attributes mainAttributes = manifest.getMainAttributes();
+		final String require = mainAttributes.getValue("Require-Bundle");
+
+		if ((require == null) || (require.isEmpty()))
+			mainAttributes.putValue("Require-Bundle", "org.eclipse.help;bundle-version=\"[3.5.0,4.0.0)\"");
+
+		else if (!require.contains("org.eclipse.help"))
+			mainAttributes.putValue("Require-Bundle", "org.eclipse.help;bundle-version=\"[3.5.0,4.0.0)\"," + require);
+
+		else
+			// manifest contains reference to org.eclipse.help, bail out
+			return;
+
+		final FileOutputStream out = new FileOutputStream(manifestFile);
+		manifest.write(out);
+		out.close();
+	}
+
+	public Set<String> createModuleTOCFiles() throws Exception {
+		final Map<String, IMemento> tocDefinitions = new HashMap<>();
+
+		// create categories
+		for (final Category category : getCategories()) {
+			final XMLMemento memento = XMLMemento.createWriteRoot("toc");
+			memento.putString("label", category.getName());
+			memento.putString("link_to", category.getHelpLink());
+
+			final IMemento topicNode = memento.createChild("topic");
+			topicNode.putString("label", category.getName());
+			topicNode.putBoolean("sort", true);
+
+			topicNode.createChild("anchor").putString("id", "modules_anchor");
+			tocDefinitions.put(category.getFileName(), memento);
+		}
+
+		// create modules
+		for (final ModuleDefinition module : getModules()) {
+			final String categoryID = module.getCategoryId();
+			final String fileName = Category.createCategoryFileName(categoryID).replace("category_", "modules_");
+
+			IMemento memento;
+			if (tocDefinitions.containsKey(fileName))
+				memento = tocDefinitions.get(fileName);
+
+			else {
+				memento = XMLMemento.createWriteRoot("toc");
+				memento.putString("label", "Modules");
+				memento.putString("link_to", Category.createCategoryLink(categoryID));
+
+				tocDefinitions.put(fileName, memento);
+			}
+
+			final IMemento topicNode = memento.createChild("topic");
+			topicNode.putString("href", "help/" + createHTMLFileName(module.getId()));
+			topicNode.putString("label", module.getName());
+		}
+
+		for (final Entry<String, IMemento> entry : tocDefinitions.entrySet())
+			Files.write(getProjectFile("help/" + entry.getKey(), false).toPath(), entry.getValue().toString().getBytes());
+
+		return tocDefinitions.keySet();
+	}
+
+	public void updatePluginXML(final Collection<String> tocs) throws Exception {
+		final HashSet<String> toDo = new HashSet<>(tocs);
+
+		final IMemento memento = getPluginDefinition();
+		for (final IMemento extensionNode : memento.getChildren("extension")) {
+			final String extensionPoint = extensionNode.getString("point");
+			if ("org.eclipse.help.toc".equals(extensionPoint)) {
+				// a help topic is already registered
+				for (final IMemento tocNode : extensionNode.getChildren("toc")) {
+					final String tocLocation = tocNode.getString("file");
+					if (tocLocation.length() > 5)
+						toDo.remove(tocLocation.substring(5));
+				}
+			}
+		}
+
+		for (final String fileLocation : toDo) {
+			// some TOCs not registered yet
+			final IMemento extensionNode = memento.createChild("extension");
+			extensionNode.putString("point", "org.eclipse.help.toc");
+			final IMemento tocNode = extensionNode.createChild("toc");
+			tocNode.putString("file", "help/" + fileLocation);
+			tocNode.putBoolean("primary", false);
+		}
+
+		if (!toDo.isEmpty()) {
+			final File pluginFile = getPluginOrFragmentFile();
+			Files.write(pluginFile.toPath(), memento.toString().replace("&#x0A;", "\n").getBytes());
+		}
+	}
+
+	public LinkProvider getLinkProvider() {
+		return fLinkProvider;
+	}
+
+	protected void registerLinks(String url) {
+		registerOfflineLinks(url, url + "/package-list");
+	}
+
+	protected void registerOfflineLinks(String remoteUrl, String packageContentUrl) {
+		try {
+			try (InputStream packageData = new URL(packageContentUrl).openStream()) {
+				final List<String> packages = new BufferedReader(new InputStreamReader(packageData)).lines().collect(Collectors.toList());
+				getLinkProvider().registerAddress(remoteUrl, packages);
+			}
+
+		} catch (final MalformedURLException e) {
+			getReporter().report(IReporter.WARNING, "Cannot open location \"" + packageContentUrl + "\"");
+		} catch (final IOException e) {
+			getReporter().report(IReporter.WARNING, "Cannot read package data from \"" + packageContentUrl + "\"");
+		}
+	}
+
+	protected abstract AbstractClassModel getClassModel(ModuleDefinition module);
+
+	protected abstract IReporter createReporter();
+}
diff --git a/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/ContentException.java b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/ContentException.java
deleted file mode 100644
index 41d3114..0000000
--- a/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/ContentException.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2019 Christian Pontesegger and others.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v2.0
- * which accompanies this distribution, and is available at
- * https://www.eclipse.org/legal/epl-2.0/
- *
- * Contributors:
- *     Christian Pontesegger - initial API and implementation
- *******************************************************************************/
-
-package org.eclipse.ease.helpgenerator;
-
-public class ContentException extends Exception {
-
-	public ContentException() {
-		super();
-	}
-
-	public ContentException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
-		super(message, cause, enableSuppression, writableStackTrace);
-	}
-
-	public ContentException(String message, Throwable cause) {
-		super(message, cause);
-	}
-
-	public ContentException(String message) {
-		super(message);
-	}
-
-	public ContentException(Throwable cause) {
-		super(cause);
-	}
-}
diff --git a/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/HTMLWriter.java b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/HTMLWriter.java
deleted file mode 100644
index a624c7b..0000000
--- a/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/HTMLWriter.java
+++ /dev/null
@@ -1,688 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2014 Christian Pontesegger and others.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v2.0
- * which accompanies this distribution, and is available at
- * https://www.eclipse.org/legal/epl-2.0/
- *
- * Contributors:
- *     Christian Pontesegger - initial API and implementation
- *******************************************************************************/
-package org.eclipse.ease.helpgenerator;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-
-import com.sun.javadoc.AnnotationDesc;
-import com.sun.javadoc.AnnotationDesc.ElementValuePair;
-import com.sun.javadoc.ClassDoc;
-import com.sun.javadoc.FieldDoc;
-import com.sun.javadoc.MethodDoc;
-import com.sun.javadoc.ParamTag;
-import com.sun.javadoc.Parameter;
-import com.sun.javadoc.ProgramElementDoc;
-import com.sun.javadoc.Tag;
-import com.sun.javadoc.ThrowsTag;
-import com.sun.javadoc.Type;
-
-public class HTMLWriter {
-
-	private class Overview implements Comparable<Overview> {
-		private final String fTitle;
-		private final String fLinkID;
-		private final String fDescription;
-		private final boolean fDeprecated;
-
-		public Overview(final String title, final String linkID, final String description, final boolean deprecated) {
-			fTitle = title;
-			fLinkID = linkID;
-			fDescription = description;
-			fDeprecated = deprecated;
-		}
-
-		@Override
-		public int compareTo(final Overview arg0) {
-			return fTitle.compareTo(arg0.fTitle);
-		}
-	};
-
-	private interface CommentExtractor {
-		String extract(MethodDoc method);
-	};
-
-	private static final String WRAP_TO_SCRIPT = "WrapToScript";
-	private static final String QUALIFIED_WRAP_TO_SCRIPT = "org.eclipse.ease.modules." + WRAP_TO_SCRIPT;
-	private static final Object SCRIPT_PARAMETER = "ScriptParameter";
-	private static final Object QUALIFIED_SCRIPT_PARAMETER = "org.eclipse.ease.modules." + SCRIPT_PARAMETER;
-
-	private static final String LINE_DELIMITER = "\n";
-
-	private final LinkProvider fLinkProvider;
-	private final ClassDoc fClazz;
-	private final IMemento[] fDependencies;
-
-	private final Collection<String> fDocumentationErrors = new ArrayList<>();
-
-	public HTMLWriter(final ClassDoc clazz, final LinkProvider linkProvider, final IMemento[] dependencies) {
-		fClazz = clazz;
-		fLinkProvider = linkProvider;
-		fDependencies = dependencies;
-	}
-
-	public String createContents(final String name) {
-		final StringBuffer buffer = new StringBuffer();
-
-		addLine(buffer, "<html>");
-		addLine(buffer, "<head>");
-		addLine(buffer, "	<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>");
-		addLine(buffer, "	<link rel=\"stylesheet\" type=\"text/css\" href=\"../../org.eclipse.ease.help/help/css/modules_reference.css\" />");
-		addLine(buffer, "</head>");
-		addLine(buffer, "<body>");
-		addText(buffer, "	<div class=\"module\" title=\"");
-		addText(buffer, name);
-		addLine(buffer, " Module\">");
-
-		// header
-		addText(buffer, "		<h1>");
-		addText(buffer, name);
-		addLine(buffer, " Module</h1>");
-
-		// class description
-		addText(buffer, "		<p class=\"description\">");
-		final String classComment = fClazz.commentText();
-		if ((classComment != null) && (!classComment.isEmpty()))
-			addText(buffer, fLinkProvider.insertLinks(fClazz, fClazz.commentText()));
-
-		else
-			addDocumentationError("Missing class comment for " + fClazz.name());
-
-		addLine(buffer, "</p>");
-
-		// dependencies
-		addLine(buffer, createDependenciesSection());
-
-		// end title div
-		addLine(buffer, "	</div>");
-
-		// constants
-		addLine(buffer, createConstantsSection());
-
-		// function overview
-		addLine(buffer, createOverviewSection());
-
-		// function details
-		addLine(buffer, createDetailSection());
-
-		addLine(buffer, "</body>");
-		addLine(buffer, "</html>");
-
-		return buffer.toString();
-	}
-
-	private String createDependenciesSection() {
-
-		if (fDependencies.length > 0) {
-
-			final StringBuffer buffer = new StringBuffer();
-			addLine(buffer, "\t<h3>Dependencies</h3>");
-			addLine(buffer, "\t<p>This module depends on following other modules which will automatically be loaded.</p>");
-			addLine(buffer, "\t<ul class=\"dependency\">");
-
-			for (final IMemento dependency : fDependencies)
-				addLine(buffer, "\t\t<li>{@module " + dependency.getString("module") + "}</li>");
-
-			addLine(buffer, "\t</ul>");
-
-			return fLinkProvider.insertLinks(fClazz, buffer.toString());
-		}
-
-		return "";
-	}
-
-	private Object createDetailSection() {
-		final StringBuffer buffer = new StringBuffer();
-
-		addLine(buffer, "\t<h2>Methods</h2>");
-
-		for (final MethodDoc method : getExportedMethods()) {
-			// heading
-			addText(buffer, "\t<div class=\"command");
-			if (isDeprecated(method))
-				addText(buffer, " deprecated");
-			addText(buffer, "\" data-method=\"");
-			addText(buffer, method.name());
-			addLine(buffer, "\">");
-
-			addLine(buffer,
-					"\t\t<h3" + (isDeprecated(method) ? " class=\"deprecatedText\"" : "") + "><a id=\"" + method.name() + "\">" + method.name() + "</a></h3>");
-
-			final Parameter[] parameters = method.parameters();
-			if (parameters.length > 0) {
-				// Add a link anchor to method name + parameters
-				final StringBuilder anchorBuilder = new StringBuilder();
-				anchorBuilder.append(method.name()).append('-');
-				for (final Parameter parameter : parameters) {
-					anchorBuilder.append(LinkProvider.removeGenericsTags(parameter.typeName())).append('-');
-				}
-				addLine(buffer, "\t\t<a id=\"" + anchorBuilder.toString() + "\"></a>");
-			}
-
-			// synopsis
-			addLine(buffer, createSynopsis(method));
-
-			// main description
-			addLine(buffer, "\t\t<p class=\"description\">" + fLinkProvider.insertLinks(fClazz, getMethodComment(fClazz, method)) + "</p>");
-
-			if (isDeprecated(method)) {
-				String deprecationText = method.tags("deprecated")[0].text();
-				if (deprecationText.isEmpty())
-					deprecationText = "This method is deprecated and might be removed in future versions.";
-
-				addLine(buffer, "\t\t<p class=\"warning\"><b>Deprecation warning:</b> " + fLinkProvider.insertLinks(fClazz, deprecationText) + "</p>");
-			}
-
-			// aliases
-			addLine(buffer, createAliases(method));
-
-			// parameters
-			addLine(buffer, createParametersArea(method));
-
-			// return value
-			addLine(buffer, createReturnValueArea(method));
-
-			// declared exceptions
-			addLine(buffer, createExceptionArea(method));
-
-			// examples
-			addLine(buffer, createExampleArea(method));
-
-			addLine(buffer, "\t</div>");
-		}
-
-		return buffer;
-	}
-
-	private StringBuffer createExampleArea(final MethodDoc method) {
-		final StringBuffer buffer = new StringBuffer();
-
-		final Tag[] tags = method.tags("scriptExample");
-		if (tags.length > 0) {
-			addLine(buffer, "		<dl class=\"examples\">");
-
-			for (final Tag tag : tags) {
-				final String fullText = tag.text();
-
-				// extract end position of example code
-				int pos = fullText.indexOf('(');
-				if (pos > 0) {
-					int open = 1;
-					for (int index = pos + 1; index < fullText.length(); index++) {
-						if (fullText.charAt(index) == ')')
-							open--;
-						else if (fullText.charAt(index) == '(')
-							open++;
-
-						if (open == 0) {
-							pos = index + 1;
-							break;
-						}
-					}
-				}
-				final String codeText = (pos > 0) ? fullText.substring(0, pos) : fullText;
-				final String description = ((pos > 0) ? fullText.substring(pos).trim() : "");
-
-				addLine(buffer, "			<dt>" + codeText + "</dt>");
-				addText(buffer, "			<dd class=\"description\">" + fLinkProvider.insertLinks(fClazz, description));
-				addLine(buffer, "</dd>");
-			}
-
-			addLine(buffer, "		</dl>");
-		}
-
-		return buffer;
-	}
-
-	private StringBuffer createReturnValueArea(final MethodDoc method) {
-		final StringBuffer buffer = new StringBuffer();
-
-		if (!"void".equals(method.returnType().qualifiedTypeName())) {
-			addText(buffer, "		<p class=\"return\">");
-
-			final Tag[] tags = method.tags("return");
-			if (tags.length > 0) {
-				if (tags[0].text().isEmpty())
-					addDocumentationError("Missing return statement documentation for " + method.containingClass().name() + "." + method.name() + "()");
-
-				addText(buffer, fLinkProvider.insertLinks(fClazz, tags[0].text()));
-			}
-
-			addLine(buffer, "</p>");
-		}
-
-		return buffer;
-	}
-
-	private StringBuffer createParametersArea(final MethodDoc method) {
-		final StringBuffer buffer = new StringBuffer();
-
-		if (method.parameters().length > 0) {
-
-			addLine(buffer, "		<dl class=\"parameters\">");
-
-			for (final Parameter parameter : method.parameters()) {
-				addLine(buffer, "			<dt>" + parameter.name() + "</dt>");
-				addText(buffer, "			<dd class=\"description\" data-parameter=\"" + parameter.name() + "\">"
-						+ fLinkProvider.insertLinks(fClazz, getParameterComment(method, parameter.name())));
-
-				final AnnotationDesc parameterAnnotation = getScriptParameterAnnotation(parameter);
-				if (parameterAnnotation != null) {
-					addText(buffer, "<span class=\"optional\"><b>Optional:</b> defaults to &lt;<i>");
-					for (final ElementValuePair pair : parameterAnnotation.elementValues()) {
-						if ("org.eclipse.ease.modules.ScriptParameter.defaultValue()".equals(pair.element().toString())) {
-							String defaultValue = pair.value().toString();
-
-							if ((!String.class.getName().equals(parameter.type().qualifiedTypeName())) && (defaultValue.length() > 2))
-								// remove quotes from default
-								// value
-								defaultValue = defaultValue.substring(1, defaultValue.length() - 1);
-
-							if (defaultValue.contains("org.eclipse.ease.modules.ScriptParameter.null"))
-								addText(buffer, "null");
-
-							else
-								addText(buffer, escapeText(defaultValue));
-						}
-					}
-					addText(buffer, "</i>&gt;.</span>");
-				}
-				addLine(buffer, "</dd>");
-			}
-			addLine(buffer, "		</dl>");
-		}
-
-		return buffer;
-	}
-
-	private StringBuffer createExceptionArea(final MethodDoc method) {
-		final StringBuffer buffer = new StringBuffer();
-
-		if (method.thrownExceptionTypes().length > 0) {
-
-			addLine(buffer, "		<dl class=\"exceptions\">");
-
-			for (final Type exceptionType : method.thrownExceptionTypes()) {
-				addLine(buffer, "			<dt>" + exceptionType.simpleTypeName() + "</dt>");
-				addText(buffer, "			<dd class=\"description\" data-exception=\"" + exceptionType.simpleTypeName() + "\">"
-						+ fLinkProvider.insertLinks(fClazz, getExceptionComment(method, exceptionType)));
-
-				addLine(buffer, "</dd>");
-			}
-
-			addLine(buffer, "		</dl>");
-		}
-
-		return buffer;
-	}
-
-	private String getExceptionComment(MethodDoc method, Type exceptionType) {
-		final String comment = extractComment(method, method1 -> {
-
-			for (final ThrowsTag tag : method1.throwsTags()) {
-				if ((exceptionType.simpleTypeName().equals(tag.exceptionName())) || (exceptionType.typeName().equals(tag.exceptionName())))
-					return tag.exceptionComment();
-			}
-
-			return "";
-		});
-
-		if (comment.isEmpty())
-			addDocumentationError(
-					"Missing exception documentation for " + method.containingClass().name() + "." + method.name() + "() - " + exceptionType.simpleTypeName());
-
-		return comment;
-	}
-
-	private StringBuffer createAliases(final MethodDoc method) {
-		final StringBuffer buffer = new StringBuffer();
-
-		final Collection<String> aliases = getFunctionAliases(method);
-		if (!aliases.isEmpty()) {
-			addLine(buffer, "		<p class=\"synonyms\"><em>Alias:</em>");
-
-			for (final String alias : aliases)
-				addText(buffer, " " + alias + "(),");
-
-			// remove last comma
-			buffer.deleteCharAt(buffer.length() - 1);
-
-			addLine(buffer, "</p>");
-		}
-
-		return buffer;
-	}
-
-	private StringBuffer createSynopsis(final MethodDoc method) {
-		final StringBuffer buffer = new StringBuffer();
-
-		addText(buffer, "		<p class=\"synopsis\">");
-		addText(buffer, fLinkProvider.createClassText(LinkProvider.resolveClassName(method.returnType().qualifiedTypeName(), fClazz)));
-		addText(buffer, " ");
-		addText(buffer, method.name());
-		addText(buffer, "(");
-		for (final Parameter parameter : method.parameters()) {
-			final AnnotationDesc parameterAnnotation = getScriptParameterAnnotation(parameter);
-			if (parameterAnnotation != null)
-				addText(buffer, "[");
-
-			addText(buffer, fLinkProvider.createClassText(LinkProvider.resolveClassName(parameter.type().qualifiedTypeName(), fClazz)));
-			addText(buffer, " ");
-			addText(buffer, parameter.name());
-			if (parameterAnnotation != null)
-				addText(buffer, "]");
-
-			addText(buffer, ", ");
-		}
-		if (method.parameters().length > 0)
-			buffer.delete(buffer.length() - 2, buffer.length());
-
-		addText(buffer, ")");
-		addLine(buffer, "</p>");
-
-		return buffer;
-	}
-
-	private StringBuffer createOverviewSection() {
-		final StringBuffer buffer = new StringBuffer();
-
-		addLine(buffer, "	<h2>Method Overview</h2>");
-		addLine(buffer, "	<table class=\"functions\">");
-		addLine(buffer, "		<tr>");
-		addLine(buffer, "			<th>Method</th>");
-		addLine(buffer, "			<th>Description</th>");
-		addLine(buffer, "		</tr>");
-
-		final List<Overview> overview = new ArrayList<>();
-
-		for (final MethodDoc method : getExportedMethods()) {
-			overview.add(new Overview(method.name(), method.name(), getMethodComment(fClazz, method), isDeprecated(method)));
-			for (final String alias : getFunctionAliases(method))
-				overview.add(
-						new Overview(alias, method.name(), "Alias for <a href=\"#" + method.name() + "\">" + method.name() + "</a>.", isDeprecated(method)));
-		}
-
-		Collections.sort(overview);
-
-		for (final Overview entry : overview) {
-			addLine(buffer, "		<tr>");
-			if (!entry.fDeprecated) {
-				addLine(buffer, "			<td><a href=\"#" + entry.fLinkID + "\">" + entry.fTitle + "</a>()</td>");
-				addLine(buffer, "			<td>" + fLinkProvider.insertLinks(fClazz, getFirstSentence(entry.fDescription)) + "</td>");
-
-			} else {
-				addLine(buffer, "			<td class=\"deprecatedText\"><a href=\"#" + entry.fLinkID + "\">" + entry.fTitle + "</a>()</td>");
-				addLine(buffer, "			<td class=\"deprecatedDescription\"><b>Deprecated:</b> "
-						+ fLinkProvider.insertLinks(fClazz, getFirstSentence(entry.fDescription)) + "</td>");
-			}
-			addLine(buffer, "		</tr>");
-		}
-
-		addLine(buffer, "	</table>");
-		addLine(buffer, "");
-
-		return buffer;
-	}
-
-	private String getMethodComment(ClassDoc baseClass, MethodDoc method) {
-		final String comment = extractComment(method, method1 -> method1.commentText());
-
-		if (comment.isEmpty())
-			addDocumentationError("Missing comment for " + method.containingClass().name() + "." + method.name() + "()");
-
-		return comment;
-	}
-
-	private StringBuffer createConstantsSection() {
-		final StringBuffer buffer = new StringBuffer();
-
-		final List<FieldDoc> fields = getExportedFields();
-		if (!fields.isEmpty()) {
-			addLine(buffer, "");
-			addLine(buffer, "	<h2>Constants</h2>");
-			addLine(buffer, "	<table class=\"constants\">");
-			addLine(buffer, "		<tr>");
-			addLine(buffer, "			<th>Constant</th>");
-			addLine(buffer, "			<th>Description</th>");
-			addLine(buffer, "		</tr>");
-
-			for (final FieldDoc field : fields) {
-				addLine(buffer, "\t\t<tr>");
-
-				if (field.commentText().isEmpty())
-					addDocumentationError("Field domentation missing for " + fClazz.name() + "." + field.name());
-
-				if (!isDeprecated(field)) {
-					addLine(buffer, "			<td><a id=\"" + field.name() + "\">" + field.name() + "</a></td>");
-					addLine(buffer, "			<td class=\"description\" data-field=\"" + field.name() + "\">"
-							+ fLinkProvider.insertLinks(fClazz, field.commentText()) + "</td>");
-
-				} else {
-					addLine(buffer, "			<td><a id=\"" + field.name() + "\" class=\"deprecatedText\">" + field.name() + "</a></td>");
-					addText(buffer, "			<td>" + fLinkProvider.insertLinks(fClazz, field.commentText()));
-					String deprecationText = field.tags("deprecated")[0].text();
-					if (deprecationText.isEmpty())
-						deprecationText = "This constant is deprecated and might be removed in future versions.";
-
-					addText(buffer, "				<div class=\"warning\"><b>Deprecation warning:</b> " + fLinkProvider.insertLinks(fClazz, deprecationText)
-							+ "</div>");
-					addLine(buffer, "</td>");
-				}
-
-				addLine(buffer, "		</tr>");
-			}
-
-			addLine(buffer, "	</table>");
-			addLine(buffer, "");
-		}
-
-		return buffer;
-	}
-
-	private Collection<String> getFunctionAliases(final MethodDoc method) {
-		final Collection<String> aliases = new HashSet<>();
-		final AnnotationDesc annotation = getWrapAnnotation(method);
-		if (annotation != null) {
-			for (final ElementValuePair pair : annotation.elementValues()) {
-				if ("alias".equals(pair.element().name())) {
-					String candidates = pair.value().toString();
-					candidates = candidates.substring(1, candidates.length() - 1);
-					for (final String token : candidates.split("[,;]")) {
-						if (!token.trim().isEmpty())
-							aliases.add(token.trim());
-					}
-				}
-			}
-		}
-
-		return aliases;
-	}
-
-	private static String getFirstSentence(final String description) {
-		final int pos = description.indexOf('.');
-
-		return (pos > 0) ? description.substring(0, pos + 1) : description;
-	}
-
-	private static void addText(final StringBuffer buffer, final Object text) {
-		buffer.append(text);
-	}
-
-	private static void addLine(final StringBuffer buffer, final Object text) {
-		buffer.append(text).append(LINE_DELIMITER);
-	}
-
-	private static boolean isDeprecated(final MethodDoc method) {
-		final Tag[] tags = method.tags("deprecated");
-		return (tags != null) && (tags.length > 0);
-	}
-
-	private static boolean isDeprecated(final FieldDoc field) {
-		final Tag[] tags = field.tags("deprecated");
-		return (tags != null) && (tags.length > 0);
-	}
-
-	private static AnnotationDesc getScriptParameterAnnotation(final Parameter parameter) {
-		for (final AnnotationDesc annotation : parameter.annotations()) {
-			if (isScriptParameterAnnotation(annotation))
-				return annotation;
-		}
-
-		return null;
-	}
-
-	private static boolean isScriptParameterAnnotation(final AnnotationDesc annotation) {
-		return (QUALIFIED_SCRIPT_PARAMETER.equals(annotation.annotationType().qualifiedName()))
-				|| (SCRIPT_PARAMETER.equals(annotation.annotationType().qualifiedName()));
-	}
-
-	private String getParameterComment(final MethodDoc method, final String name) {
-		final String comment = extractComment(method, method1 -> {
-			for (final ParamTag paramTags : method1.paramTags()) {
-				if (name.equals(paramTags.parameterName()))
-					return paramTags.parameterComment();
-			}
-
-			return "";
-		});
-
-		if (comment.isEmpty())
-			addDocumentationError("Missing parameter documentation for " + method.containingClass().name() + "." + method.name() + "(" + name + ")");
-
-		return comment;
-	}
-
-	private String extractComment(MethodDoc method, CommentExtractor extractor) {
-		String comment = extractor.extract(method);
-		if ((comment != null) && (!comment.isEmpty()))
-			return comment;
-
-		// try to look up interfaces
-		for (final ClassDoc iface : method.containingClass().interfaces()) {
-			for (final MethodDoc ifaceMethod : iface.methods()) {
-				if (method.overrides(ifaceMethod)) {
-					comment = extractComment(ifaceMethod, extractor);
-					if ((comment != null) && (!comment.isEmpty()))
-						return comment;
-				}
-			}
-		}
-
-		// not found, retry with super class
-		final ClassDoc parent = method.containingClass().superclass();
-		if (parent != null) {
-			for (final MethodDoc superMethod : parent.methods()) {
-				if (method.overrides(superMethod))
-					return (extractComment(superMethod, extractor));
-			}
-		}
-
-		return "";
-	}
-
-	private List<MethodDoc> getExportedMethods() {
-		final List<MethodDoc> methods = new ArrayList<>();
-		final boolean hasAnnotation = hasWrapToScriptAnnotation(fClazz);
-
-		ClassDoc clazzDoc = fClazz;
-		while ((clazzDoc != null) && (!Object.class.getName().equals(clazzDoc.qualifiedName()))) {
-			for (final MethodDoc method : clazzDoc.methods()) {
-				if ((!hasAnnotation) || (getWrapAnnotation(method) != null))
-					methods.add(method);
-			}
-
-			clazzDoc = clazzDoc.superclass();
-		}
-
-		// sort methods alphabetically
-		Collections.sort(methods, (o1, o2) -> o1.name().compareTo(o2.name()));
-
-		return methods;
-	}
-
-	private List<FieldDoc> getExportedFields() {
-		final List<FieldDoc> fields = new ArrayList<>();
-
-		final boolean hasAnnotation = hasWrapToScriptAnnotation(fClazz);
-
-		final ArrayList<ClassDoc> candidates = new ArrayList<>();
-		candidates.add(fClazz);
-		while (!candidates.isEmpty()) {
-			final ClassDoc clazzDoc = candidates.remove(0);
-
-			for (final FieldDoc field : clazzDoc.fields()) {
-				if ((!hasAnnotation) || (getWrapAnnotation(field) != null))
-					fields.add(field);
-			}
-
-			// add interfaces
-			candidates.addAll(Arrays.asList(clazzDoc.interfaces()));
-
-			// add super class/interface
-			final ClassDoc nextCandidate = clazzDoc.superclass();
-			if ((nextCandidate != null) && (!Object.class.getName().equals(nextCandidate.qualifiedName())))
-				candidates.add(nextCandidate);
-		}
-
-		// sort fields alphabetically
-		Collections.sort(fields, (o1, o2) -> o1.name().compareTo(o2.name()));
-
-		return fields;
-	}
-
-	private static boolean hasWrapToScriptAnnotation(ClassDoc clazzDoc) {
-		while (clazzDoc != null) {
-			for (final MethodDoc method : clazzDoc.methods()) {
-				if (getWrapAnnotation(method) != null)
-					return true;
-			}
-
-			for (final FieldDoc field : clazzDoc.fields()) {
-				if (getWrapAnnotation(field) != null)
-					return true;
-			}
-
-			clazzDoc = clazzDoc.superclass();
-		}
-
-		return false;
-	}
-
-	private static AnnotationDesc getWrapAnnotation(final ProgramElementDoc method) {
-		for (final AnnotationDesc annotation : method.annotations()) {
-			if (isWrapToScriptAnnotation(annotation))
-				return annotation;
-		}
-
-		return null;
-	}
-
-	private static boolean isWrapToScriptAnnotation(final AnnotationDesc annotation) {
-		return (QUALIFIED_WRAP_TO_SCRIPT.equals(annotation.annotationType().qualifiedName()))
-				|| (WRAP_TO_SCRIPT.equals(annotation.annotationType().qualifiedName()));
-	}
-
-	public static String escapeText(String text) {
-		return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
-	}
-
-	public Collection<String> getDocumentationErrors() {
-		return fDocumentationErrors;
-	}
-
-	private void addDocumentationError(String message) {
-		fDocumentationErrors.add(message);
-	}
-}
diff --git a/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/HtmlWriter.java b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/HtmlWriter.java
new file mode 100644
index 0000000..a53dbc5
--- /dev/null
+++ b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/HtmlWriter.java
@@ -0,0 +1,440 @@
+/*******************************************************************************
+ * Copyright (c) 2014 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ease.helpgenerator;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.eclipse.ease.helpgenerator.model.AbstractClassModel;
+import org.eclipse.ease.helpgenerator.model.Description;
+import org.eclipse.ease.helpgenerator.model.ExceptionValue;
+import org.eclipse.ease.helpgenerator.model.Field;
+import org.eclipse.ease.helpgenerator.model.Method;
+import org.eclipse.ease.helpgenerator.model.ModuleDefinition;
+import org.eclipse.ease.helpgenerator.model.Parameter;
+import org.eclipse.ease.helpgenerator.model.ScriptExample;
+
+public class HtmlWriter {
+
+	private static final String LINE_DELIMITER = "\n";
+
+	private static void addText(final StringBuffer buffer, final Object text) {
+		buffer.append(text);
+	}
+
+	private static void addLine(final StringBuffer buffer, final Object text) {
+		buffer.append(text).append(LINE_DELIMITER);
+	}
+
+	private static String escapeText(String text) {
+		return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
+	}
+
+	private final IReporter fReporter;
+	private final ModuleDefinition fModule;
+	private final AbstractClassModel fClassModel;
+	private final LinkProvider fLinkProvider;
+
+	public HtmlWriter(ModuleDefinition module, AbstractClassModel classModel, LinkProvider linkProvider, IReporter reporter) {
+		fModule = module;
+		fClassModel = classModel;
+		fLinkProvider = linkProvider;
+		fReporter = reporter;
+	}
+
+	protected IReporter getReporter() {
+		return fReporter;
+	}
+
+	private String getModuleName() {
+		return fModule.getName();
+	}
+
+	public String createContents() {
+		final StringBuffer buffer = new StringBuffer();
+
+		addLine(buffer, "<html>");
+		addLine(buffer, "<head>");
+		addLine(buffer, "	<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>");
+		addLine(buffer, "	<link rel=\"stylesheet\" type=\"text/css\" href=\"../../org.eclipse.ease.help/help/css/modules_reference.css\" />");
+		addLine(buffer, "</head>");
+		addLine(buffer, "<body>");
+		addText(buffer, "	<div class=\"module\" title=\"");
+		addText(buffer, getModuleName());
+		addLine(buffer, " Module\">");
+
+		// header
+		addText(buffer, "		<h1>");
+		addText(buffer, getModuleName());
+		addLine(buffer, " Module</h1>");
+
+		// class description
+		addText(buffer, "		<p class=\"description\">");
+		final Description classComment = fClassModel.getClassDocumentation();
+		if (!classComment.isEmpty())
+			addText(buffer, classComment);
+		else
+			getReporter().reportMissingDocs("Missing class comment for " + fClassModel.getClassName());
+
+		addLine(buffer, "</p>");
+
+		// dependencies
+		addLine(buffer, createDependenciesSection());
+
+		// end title div
+		addLine(buffer, "	</div>");
+
+		// constants
+		addLine(buffer, createConstantsSection());
+
+		// function overview
+		addLine(buffer, createOverviewSection());
+
+		// function details
+		addLine(buffer, createDetailSection());
+
+		addLine(buffer, "</body>");
+		addLine(buffer, "</html>");
+
+		return fLinkProvider.insertLinks(buffer.toString());
+	}
+
+	private StringBuffer createDependenciesSection() {
+
+		final StringBuffer buffer = new StringBuffer();
+		if (fModule.hasDependencies()) {
+
+			addLine(buffer, "\t<h3>Dependencies</h3>");
+			addLine(buffer, "\t<p>This module depends on following other modules which will automatically be loaded.</p>");
+			addLine(buffer, "\t<ul class=\"dependency\">");
+
+			for (final ModuleDefinition dependency : fModule.getDependencies())
+				addLine(buffer, "\t\t<li>{@module " + dependency.getId() + "}</li>");
+
+			addLine(buffer, "\t</ul>");
+		}
+
+		return buffer;
+	}
+
+	private StringBuffer createDetailSection() {
+		final StringBuffer buffer = new StringBuffer();
+
+		addLine(buffer, "\t<h2>Methods</h2>");
+
+		for (final Method method : fClassModel.getExportedMethods()) {
+			// heading
+			addText(buffer, "\t<div class=\"command");
+			if (method.isDeprecated())
+				addText(buffer, " deprecated");
+			addText(buffer, "\" data-method=\"");
+			addText(buffer, method.getName());
+			addLine(buffer, "\">");
+
+			addLine(buffer, "\t\t<h3" + (method.isDeprecated() ? " class=\"deprecatedText\"" : "") + "><a id=\"" + method.getName() + "\">" + method.getName()
+					+ "</a></h3>");
+
+			// synopsis
+			addLine(buffer, createSynopsis(method));
+
+			// main description
+			addLine(buffer, "\t\t<p class=\"description\">" + method.getComment() + "</p>");
+			if (method.getComment().isEmpty())
+				getReporter().reportMissingDocs("Missing comment for " + fClassModel.getClassName() + "." + method.getName() + "()");
+
+			if (method.isDeprecated()) {
+				String deprecationText = method.getDeprecationMessage();
+				if (deprecationText.isEmpty())
+					deprecationText = "This method is deprecated and might be removed in future versions.";
+
+				addLine(buffer, "\t\t<p class=\"warning\"><b>Deprecation warning:</b> " + deprecationText + "</p>");
+			}
+
+			// aliases
+			addLine(buffer, createAliases(method));
+
+			// parameters
+			addLine(buffer, createParametersArea(method));
+
+			// return value
+			addLine(buffer, createReturnValueArea(method));
+
+			// declared exceptions
+			addLine(buffer, createExceptionArea(method));
+
+			// examples
+			addLine(buffer, createExampleArea(method));
+
+			addLine(buffer, "\t</div>");
+		}
+
+		return buffer;
+	}
+
+	private StringBuffer createExampleArea(final Method method) {
+		final StringBuffer buffer = new StringBuffer();
+
+		if (!method.getExamples().isEmpty()) {
+			addLine(buffer, "		<dl class=\"examples\">");
+
+			for (final ScriptExample example : method.getExamples()) {
+				addLine(buffer, "			<dt>" + example.getCode() + "</dt>");
+				addText(buffer, "			<dd class=\"description\">" + example.getComment());
+				addLine(buffer, "           </dd>");
+			}
+
+			addLine(buffer, "		</dl>");
+		}
+
+		return buffer;
+	}
+
+	private StringBuffer createReturnValueArea(final Method method) {
+		final StringBuffer buffer = new StringBuffer();
+
+		if (!method.getReturnType().isVoid()) {
+			addText(buffer, "		<p class=\"return\">");
+
+			final Description comment = method.getReturnType().getComment();
+			if (comment.isEmpty())
+				getReporter().reportMissingDocs("Missing return statement documentation for " + fClassModel.getClassName() + "." + method.getName() + "()");
+			else
+				addText(buffer, comment);
+
+			addLine(buffer, "</p>");
+		}
+
+		return buffer;
+	}
+
+	private StringBuffer createParametersArea(final Method method) {
+		final StringBuffer buffer = new StringBuffer();
+
+		if (!method.getParameters().isEmpty()) {
+
+			addLine(buffer, "		<dl class=\"parameters\">");
+
+			for (final Parameter parameter : method.getParameters()) {
+				addLine(buffer, "			<dt>" + parameter.getName() + "</dt>");
+				addText(buffer, "			<dd class=\"description\" data-parameter=\"" + parameter.getName() + "\">" + parameter.getComment());
+				if (parameter.getComment().isEmpty())
+					getReporter().reportMissingDocs(
+							"Missing parameter documentation for " + fClassModel.getClassName() + "." + method.getName() + "(" + parameter.getName() + ")");
+
+				String defaultValue = parameter.getDefaultValue();
+				if (defaultValue != null) {
+					addText(buffer, "<span class=\"optional\"><b>Optional:</b> defaults to &lt;<i>");
+
+					if ((!String.class.getName().equals(parameter.getTypeName())) && (defaultValue.length() > 2))
+						// remove quotes from default value
+						defaultValue = defaultValue.substring(1, defaultValue.length() - 1);
+
+					if (defaultValue.contains("org.eclipse.ease.modules.ScriptParameter.null"))
+						addText(buffer, "null");
+					else
+						addText(buffer, escapeText(defaultValue));
+
+					addText(buffer, "</i>&gt;.</span>");
+				}
+				addLine(buffer, "</dd>");
+			}
+			addLine(buffer, "		</dl>");
+		}
+
+		return buffer;
+	}
+
+	private StringBuffer createExceptionArea(final Method method) {
+		final StringBuffer buffer = new StringBuffer();
+
+		if (!method.getExceptions().isEmpty()) {
+			addLine(buffer, "		<dl class=\"exceptions\">");
+
+			for (final Parameter exception : method.getExceptions()) {
+				addLine(buffer, "			<dt>{@link " + exception.getName() + "}</dt>");
+				addText(buffer, "			<dd class=\"description\" data-exception=\"" + exception.getName() + "\">" + exception.getComment());
+				addLine(buffer, "           </dd>");
+
+				if (exception.getComment().isEmpty())
+					getReporter().reportMissingDocs(
+							"Missing exception documentation for " + fClassModel.getClassName() + "." + method.getName() + "() - " + exception.getName());
+			}
+
+			addLine(buffer, "		</dl>");
+		}
+
+		return buffer;
+	}
+
+	private StringBuffer createAliases(final Method method) {
+		final StringBuffer buffer = new StringBuffer();
+
+		final Collection<String> aliases = method.getAliases();
+		if (!aliases.isEmpty()) {
+			addLine(buffer, "		<p class=\"synonyms\"><em>Alias:</em>");
+
+			for (final String alias : aliases)
+				addText(buffer, " " + alias + "(),");
+
+			// remove last comma
+			buffer.deleteCharAt(buffer.length() - 1);
+
+			addLine(buffer, "</p>");
+		}
+
+		return buffer;
+	}
+
+	private StringBuffer createSynopsis(final Method method) {
+		final StringBuffer buffer = new StringBuffer();
+
+		addText(buffer, "		<p class=\"synopsis\">");
+		addText(buffer, "{@link " + method.getReturnType().getTypeName() + "}");
+		addText(buffer, " ");
+		addText(buffer, method.getName());
+		addText(buffer, "(");
+		for (final Parameter parameter : method.getParameters()) {
+			final String defaultValue = parameter.getDefaultValue();
+			if (defaultValue != null)
+				addText(buffer, "<i>[");
+
+			addText(buffer, "{@link " + parameter.getTypeName() + "}");
+			addText(buffer, " ");
+			addText(buffer, parameter.getName());
+			if (defaultValue != null)
+				addText(buffer, "]</i>");
+
+			addText(buffer, ", ");
+		}
+		if (!method.getParameters().isEmpty())
+			buffer.delete(buffer.length() - 2, buffer.length());
+
+		addText(buffer, ")");
+
+		final List<ExceptionValue> exceptions = method.getExceptions();
+		if (!exceptions.isEmpty()) {
+			addText(buffer, " ");
+			addText(buffer, exceptions.stream().map(e -> "{@link " + e.getTypeName() + "}").collect(Collectors.joining(", ")));
+		}
+
+		addLine(buffer, "</p>");
+
+		return buffer;
+	}
+
+	private StringBuffer createOverviewSection() {
+		final StringBuffer buffer = new StringBuffer();
+
+		addLine(buffer, "	<h2>Method Overview</h2>");
+		addLine(buffer, "	<table class=\"functions\">");
+		addLine(buffer, "		<tr>");
+		addLine(buffer, "			<th>Method</th>");
+		addLine(buffer, "			<th>Description</th>");
+		addLine(buffer, "		</tr>");
+
+		final List<Overview> overview = new ArrayList<>();
+
+		for (final Method method : fClassModel.getExportedMethods()) {
+			overview.add(new Overview(method.getName(), method.getName(), method.getComment(), method.isDeprecated()));
+			for (final String alias : method.getAliases())
+				overview.add(new Overview(alias, method.getName(),
+						new Description("Alias for <a href=\"#" + method.getName() + "\">" + method.getName() + "()</a>."), method.isDeprecated()));
+		}
+
+		Collections.sort(overview);
+
+		for (final Overview entry : overview) {
+			addLine(buffer, "		<tr>");
+			if (!entry.fDeprecated) {
+				addLine(buffer, "			<td><a href=\"#" + entry.fLinkID + "\">" + entry.fTitle + "</a>()</td>");
+				addLine(buffer, "			<td>" + entry.fDescription.getFirstSentence() + "</td>");
+
+			} else {
+				addLine(buffer, "			<td class=\"deprecatedText\"><a href=\"#" + entry.fLinkID + "\">" + entry.fTitle + "</a>()</td>");
+				addLine(buffer, "			<td class=\"deprecatedDescription\"><b>Deprecated:</b> " + entry.fDescription.getFirstSentence() + "</td>");
+			}
+			addLine(buffer, "		</tr>");
+		}
+
+		addLine(buffer, "	</table>");
+		addLine(buffer, "");
+
+		return buffer;
+	}
+
+	private StringBuffer createConstantsSection() {
+		final StringBuffer buffer = new StringBuffer();
+
+		final List<Field> fields = fClassModel.getExportedFields();
+		Collections.sort(fields, (o1, o2) -> o1.getName().compareTo(o2.getName()));
+
+		if (!fields.isEmpty()) {
+			addLine(buffer, "");
+			addLine(buffer, "	<h2>Constants</h2>");
+			addLine(buffer, "	<table class=\"constants\">");
+			addLine(buffer, "		<tr>");
+			addLine(buffer, "			<th>Constant</th>");
+			addLine(buffer, "			<th>Description</th>");
+			addLine(buffer, "		</tr>");
+
+			for (final Field field : fields) {
+				addLine(buffer, "\t\t<tr>");
+
+				if (field.getComment().isEmpty())
+					getReporter().reportMissingDocs("Field documentation missing for " + fModule.getClassName() + "." + field.getName());
+
+				if (!field.isDeprecated()) {
+					addLine(buffer, " <td><a id=\"" + field.getName() + "\">" + field.getName() + "</a></td>");
+					addLine(buffer, " <td class=\"description\" data-field=\"" + field.getName() + "\">" + field.getComment() + "</td>");
+
+				} else {
+					addLine(buffer, " <td><a id=\"" + field.getName() + "\" class=\"deprecatedText\">" + field.getName() + "</a></td>");
+					addText(buffer, " <td>" + field.getComment());
+					String deprecationText = field.getDeprecationMessage();
+					if (deprecationText.isEmpty())
+						deprecationText = "This constant is deprecated and might be removed in future versions.";
+
+					addText(buffer, " <div class=\"warning\"><b>Deprecation warning:</b> " + deprecationText + "</div>");
+					addLine(buffer, "</td>");
+				}
+
+				addLine(buffer, " </tr>");
+			}
+
+			addLine(buffer, "	</table>");
+			addLine(buffer, "");
+		}
+
+		return buffer;
+	}
+
+	private class Overview implements Comparable<Overview> {
+		private final String fTitle;
+		private final String fLinkID;
+		private final Description fDescription;
+		private final boolean fDeprecated;
+
+		public Overview(final String title, final String linkID, final Description description, final boolean deprecated) {
+			fTitle = title;
+			fLinkID = linkID;
+			fDescription = description;
+			fDeprecated = deprecated;
+		}
+
+		@Override
+		public int compareTo(final Overview arg0) {
+			return fTitle.compareTo(arg0.fTitle);
+		}
+	};
+}
diff --git a/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/IReporter.java b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/IReporter.java
new file mode 100644
index 0000000..2faa5ee
--- /dev/null
+++ b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/IReporter.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright (c) 2019 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.helpgenerator;
+
+public interface IReporter {
+
+	static int INFO = 0;
+	static int WARNING = 1;
+	static int ERROR = 2;
+
+	/**
+	 * Report a message.
+	 *
+	 * @param status
+	 *            one of {@link #INFO}, {@link #WARNING}, {@link #ERROR}
+	 * @param message
+	 *            message to be reported
+	 */
+	void report(int status, String message);
+
+	void reportMissingDocs(String message);
+
+	void reportInvalidHtml(String message);
+
+	/**
+	 * @return
+	 */
+	boolean hasErrors();
+}
diff --git a/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/LinkProvider.java b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/LinkProvider.java
index aa7f2c4..bfb6500 100644
--- a/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/LinkProvider.java
+++ b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/LinkProvider.java
@@ -10,92 +10,257 @@
  *******************************************************************************/
 package org.eclipse.ease.helpgenerator;
 
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 
-import com.sun.javadoc.ClassDoc;
+import org.eclipse.ease.helpgenerator.model.AbstractClassModel;
 
 /**
  * Collects registered packages and converts classes & API links to http anchors.
  */
 public class LinkProvider {
 
-	/** Pattern to detect a link token. */
+	public static enum JavaDocApi {
+		JAVA_8, JAVA_10
+	};
+
+	/**
+	 * Pattern to detect a link token: group(1) ... link or module identifier group(2) ... the link target
+	 */
 	private static final Pattern PATTERN_LINK = Pattern.compile("\\{@(link|module)\\s+(.*?)\\}", Pattern.DOTALL);
 
-	/** Pattern to parse a link. */
-	private static final Pattern PATTERN_INNER_LINK = Pattern.compile("(\\w+(?:\\.\\w+)*)?(?:#(\\w+)(?:\\((.*?)\\))?)?");
+	/**
+	 * Pattern to parse a link: group(1) ... FQN of class group(2) ... method name without method signature group(3) ... method parameters
+	 */
+	private static final Pattern PATTERN_INNER_LINK = Pattern.compile("(\\w+(?:\\.\\w+)*)?(?:#(\\w+)(?:(\\(.*?\\)))?)?");
+
+	private static final Collection<String> CLASSES_IN_JAVA_LANG = List.of("Appendable", "AutoCloseable", "CharSequence", "Cloneable", "Comparable", "Iterable",
+			"Readable", "Runnable", "Thread.UncaughtExceptionHandler", "Boolean", "Byte", "Character", "Character.Subset", "Character.UnicodeBlock", "Class",
+			"ClassLoader", "ClassValue", "Compiler", "Double", "Enum", "Float", "InheritableThreadLocal", "Integer", "Long", "Math", "Number", "Object",
+			"Package", "Process", "ProcessBuilder", "ProcessBuilder.Redirect", "Runtime", "RuntimePermission", "SecurityManager", "Short", "StackTraceElement",
+			"StrictMath", "String", "StringBuffer", "StringBuilder", "System", "Thread", "ThreadGroup", "ThreadLocal", "Throwable", "Void",
+			"Character.UnicodeScript", "ProcessBuilder.Redirect.Type", "Thread.State", "ArithmeticException", "ArrayIndexOutOfBoundsException",
+			"ArrayStoreException", "ClassCastException", "ClassNotFoundException", "CloneNotSupportedException", "EnumConstantNotPresentException", "Exception",
+			"IllegalAccessException", "IllegalArgumentException", "IllegalMonitorStateException", "IllegalStateException", "IllegalThreadStateException",
+			"IndexOutOfBoundsException", "InstantiationException", "InterruptedException", "NegativeArraySizeException", "NoSuchFieldException",
+			"NoSuchMethodException", "NullPointerException", "NumberFormatException", "ReflectiveOperationException", "RuntimeException", "SecurityException",
+			"StringIndexOutOfBoundsException", "TypeNotPresentException", "UnsupportedOperationException", "AbstractMethodError", "AssertionError",
+			"BootstrapMethodError", "ClassCircularityError", "ClassFormatError", "Error", "ExceptionInInitializerError", "IllegalAccessError",
+			"IncompatibleClassChangeError", "InstantiationError", "InternalError", "LinkageError", "NoClassDefFoundError", "NoSuchFieldError",
+			"NoSuchMethodError", "OutOfMemoryError", "StackOverflowError", "ThreadDeath", "UnknownError", "UnsatisfiedLinkError",
+			"UnsupportedClassVersionError", "VerifyError", "VirtualMachineError", "Deprecated", "FunctionalInterface", "Override", "SafeVarargs",
+			"SuppressWarnings");
+
+	private static String removeGenerics(String text) {
+		String result = text;
+		while (result.contains("<")) {
+			final int start = result.lastIndexOf('<');
+			final int end = result.indexOf('>', start);
+
+			if (end > start)
+				result = result.substring(0, start) + result.substring(end + 1);
+			else
+				return result;
+		}
+
+		return result;
+	}
+
+	private static String getPackageName(String qualifiedName) {
+		return (qualifiedName.contains(".")) ? qualifiedName.substring(0, qualifiedName.lastIndexOf('.')) : "";
+	}
+
+	private static String getSimpleClassName(String qualifiedName) {
+		return (qualifiedName.contains(".")) ? qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1) : qualifiedName;
+	}
+
+	private static String getPluginIdFromModuleId(String moduleId) {
+		return moduleId.substring(0, moduleId.lastIndexOf('.'));
+	}
+
+	private static String getModuleNameFromModuleId(String moduleId) {
+		return moduleId.substring(moduleId.lastIndexOf('.') + 1);
+	}
 
 	/** Maps (URL to use) -> Collection of package names. */
 	private final Map<String, Collection<String>> fExternalDocs = new HashMap<>();
 
+	private AbstractClassModel fClassModel;
+
+	private JavaDocApi fApiIdentifier = JavaDocApi.JAVA_8;
+
 	public void registerAddress(final String location, final Collection<String> packages) {
 		fExternalDocs.put(location, packages);
 	}
 
-	public static String resolveClassName(final String candidate, final ClassDoc clazz) {
-		final String foundCandidate = findClass(candidate, clazz);
-		return (foundCandidate != null) ? foundCandidate : candidate;
+	public void setClassModel(AbstractClassModel classModel) {
+		fClassModel = classModel;
 	}
 
-	public String createClassText(String qualifiedName) {
-		if (qualifiedName.contains(".")) {
+	public String insertLinks(final String text) {
 
-			final String urlLocation = findClassURL(qualifiedName);
-			if (urlLocation != null) {
-				final String packageName = qualifiedName.substring(0, qualifiedName.lastIndexOf('.'));
+		if (text == null)
+			return null;
 
-				// first run, look for exact package match
-				for (final Entry<String, Collection<String>> entry : fExternalDocs.entrySet()) {
-					if (entry.getValue().contains(packageName))
-						return "<a href=\"" + urlLocation + "\" title=\"" + HTMLWriter.escapeText(qualifiedName) + "\">"
-								+ HTMLWriter.escapeText(qualifiedName.substring(packageName.length() + 1)) + "</a>";
-				}
+		final StringBuilder output = new StringBuilder();
+		int startPos = 0;
+		final Matcher matcher = PATTERN_LINK.matcher(text);
 
-				// not found; try to locate matching parent package and hope for
-				// the best
-				for (final Entry<String, Collection<String>> entry : fExternalDocs.entrySet()) {
-					for (final String entryPackage : entry.getValue()) {
-						if (packageName.startsWith(entryPackage))
-							return "<a href=\"" + urlLocation + "\" title=\"" + HTMLWriter.escapeText(qualifiedName) + "\">"
-									+ HTMLWriter.escapeText(qualifiedName.substring(packageName.length() + 1)) + "</a>";
+		while (matcher.find()) {
+			output.append(text.substring(startPos, matcher.start()));
+			startPos = matcher.end();
+
+			// remove generics
+			String linkTarget = matcher.group(2).replace('\r', ' ').replace('\n', ' ');
+			linkTarget = removeGenerics(linkTarget);
+
+			final Matcher linkMatcher = PATTERN_INNER_LINK.matcher(linkTarget);
+			if (linkMatcher.matches()) {
+				// group 1 = class
+				// group 2 = method name
+				// group 3 = params (with parenthesis)
+
+				final String fullClassName = resolveClassName(linkMatcher.group(1));
+				final String methodName = linkMatcher.group(2);
+				String methodParameters = linkMatcher.group(3);
+				if (methodParameters == null)
+					methodParameters = "()";
+
+				if ("link".equals(matcher.group(1))) {
+					// link to java API
+
+					final String link = createMethodLink(methodName, methodParameters);
+
+					if (fullClassName == null) {
+						// link to same document
+						output.append("<a href='" + link + "'>" + methodName + methodParameters + "</a>");
+
+					} else {
+						// external document
+						final String classURL = findClassURL(fullClassName);
+						if (classURL != null) {
+							output.append("<a href='" + classURL + link + "'>");
+							output.append(getSimpleClassName(fullClassName));
+
+							if (methodName != null) {
+								output.append('.');
+								output.append(methodName);
+								if (methodParameters != null)
+									output.append(methodParameters);
+							}
+
+							output.append("</a>");
+
+						} else
+							output.append(fullClassName);
+					}
+
+				} else if ("module".equals(matcher.group(1))) {
+					// link to a scripting module
+					if (fullClassName == null) {
+						// link to same document
+						output.append("<a href='#" + methodName + "'>" + methodName + methodParameters + "</a>");
+					} else {
+						// external document
+						final String moduleId = linkMatcher.group(1);
+						final String pluginId = getPluginIdFromModuleId(moduleId);
+						if (linkMatcher.group(2) != null) {
+							final List<String> parameters = extractParameters(methodParameters);
+							final String parameterString = parameters.stream().map(LinkProvider::getSimpleClassName).collect(Collectors.joining(", "));
+
+							output.append("<a href='../../" + pluginId + "/help/" + AbstractModuleDoclet.createHTMLFileName(linkMatcher.group(1)) + "#"
+									+ methodName + "'>" + getModuleNameFromModuleId(moduleId) + "." + methodName + "(" + parameterString + ")</a>");
+						} else
+							output.append("<a href='../../" + pluginId + "/help/" + AbstractModuleDoclet.createHTMLFileName(moduleId) + "'>"
+									+ getModuleNameFromModuleId(moduleId) + " module</a>");
 					}
 				}
-
-			} else
-				qualifiedName = HTMLWriter.escapeText(qualifiedName);
-
-		} else
-			qualifiedName = HTMLWriter.escapeText(qualifiedName);
-
-		return qualifiedName;
-	}
-
-	private static String findClass(final String name, final ClassDoc baseClass) {
-		try {
-			for (final ClassDoc doc : baseClass.importedClasses()) {
-				if (doc.toString().endsWith(name))
-					return doc.toString();
 			}
-		} catch (final NullPointerException e) {
-			// sometimes thrown by ClassDoc.importedClasses(). Nothing we can do here but ignore
 		}
 
-		final ClassDoc target = baseClass.findClass(name);
-		return (target != null) ? target.toString() : null;
+		if (startPos == 0)
+			return text;
+
+		output.append(text.substring(startPos));
+
+		return output.toString();
+	}
+
+	/**
+	 * Create the link for a javaDoc method documentation.
+	 *
+	 * @param methodName
+	 *            name of method
+	 * @param methodParameters
+	 *            method parameters
+	 * @return link
+	 */
+	private String createMethodLink(String methodName, String methodParameters) {
+		final StringBuilder result = new StringBuilder();
+
+		if (methodName != null) {
+			result.append("#");
+			result.append(methodName);
+
+			final List<String> parameters = extractParameters(methodParameters);
+
+			switch (getApiIdentifier()) {
+			case JAVA_8:
+				result.append('-');
+				result.append(parameters.stream().map(p -> p.replace("[]", ":A")).collect(Collectors.joining("-")));
+				result.append('-');
+				break;
+			// link += methodParameters.replaceAll(" ", "%20");
+			}
+		}
+
+		return result.toString();
+	}
+
+	/**
+	 * Extracts FQ class names for all parameters.
+	 *
+	 * @param parameterString
+	 *            parameter signature including brackets, eg "(int, byte[], Collection&lt;String&gt; data)"
+	 * @return list of FQ class names
+	 */
+	private List<String> extractParameters(String parameterString) {
+		final String[] tokens = parameterString.replaceAll("\\s", "").replaceAll("[()]", "").split(",");
+
+		return Arrays.asList(tokens).stream().map(p -> resolveClassName(p)).collect(Collectors.toList());
+	}
+
+	private String resolveClassName(final String candidate) {
+		if (candidate == null)
+			return null;
+
+		if (candidate.contains("."))
+			return candidate;
+
+		// check for classes in java.lang package
+		if (CLASSES_IN_JAVA_LANG.contains(candidate))
+			return "java.lang." + candidate;
+
+		// check for an import
+		for (final String importStatement : fClassModel.getImportedClasses()) {
+			if (importStatement.endsWith("." + candidate))
+				return importStatement;
+		}
+
+		return candidate;
 	}
 
 	private String findClassURL(String qualifiedName) {
-		if (qualifiedName.contains("<"))
-			qualifiedName = qualifiedName.substring(0, qualifiedName.indexOf("<"));
 
-		if (qualifiedName.contains(".")) {
-			final String packageName = qualifiedName.substring(0, qualifiedName.lastIndexOf('.'));
-
+		final String packageName = getPackageName(qualifiedName);
+		if (!packageName.isEmpty()) {
 			// first run, look for exact package match
 			for (final Entry<String, Collection<String>> entry : fExternalDocs.entrySet()) {
 				if (entry.getValue().contains(packageName))
@@ -115,125 +280,11 @@
 		return null;
 	}
 
-	public String insertLinks(final ClassDoc clazz, final String text) {
-
-		final StringBuilder output = new StringBuilder();
-		int startPos = 0;
-		final Matcher matcher = PATTERN_LINK.matcher(text);
-
-		while (matcher.find()) {
-			output.append(text.substring(startPos, matcher.start()));
-			startPos = matcher.end();
-
-			final Matcher linkMatcher = PATTERN_INNER_LINK.matcher(matcher.group(2).replace('\r', ' ').replace('\n', ' '));
-			if (linkMatcher.matches()) {
-				// group 1 = class
-				// group 2 = method (optional)
-				// group 3 = params (without parenthesis)
-
-				if ("link".equals(matcher.group(1))) {
-					// link to java API
-
-					final StringBuilder link = new StringBuilder();
-					if (linkMatcher.group(2) != null) {
-						link.append("#");
-
-						link.append(linkMatcher.group(2));
-						if (linkMatcher.group(3) != null) {
-							link.append("-");
-
-							for (String parameter : linkMatcher.group(3).split(",")) {
-								parameter = parameter.trim().replace(" ", "");
-								if (parameter.endsWith("]"))
-									link.append(removeGenericsTags(resolveClassName(parameter.substring(0, parameter.indexOf('[')), clazz)));
-								else
-									link.append(removeGenericsTags(resolveClassName(parameter, clazz)));
-
-								while (parameter.endsWith("]")) {
-									link.append(":A");
-									parameter = parameter.substring(0, parameter.lastIndexOf('[')).trim();
-								}
-								link.append("-");
-							}
-
-							if (link.charAt(link.length() - 1) != '-')
-								link.append("-");
-						}
-					}
-
-					if (linkMatcher.group(1) == null) {
-						// link to same document
-						output.append("<a href=\"" + link + "\">" + linkMatcher.group(2)
-								+ ((linkMatcher.group(3) != null) ? "(" + linkMatcher.group(3) + ")" : "") + "</a>");
-					} else {
-						// external document
-
-						final String classURL = findClassURL(resolveClassName(linkMatcher.group(1), clazz));
-						if (classURL != null)
-							output.append("<a href=\"" + classURL + link + "\">");
-
-						output.append(linkMatcher.group(1));
-
-						if (linkMatcher.group(2) != null) {
-							output.append(linkMatcher.group(2));
-							if (linkMatcher.group(3) != null) {
-								output.append('(');
-								output.append(linkMatcher.group(3));
-								output.append(')');
-							}
-						}
-
-						if (classURL != null)
-							output.append("</a>");
-					}
-
-				} else if ("module".equals(matcher.group(1))) {
-					// link to a scripting module
-					if (linkMatcher.group(1) == null) {
-						// link to same document
-						output.append(
-								"<a href=\"#" + linkMatcher.group(2) + "\">" + linkMatcher.group(2) + ((linkMatcher.group(3) != null) ? "()" : "") + "</a>");
-					} else {
-						// external document
-						final String plugin = linkMatcher.group(1).substring(0, linkMatcher.group(1).lastIndexOf('.'));
-						if (linkMatcher.group(2) != null)
-							output.append("<a href=\"../../" + plugin + "/help/" + ModuleDoclet.createHTMLFileName(linkMatcher.group(1)) + "#"
-									+ linkMatcher.group(2) + "\">" + linkMatcher.group(2) + ((linkMatcher.group(3) != null) ? "()" : "") + "</a>");
-						else
-							output.append("<a href=\"../../" + plugin + "/help/" + ModuleDoclet.createHTMLFileName(linkMatcher.group(1)) + "\">"
-									+ capitalizeFirst(linkMatcher.group(1).substring(linkMatcher.group(1).lastIndexOf('.') + 1)) + " module</a>");
-					}
-				}
-			}
-		}
-
-		if (startPos == 0)
-			return text;
-
-		output.append(text.substring(startPos));
-
-		return output.toString();
+	public void setApi(JavaDocApi apiIdentifier) {
+		fApiIdentifier = apiIdentifier;
 	}
 
-	/**
-	 * Remove the generic tags from class name
-	 *
-	 * @param className
-	 *            The complete name of the class, including generic tags
-	 * @return the class name without the generic tags
-	 */
-	public static String removeGenericsTags(String className) {
-		if (className == null) {
-			return null;
-		}
-		final int indexOf = className.indexOf('<');
-		return indexOf < 0 ? className : className.substring(0, className.indexOf('<'));
-	}
-
-	private static String capitalizeFirst(final String content) {
-		if (!content.isEmpty())
-			return content.substring(0, 1).toUpperCase() + content.substring(1);
-
-		return content;
+	public JavaDocApi getApiIdentifier() {
+		return fApiIdentifier;
 	}
 }
diff --git a/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/ModuleDoclet.java b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/ModuleDoclet.java
index d1d3233..e7260d2 100644
--- a/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/ModuleDoclet.java
+++ b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/ModuleDoclet.java
@@ -10,77 +10,60 @@
  *******************************************************************************/
 package org.eclipse.ease.helpgenerator;
 
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.StringReader;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.Collection;
-import java.util.HashMap;
+import java.util.ArrayList;
 import java.util.HashSet;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Properties;
+import java.util.List;
+import java.util.Locale;
 import java.util.Set;
-import java.util.jar.Attributes;
-import java.util.jar.Manifest;
 
-import com.sun.javadoc.ClassDoc;
+import javax.lang.model.SourceVersion;
+
+import org.eclipse.ease.helpgenerator.docletapi.Jep221ModuleDoclet;
+import org.eclipse.ease.helpgenerator.sunapi.Java5ModuleDoclet;
+
 import com.sun.javadoc.DocErrorReporter;
 import com.sun.javadoc.Doclet;
 import com.sun.javadoc.LanguageVersion;
 import com.sun.javadoc.RootDoc;
 
-public class ModuleDoclet extends Doclet {
+import jdk.javadoc.doclet.DocletEnvironment;
+import jdk.javadoc.doclet.Reporter;
 
-	/**
-	 * Method to locally test this doclet. Not needed for productive use.
-	 */
-	public static void main(final String[] args) {
-		String docletProjectRootDir = new File(System.getProperty("user.dir")).getAbsolutePath();
-		docletProjectRootDir = docletProjectRootDir.replaceAll("\\\\", "/");
+public class ModuleDoclet extends Doclet implements jdk.javadoc.doclet.Doclet {
 
-		String repositoryRootDir = new File(System.getProperty("user.dir")).getParentFile().getParent();
-		repositoryRootDir = repositoryRootDir.replaceAll("\\\\", "/");
+	public static final String OPTION_PROJECT_ROOT = "-root";
+	public static final String OPTION_LINK = "-link";
+	public static final String OPTION_LINK_OFFLINE = "-linkoffline";
+	public static final String OPTION_FAIL_ON_HTML_ERRORS = "-failOnHTMLError";
+	public static final String OPTION_FAIL_ON_MISSING_DOCS = "-failOnMissingDocs";
 
-		final String projectDir = repositoryRootDir + "/../org.eclipse.ease.modules/plugins/org.eclipse.ease.modules.platform";
-
-		// @formatter:off
-		final String[] javadocargs = {
-				"-sourcepath", projectDir + "/src",
-				"-root", projectDir,
-				"-doclet", ModuleDoclet.class.getName(),
-				"-docletpath",  docletProjectRootDir + "/bin",
-
-				"-failOnHTMLError", "true",
-				"-failOnMissingDocs", "false",
-
-				"-link", "https://docs.oracle.com/javase/8/docs/api/",
-
-				projectDir.substring(projectDir.lastIndexOf('/') + 1),
-				"org.eclipse.ease"
-		};
-		// @formatter:on
-
-		com.sun.tools.javadoc.Main.execute(javadocargs);
-	}
-
-	private static final String OPTION_PROJECT_ROOT = "-root";
-	private static final Object OPTION_LINK = "-link";
-	private static final Object OPTION_LINK_OFFLINE = "-linkoffline";
-	private static final Object OPTION_FAIL_ON_HTML_ERRORS = "-failOnHTMLError";
-	private static final Object OPTION_FAIL_ON_MISSING_DOCS = "-failOnMissingDocs";
+	// ---------- Java 1.5 API
 
 	public static boolean start(final RootDoc root) {
-		final ModuleDoclet doclet = new ModuleDoclet();
-		return doclet.process(root);
+		final Java5ModuleDoclet doclet = new Java5ModuleDoclet();
+		doclet.setRootDoc(root);
+
+		// parse options
+		final String[][] options = root.options();
+		for (final String[] option : options) {
+
+			if (OPTION_PROJECT_ROOT.equals(option[0]))
+				doclet.setParameter(OPTION_PROJECT_ROOT, List.of(option[1]));
+
+			else if (OPTION_LINK.equals(option[0]))
+				doclet.registerLinks(option[1]);
+
+			else if (OPTION_LINK_OFFLINE.equals(option[0]))
+				doclet.registerOfflineLinks(option[1], option[2] + "/package-list");
+
+			else if (OPTION_FAIL_ON_HTML_ERRORS.equals(option[0]))
+				doclet.setParameter(OPTION_FAIL_ON_HTML_ERRORS, List.of(option[1]));
+
+			else if (OPTION_FAIL_ON_MISSING_DOCS.equals(option[0]))
+				doclet.setParameter(OPTION_FAIL_ON_MISSING_DOCS, List.of(option[1]));
+		}
+
+		return doclet.run();
 	}
 
 	public static LanguageVersion languageVersion() {
@@ -143,419 +126,93 @@
 		return true;
 	}
 
-	/** Maps module.class.name to module definition XML memento. */
-	private Map<String, IMemento> fModuleNodes;
-	private File fRootFolder = null;
-	private final Collection<IMemento> fCategoryNodes = new HashSet<>();
+	// ---------- Java 11 API
 
-	private LinkProvider fLinkProvider;
-	private boolean fFailOnHTMLErrors = true;
-	private boolean fFailOnMissingDocs = false;
+	private AbstractModuleDoclet fModuleDoclet = null;
 
-	private boolean process(final RootDoc root) {
+	@Override
+	public void init(Locale locale, Reporter reporter) {
+		fModuleDoclet = new Jep221ModuleDoclet(reporter);
+	}
 
-		fLinkProvider = new LinkProvider();
+	@Override
+	public String getName() {
+		return getClass().getSimpleName();
+	}
 
-		// parse options
-		final String[][] options = root.options();
-		for (final String[] option : options) {
+	@Override
+	public Set<? extends jdk.javadoc.doclet.Doclet.Option> getSupportedOptions() {
+		final HashSet<Option> options = new HashSet<>();
 
-			if (OPTION_PROJECT_ROOT.equals(option[0]))
-				fRootFolder = new File(option[1]);
+		options.add(new Option(OPTION_PROJECT_ROOT, "Root folder of the plugin (the folder containing the .project file)", "<path>", 1));
+		options.add(new Option(OPTION_FAIL_ON_HTML_ERRORS, "Fail the documentation process when HTML comments are not well formed. Defaults to true.",
+				"<boolean>", 1));
+		options.add(new Option(OPTION_FAIL_ON_MISSING_DOCS,
+				"Fail the documentation process when classes/methods/parameters do miss documentation. Defaults to true.", "<boolean>", 1));
+		options.add(new Option(OPTION_LINK,
+				"Link used classes to existing API documentation. Provide base URI of JavaDoc, eg https://docs.oracle.com/javase/8/docs/api/", "<URL>", 1));
+		options.add(new Option(OPTION_LINK_OFFLINE,
+				"Link used classes to existing API documentation using a given package file. Provide base URI of JavaDoc as first link, URI of package-list file as second link. Only the package-list file needs to be accessible during documentation creation.",
+				"<URL, URL>", 2));
 
-			else if (OPTION_LINK.equals(option[0])) {
-				try {
-					fLinkProvider.registerAddress(option[1], parsePackages(new URL(option[1] + "/package-list").openStream()));
-				} catch (final MalformedURLException e) {
-					System.out.println("Error: cannot parse external URL " + option[1]);
-				} catch (final IOException e) {
-					System.out.println("Error: cannot read from " + option[1]);
+		return options;
+	}
 
-				}
+	@Override
+	public SourceVersion getSupportedSourceVersion() {
+		return SourceVersion.RELEASE_9;
+	}
 
-			} else if (OPTION_LINK_OFFLINE.equals(option[0])) {
+	@Override
+	public boolean run(DocletEnvironment environment) {
+		((Jep221ModuleDoclet) fModuleDoclet).setDocletEnvironment(environment);
+		return ((Jep221ModuleDoclet) fModuleDoclet).run();
+	}
 
-				try {
-					final URL url = new URL(option[2] + "/package-list");
-					fLinkProvider.registerAddress(option[1], parsePackages(url.openStream()));
+	private class Option implements jdk.javadoc.doclet.Doclet.Option {
 
-				} catch (final MalformedURLException e) {
-					// invalid URI
+		private final String fIdentifier;
+		private final String fDescription;
+		private final String fParameters;
+		private final int fArgumentCount;
 
-					try {
-						// try to read from local file
-						fLinkProvider.registerAddress(option[1], parsePackages(new FileInputStream(option[2] + File.separator + "package-list")));
-					} catch (final FileNotFoundException e1) {
-						System.out.println("Error: cannot read from " + option[2]);
-					}
-				} catch (final IOException e) {
-					System.out.println("Error: cannot read from " + option[2]);
-				}
+		public Option(String identifier, String description, String parameters, int argumentCount) {
+			fIdentifier = identifier;
+			fDescription = description;
+			fParameters = parameters;
 
-			} else if (OPTION_FAIL_ON_HTML_ERRORS.equals(option[0])) {
-				fFailOnHTMLErrors = Boolean.parseBoolean(option[1]);
-
-			} else if (OPTION_FAIL_ON_MISSING_DOCS.equals(option[0])) {
-				fFailOnMissingDocs = Boolean.parseBoolean(option[1]);
-			}
+			fArgumentCount = argumentCount;
 		}
 
-		final ClassDoc[] classes = root.classes();
+		@Override
+		public int getArgumentCount() {
+			return fArgumentCount;
+		}
 
-		// write to output file
-		if (fRootFolder != null) {
-			try {
-				// create lookup table with module data
-				createModuleLookupTable();
+		@Override
+		public String getDescription() {
+			return fDescription;
+		}
 
-				// create HTML help files
-				boolean created = createHTMLFiles(classes);
+		@Override
+		public Kind getKind() {
+			return Kind.STANDARD;
+		}
 
-				// create category TOCs
-				created |= createCategories();
+		@Override
+		public List<String> getNames() {
+			return List.of(fIdentifier);
+		}
 
-				if (created) {
-					// some files were created, update project, ...
+		@Override
+		public String getParameters() {
+			return fParameters;
+		}
 
-					// create module TOC files
-					final Set<String> tocFiles = createModuleTOCFiles();
-
-					// update plugin.xml
-					updatePluginXML(fRootFolder, tocFiles);
-
-					// update MANIFEST.MF
-					updateManifest(fRootFolder);
-
-					// update build.properties
-					updateBuildProperties(fRootFolder);
-				}
-
-			} catch (final ContentException e) {
-				return false;
-
-			} catch (final Exception e) {
-				e.printStackTrace();
-				return false;
-			}
-
+		@Override
+		public boolean process(String option, List<String> arguments) {
+			fModuleDoclet.setParameter(option, new ArrayList<>(arguments.subList(0, getArgumentCount())));
 			return true;
 		}
-
-		return false;
-	}
-
-	private static Collection<String> parsePackages(final InputStream inputStream) {
-		final Collection<String> packages = new HashSet<>();
-
-		final BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
-		try {
-			String line = reader.readLine();
-			while (line != null) {
-				packages.add(line);
-				line = reader.readLine();
-			}
-		} catch (final IOException e) {
-			// could not read, ignore
-		}
-
-		return packages;
-	}
-
-	private boolean createCategories() throws IOException {
-		boolean created = false;
-
-		for (final IMemento node : fCategoryNodes) {
-			final XMLMemento memento = XMLMemento.createWriteRoot("toc");
-			memento.putString("label", node.getString("name"));
-			memento.putString("link_to", createCategoryLink(node.getString("parent")));
-
-			final IMemento topicNode = memento.createChild("topic");
-			topicNode.putString("label", node.getString("name"));
-			topicNode.putBoolean("sort", true);
-			topicNode.createChild("anchor").putString("id", "modules_anchor");
-
-			final File targetFile = getChild(getChild(fRootFolder, "help"), createCategoryFileName(node.getString("id")));
-			writeFile(targetFile, memento.toString());
-			created = true;
-		}
-
-		return created;
-	}
-
-	private static String extractCategoryName(final String categoryId) {
-		if (categoryId != null) {
-			final int index = categoryId.indexOf(".category.");
-			if (index != -1)
-				return categoryId.substring(index + ".category.".length());
-		}
-
-		return null;
-	}
-
-	private static String createCategoryLink(final String categoryId) {
-		String pluginID = "org.eclipse.ease.help";
-		if (categoryId != null) {
-			final int index = categoryId.indexOf(".category.");
-			if (index != -1)
-				pluginID = categoryId.substring(0, index);
-		}
-
-		return "../" + pluginID + "/help/" + createCategoryFileName(categoryId) + "#modules_anchor";
-	}
-
-	private static String createCategoryFileName(final String categoryId) {
-		final String category = extractCategoryName(categoryId);
-		return (category != null) ? "category_" + category + ".xml" : "reference.xml";
-	}
-
-	private File getChild(final File folder, final String name) {
-		// if the folder exists, it needs to be a directory
-		// if it does not exist, it will be created by the writeFile() method
-		if ((folder.isDirectory()) || (!folder.exists()))
-			return new File(folder.getPath() + File.separator + name);
-
-		return null;
-	}
-
-	private void updateManifest(final File rootFolder) throws IOException {
-		final File manifestFile = getChild(getChild(rootFolder, "META-INF"), "MANIFEST.MF");
-
-		final Manifest manifest = new Manifest();
-		manifest.read(new FileInputStream(manifestFile));
-
-		final Attributes mainAttributes = manifest.getMainAttributes();
-		final String require = mainAttributes.getValue("Require-Bundle");
-
-		if ((require == null) || (require.isEmpty()))
-			mainAttributes.putValue("Require-Bundle", "org.eclipse.help;bundle-version=\"[3.5.0,4.0.0)\"");
-
-		else if (!require.contains("org.eclipse.help"))
-			mainAttributes.putValue("Require-Bundle", "org.eclipse.help;bundle-version=\"[3.5.0,4.0.0)\"," + require);
-
-		else
-			// manifest contains reference to org.eclipse.help, bail out
-			return;
-
-		final FileOutputStream out = new FileOutputStream(manifestFile);
-		manifest.write(out);
-		out.close();
-	}
-
-	private void updateBuildProperties(final File rootFolder) throws IOException {
-		final File buildFile = getChild(rootFolder, "build.properties");
-
-		final Properties properties = new Properties();
-		properties.load(new FileInputStream(buildFile));
-		final String property = properties.getProperty("bin.includes");
-		if (!property.contains("help/")) {
-			if (property.trim().isEmpty())
-				properties.setProperty("bin.includes", "help/");
-			else
-				properties.setProperty("bin.includes", "help/," + property.trim());
-
-			final FileOutputStream out = new FileOutputStream(buildFile);
-			properties.store(out, "");
-			out.close();
-		}
-	}
-
-	private void updatePluginXML(final File rootFolder, final Collection<String> tocs) throws Exception {
-		final HashSet<String> toDo = new HashSet<>(tocs);
-
-		File pluginFile = getChild(rootFolder, "plugin.xml");
-		if (!pluginFile.exists())
-			pluginFile = getChild(rootFolder, "fragment.xml");
-
-		final XMLMemento memento = XMLMemento.createReadRoot(new InputStreamReader(new FileInputStream(pluginFile)));
-		for (final IMemento extensionNode : memento.getChildren("extension")) {
-			final String extensionPoint = extensionNode.getString("point");
-			if ("org.eclipse.help.toc".equals(extensionPoint)) {
-				// a help topic is already registered
-				for (final IMemento tocNode : extensionNode.getChildren("toc")) {
-					final String tocLocation = tocNode.getString("file");
-					if (tocLocation.length() > 5)
-						toDo.remove(tocLocation.substring(5));
-				}
-			}
-		}
-
-		for (final String fileLocation : toDo) {
-			// some TOCs not registered yet
-			final IMemento extensionNode = memento.createChild("extension");
-			extensionNode.putString("point", "org.eclipse.help.toc");
-			final IMemento tocNode = extensionNode.createChild("toc");
-			tocNode.putString("file", "help/" + fileLocation);
-			tocNode.putBoolean("primary", false);
-
-		}
-
-		if (!toDo.isEmpty())
-			// we had to modify the file
-			writeFile(pluginFile, memento.toString().replace("&#x0A;", "\n"));
-	}
-
-	private Set<String> createModuleTOCFiles() throws IOException {
-		final Map<String, IMemento> tocDefinitions = new HashMap<>();
-
-		// create categories
-		for (final IMemento categoryDefinition : fCategoryNodes) {
-			final XMLMemento memento = XMLMemento.createWriteRoot("toc");
-			memento.putString("label", categoryDefinition.getString("name"));
-			memento.putString("link_to", createCategoryLink(categoryDefinition.getString("parent")));
-
-			final IMemento topicNode = memento.createChild("topic");
-			topicNode.putString("label", categoryDefinition.getString("name"));
-			topicNode.putBoolean("sort", true);
-
-			topicNode.createChild("anchor").putString("id", "modules_anchor");
-			tocDefinitions.put(createCategoryFileName(categoryDefinition.getString("id")), memento);
-		}
-
-		// create modules
-		if (!fModuleNodes.isEmpty()) {
-
-			for (final IMemento moduleDefinition : fModuleNodes.values()) {
-				final String categoryID = moduleDefinition.getString("category");
-				final String fileName = createCategoryFileName(categoryID).replace("category_", "modules_");
-
-				IMemento memento;
-				if (tocDefinitions.containsKey(fileName))
-					memento = tocDefinitions.get(fileName);
-
-				else {
-					memento = XMLMemento.createWriteRoot("toc");
-					memento.putString("label", "Modules");
-					memento.putString("link_to", createCategoryLink(categoryID));
-
-					tocDefinitions.put(fileName, memento);
-				}
-
-				final IMemento topicNode = memento.createChild("topic");
-				topicNode.putString("href", "help/" + createHTMLFileName(moduleDefinition.getString("id")));
-				topicNode.putString("label", moduleDefinition.getString("name"));
-			}
-		}
-
-		for (final Entry<String, IMemento> entry : tocDefinitions.entrySet()) {
-			final File targetFile = getChild(getChild(fRootFolder, "help"), entry.getKey());
-			writeFile(targetFile, entry.getValue().toString());
-		}
-
-		return tocDefinitions.keySet();
-	}
-
-	public static String createHTMLFileName(final String moduleID) {
-		return "module_" + escape(moduleID) + ".html";
-	}
-
-	/**
-	 * Create HTML help pages for module classes.
-	 *
-	 * @param classes
-	 * @return <code>true</code> when at least 1 HTML file was created
-	 * @throws ContentException
-	 *             on error within the java comments
-	 * @throws Exception
-	 *             on file creation errors
-	 */
-	private boolean createHTMLFiles(final ClassDoc[] classes) throws IOException, ContentException {
-		boolean createdFiles = false;
-		boolean documentationErrors = false;
-		boolean invalidFileContent = false;
-
-		for (final ClassDoc clazz : classes) {
-
-			// only add classes which are registered in our modules lookup table
-			if (fModuleNodes.containsKey(clazz.qualifiedName())) {
-				// class found to create help for
-				final HTMLWriter htmlWriter = new HTMLWriter(clazz, fLinkProvider, fModuleNodes.get(clazz.qualifiedName()).getChildren("dependency"));
-				final String content = htmlWriter.createContents(fModuleNodes.get(clazz.qualifiedName()).getString("name"));
-
-				if (!htmlWriter.getDocumentationErrors().isEmpty()) {
-					documentationErrors = true;
-
-					// print errors
-					System.out.println((fFailOnMissingDocs ? "ERROR" : "WARNING") + ": missing documentation content for " + clazz.name() + ":");
-					for (final String errorMessage : htmlWriter.getDocumentationErrors())
-						System.out.println("\t" + errorMessage);
-
-					System.out.println("");
-				}
-
-				try {
-					verifyContent(content);
-				} catch (final Exception e) {
-					System.out.println((fFailOnHTMLErrors ? "ERROR" : "WARNING") + ": invalid file content for " + clazz.name() + ":");
-					System.out.println("\t" + e.getMessage());
-					System.out.println("");
-
-					invalidFileContent = true;
-				}
-
-				// write document
-				final File targetFile = getChild(getChild(fRootFolder, "help"), createHTMLFileName(fModuleNodes.get(clazz.qualifiedName()).getString("id")));
-				writeFile(targetFile, content);
-				createdFiles = true;
-			}
-		}
-
-		if ((fFailOnMissingDocs) && (documentationErrors))
-			throw new ContentException("Documentation is not complete");
-
-		if ((fFailOnHTMLErrors) && (invalidFileContent))
-			throw new ContentException("Documentation invalid");
-
-		return createdFiles;
-	}
-
-	/**
-	 * Verifies that the HTML content is well formed and correct. This guarantees that the code can be displayed in help hovers and code completion proposals.
-	 *
-	 * @throws Exception
-	 *             when content is not well formed
-	 */
-	private void verifyContent(String content) throws Exception {
-		// try to read content into an XMLMemento
-		XMLMemento.createReadRoot(new StringReader(content));
-	}
-
-	private static void writeFile(final File file, final String data) throws IOException {
-		if (!file.getParentFile().exists())
-			file.getParentFile().mkdirs();
-
-		// save data to file
-		if (!file.exists())
-			file.createNewFile();
-
-		final FileWriter writer = new FileWriter(file);
-		writer.write(data);
-		writer.close();
-	}
-
-	private static String escape(final String data) {
-		return data.replace(' ', '_').toLowerCase();
-	}
-
-	private void createModuleLookupTable() {
-		fModuleNodes = new HashMap<>();
-
-		// read plugin.xml
-		File pluginXML = getChild(fRootFolder, "plugin.xml");
-		if (!pluginXML.exists())
-			pluginXML = getChild(fRootFolder, "fragment.xml");
-
-		try {
-			final IMemento root = XMLMemento.createReadRoot(new InputStreamReader(new FileInputStream(pluginXML)));
-			for (final IMemento extensionNode : root.getChildren("extension")) {
-				if ("org.eclipse.ease.modules".equals(extensionNode.getString("point"))) {
-					for (final IMemento instanceNode : extensionNode.getChildren("module"))
-						fModuleNodes.put(instanceNode.getString("class"), instanceNode);
-
-					for (final IMemento instanceNode : extensionNode.getChildren("category"))
-						fCategoryNodes.add(instanceNode);
-				}
-			}
-		} catch (final Exception e) {
-		}
 	}
 }
diff --git a/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/docletapi/Jep221ClassModel.java b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/docletapi/Jep221ClassModel.java
new file mode 100644
index 0000000..b700e39
--- /dev/null
+++ b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/docletapi/Jep221ClassModel.java
@@ -0,0 +1,345 @@
+/*******************************************************************************
+ * Copyright (c) 2019 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.helpgenerator.docletapi;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.NoType;
+import javax.lang.model.util.ElementScanner9;
+
+import org.eclipse.ease.helpgenerator.model.AbstractClassModel;
+import org.eclipse.ease.helpgenerator.model.Description;
+import org.eclipse.ease.helpgenerator.model.ExceptionValue;
+import org.eclipse.ease.helpgenerator.model.Field;
+import org.eclipse.ease.helpgenerator.model.Method;
+import org.eclipse.ease.helpgenerator.model.Parameter;
+import org.eclipse.ease.helpgenerator.model.ReturnValue;
+import org.eclipse.ease.helpgenerator.model.ScriptExample;
+import org.eclipse.ease.modules.ScriptParameter;
+import org.eclipse.ease.modules.WrapToScript;
+
+import com.sun.source.doctree.DeprecatedTree;
+import com.sun.source.doctree.DocCommentTree;
+import com.sun.source.doctree.DocTree;
+import com.sun.source.doctree.ParamTree;
+import com.sun.source.doctree.ReturnTree;
+import com.sun.source.doctree.ThrowsTree;
+import com.sun.source.doctree.UnknownBlockTagTree;
+import com.sun.source.tree.ImportTree;
+import com.sun.source.util.SimpleDocTreeVisitor;
+import com.sun.source.util.SimpleTreeVisitor;
+import com.sun.tools.javac.code.Symbol.ClassSymbol;
+import com.sun.tools.javac.code.Type;
+
+import jdk.javadoc.doclet.DocletEnvironment;
+
+public class Jep221ClassModel extends AbstractClassModel {
+
+	private final ClassSymbol fClassSymbol;
+	private final DocletEnvironment fEnvironment;
+
+	public Jep221ClassModel(DocletEnvironment environment, ClassSymbol classSymbol) {
+		fEnvironment = environment;
+		fClassSymbol = classSymbol;
+	}
+
+	@Override
+	public void populateModel() {
+		setClassName(fClassSymbol.className());
+
+		setClassDocumentation(new Description(getCommentFor(fClassSymbol)));
+
+		final boolean hasAnnotation = hasWrapToScriptAnnotation();
+
+		setExportedFields(new FieldScanner().scan(fClassSymbol, hasAnnotation));
+		setExportedMethods(new MethodScanner().scan(fClassSymbol, hasAnnotation));
+
+		setImportedClasses(Collections.emptyList());
+	}
+
+	private String getCommentFor(Element element) {
+		final DocCommentTree elementComment = fEnvironment.getDocTrees().getDocCommentTree(element);
+		if (elementComment != null) {
+			final List<? extends DocTree> body = elementComment.getFullBody();
+			return body.stream().map(e -> e.toString()).collect(Collectors.joining());
+		}
+
+		return null;
+	}
+
+	private boolean hasWrapToScriptAnnotation() {
+		return new WrapToScriptScanner().scan(fClassSymbol);
+	}
+
+	private class ImportScanner extends SimpleTreeVisitor<Void, Void> {
+
+		@Override
+		public Void visitImport(ImportTree node, Void p) {
+			// TODO Auto-generated method stub
+			return super.visitImport(node, p);
+		}
+	}
+
+	private abstract class RecursiveScanner<R, P> extends ElementScanner9<R, P> {
+
+		@Override
+		public R scan(Element e, P p) {
+			super.scan(e, p);
+
+			if (e instanceof ClassSymbol) {
+				for (final Type iface : ((ClassSymbol) e).getInterfaces())
+					scan(iface.asElement(), p);
+
+				final Type superclass = ((ClassSymbol) e).getSuperclass();
+				if (!(superclass instanceof NoType))
+					scan(superclass.asElement(), p);
+			}
+
+			return getResult();
+		}
+
+		protected abstract R getResult();
+	}
+
+	private class WrapToScriptScanner extends RecursiveScanner<Boolean, Void> {
+
+		private boolean fHasAnnotation = false;
+
+		@Override
+		public Boolean visitExecutable(ExecutableElement e, Void p) {
+			final WrapToScript annotation = e.getAnnotation(WrapToScript.class);
+			fHasAnnotation |= (annotation != null);
+
+			return getResult();
+		}
+
+		@Override
+		public Boolean visitVariable(VariableElement e, Void p) {
+			final WrapToScript annotation = e.getAnnotation(WrapToScript.class);
+			fHasAnnotation |= (annotation != null);
+
+			return getResult();
+		}
+
+		@Override
+		protected Boolean getResult() {
+			return fHasAnnotation;
+		}
+	}
+
+	private class FieldScanner extends RecursiveScanner<List<Field>, Boolean> {
+
+		private final List<Field> fFields = new ArrayList<>();
+
+		@Override
+		public List<Field> visitVariable(VariableElement e, Boolean p) {
+
+			if (!p || (e.getAnnotation(WrapToScript.class) != null)) {
+				if ((e.getModifiers().contains(Modifier.STATIC)) && (e.getModifiers().contains(Modifier.PUBLIC))
+						&& (e.getModifiers().contains(Modifier.FINAL))) {
+
+					final DocCommentTree tree = fEnvironment.getDocTrees().getDocCommentTree(e);
+					String deprecationText = new DeprecationMessageScanner().visit(tree, null);
+					if ((deprecationText == null) && (e.getAnnotation(Deprecated.class) != null))
+						deprecationText = "";
+
+					fFields.add(new Field(e.getSimpleName().toString(), getCommentFor(e), deprecationText));
+				}
+			}
+
+			return super.visitVariable(e, p);
+		}
+
+		@Override
+		protected List<Field> getResult() {
+			return fFields;
+		}
+	}
+
+	private class MethodScanner extends RecursiveScanner<List<Method>, Boolean> {
+
+		private final List<Method> fMethods = new ArrayList<>();
+
+		@Override
+		public List<Method> visitExecutable(ExecutableElement e, Boolean hasWrapToScriptAnnotation) {
+			final Method registeredMethod = findRegisteredMethod(e.getSimpleName().toString());
+
+			final WrapToScript wrapAnnotation = e.getAnnotation(WrapToScript.class);
+			if (!hasWrapToScriptAnnotation || (wrapAnnotation != null) || (registeredMethod != null)) {
+				if (e.getModifiers().contains(Modifier.PUBLIC)) {
+
+					final DocCommentTree tree = fEnvironment.getDocTrees().getDocCommentTree(e);
+					String deprecationText = new DeprecationMessageScanner().visit(tree, null);
+					if ((deprecationText == null) && (e.getAnnotation(Deprecated.class) != null))
+						deprecationText = "";
+
+					final Collection<String> aliases = new HashSet<>();
+					if (wrapAnnotation != null) {
+						final String candidates = wrapAnnotation.alias();
+						for (final String token : candidates.split("[,;]")) {
+							if (!token.trim().isEmpty())
+								aliases.add(token.trim());
+						}
+					}
+
+					final ReturnValue returnValue = new ReturnValue(e.getReturnType().toString(), new ReturnTypeMessageScanner().visit(tree, null));
+
+					final List<Parameter> parameters = e.getParameters().stream().map(param -> {
+						final ScriptParameter scriptParameter = param.getAnnotation(ScriptParameter.class);
+						final String defaultValue = (scriptParameter != null) ? scriptParameter.defaultValue() : null;
+
+						final String comment = new ParameterCommentScanner().visit(tree, param.getSimpleName().toString());
+
+						return new Parameter(param.getSimpleName().toString(), param.asType().toString(), comment, defaultValue);
+					}).collect(Collectors.toList());
+
+					final List<ExceptionValue> exceptions = e.getThrownTypes().stream().map(type -> {
+						return new ExceptionValue(type.toString(), new ExceptionMessageScanner().visit(tree, type.toString()));
+					}).collect(Collectors.toList());
+
+					final List<ScriptExample> examples = new ScriptExampleScanner().visit(tree, null);
+
+					addMethod(new Method(e.getSimpleName().toString(), getCommentFor(e), deprecationText, aliases, returnValue, parameters, exceptions,
+							examples));
+				}
+			}
+
+			return getResult();
+		}
+
+		private Method findRegisteredMethod(String methodName) {
+			return fMethods.stream().filter(m -> methodName.equals(m.getName())).findFirst().orElse(null);
+		}
+
+		private void addMethod(Method method) {
+			final Method existingMethod = findRegisteredMethod(method.getName());
+			if (existingMethod != null)
+				existingMethod.fetchDetailsFrom(method);
+			else
+				fMethods.add(method);
+		}
+
+		@Override
+		protected List<Method> getResult() {
+			return fMethods;
+		}
+	}
+
+	private class ScriptExampleScanner extends SimpleDocTreeVisitor<List<ScriptExample>, Void> {
+
+		private final List<ScriptExample> fScriptExamples = new ArrayList<>();
+
+		@Override
+		public List<ScriptExample> visitDocComment(DocCommentTree node, Void p) {
+			visit(node.getBlockTags(), p);
+
+			return fScriptExamples;
+		}
+
+		@Override
+		public List<ScriptExample> visitUnknownBlockTag(UnknownBlockTagTree node, Void p) {
+			final String content = node.getContent().stream().map(e -> e.toString()).collect(Collectors.joining());
+			fScriptExamples.add(new ScriptExample(content));
+
+			return fScriptExamples;
+		}
+	}
+
+	private class ParameterCommentScanner extends SimpleDocTreeVisitor<String, String> {
+
+		private String fComment;
+
+		@Override
+		public String visitDocComment(DocCommentTree node, String p) {
+			visit(node.getBlockTags(), p);
+
+			return fComment;
+		}
+
+		@Override
+		public String visitParam(ParamTree node, String p) {
+			final List<? extends DocTree> body = node.getDescription();
+			if (p.equals(node.getName().toString()))
+				fComment = body.stream().map(e -> e.toString()).collect(Collectors.joining());
+
+			return super.visitParam(node, p);
+		}
+	}
+
+	private class ExceptionMessageScanner extends SimpleDocTreeVisitor<String, String> {
+		private String fComment;
+
+		@Override
+		public String visitDocComment(DocCommentTree node, String p) {
+			visit(node.getBlockTags(), p);
+
+			return fComment;
+		}
+
+		@Override
+		public String visitThrows(ThrowsTree node, String p) {
+			final List<? extends DocTree> body = node.getDescription();
+			if (p.endsWith(node.getExceptionName().toString()))
+				fComment = body.stream().map(e -> e.toString()).collect(Collectors.joining());
+
+			return super.visitThrows(node, p);
+		}
+	}
+
+	private class ReturnTypeMessageScanner extends SimpleDocTreeVisitor<String, Void> {
+
+		String fDocumentation;
+
+		@Override
+		public String visitDocComment(DocCommentTree node, Void p) {
+			visit(node.getBlockTags(), p);
+
+			return fDocumentation;
+		}
+
+		@Override
+		public String visitReturn(ReturnTree node, Void p) {
+			final List<? extends DocTree> body = node.getDescription();
+			fDocumentation = body.stream().map(e -> e.toString()).collect(Collectors.joining());
+
+			return fDocumentation;
+		}
+	}
+
+	private class DeprecationMessageScanner extends SimpleDocTreeVisitor<String, Void> {
+		String fDocumentation;
+
+		@Override
+		public String visitDocComment(DocCommentTree node, Void p) {
+			visit(node.getBlockTags(), p);
+
+			return fDocumentation;
+		}
+
+		@Override
+		public String visitDeprecated(DeprecatedTree node, Void p) {
+			final List<? extends DocTree> body = node.getBody();
+			fDocumentation = body.stream().map(e -> e.toString()).collect(Collectors.joining());
+
+			return fDocumentation;
+		}
+	}
+}
diff --git a/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/docletapi/Jep221ModuleDoclet.java b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/docletapi/Jep221ModuleDoclet.java
new file mode 100644
index 0000000..71064bc
--- /dev/null
+++ b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/docletapi/Jep221ModuleDoclet.java
@@ -0,0 +1,105 @@
+/*******************************************************************************
+ * Copyright (c) 2019 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.helpgenerator.docletapi;
+
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.tools.Diagnostic.Kind;
+
+import org.eclipse.ease.helpgenerator.AbstractModuleDoclet;
+import org.eclipse.ease.helpgenerator.IReporter;
+import org.eclipse.ease.helpgenerator.model.AbstractClassModel;
+import org.eclipse.ease.helpgenerator.model.ModuleDefinition;
+
+import com.sun.tools.javac.code.Symbol.ClassSymbol;
+
+import jdk.javadoc.doclet.DocletEnvironment;
+import jdk.javadoc.doclet.Reporter;
+
+public class Jep221ModuleDoclet extends AbstractModuleDoclet {
+
+	private DocletEnvironment fEnvironment;
+	private final Reporter fReporter;
+
+	public Jep221ModuleDoclet(Reporter reporter) {
+		fReporter = reporter;
+	}
+
+	@Override
+	protected IReporter createReporter() {
+		return new Java11DocletReporter(fReporter);
+	}
+
+	public void setDocletEnvironment(DocletEnvironment environment) {
+		fEnvironment = environment;
+	}
+
+	@Override
+	protected AbstractClassModel getClassModel(ModuleDefinition module) {
+		for (final Element element : fEnvironment.getIncludedElements()) {
+			if (ElementKind.CLASS.equals(element.getKind())) {
+				final ClassSymbol classSymbol = ((ClassSymbol) element);
+
+				if (classSymbol.className().equals(module.getClassName())) {
+					final AbstractClassModel classModel = new Jep221ClassModel(fEnvironment, classSymbol);
+					classModel.populateModel();
+					return classModel;
+				}
+			}
+		}
+
+		return null;
+	}
+
+	private class Java11DocletReporter implements IReporter {
+
+		private final Reporter fReporter;
+		private boolean fHasErrors = false;
+
+		public Java11DocletReporter(Reporter reporter) {
+			fReporter = reporter;
+		}
+
+		@Override
+		public void report(int status, String message) {
+			switch (status) {
+			case INFO:
+				fReporter.print(Kind.NOTE, message);
+				break;
+			case WARNING:
+				fReporter.print(Kind.WARNING, message);
+				break;
+			case ERROR:
+				// fall through
+			default:
+				fReporter.print(Kind.ERROR, message);
+				fHasErrors = true;
+				break;
+			}
+		}
+
+		@Override
+		public boolean hasErrors() {
+			return fHasErrors;
+		}
+
+		@Override
+		public void reportMissingDocs(String message) {
+			report(failOnMissingDocs() ? ERROR : WARNING, message);
+		}
+
+		@Override
+		public void reportInvalidHtml(String message) {
+			report(failOnHtmlErrors() ? ERROR : WARNING, message);
+		}
+	}
+}
diff --git a/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/model/AbstractClassModel.java b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/model/AbstractClassModel.java
new file mode 100644
index 0000000..66c6665
--- /dev/null
+++ b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/model/AbstractClassModel.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * Copyright (c) 2019 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.helpgenerator.model;
+
+import java.util.Collection;
+import java.util.List;
+
+public abstract class AbstractClassModel {
+
+	private String fClassName;
+	private Description fClassDocumentation;
+	private List<Field> fExportedFields;
+	private List<Method> fExportedMethods;
+	private Collection<String> fImportedClasses;
+
+	public String getClassName() {
+		return fClassName;
+	}
+
+	public Description getClassDocumentation() {
+		return fClassDocumentation;
+	}
+
+	public List<Field> getExportedFields() {
+		return fExportedFields;
+	}
+
+	public List<Method> getExportedMethods() {
+		return fExportedMethods;
+	}
+
+	public Collection<String> getImportedClasses() {
+		return fImportedClasses;
+	}
+
+	protected void setClassName(String className) {
+		fClassName = className;
+	}
+
+	protected void setClassDocumentation(Description classDocumentation) {
+		fClassDocumentation = classDocumentation;
+	}
+
+	protected void setExportedFields(List<Field> exportedFields) {
+		fExportedFields = exportedFields;
+		fExportedFields.sort((a, b) -> a.getName().compareTo(b.getName()));
+	}
+
+	protected void setExportedMethods(List<Method> exportedMethods) {
+		fExportedMethods = exportedMethods;
+		fExportedMethods.sort((a, b) -> a.getName().compareTo(b.getName()));
+	}
+
+	public void setImportedClasses(Collection<String> importedClasses) {
+		fImportedClasses = importedClasses;
+	}
+
+	public abstract void populateModel();
+
+}
diff --git a/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/model/Category.java b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/model/Category.java
new file mode 100644
index 0000000..e584ead
--- /dev/null
+++ b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/model/Category.java
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * Copyright (c) 2019 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.helpgenerator.model;
+
+import org.eclipse.ease.helpgenerator.IMemento;
+
+public class Category {
+
+	private final IMemento fDefinition;
+
+	public Category(IMemento definition) {
+		fDefinition = definition;
+	}
+
+	public String getName() {
+		return fDefinition.getString("name");
+	}
+
+	public String getHelpLink() {
+		return createCategoryLink(fDefinition.getString("parent"));
+	}
+
+	public String getFileName() {
+		return createCategoryFileName(fDefinition.getString("id"));
+	}
+
+	public static String createCategoryLink(final String categoryId) {
+		String pluginID = "org.eclipse.ease.help";
+		if (categoryId != null) {
+			final int index = categoryId.indexOf(".category.");
+			if (index != -1)
+				pluginID = categoryId.substring(0, index);
+		}
+
+		return "../" + pluginID + "/help/" + createCategoryFileName(categoryId) + "#modules_anchor";
+	}
+
+	public static String createCategoryFileName(final String categoryId) {
+		final String category = extractCategoryName(categoryId);
+		return (category != null) ? "category_" + category + ".xml" : "reference.xml";
+	}
+
+	private static String extractCategoryName(final String categoryId) {
+		if (categoryId != null) {
+			final int index = categoryId.indexOf(".category.");
+			if (index != -1)
+				return categoryId.substring(index + ".category.".length());
+		}
+
+		return null;
+	}
+}
diff --git a/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/model/Description.java b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/model/Description.java
new file mode 100644
index 0000000..c015ca9
--- /dev/null
+++ b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/model/Description.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 2019 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.helpgenerator.model;
+
+public class Description {
+
+	private final String fComment;
+
+	public Description(String comment) {
+		fComment = comment;
+	}
+
+	public String getComment() {
+		return fComment;
+	}
+
+	public String getFirstSentence() {
+		if (isEmpty())
+			return null;
+
+		final int pos = getComment().indexOf('.');
+		return (pos > 0) ? getComment().substring(0, pos + 1) : getComment();
+	}
+
+	@Override
+	public String toString() {
+		return getComment();
+	}
+
+	public boolean isEmpty() {
+		return (getComment() == null) || getComment().isEmpty();
+	}
+}
diff --git a/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/model/ExceptionValue.java b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/model/ExceptionValue.java
new file mode 100644
index 0000000..8aea7fc
--- /dev/null
+++ b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/model/ExceptionValue.java
@@ -0,0 +1,19 @@
+/*******************************************************************************
+ * Copyright (c) 2019 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.helpgenerator.model;
+
+public class ExceptionValue extends Parameter {
+
+	public ExceptionValue(String typeName, String comment) {
+		super(typeName, typeName, comment, null);
+	}
+}
diff --git a/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/model/Field.java b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/model/Field.java
new file mode 100644
index 0000000..83a41a9
--- /dev/null
+++ b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/model/Field.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright (c) 2019 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.helpgenerator.model;
+
+public class Field {
+
+	private Description fComment;
+	private final String fName;
+	private String fDeprecationMessage;
+
+	public Field(String name, String comment, String deprecationMessage) {
+		fName = name;
+		fComment = new Description(comment);
+		fDeprecationMessage = deprecationMessage;
+	}
+
+	public String getName() {
+		return fName;
+	}
+
+	public Description getComment() {
+		return fComment;
+	}
+
+	public String getDeprecationMessage() {
+		return fDeprecationMessage;
+	}
+
+	public boolean isDeprecated() {
+		return fDeprecationMessage != null;
+	}
+
+	public void setComment(Description comment) {
+		fComment = comment;
+	}
+
+	public void setDeprecationMessage(String deprecationMessage) {
+		fDeprecationMessage = deprecationMessage;
+	}
+}
diff --git a/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/model/Method.java b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/model/Method.java
new file mode 100644
index 0000000..bc59828
--- /dev/null
+++ b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/model/Method.java
@@ -0,0 +1,95 @@
+/*******************************************************************************
+ * Copyright (c) 2019 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.helpgenerator.model;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+public class Method extends Field {
+
+	private final Collection<String> fAliases;
+	private final List<Parameter> fParameters;
+	private final List<ExceptionValue> fExceptions;
+	private List<ScriptExample> fExamples;
+	private final ReturnValue fReturnType;
+
+	public Method(String name, String comment, String deprecationMessage, Collection<String> aliases, ReturnValue returnType, List<Parameter> parameters,
+			List<ExceptionValue> exceptions, List<ScriptExample> examples) {
+		super(name, comment, deprecationMessage);
+
+		fAliases = aliases;
+		fReturnType = returnType;
+		fParameters = parameters;
+		fExceptions = exceptions;
+		fExamples = (examples != null) ? examples : Collections.emptyList();
+	}
+
+	public Collection<String> getAliases() {
+		return fAliases;
+	}
+
+	public List<Parameter> getParameters() {
+		return fParameters;
+	}
+
+	public ReturnValue getReturnType() {
+		return fReturnType;
+	}
+
+	public List<ExceptionValue> getExceptions() {
+		return fExceptions;
+	}
+
+	public List<ScriptExample> getExamples() {
+		return fExamples;
+	}
+
+	public void fetchDetailsFrom(Method method) {
+		if (getComment().isEmpty())
+			setComment(method.getComment());
+
+		if (getDeprecationMessage() == null)
+			setDeprecationMessage(method.getDeprecationMessage());
+
+		if (getExamples().isEmpty())
+			fExamples = method.getExamples();
+
+		if (getReturnType().getComment().isEmpty())
+			getReturnType().setComment(method.getReturnType().getComment());
+
+		for (final Parameter parameter : getParameters()) {
+			if (parameter.getComment().isEmpty()) {
+				final Optional<Parameter> candidate = method.getParameters().stream().filter(p -> p.getName().equals(parameter.getName())).findFirst();
+				if (candidate.isPresent())
+					parameter.setComment(candidate.get().getComment());
+			}
+		}
+
+		for (final ExceptionValue exception : getExceptions()) {
+			if (exception.getComment().isEmpty()) {
+				final Optional<ExceptionValue> candidate = method.getExceptions().stream().filter(e -> e.getTypeName().equals(exception.getTypeName()))
+						.findFirst();
+				if (candidate.isPresent())
+					exception.setComment(candidate.get().getComment());
+			}
+		}
+	}
+
+	@Override
+	public String toString() {
+		final String parameters = getParameters().stream().map(p -> p.getName()).collect(Collectors.joining(", "));
+		return getReturnType().getTypeName() + " " + getName() + "(" + parameters + ")";
+	}
+}
diff --git a/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/model/ModuleDefinition.java b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/model/ModuleDefinition.java
new file mode 100644
index 0000000..fb3c1e0
--- /dev/null
+++ b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/model/ModuleDefinition.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (c) 2019 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.helpgenerator.model;
+
+import java.util.Collection;
+import java.util.HashSet;
+
+import org.eclipse.ease.helpgenerator.IMemento;
+
+public class ModuleDefinition {
+	private final IMemento fDefinition;
+
+	public ModuleDefinition(IMemento definition) {
+		fDefinition = definition;
+	}
+
+	public String getId() {
+		return fDefinition.getString("id");
+	}
+
+	public String getName() {
+		return fDefinition.getString("name");
+	}
+
+	public String getCategoryId() {
+		return fDefinition.getString("category");
+	}
+
+	public String getClassName() {
+		return fDefinition.getString("class");
+	}
+
+	public boolean hasDependencies() {
+		return !getDependencies().isEmpty();
+	}
+
+	public Collection<ModuleDefinition> getDependencies() {
+		final Collection<ModuleDefinition> dependencies = new HashSet<>();
+		for (final IMemento node : fDefinition.getChildren("dependency"))
+			dependencies.add(new ModuleDefinition(null) {
+
+				@Override
+				public String getId() {
+					return node.getString("module");
+				}
+			});
+
+		return dependencies;
+	}
+}
diff --git a/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/model/Parameter.java b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/model/Parameter.java
new file mode 100644
index 0000000..c57707f
--- /dev/null
+++ b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/model/Parameter.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright (c) 2019 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.helpgenerator.model;
+
+public class Parameter {
+
+	private Description fComment;
+	private final String fName;
+
+	private final String fTypeName;
+
+	private final String fDefaultValue;
+
+	public Parameter(String name, String typeName, String comment, String defaultValue) {
+		fName = name;
+		fTypeName = typeName;
+		fDefaultValue = defaultValue;
+		fComment = new Description(comment);
+	}
+
+	public String getName() {
+		return fName;
+	}
+
+	public Description getComment() {
+		return fComment;
+	}
+
+	public String getTypeName() {
+		return fTypeName;
+	}
+
+	public String getDefaultValue() {
+		return fDefaultValue;
+	}
+
+	public void setComment(Description comment) {
+		fComment = comment;
+	}
+}
diff --git a/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/model/ReturnValue.java b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/model/ReturnValue.java
new file mode 100644
index 0000000..81570f8
--- /dev/null
+++ b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/model/ReturnValue.java
@@ -0,0 +1,23 @@
+/*******************************************************************************
+ * Copyright (c) 2019 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.helpgenerator.model;
+
+public class ReturnValue extends Parameter {
+
+	public ReturnValue(String typeName, String comment) {
+		super(null, typeName, comment, null);
+	}
+
+	public boolean isVoid() {
+		return "void".equals(getTypeName());
+	}
+}
diff --git a/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/model/ScriptExample.java b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/model/ScriptExample.java
new file mode 100644
index 0000000..bc66d02
--- /dev/null
+++ b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/model/ScriptExample.java
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * Copyright (c) 2019 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.helpgenerator.model;
+
+public class ScriptExample {
+
+	private final String fComment;
+	private final String fCode;
+
+	public ScriptExample(String content) {
+		if (content.contains("...")) {
+			fCode = content.substring(0, content.indexOf("...")).trim();
+			fComment = content.substring(content.indexOf("...") + 3).trim();
+
+		} else {
+			int pos = content.indexOf('(');
+			if (pos > 0) {
+				int open = 1;
+				for (int index = pos + 1; index < content.length(); index++) {
+					if (content.charAt(index) == ')')
+						open--;
+					else if (content.charAt(index) == '(')
+						open++;
+
+					if (open == 0) {
+						pos = index + 1;
+						break;
+					}
+				}
+				fCode = content.substring(0, pos);
+				fComment = content.substring(pos).trim();
+
+			} else {
+				fCode = content;
+				fComment = "";
+			}
+		}
+	}
+
+	public ScriptExample(String comment, String code) {
+		fComment = comment;
+		fCode = code;
+	}
+
+	public String getComment() {
+		return fComment;
+	}
+
+	public String getCode() {
+		return fCode;
+	}
+}
diff --git a/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/sunapi/Java5ClassModel.java b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/sunapi/Java5ClassModel.java
new file mode 100644
index 0000000..621fceb
--- /dev/null
+++ b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/sunapi/Java5ClassModel.java
@@ -0,0 +1,303 @@
+/*******************************************************************************
+ * Copyright (c) 2014 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ease.helpgenerator.sunapi;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.eclipse.ease.helpgenerator.model.AbstractClassModel;
+import org.eclipse.ease.helpgenerator.model.Description;
+import org.eclipse.ease.helpgenerator.model.ExceptionValue;
+import org.eclipse.ease.helpgenerator.model.Field;
+import org.eclipse.ease.helpgenerator.model.Method;
+import org.eclipse.ease.helpgenerator.model.Parameter;
+import org.eclipse.ease.helpgenerator.model.ReturnValue;
+import org.eclipse.ease.helpgenerator.model.ScriptExample;
+
+import com.sun.javadoc.AnnotationDesc;
+import com.sun.javadoc.AnnotationDesc.ElementValuePair;
+import com.sun.javadoc.ClassDoc;
+import com.sun.javadoc.FieldDoc;
+import com.sun.javadoc.MemberDoc;
+import com.sun.javadoc.MethodDoc;
+import com.sun.javadoc.ParamTag;
+import com.sun.javadoc.ProgramElementDoc;
+import com.sun.javadoc.Tag;
+import com.sun.javadoc.ThrowsTag;
+import com.sun.javadoc.Type;
+
+public class Java5ClassModel extends AbstractClassModel {
+
+	private static final String WRAP_TO_SCRIPT = "WrapToScript";
+	private static final String QUALIFIED_WRAP_TO_SCRIPT = "org.eclipse.ease.modules." + WRAP_TO_SCRIPT;
+
+	private static final Object SCRIPT_PARAMETER = "ScriptParameter";
+	private static final Object QUALIFIED_SCRIPT_PARAMETER = "org.eclipse.ease.modules." + SCRIPT_PARAMETER;
+
+	private static String getParameterComment(final MethodDoc method, final String name) {
+		final String comment = extractComment(method, method1 -> {
+			for (final ParamTag paramTags : method1.paramTags()) {
+				if (name.equals(paramTags.parameterName()))
+					return paramTags.parameterComment();
+			}
+
+			return "";
+		});
+
+		return comment;
+	}
+
+	private static String getDefaultValue(com.sun.javadoc.Parameter param) {
+		final AnnotationDesc parameterAnnotation = getScriptParameterAnnotation(param);
+
+		if (parameterAnnotation != null) {
+			for (final ElementValuePair pair : parameterAnnotation.elementValues()) {
+				if ("org.eclipse.ease.modules.ScriptParameter.defaultValue()".equals(pair.element().toString()))
+					return pair.value().toString();
+			}
+		}
+
+		return null;
+	}
+
+	private static AnnotationDesc getScriptParameterAnnotation(final com.sun.javadoc.Parameter parameter) {
+		for (final AnnotationDesc annotation : parameter.annotations()) {
+			if (isScriptParameterAnnotation(annotation))
+				return annotation;
+		}
+
+		return null;
+	}
+
+	private static boolean isScriptParameterAnnotation(final AnnotationDesc annotation) {
+		return (QUALIFIED_SCRIPT_PARAMETER.equals(annotation.annotationType().qualifiedName()))
+				|| (SCRIPT_PARAMETER.equals(annotation.annotationType().qualifiedName()));
+	}
+
+	private static String extractComment(MethodDoc method, CommentExtractor extractor) {
+		String comment = extractor.extract(method);
+		if ((comment != null) && (!comment.isEmpty()))
+			return comment;
+
+		// try to look up interfaces
+		for (final ClassDoc iface : method.containingClass().interfaces()) {
+			for (final MethodDoc ifaceMethod : iface.methods()) {
+				if (method.overrides(ifaceMethod)) {
+					comment = extractComment(ifaceMethod, extractor);
+					if ((comment != null) && (!comment.isEmpty()))
+						return comment;
+				}
+			}
+		}
+
+		// not found, retry with super class
+		final ClassDoc parent = method.containingClass().superclass();
+		if (parent != null) {
+			for (final MethodDoc superMethod : parent.methods()) {
+				if (method.overrides(superMethod))
+					return (extractComment(superMethod, extractor));
+			}
+		}
+
+		return "";
+	}
+
+	private static AnnotationDesc getWrapAnnotation(final ProgramElementDoc method) {
+		for (final AnnotationDesc annotation : method.annotations()) {
+			if (isWrapToScriptAnnotation(annotation))
+				return annotation;
+		}
+
+		return null;
+	}
+
+	private static boolean isWrapToScriptAnnotation(final AnnotationDesc annotation) {
+		return (QUALIFIED_WRAP_TO_SCRIPT.equals(annotation.annotationType().qualifiedName()))
+				|| (WRAP_TO_SCRIPT.equals(annotation.annotationType().qualifiedName()));
+	}
+
+	private static boolean isDeprecated(final MemberDoc field) {
+		final Tag[] tags = field.tags("deprecated");
+		return (tags != null) && (tags.length > 0);
+	}
+
+	private static Collection<String> getFunctionAliases(final MethodDoc method) {
+		final Collection<String> aliases = new HashSet<>();
+		final AnnotationDesc annotation = getWrapAnnotation(method);
+		if (annotation != null) {
+			for (final ElementValuePair pair : annotation.elementValues()) {
+				if ("alias".equals(pair.element().name())) {
+					String candidates = pair.value().toString();
+					candidates = candidates.substring(1, candidates.length() - 1);
+					for (final String token : candidates.split("[,;]")) {
+						if (!token.trim().isEmpty())
+							aliases.add(token.trim());
+					}
+				}
+			}
+		}
+
+		return aliases;
+	}
+
+	private final ClassDoc fClassDoc;
+
+	public Java5ClassModel(ClassDoc classDoc) {
+		fClassDoc = classDoc;
+	}
+
+	@Override
+	public void populateModel() {
+		setClassName(fClassDoc.name());
+
+		setClassDocumentation(new Description(fClassDoc.commentText()));
+
+		setExportedFields(fetchExportedFields());
+		setExportedMethods(fetchExportedMethods());
+
+		setImportedClasses(fetchImportedClasses());
+	};
+
+	private List<String> fetchImportedClasses() {
+		return Arrays.asList(fClassDoc.importedClasses()).stream().map(f -> f.toString()).collect(Collectors.toList());
+	}
+
+	private List<Field> fetchExportedFields() {
+		final List<FieldDoc> fields = new ArrayList<>();
+
+		final boolean hasAnnotation = hasWrapToScriptAnnotation();
+
+		final ArrayList<ClassDoc> candidates = new ArrayList<>();
+		candidates.add(fClassDoc);
+		while (!candidates.isEmpty()) {
+			final ClassDoc clazzDoc = candidates.remove(0);
+
+			for (final FieldDoc field : clazzDoc.fields()) {
+				if ((!hasAnnotation) || (getWrapAnnotation(field) != null))
+					fields.add(field);
+			}
+
+			// add interfaces
+			candidates.addAll(Arrays.asList(clazzDoc.interfaces()));
+
+			// add super class/interface
+			final ClassDoc nextCandidate = clazzDoc.superclass();
+			if ((nextCandidate != null) && (!Object.class.getName().equals(nextCandidate.qualifiedName())))
+				candidates.add(nextCandidate);
+		}
+
+		// sort fields alphabetically
+		return fields.stream().map(doc -> {
+			final String deprecationMessage = isDeprecated(doc) ? doc.tags("deprecated")[0].text() : null;
+			return new Field(doc.name(), doc.commentText(), deprecationMessage);
+		}).collect(Collectors.toList());
+	}
+
+	private boolean hasWrapToScriptAnnotation() {
+		ClassDoc classDoc = fClassDoc;
+		while (classDoc != null) {
+			for (final MethodDoc method : classDoc.methods()) {
+				if (getWrapAnnotation(method) != null)
+					return true;
+			}
+
+			for (final FieldDoc field : classDoc.fields()) {
+				if (getWrapAnnotation(field) != null)
+					return true;
+			}
+
+			classDoc = classDoc.superclass();
+		}
+
+		return false;
+	}
+
+	private List<Method> fetchExportedMethods() {
+		return new MethodExtractor().getMethods(fClassDoc, hasWrapToScriptAnnotation());
+	}
+
+	private String getExceptionComment(MethodDoc method, Type exceptionType) {
+		final String comment = extractComment(method, method1 -> {
+
+			for (final ThrowsTag tag : method1.throwsTags()) {
+				if ((exceptionType.simpleTypeName().equals(tag.exceptionName())) || (exceptionType.typeName().equals(tag.exceptionName())))
+					return tag.exceptionComment();
+			}
+
+			return "";
+		});
+
+		return comment;
+	}
+
+	private static interface CommentExtractor {
+		String extract(MethodDoc method);
+	}
+
+	private class MethodExtractor {
+		private final List<Method> fMethods = new ArrayList<>();
+
+		private Method findRegisteredMethod(String methodName) {
+			return fMethods.stream().filter(m -> methodName.equals(m.getName())).findFirst().orElse(null);
+		}
+
+		private void addMethod(Method method) {
+			final Method existingMethod = findRegisteredMethod(method.getName());
+			if (existingMethod != null)
+				existingMethod.fetchDetailsFrom(method);
+			else
+				fMethods.add(method);
+		}
+
+		public List<Method> getMethods(ClassDoc classDoc, boolean hasWrapToScriptAnnotation) {
+
+			for (final MethodDoc doc : classDoc.methods()) {
+				final Method registeredMethod = findRegisteredMethod(doc.name());
+				final AnnotationDesc wrapAnnotation = getWrapAnnotation(doc);
+				if (!hasWrapToScriptAnnotation || (wrapAnnotation != null) || (registeredMethod != null)) {
+					if (doc.isPublic()) {
+						final String deprecationMessage = isDeprecated(doc) ? doc.tags("deprecated")[0].text() : null;
+
+						final String returnComment = (doc.tags("return").length > 0) ? doc.tags("return")[0].text() : null;
+						final ReturnValue returnValue = new ReturnValue(doc.returnType().qualifiedTypeName(), returnComment);
+
+						final List<Parameter> parameters = Arrays.asList(doc.parameters()).stream().map(param -> {
+							final String defaultValue = getDefaultValue(param);
+							return new Parameter(param.name(), param.typeName(), getParameterComment(doc, param.name()), defaultValue);
+						}).collect(Collectors.toList());
+
+						final List<ExceptionValue> exceptions = Arrays.asList(doc.thrownExceptionTypes()).stream()
+								.map(e -> new ExceptionValue(e.qualifiedTypeName(), getExceptionComment(doc, e))).collect(Collectors.toList());
+
+						final List<ScriptExample> examples = Arrays.asList(doc.tags("scriptExample")).stream().map(example -> new ScriptExample(example.text()))
+								.collect(Collectors.toList());
+
+						addMethod(new Method(doc.name(), extractComment(doc, d -> d.commentText()), deprecationMessage, getFunctionAliases(doc), returnValue,
+								parameters, exceptions, examples));
+					}
+				}
+			}
+
+			for (final ClassDoc interfaceDoc : classDoc.interfaces())
+				getMethods(interfaceDoc, hasWrapToScriptAnnotation);
+
+			final ClassDoc superclassDoc = classDoc.superclass();
+			if ((superclassDoc != null) && (!Object.class.getName().equals(superclassDoc.qualifiedName())))
+				getMethods(superclassDoc, hasWrapToScriptAnnotation);
+
+			return fMethods;
+		}
+	}
+}
diff --git a/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/sunapi/Java5ModuleDoclet.java b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/sunapi/Java5ModuleDoclet.java
new file mode 100644
index 0000000..4706074
--- /dev/null
+++ b/developers/org.eclipse.ease.helpgenerator/src/org/eclipse/ease/helpgenerator/sunapi/Java5ModuleDoclet.java
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * Copyright (c) 2014 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ease.helpgenerator.sunapi;
+
+import org.eclipse.ease.helpgenerator.AbstractModuleDoclet;
+import org.eclipse.ease.helpgenerator.IReporter;
+import org.eclipse.ease.helpgenerator.model.AbstractClassModel;
+import org.eclipse.ease.helpgenerator.model.ModuleDefinition;
+
+import com.sun.javadoc.ClassDoc;
+import com.sun.javadoc.RootDoc;
+
+public class Java5ModuleDoclet extends AbstractModuleDoclet {
+
+	private RootDoc fRootDoc;
+
+	@Override
+	protected IReporter createReporter() {
+		return new OutputStreamReporter();
+	}
+
+	public void setRootDoc(RootDoc rootDoc) {
+		fRootDoc = rootDoc;
+	}
+
+	@Override
+	protected AbstractClassModel getClassModel(ModuleDefinition module) {
+		for (final ClassDoc classDoc : fRootDoc.classes()) {
+			if (classDoc.qualifiedName().equals(module.getClassName())) {
+				final AbstractClassModel classModel = new Java5ClassModel(classDoc);
+				classModel.populateModel();
+				return classModel;
+			}
+		}
+
+		return null;
+	}
+
+	private class OutputStreamReporter implements IReporter {
+
+		private boolean fHasErrors = false;
+
+		@Override
+		public void report(int status, String message) {
+			switch (status) {
+			case INFO:
+				System.out.println(message);
+				break;
+			case WARNING:
+				System.out.println("WARNING: " + message);
+				break;
+			case ERROR:
+				// fall through
+			default:
+				System.err.println("ERROR: " + message);
+				fHasErrors = true;
+				break;
+			}
+		}
+
+		@Override
+		public boolean hasErrors() {
+			return fHasErrors;
+		}
+
+		@Override
+		public void reportMissingDocs(String message) {
+			report(failOnMissingDocs() ? ERROR : WARNING, message);
+		}
+
+		@Override
+		public void reportInvalidHtml(String message) {
+			report(failOnHtmlErrors() ? ERROR : WARNING, message);
+		}
+	}
+}
diff --git a/developers/org.eclipse.ease.helpgenerator/test/org/eclipse/ease/helpgenerator/IntegrationTestJava11API.java b/developers/org.eclipse.ease.helpgenerator/test/org/eclipse/ease/helpgenerator/IntegrationTestJava11API.java
new file mode 100644
index 0000000..f854d97
--- /dev/null
+++ b/developers/org.eclipse.ease.helpgenerator/test/org/eclipse/ease/helpgenerator/IntegrationTestJava11API.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * Copyright (c) 2019 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.helpgenerator;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+
+import javax.tools.DocumentationTool;
+import javax.tools.ToolProvider;
+
+import org.junit.Test;
+
+public class IntegrationTestJava11API {
+
+	private static int buildDocs(boolean failOnHtmlError, boolean failOnMissingDocs, String packageName) {
+
+		final DocumentationTool documentationTool = ToolProvider.getSystemDocumentationTool();
+		return documentationTool.run(System.in, System.out, System.err,
+
+				"-sourcepath", new File("./resources/org.eclipse.ease.helpgenerator.testproject/src").getAbsolutePath(), "-root",
+				new File("./resources/org.eclipse.ease.helpgenerator.testproject").getAbsolutePath(), "-doclet", ModuleDoclet.class.getName(), "-docletpath",
+				new File("./target/classes").getAbsolutePath(),
+
+				"-failOnHTMLError", Boolean.toString(failOnHtmlError), "-failOnMissingDocs", Boolean.toString(failOnMissingDocs),
+
+				"-link", "https://docs.oracle.com/javase/8/docs/api",
+
+				packageName);
+	}
+
+	@Test
+	public void validModule() {
+		assertEquals(0, buildDocs(true, true, "org.eclipse.ease.helpgenerator.testproject.valid"));
+	}
+
+	@Test
+	public void invalidXMLIgnoreErrors() {
+		assertEquals(0, buildDocs(false, false, "org.eclipse.ease.helpgenerator.testproject.invalidxml"));
+	}
+
+	@Test
+	public void invalidXMLShouldFail() {
+		assertEquals(1, buildDocs(true, false, "org.eclipse.ease.helpgenerator.testproject.invalidxml"));
+	}
+
+	@Test
+	public void missingDocsIgnoreErrors() {
+		assertEquals(0, buildDocs(false, false, "org.eclipse.ease.helpgenerator.testproject.missingdocs"));
+	}
+
+	@Test
+	public void missingDocsShouldFail() {
+		assertEquals(1, buildDocs(false, true, "org.eclipse.ease.helpgenerator.testproject.missingdocs"));
+	}
+
+	@Test
+	public void verifyContent() throws IOException {
+		buildDocs(true, true, "org.eclipse.ease.helpgenerator.testproject.valid");
+		String expected = new String(
+				Files.readAllBytes(new File("./resources/expected_module_org.eclipse.ease.helpgenerator.testproject.module1.html").toPath()));
+		expected = expected.replaceAll("\r", "");
+
+		final String actual = new String(Files.readAllBytes(
+				new File("./resources/org.eclipse.ease.helpgenerator.testproject/help/module_org.eclipse.ease.helpgenerator.testproject.module1.html")
+						.toPath()));
+
+		assertEquals(expected, actual);
+	}
+}
diff --git a/developers/org.eclipse.ease.helpgenerator/test/org/eclipse/ease/helpgenerator/IntegrationTest.java b/developers/org.eclipse.ease.helpgenerator/test/org/eclipse/ease/helpgenerator/IntegrationTestJava5API.java
similarity index 73%
rename from developers/org.eclipse.ease.helpgenerator/test/org/eclipse/ease/helpgenerator/IntegrationTest.java
rename to developers/org.eclipse.ease.helpgenerator/test/org/eclipse/ease/helpgenerator/IntegrationTestJava5API.java
index ae7ea81..aa62d54 100644
--- a/developers/org.eclipse.ease.helpgenerator/test/org/eclipse/ease/helpgenerator/IntegrationTest.java
+++ b/developers/org.eclipse.ease.helpgenerator/test/org/eclipse/ease/helpgenerator/IntegrationTestJava5API.java
@@ -14,10 +14,12 @@
 import static org.junit.Assert.assertEquals;
 
 import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
 
 import org.junit.Test;
 
-public class IntegrationTest {
+public class IntegrationTestJava5API {
 
 	private static int buildDocs(boolean failOnHtmlError, boolean failOnMissingDocs, String packageName) {
 		// @formatter:off
@@ -30,7 +32,7 @@
 				"-failOnHTMLError", Boolean.toString(failOnHtmlError),
 				"-failOnMissingDocs", Boolean.toString(failOnMissingDocs),
 
-				"-link", "https://docs.oracle.com/javase/8/docs/api/",
+				"-link", "https://docs.oracle.com/javase/8/docs/api",
 
 				packageName
 		});
@@ -61,4 +63,18 @@
 	public void missingDocsShouldFail() {
 		assertEquals(1, buildDocs(false, true, "org.eclipse.ease.helpgenerator.testproject.missingdocs"));
 	}
+
+	@Test
+	public void verifyContent() throws IOException {
+		buildDocs(true, true, "org.eclipse.ease.helpgenerator.testproject.valid");
+		String expected = new String(
+				Files.readAllBytes(new File("./resources/expected_module_org.eclipse.ease.helpgenerator.testproject.module1.html").toPath()));
+		expected = expected.replaceAll("\r", "");
+
+		final String actual = new String(Files.readAllBytes(
+				new File("./resources/org.eclipse.ease.helpgenerator.testproject/help/module_org.eclipse.ease.helpgenerator.testproject.module1.html")
+						.toPath()));
+
+		assertEquals(expected, actual);
+	}
 }
diff --git a/developers/org.eclipse.ease.helpgenerator/test/org/eclipse/ease/helpgenerator/LinkProviderTest.java b/developers/org.eclipse.ease.helpgenerator/test/org/eclipse/ease/helpgenerator/LinkProviderTest.java
new file mode 100644
index 0000000..e531762
--- /dev/null
+++ b/developers/org.eclipse.ease.helpgenerator/test/org/eclipse/ease/helpgenerator/LinkProviderTest.java
@@ -0,0 +1,163 @@
+/*******************************************************************************
+ * Copyright (c) 2019 ponteseg and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     ponteseg - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.helpgenerator;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.ease.helpgenerator.model.AbstractClassModel;
+import org.junit.Before;
+import org.junit.Test;
+
+public class LinkProviderTest {
+
+	private static final String ROOT_URI = "http://test.org/api";
+
+	private LinkProvider fLinkProvider;
+
+	@Before
+	public void setup() {
+		fLinkProvider = new LinkProvider();
+		fLinkProvider.registerAddress(ROOT_URI, List.of("java.lang", "java.util", "java.io", "org.test.some"));
+		fLinkProvider.setClassModel(new AbstractClassModel() {
+
+			@Override
+			public void populateModel() {
+			}
+
+			@Override
+			public Collection<String> getImportedClasses() {
+				return List.of("java.io.IOException", "java.util.Collection");
+			}
+		});
+	}
+
+	@Test
+	public void insertLinksOnNull() {
+		assertNull(fLinkProvider.insertLinks(null));
+	}
+
+	@Test
+	public void insertLinksOnStandardText() {
+		final String text = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr";
+		assertEquals(text, fLinkProvider.insertLinks(text));
+	}
+
+	@Test
+	public void resolveQualifiedClassLink() {
+		final String input = "{@link java.lang.String}";
+		final String expected = "<a href='" + ROOT_URI + "/java/lang/String.html'>String</a>";
+		assertEquals(expected, fLinkProvider.insertLinks(input));
+	}
+
+	@Test
+	public void resolveQualifiedClassLinkWithGenerics() {
+		final String input = "{@link java.util.List<java.lang.String>}";
+		final String expected = "<a href='" + ROOT_URI + "/java/util/List.html'>List</a>";
+		assertEquals(expected, fLinkProvider.insertLinks(input));
+	}
+
+	@Test
+	public void resolveImportedClassLink() {
+		final String input = "{@link IOException}";
+		final String expected = "<a href='" + ROOT_URI + "/java/io/IOException.html'>IOException</a>";
+		assertEquals(expected, fLinkProvider.insertLinks(input));
+	}
+
+	@Test
+	public void resolveBaseApiClassLink() {
+		final String input = "{@link String}";
+		final String expected = "<a href='" + ROOT_URI + "/java/lang/String.html'>String</a>";
+		assertEquals(expected, fLinkProvider.insertLinks(input));
+	}
+
+	@Test
+	public void java8ResolveQualifiedMethodLink() {
+		fLinkProvider.setApi(LinkProvider.JavaDocApi.JAVA_8);
+		final String input = "{@link java.lang.String#charAt(int)}";
+		final String expected = "<a href='" + ROOT_URI + "/java/lang/String.html#charAt-int-'>String.charAt(int)</a>";
+		assertEquals(expected, fLinkProvider.insertLinks(input));
+	}
+
+	@Test
+	public void java8ResolveQualifiedMethodLinkWithMultipleParameters() {
+		final String input = "{@link java.lang.String#getBytes(int, int, byte[], int)}";
+		final String expected = "<a href='" + ROOT_URI + "/java/lang/String.html#getBytes-int-int-byte:A-int-'>String.getBytes(int, int, byte[], int)</a>";
+		assertEquals(expected, fLinkProvider.insertLinks(input));
+	}
+
+	@Test
+	public void java8ResolveQualifiedMethodLinkWithGenerics() {
+		final String input = "{@link java.util.List<java.lang.String>#addAll(int, Collection<java.lang.String>)}";
+		final String expected = "<a href='" + ROOT_URI + "/java/util/List.html#addAll-int-java.util.Collection-'>List.addAll(int, Collection)</a>";
+		assertEquals(expected, fLinkProvider.insertLinks(input));
+	}
+
+	@Test
+	public void java8ResolveMethodOnImportedClassLink() {
+		final String input = "{@link IOException#getStackTrace()}";
+		final String expected = "<a href='" + ROOT_URI + "/java/io/IOException.html#getStackTrace--'>IOException.getStackTrace()</a>";
+		assertEquals(expected, fLinkProvider.insertLinks(input));
+	}
+
+	@Test
+	public void java8ResolveMethodOnBaseApiClassLink() {
+		final String input = "{@link String#chars()}";
+		final String expected = "<a href='" + ROOT_URI + "/java/lang/String.html#chars--'>String.chars()</a>";
+		assertEquals(expected, fLinkProvider.insertLinks(input));
+	}
+
+	@Test
+	public void java8ResolveMethodInSameClassLink() {
+		final String input = "{@link #chars()}";
+		final String expected = "<a href='#chars--'>chars()</a>";
+		assertEquals(expected, fLinkProvider.insertLinks(input));
+	}
+
+	@Test
+	public void resolveModuleLink() {
+		final String input = "{@module org.eclipse.ease.platform.module.UIModule}";
+		final String expected = "<a href='../../org.eclipse.ease.platform.module/help/module_org.eclipse.ease.platform.module.uimodule.html'>UIModule module</a>";
+		assertEquals(expected, fLinkProvider.insertLinks(input));
+	}
+
+	@Test
+	public void resolveModuleMethodLinkWithParameters() {
+		final String input = "{@module org.eclipse.ease.platform.module.UIModule#openView(java.lang.String)}";
+		final String expected = "<a href='../../org.eclipse.ease.platform.module/help/module_org.eclipse.ease.platform.module.uimodule.html#openView'>UIModule.openView(String)</a>";
+		assertEquals(expected, fLinkProvider.insertLinks(input));
+	}
+
+	@Test
+	public void resolveModuleMethodLinkWithEmptyParameters() {
+		final String input = "{@module org.eclipse.ease.platform.module.UIModule#openView()}";
+		final String expected = "<a href='../../org.eclipse.ease.platform.module/help/module_org.eclipse.ease.platform.module.uimodule.html#openView'>UIModule.openView()</a>";
+		assertEquals(expected, fLinkProvider.insertLinks(input));
+	}
+
+	@Test
+	public void resolveModuleMethodLinkWithoutParameters() {
+		final String input = "{@module org.eclipse.ease.platform.module.UIModule#openView}";
+		final String expected = "<a href='../../org.eclipse.ease.platform.module/help/module_org.eclipse.ease.platform.module.uimodule.html#openView'>UIModule.openView()</a>";
+		assertEquals(expected, fLinkProvider.insertLinks(input));
+	}
+
+	@Test
+	public void resolveModuleMethodLinkInSameDocument() {
+		final String input = "{@module #openView}";
+		final String expected = "<a href='#openView'>openView()</a>";
+		assertEquals(expected, fLinkProvider.insertLinks(input));
+	}
+}