├── .classpath ├── .gitignore ├── .project ├── .settings ├── org.eclipse.core.resources.prefs ├── org.eclipse.jdt.apt.core.prefs ├── org.eclipse.jdt.core.prefs └── org.eclipse.m2e.core.prefs ├── README.md ├── pom.xml └── src ├── main └── java │ └── info │ └── leadinglight │ └── umljavadoclet │ ├── UmlJavaDoclet.java │ ├── model │ ├── Model.java │ ├── ModelClass.java │ ├── ModelPackage.java │ ├── ModelRel.java │ └── RelFilter.java │ └── printer │ ├── ContextDiagramPrinter.java │ ├── DiagramOption.java │ ├── DiagramOptions.java │ ├── ModelPrinter.java │ ├── OverviewDiagramPrinter.java │ ├── PackageDiagramPrinter.java │ ├── Printer.java │ └── PumlDiagramPrinter.java └── test └── java └── info └── leadinglight └── umljavadoclet ├── classes ├── AbstractClass.java └── ConcreteClass.java └── generics ├── AbstractGeneric.java ├── TestGenerics.java ├── TestGenerics2.java ├── TestGenerics3.java ├── TestGenerics4.java ├── TestGenerics5.java ├── TestGenerics6.java ├── TestGenerics7.java ├── TestGenerics8.java └── TestOption.java /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/java,maven,visualstudiocode 2 | # Edit at https://www.gitignore.io/?templates=java,maven,visualstudiocode 3 | 4 | ### Java ### 5 | # Compiled class file 6 | *.class 7 | 8 | # Log file 9 | *.log 10 | 11 | # BlueJ files 12 | *.ctxt 13 | 14 | # Mobile Tools for Java (J2ME) 15 | .mtj.tmp/ 16 | 17 | # Package Files # 18 | *.jar 19 | *.war 20 | *.nar 21 | *.ear 22 | *.zip 23 | *.tar.gz 24 | *.rar 25 | 26 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 27 | hs_err_pid* 28 | 29 | ### Maven ### 30 | target/ 31 | pom.xml.tag 32 | pom.xml.releaseBackup 33 | pom.xml.versionsBackup 34 | pom.xml.next 35 | release.properties 36 | dependency-reduced-pom.xml 37 | buildNumber.properties 38 | .mvn/timing.properties 39 | .mvn/wrapper/maven-wrapper.jar 40 | .flattened-pom.xml 41 | 42 | ### VisualStudioCode ### 43 | .vscode/* 44 | !.vscode/settings.json 45 | !.vscode/tasks.json 46 | !.vscode/launch.json 47 | !.vscode/extensions.json 48 | 49 | ### VisualStudioCode Patch ### 50 | # Ignore all local history of files 51 | .history 52 | 53 | # End of https://www.gitignore.io/api/java,maven,visualstudiocode 54 | 55 | ### IntelliJ working files 56 | .idea 57 | *.iml 58 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | uml-java-doclet 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.m2e.core.maven2Builder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.m2e.core.maven2Nature 22 | 23 | 24 | -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding//src/main/java=UTF-8 3 | encoding/=UTF-8 4 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.apt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.apt.aptEnabled=false 3 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 3 | org.eclipse.jdt.core.compiler.compliance=1.8 4 | org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled 5 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning 6 | org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore 7 | org.eclipse.jdt.core.compiler.processAnnotations=disabled 8 | org.eclipse.jdt.core.compiler.release=disabled 9 | org.eclipse.jdt.core.compiler.source=1.8 10 | -------------------------------------------------------------------------------- /.settings/org.eclipse.m2e.core.prefs: -------------------------------------------------------------------------------- 1 | activeProfiles= 2 | eclipse.preferences.version=1 3 | resolveWorkspaceProjects=true 4 | version=1 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Overview ## 2 | 3 | Add UML diagrams (using PlantUML) to Javadocs. Extends the standard Java doclet. 4 | 5 | ### Status ### 6 | 7 | The doclet tool was completely changed in Java 9; uml-java-doclet will not work with any JDK later than 8. 8 | 9 | This project is not maintained anymore, since for us it is not worth effort required to update it for later JDKs. 10 | However, if anyone wants to take it on, feel free to log any issues and I will do my best to answer. 11 | 12 | ### Background ### 13 | 14 | The inspiration for this project is better communication between the developers on my team. We want to 15 | follow the principles of DDD and Model-Driven Design 16 | (https://www.amazon.ca/dp/0321125215) 17 | and automatically generating class diagrams is a key part of that approach. 18 | 19 | ### Diagrams ### 20 | 21 | Generates three types of diagrams: 22 | 23 | - Overview: Shows all classes within all packages. Click on a class to get to the Javadoc for that class. 24 | Click on a package to get to the Javadoc for that package. 25 | - Package: Shows all of the classes within a package, and the relationships between them. Does not 26 | show relationships with classes outside the package. 27 | - Class: Shows all of the relationships with other classes. If the other class is in the same package, 28 | it is shown in yellow (default color). If in another package within the set of packages, it is shown 29 | in white. If the other class is outside any of the packages, it is shown in grey. 30 | 31 | The relationships with other classes are determined by what the Javadoc API provides, which is from the 32 | attribute and method declarations for a class. Any usages that are buried within code will not be shown. 33 | If I think a relationship is important enough to be shown, I will make it explicit (e.g. add an attribute 34 | to the other class). 35 | 36 | Layouts can appear strange. This is due to the use of GraphViz. Good enough for my purposes, although 37 | there are other options that are being explored (http://plantuml.sourceforge.net/qa/?qa=4842/graphviz-is-not-good-enough). 38 | 39 | ### Dependencies ### 40 | 41 | - GraphViz (as required by PlantUML). 42 | - Java 8 (easy enough to backport to earlier versions). 43 | 44 | NOTE: The doclet tool was completely changed in Java 9; uml-java-doclet will not work with any JDK later than 8. 45 | 46 | ### Installing ### 47 | 48 | Use http://jitpack.io to automatically build and install the JAR file. Add the JitPack repository to your POM: 49 | 50 | 51 | 52 | jitpack.io 53 | https://jitpack.io 54 | 55 | 56 | 57 | Note: Not published to Maven Central; this is a much easier alternative. 58 | 59 | ### Generating Updated Javadocs ### 60 | 61 | To generate UML diagrams for your own project, add the following to your POM: 62 | 63 | 64 | 65 | 66 | org.apache.maven.plugins 67 | maven-javadoc-plugin 68 | 3.2.0 69 | 70 | info.leadinglight.umljavadoclet.UmlJavaDoclet 71 | 72 | com.github.gboersma 73 | uml-java-doclet 74 | 1.1 75 | 76 | true 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | Note: Version 3+ of the maven-javadoc-plugin uses the `additionalOptions` tag to specify additional javadoc tags. 86 | The previous `additionalparam` tag no longer works correctly. Be sure to upgrade your POMs accordingly. 87 | 88 | ### Diagram Options ### 89 | 90 | Options for the diagrams are specified as `additionalOption` tags in the POM. 91 | 92 | |Option|Valid Values|Default|Description| 93 | |---|---|---|---| 94 | |-linetype|polyline,spline,ortho|ortho|Types of lines to display on diagrams| 95 | |-dependencies|public,protected,package,private|public|What dependencies to explicitly show on the diagram| 96 | |-package-orientation|top-to-bottom,left-to-right|top-to-bottom|Layout of packages on package diagrams| 97 | |-output-model|true,false|false|Whether to output the details of the model (useful for debugging)| 98 | |-puml-include-file|free-form|none|Name of PUML file to include in every diagram PUML| 99 | |-exclude-classes|comma-separated|none|List of qualified class names to exclude from context diagrams| 100 | |-exclude-packages|comma-separated|none|List of qualified package names to exclude from context diagrams| 101 | 102 | ### Tips ### 103 | 104 | - To generate the Javadoc to a different folder, refer to: 105 | https://maven.apache.org/plugins-archives/maven-javadoc-plugin-3.2.0/examples/output-configuration.html 106 | for how to use specify the standard doclet option. 107 | 108 | # Acknowledgments # 109 | 110 | Many thanks to the folks at PlantUML (https://github.com/plantuml/plantuml) for their 111 | fantastic support. 112 | Thanks to @bcopy for the pointer to jitpack. 113 | 114 | # License # 115 | 116 | Copyright 2016 Gerald Boersma 117 | 118 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. 119 | 120 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 121 | 122 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | info.leadinglight 5 | uml-java-doclet 6 | uml-java-doclet 7 | 1.2-SNAPSHOT 8 | jar 9 | 10 | UTF-8 11 | 1.8 12 | 1.8 13 | 14 | 15 | 16 | sun.jdk 17 | tools 18 | 1.5.0 19 | system 20 | ${java.home}/../lib/tools.jar 21 | 22 | 23 | net.sourceforge.plantuml 24 | plantuml 25 | 1.2020.4 26 | 27 | 28 | 29 | 30 | jitpack.io 31 | https://jitpack.io 32 | 33 | 34 | 35 | 36 | 37 | maven-compiler-plugin 38 | 3.8.1 39 | 40 | 1.8 41 | 1.8 42 | 43 | 44 | 45 | 46 | maven-assembly-plugin 47 | 48 | 49 | 50 | info.leadinglight.umljavadoclet.UmlJavaDoclet 51 | 52 | 53 | 54 | jar-with-dependencies 55 | 56 | 57 | 58 | 59 | 60 | org.apache.maven.plugins 61 | maven-javadoc-plugin 62 | 3.2.0 63 | 64 | info.leadinglight.umljavadoclet.UmlJavaDoclet 65 | 66 | info.leadinglight 67 | uml-java-doclet 68 | 1.2-SNAPSHOT 69 | 70 | true 71 | 72 | -output-model true 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /src/main/java/info/leadinglight/umljavadoclet/UmlJavaDoclet.java: -------------------------------------------------------------------------------- 1 | package info.leadinglight.umljavadoclet; 2 | 3 | import com.sun.javadoc.DocErrorReporter; 4 | import com.sun.javadoc.LanguageVersion; 5 | import com.sun.javadoc.RootDoc; 6 | import com.sun.tools.doclets.standard.Standard; 7 | import info.leadinglight.umljavadoclet.model.Model; 8 | import info.leadinglight.umljavadoclet.model.ModelClass; 9 | import info.leadinglight.umljavadoclet.model.ModelPackage; 10 | import info.leadinglight.umljavadoclet.printer.ContextDiagramPrinter; 11 | import info.leadinglight.umljavadoclet.printer.DiagramOptions; 12 | import info.leadinglight.umljavadoclet.printer.ModelPrinter; 13 | import info.leadinglight.umljavadoclet.printer.OverviewDiagramPrinter; 14 | import info.leadinglight.umljavadoclet.printer.PackageDiagramPrinter; 15 | import java.io.BufferedOutputStream; 16 | import java.io.BufferedReader; 17 | import java.io.BufferedWriter; 18 | import java.io.File; 19 | import java.io.FileInputStream; 20 | import java.io.FileOutputStream; 21 | import java.io.IOException; 22 | import java.io.InputStreamReader; 23 | import java.io.OutputStream; 24 | import java.io.OutputStreamWriter; 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | import java.util.regex.Pattern; 28 | import net.sourceforge.plantuml.FileFormat; 29 | import net.sourceforge.plantuml.FileFormatOption; 30 | import net.sourceforge.plantuml.SourceStringReader; 31 | import net.sourceforge.plantuml.version.Version; 32 | 33 | public class UmlJavaDoclet extends Standard { 34 | public static boolean start(RootDoc root) { 35 | // Generate Javadocs using standard doclet. 36 | System.out.println("Generating Javadocs..."); 37 | System.out.println("Using arguments:"); 38 | 39 | int idx = -1; 40 | for (int i = 0; i < root.options().length; i++){ 41 | for (int j = 0; j < root.options()[i].length; j++){ 42 | String s = root.options()[i][j]; 43 | System.out.print(s + " "); 44 | if (s.trim().equalsIgnoreCase("-d")){ 45 | idx = i; 46 | } 47 | } 48 | System.out.println(""); 49 | } 50 | 51 | javaDocDir = idx != -1 && root.options()[idx].length == 2 ? root.options()[idx][1] : "."; 52 | System.out.println("Using java doc dir: " + javaDocDir); 53 | 54 | // Set the uml-java-doclet options. 55 | DiagramOptions options = new DiagramOptions(); 56 | options.set(root.options()); 57 | System.out.println("uml-java-doclet options: " + options.getOptionValuesAsString()); 58 | 59 | // Standard javadoc generation 60 | generateJavadoc(root); 61 | 62 | // Extract the Model. 63 | Model model = new Model(root); 64 | model.map(); 65 | 66 | // Print the model. 67 | if (options.isOutputModel()) { 68 | ModelPrinter printer = new ModelPrinter(model); 69 | printer.generate(); 70 | System.out.println("======================================================================================="); 71 | System.out.println(printer.toString()); 72 | System.out.println("======================================================================================="); 73 | } 74 | 75 | // Generate the diagrams. 76 | System.out.println("Using PlantUML version " + Version.versionString()); 77 | System.out.println("Generating diagrams..."); 78 | generateContextDiagrams(model, options); 79 | generatePackageDiagrams(model, options); 80 | generateOverviewDiagram(model, options); 81 | 82 | return true; 83 | } 84 | 85 | /** 86 | * Specify the language version. 87 | * This is EXTREMELY important. It it is not set, none of the generic parameters 88 | * are properly returned. Grrrr. Thanks Java. 89 | * @return Version of the language. 90 | */ 91 | public static LanguageVersion languageVersion() { 92 | return Standard.languageVersion(); 93 | } 94 | 95 | public static int optionLength(String option) { 96 | DiagramOptions options = new DiagramOptions(); 97 | if (options.isValidOption(option)) { 98 | return options.getOptionLength(option); 99 | } else { 100 | return Standard.optionLength(option); 101 | } 102 | } 103 | 104 | public static boolean validOptions(String[][] settings, DocErrorReporter reporter) { 105 | DiagramOptions options = new DiagramOptions(); 106 | // Iterate through all of the options, checking to see if an option is valid. 107 | List standardOptions = new ArrayList<>(); 108 | for (String[] setting: settings) { 109 | String name = setting[0]; 110 | if (options.isValidOption(name)) { 111 | String error = options.checkOption(setting); 112 | if (error != null && error.length() > 0) { 113 | reporter.printError(error); 114 | } 115 | } else { 116 | standardOptions.add(setting); 117 | } 118 | } 119 | return Standard.validOptions(standardOptions.toArray(new String[][]{}), reporter); 120 | } 121 | 122 | private static void generateJavadoc(RootDoc rootDoc) { 123 | Standard.start(rootDoc); 124 | } 125 | 126 | private static void generateContextDiagrams(Model model, DiagramOptions options) { 127 | for (ModelClass modelClass: model.classes()) { 128 | if (modelClass.isInternal()) { 129 | generateContextDiagram(model, modelClass, options); 130 | } 131 | } 132 | } 133 | 134 | private static void generateContextDiagram(Model model, ModelClass modelClass, DiagramOptions options) { 135 | ContextDiagramPrinter generator = new ContextDiagramPrinter(model, modelClass, options); 136 | generator.generate(); 137 | File file = createFile(modelClass.packageName(), modelClass.shortNameWithoutParameters(), "puml"); 138 | boolean success = generator.toFile(file); 139 | if (success && executePlantUML(modelClass.packageName(), modelClass.shortNameWithoutParameters(), generator.stringBuilder())) { 140 | if (updateHtml( 141 | fileForName(modelClass.packageName()), 142 | modelClass.shortNameWithoutParameters(), 143 | Pattern.compile(".*(Class|Interface|Enum) " + modelClass.shortNameWithoutParameters() + ".*"))) { 144 | System.out.println("Generated diagram for class " + modelClass.fullName()); 145 | } else { 146 | System.out.println("ERROR: Could not update html page for class " + modelClass.fullName()); 147 | } 148 | } else { 149 | System.out.println("ERROR: Could not generate diagram for class " + modelClass.fullName()); 150 | } 151 | } 152 | 153 | private static void generatePackageDiagrams(Model model, DiagramOptions options) { 154 | for (ModelPackage modelPackage: model.packages()) { 155 | generatePackageDiagram(model, modelPackage, options); 156 | } 157 | } 158 | 159 | private static void generatePackageDiagram(Model model, ModelPackage modelPackage, DiagramOptions options) { 160 | PackageDiagramPrinter generator = new PackageDiagramPrinter(model, modelPackage, options); 161 | generator.generate(); 162 | File file = createFile(modelPackage.fullName(), "package-summary", "puml"); 163 | boolean success = generator.toFile(file); 164 | if (success && executePlantUML(modelPackage.fullName(), "package-summary", generator.stringBuilder())) { 165 | if (updateHtml( 166 | fileForName(modelPackage.fullName()), 167 | "package-summary", 168 | Pattern.compile("()|(

"))) { 188 | System.out.println("Generated overview diagram"); 189 | } else { 190 | System.out.println("ERROR: Could not update html page for overview diagram"); 191 | } 192 | } else { 193 | System.out.println("ERROR: Could not generate overview diagram"); 194 | } 195 | } 196 | 197 | private static boolean executePlantUML(String name, String baseName, StringBuilder content) { 198 | File file = createFile(name, baseName, "svg"); 199 | try { 200 | OutputStream imageOutput = new BufferedOutputStream(new FileOutputStream(file)); 201 | SourceStringReader reader = new SourceStringReader(content.toString()); 202 | // http://plantuml.sourceforge.net/qa/?qa=4969/skinparam-svglinktarget-not-working-for-api 203 | reader.generateImage(imageOutput, new FileFormatOption(FileFormat.SVG).withSvgLinkTarget("_parent")); 204 | return true; 205 | } catch (IOException e) { 206 | //e.printStackTrace(); 207 | return false; 208 | } 209 | } 210 | 211 | private static File createFile(String name, String baseName, String extension) { 212 | try { 213 | File dir = fileForName(name); 214 | if (dir.exists() || dir.mkdirs()) { 215 | File file = new File(dir, baseName + "." + extension); 216 | if (file.exists() || file.createNewFile()) { 217 | return file; 218 | } 219 | } 220 | return null; 221 | } catch (IOException e) { 222 | //e.printStackTrace(); 223 | return null; 224 | } 225 | } 226 | 227 | private static File fileForName(String name) { 228 | File file = new File(javaDocDir); 229 | for (String part : name.split("\\.")) { 230 | if (part.trim().length() > 0) { 231 | file = new File(file, part); 232 | } 233 | } 234 | return file; 235 | } 236 | 237 | private static boolean updateHtml(File directory, String baseName, Pattern insertPointPattern) { 238 | File htmlFile = new File(directory, baseName + ".html"); 239 | if (!htmlFile.exists()) { 240 | System.out.println("ERROR: Could not find html file " + htmlFile.getName()); 241 | return false; 242 | } 243 | 244 | File svgFile = new File(directory, baseName + ".svg"); 245 | if (!svgFile.exists()) { 246 | System.out.println("ERROR: Could not find svg file " + svgFile.getName()); 247 | return false; 248 | } 249 | 250 | File updatedHtml = new File(directory, baseName + ".uml"); 251 | 252 | boolean matched = false; 253 | BufferedWriter writer = null; 254 | BufferedReader reader = null; 255 | try { 256 | writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(updatedHtml), "UTF-8")); 257 | reader = new BufferedReader(new InputStreamReader(new FileInputStream(htmlFile), "UTF-8")); 258 | String line; 259 | while ((line = reader.readLine()) != null) { 260 | writer.write(line); 261 | writer.newLine(); 262 | if (!matched && insertPointPattern.matcher(line).matches()) { 263 | matched = true; 264 | String tag = String.format(UML_DIV_TAG, baseName); 265 | writer.newLine(); 266 | writer.write(tag); 267 | writer.newLine(); 268 | } 269 | } 270 | } catch (IOException e) { 271 | //e.printStackTrace(); 272 | return false; 273 | } finally { 274 | try { 275 | if (writer != null) 276 | writer.close(); 277 | if (reader != null) 278 | reader.close(); 279 | } catch(IOException e) { 280 | //e.printStackTrace(); 281 | return false; 282 | } 283 | } 284 | 285 | // if altered, delete old file and rename new one to the old file name 286 | if (matched) { 287 | htmlFile.delete(); 288 | updatedHtml.renameTo(htmlFile); 289 | return true; 290 | } else { 291 | System.out.println("ERROR: Could not insert diagram into HTML file " + htmlFile.getName()); 292 | htmlFile.delete(); 293 | return false; 294 | } 295 | } 296 | 297 | // TODO Not specifying the width and height of diagrams means that they may be bigger than the page. 298 | // However, if I specify a width and height, the diagrams do not resize if I zoom in and out. 299 | // Need to investigate how to do this to keep the diagrams consistent and the browser happy. 300 | private static final String UML_DIV_TAG = 301 | "
" + 302 | "" + 303 | "
"; 304 | 305 | private static String javaDocDir; 306 | } 307 | -------------------------------------------------------------------------------- /src/main/java/info/leadinglight/umljavadoclet/model/Model.java: -------------------------------------------------------------------------------- 1 | package info.leadinglight.umljavadoclet.model; 2 | 3 | import com.sun.javadoc.ClassDoc; 4 | import com.sun.javadoc.PackageDoc; 5 | import com.sun.javadoc.RootDoc; 6 | import com.sun.javadoc.Type; 7 | import java.util.ArrayList; 8 | import java.util.LinkedHashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | /** 13 | * A representation of a set of classes and the relationships between them. 14 | */ 15 | public class Model { 16 | public Model(RootDoc rootDoc) { 17 | _rootDoc = rootDoc; 18 | } 19 | 20 | public List classes() { 21 | return new ArrayList<>(_classes.values()); 22 | } 23 | 24 | public List internalClasses() { 25 | List internalClasses = new ArrayList<>(); 26 | for (ModelClass modelClass: classes()) { 27 | if (modelClass.isInternal()) { 28 | internalClasses.add(modelClass); 29 | } 30 | } 31 | return internalClasses; 32 | } 33 | 34 | public ModelClass modelClass(String fullName) { 35 | return _classes.get(fullName); 36 | } 37 | 38 | public List packages() { 39 | return new ArrayList<>(_packages.values()); 40 | } 41 | 42 | public ModelPackage modelPackage(String fullName) { 43 | return _packages.get(fullName); 44 | } 45 | 46 | public ModelPackage parentPackage(ModelPackage childPackage) { 47 | String parentPackageName = childPackage.parentPackageFullName(); 48 | if (parentPackageName != null) { 49 | return modelPackage(parentPackageName); 50 | } else { 51 | return null; 52 | } 53 | } 54 | 55 | public List childPackages(ModelPackage parentPackage) { 56 | List childPackages = new ArrayList<>(); 57 | for (ModelPackage modelPackage: packages()) { 58 | if (modelPackage.isChildPackage(parentPackage)) { 59 | childPackages.add(modelPackage); 60 | } 61 | } 62 | return childPackages; 63 | } 64 | 65 | public List rootPackages() { 66 | List rootPackages = new ArrayList<>(); 67 | for (ModelPackage modelPackage: packages()) { 68 | if (isRootPackage(modelPackage)) { 69 | rootPackages.add(modelPackage); 70 | } 71 | } 72 | return rootPackages; 73 | } 74 | 75 | public boolean isRootPackage(ModelPackage modelPackage) { 76 | // In order for a package to be a root package, no portion of the 77 | // package name may appear in the model. 78 | // Because of the way the doclet works, not all packages in scope are 79 | // included in the model- only the ones that have package-summary or classes. 80 | String parentName = modelPackage.parentPackageFullName(); 81 | while (parentName != null) { 82 | if (modelPackage(parentName) != null) { 83 | return false; 84 | } 85 | 86 | if (parentName.lastIndexOf(".") == -1) { 87 | return true; 88 | } 89 | 90 | parentName = parentName.substring(0, parentName.lastIndexOf(".")); 91 | } 92 | return false; 93 | } 94 | 95 | // Updating Model 96 | 97 | public ModelClass createClassIfNotExists(Type classType) { 98 | String fullName = ModelClass.fullName(classType); 99 | ModelClass modelClass = _classes.get(fullName); 100 | if (modelClass == null) { 101 | modelClass = new ModelClass(this, classType, false); 102 | modelClass.map(); 103 | _classes.put(fullName, modelClass); 104 | } 105 | return modelClass; 106 | } 107 | 108 | // Mapping 109 | 110 | public void map() { 111 | mapClasses(); 112 | mapRelationships(); 113 | createPackages(); 114 | mapPackages(); 115 | } 116 | 117 | private void mapClasses() { 118 | // First add the classes and packages to the model. 119 | for (ClassDoc classDoc: _rootDoc.classes()) { 120 | ModelClass modelClass = new ModelClass(this, classDoc, true); 121 | String fullName = ModelClass.fullName(classDoc); 122 | _classes.put(fullName, modelClass); 123 | } 124 | } 125 | 126 | private void mapRelationships() { 127 | // Then map all the classes added to the model. 128 | for (ClassDoc classDoc: _rootDoc.classes()) { 129 | String fullName = ModelClass.fullName(classDoc); 130 | ModelClass modelClass = _classes.get(fullName); 131 | modelClass.map(); 132 | } 133 | } 134 | 135 | private void createPackages() { 136 | for (ClassDoc classDoc: _rootDoc.classes()) { 137 | PackageDoc packageDoc = classDoc.containingPackage(); 138 | String fullName = ModelPackage.fullName(packageDoc); 139 | ModelPackage modelPackage = _packages.get(fullName); 140 | if (modelPackage == null) { 141 | modelPackage = new ModelPackage(this, packageDoc); 142 | _packages.put(fullName, modelPackage); 143 | } 144 | String classFullName = ModelClass.fullName(classDoc); 145 | ModelClass modelClass = _classes.get(classFullName); 146 | modelPackage.addClass(modelClass); 147 | } 148 | } 149 | 150 | public void mapPackages() { 151 | // Map the packages once all the classes are added. 152 | // They have all the info need for mapping relationships. 153 | for (ModelPackage modelPackage: _packages.values()) { 154 | modelPackage.map(); 155 | } 156 | } 157 | 158 | private final RootDoc _rootDoc; 159 | private final Map _classes = new LinkedHashMap<>(); 160 | private final Map _packages = new LinkedHashMap<>(); 161 | } 162 | -------------------------------------------------------------------------------- /src/main/java/info/leadinglight/umljavadoclet/model/ModelClass.java: -------------------------------------------------------------------------------- 1 | package info.leadinglight.umljavadoclet.model; 2 | 3 | import com.sun.javadoc.AnnotationDesc; 4 | import com.sun.javadoc.ClassDoc; 5 | import com.sun.javadoc.ConstructorDoc; 6 | import com.sun.javadoc.FieldDoc; 7 | import com.sun.javadoc.MethodDoc; 8 | import com.sun.javadoc.Parameter; 9 | import com.sun.javadoc.ParameterizedType; 10 | import com.sun.javadoc.ProgramElementDoc; 11 | import com.sun.javadoc.Type; 12 | import com.sun.javadoc.TypeVariable; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | /** 18 | * Represents a class internal or external to the model. 19 | */ 20 | public class ModelClass { 21 | public ModelClass(Model model, Type type, boolean isInternal) { 22 | _model = model; 23 | _type = type; 24 | _isInternal = isInternal; 25 | _classDoc = _type.asClassDoc(); 26 | } 27 | 28 | public enum Visibility { 29 | PUBLIC, PROTECTED, PRIVATE, PACKAGE_PRIVATE 30 | } 31 | 32 | public enum ClassType { 33 | INTERFACE, ENUM, CLASS, ABSTRACT 34 | } 35 | 36 | public static class VisibilityItem { 37 | public VisibilityItem(Visibility visibility) { 38 | this.visibility = visibility; 39 | } 40 | 41 | public Visibility visibility; 42 | } 43 | 44 | public static class Field extends VisibilityItem { 45 | public Field(String name, String type, Visibility visibility, boolean isStatic) { 46 | super(visibility); 47 | this.name = name; 48 | this.type = type; 49 | this.isStatic = isStatic; 50 | } 51 | 52 | public String name; 53 | public String type; 54 | public boolean isStatic; 55 | } 56 | 57 | public static class Constructor extends VisibilityItem { 58 | public Constructor(String name, List parameters, Visibility visibility) { 59 | super(visibility); 60 | this.name = name; 61 | this.parameters = parameters; 62 | } 63 | 64 | public String name; 65 | public List parameters; 66 | } 67 | 68 | public static class Method extends VisibilityItem { 69 | public Method(String name, List parameters, String returnType, Visibility visibility, boolean isAbstract, boolean isStatic) { 70 | super(visibility); 71 | this.name = name; 72 | this.parameters = parameters; 73 | this.returnType = returnType; 74 | this.isAbstract = isAbstract; 75 | this.isStatic = isStatic; 76 | } 77 | 78 | public String name; 79 | public List parameters; 80 | public String returnType; 81 | public boolean isAbstract; 82 | public boolean isStatic; 83 | } 84 | 85 | public static class MethodParameter { 86 | public MethodParameter(String type, String name) { 87 | this.type = type; 88 | this.name = name; 89 | } 90 | 91 | public String type; 92 | public String name; 93 | } 94 | 95 | public String fullName() { 96 | return fullName(_type); 97 | } 98 | 99 | public String fullNameWithoutParameters() { 100 | return fullNameWithoutParameters(_type); 101 | } 102 | 103 | public String shortName() { 104 | return shortName(_type); 105 | } 106 | 107 | public String shortNameWithoutParameters() { 108 | return shortNameWithoutParameters(_type); 109 | } 110 | 111 | public String packageName() { 112 | return _classDoc.containingPackage().name(); 113 | } 114 | 115 | public static String fullName(Type type) { 116 | // The full name of the class is not used for display purposes, 117 | // but rather to uniquely identify the class across all packages. 118 | // This needs to include the parameters of the class. 119 | return type.toString(); 120 | } 121 | 122 | public static String shortName(Type type) { 123 | // The short name of the class is used for display purposes. 124 | // This can be for the name of the class, an attribute, or a method parameter / return type. 125 | // The logic for determining the name of a class is very complicated, 126 | // and needs to take into account parameterized types, type variables, etc. 127 | // There is no clean way to do that from the Javadoc classes. 128 | // The hack is to use the result of the toString() method and massage it. 129 | String fullName = type.toString(); 130 | String shortName = getShortName(fullName); 131 | return shortName; 132 | } 133 | 134 | private static String getShortName(String fullName) { 135 | // The toString() contains package identifiers for the classes that are referenced. 136 | // We need to remove all of the package identifiers. 137 | // We need to do this recursively on any generics, since they can be embedded. 138 | int paramIndex = fullName.indexOf('<'); 139 | 140 | String shortName = paramIndex != -1 141 | ? stripQualifier(fullName.substring(0, paramIndex)) 142 | : stripQualifier(fullName); 143 | 144 | // Parameter declaration: clean up all the parameters. 145 | if (paramIndex != -1) { 146 | String paramStr = fullName.substring(paramIndex + 1); 147 | String[] parts = paramStr.split(" "); 148 | String shortParamStr = ""; 149 | for (String part : parts) { 150 | shortParamStr += getShortName(part) + " "; 151 | } 152 | shortName += "<" + shortParamStr; 153 | } 154 | return shortName; 155 | } 156 | 157 | private static String stripQualifier(String name) { 158 | String[] parts = name.split("\\."); 159 | int index = 0; 160 | for (String part: parts) { 161 | // We assume that an uppercase character indicates the name of a class. 162 | if (Character.isUpperCase(part.charAt(0))) { 163 | break; 164 | } 165 | index++; 166 | } 167 | 168 | // Assemble the remaining parts. 169 | String result = ""; 170 | for (int i = index; i < parts.length - 1; i++) { 171 | result += parts[i] + "."; 172 | } 173 | result += parts[parts.length - 1]; 174 | 175 | return result; 176 | } 177 | 178 | public static String fullNameWithoutParameters(Type type) { 179 | String fullName = ""; 180 | ClassDoc classDoc = type.asClassDoc(); 181 | if (classDoc != null) { 182 | fullName = classDoc.containingPackage().name() + "." + shortNameWithoutParameters(type); 183 | } else { 184 | fullName = type.qualifiedTypeName(); 185 | } 186 | return fullName; 187 | } 188 | 189 | public static String shortNameWithoutParameters(Type type) { 190 | ClassDoc classDoc = type.asClassDoc(); 191 | if (classDoc != null) { 192 | // If this is an inner class, put the name of the enclosing class 193 | // as the first part of this class' short name. 194 | if (isInnerClass(classDoc)) { 195 | return classDoc.containingClass().simpleTypeName() + "." + type.simpleTypeName(); 196 | } else { 197 | return classDoc.simpleTypeName(); 198 | } 199 | } else { 200 | return type.simpleTypeName(); 201 | } 202 | } 203 | 204 | public ClassType type() { 205 | if (_classDoc.isInterface()) { 206 | return ClassType.INTERFACE; 207 | } else if (_classDoc.isEnum()) { 208 | return ClassType.ENUM; 209 | } else if (_classDoc.isAbstract()) { 210 | return ClassType.ABSTRACT; 211 | } else { 212 | return ClassType.CLASS; 213 | } 214 | } 215 | 216 | public List annotations() { 217 | List annotations = new ArrayList<>(); 218 | for (AnnotationDesc annotation: _classDoc.annotations()) { 219 | annotations.add(annotation.annotationType().simpleTypeName()); 220 | } 221 | return annotations; 222 | } 223 | 224 | public boolean isInternal() { 225 | return _isInternal; 226 | } 227 | 228 | public boolean isExternal() { 229 | return !isInternal(); 230 | } 231 | 232 | public boolean isParameterized() { 233 | return _type.asParameterizedType() != null; 234 | } 235 | 236 | public boolean isInnerClass() { 237 | return isInnerClass(_classDoc); 238 | } 239 | 240 | public static boolean isInnerClass(ClassDoc classDoc) { 241 | return classDoc.containingClass() != null; 242 | } 243 | 244 | public List parameters() { 245 | return buildParameters(_type); 246 | } 247 | 248 | private static List buildParameters(Type type) { 249 | List params = new ArrayList<>(); 250 | ParameterizedType paramType = type.asParameterizedType(); 251 | if (paramType != null) { 252 | for (Type param : paramType.typeArguments()) { 253 | String name = ModelClass.shortName(param); 254 | params.add(name); 255 | } 256 | } 257 | return params; 258 | } 259 | 260 | public List parameterClasses() { 261 | return _params; 262 | } 263 | 264 | public boolean isCollectionClass() { 265 | return _type.qualifiedTypeName().equals("java.util.List") || _type.qualifiedTypeName().equals("java.util.Map"); 266 | } 267 | 268 | public ModelPackage modelPackage() { 269 | String packageName = _classDoc.containingPackage().name(); 270 | ModelPackage modelPackage = _model.modelPackage(packageName); 271 | return modelPackage; 272 | } 273 | 274 | public List relationships() { 275 | return _rels; 276 | } 277 | 278 | public RelFilter relationshipsFilter() { 279 | return new RelFilter(_rels); 280 | } 281 | 282 | public ModelClass superclass() { 283 | ModelRel rel = relationshipsFilter().source(this).kind(ModelRel.Kind.GENERALIZATION).first(); 284 | return rel != null ? rel.destination() : null; 285 | } 286 | 287 | public List interfaces() { 288 | return relationshipsFilter().source(this).kind(ModelRel.Kind.REALIZATION).destinationClasses(); 289 | } 290 | 291 | public List sourceAssociations() { 292 | return relationshipsFilter().source(this).kind(ModelRel.Kind.DIRECTED_ASSOCIATION).all(); 293 | } 294 | 295 | public List destinationAssociations() { 296 | return relationshipsFilter().destination(this).kind(ModelRel.Kind.DIRECTED_ASSOCIATION).all(); 297 | } 298 | 299 | public List dependencies() { 300 | return relationshipsFilter().source(this).kind(ModelRel.Kind.DEPENDENCY).destinationClasses(); 301 | } 302 | 303 | public List dependents() { 304 | return relationshipsFilter().destination(this).kind(ModelRel.Kind.DEPENDENCY).sourceClasses(); 305 | } 306 | 307 | public boolean hasRelationshipWith(ModelClass dest) { 308 | return relationshipsFilter().source(this).destination(dest).first() != null; 309 | } 310 | 311 | public ModelRel dependencyWith(ModelClass dest) { 312 | return relationshipsFilter().source(this).destination(dest).kind(ModelRel.Kind.DEPENDENCY).first(); 313 | } 314 | 315 | public boolean hasDependencyWith(ModelClass dest) { 316 | return dependencyWith(dest) != null; 317 | } 318 | 319 | public List fields() { 320 | return _fields; 321 | } 322 | 323 | public List constructors() { 324 | return _constructors; 325 | } 326 | 327 | public List methods() { 328 | return _methods; 329 | } 330 | 331 | // Update Model 332 | 333 | public void addRelationship(ModelRel rel) { 334 | _rels.add(rel); 335 | } 336 | 337 | // Mapping 338 | 339 | public void map() { 340 | mapParameters(); 341 | if (isInternal()) { 342 | // Only map internal classes. 343 | mapInternals(); 344 | mapRelationships(); 345 | } 346 | } 347 | 348 | private void mapInternals() { 349 | mapFields(); 350 | mapConstructors(); 351 | mapMethods(); 352 | } 353 | 354 | private void mapRelationships() { 355 | // Map field associations first, since that will establish the has relationships, which are stronger 356 | // than any of the dependency relationships that may follow. 357 | mapFieldAssociations(); 358 | mapSuperclass(); 359 | mapInterfaces(); 360 | mapConstructorDependencies(); 361 | mapMethodDependencies(); 362 | } 363 | 364 | private void mapParameters() { 365 | ParameterizedType paramType = _type.asParameterizedType(); 366 | if (paramType != null) { 367 | for (Type type: paramType.typeArguments()) { 368 | String typeName = type.qualifiedTypeName(); 369 | if (!typeName.startsWith("java.lang.") && !type.isPrimitive()) { 370 | ModelClass param = _model.createClassIfNotExists(type); 371 | _params.add(param); 372 | } 373 | } 374 | } 375 | } 376 | 377 | private void mapSuperclass() { 378 | Type superclassType = _classDoc.superclassType(); 379 | if (superclassType != null) { 380 | String superclassName = superclassType.qualifiedTypeName(); 381 | // Do not include standard Java superclasses in the model. 382 | if (!superclassName.equals("java.lang.Object") && !superclassName.equals("java.lang.Enum")) { 383 | ModelClass dest = _model.createClassIfNotExists(superclassType); 384 | ModelRel rel = new ModelRel(ModelRel.Kind.GENERALIZATION, this, dest); 385 | mapSourceRel(rel); 386 | mapParamDependencies(dest); 387 | } 388 | } 389 | } 390 | 391 | private void mapInterfaces() { 392 | for (Type interfaceType: _classDoc.interfaceTypes()) { 393 | ModelClass dest = _model.createClassIfNotExists(interfaceType); 394 | // If source class is an interface, than the relationship is a generalization, not a realization. 395 | ModelRel.Kind kind = _classDoc.isInterface() ? ModelRel.Kind.GENERALIZATION : ModelRel.Kind.REALIZATION; 396 | ModelRel rel = new ModelRel(kind, this, dest); 397 | mapSourceRel(rel); 398 | mapParamDependencies(dest); 399 | } 400 | } 401 | 402 | private void mapFieldAssociations() { 403 | for (FieldDoc fieldDoc: _classDoc.fields(false)) { 404 | Type type = fieldDoc.type(); 405 | String typeName = type.qualifiedTypeName(); 406 | // TODO Relationships through collection types. 407 | if (!type.simpleTypeName().equals("void") && !typeName.startsWith("java.lang.") && !type.isPrimitive()) { 408 | ModelClass dest = _model.createClassIfNotExists(type); 409 | ModelRel rel = new ModelRel(ModelRel.Kind.DIRECTED_ASSOCIATION, this, dest, fieldDoc.name()); 410 | mapSourceRel(rel); 411 | mapParamAssociations(fieldDoc, dest); 412 | mapParamDependencies(dest); 413 | } 414 | } 415 | } 416 | 417 | private void mapParamAssociations(FieldDoc fieldDoc, ModelClass modelClass) { 418 | // If the modelclass is a parameterized collection, then we want to model a 1..many relationship with the collection. 419 | if (modelClass.isParameterized() && modelClass.isCollectionClass()) { 420 | for (ModelClass param: modelClass.parameterClasses()) { 421 | ModelRel rel = new ModelRel(ModelRel.Kind.DIRECTED_ASSOCIATION, this, param, fieldDoc.name(), ModelRel.Multiplicity.MANY); 422 | mapSourceRel(rel); 423 | } 424 | } 425 | } 426 | 427 | private void mapConstructorDependencies() { 428 | for (ConstructorDoc constructorDoc: _classDoc.constructors(false)) { 429 | for (Parameter param: constructorDoc.parameters()) { 430 | Type type = param.type(); 431 | mapTypeDependency(type, constructorDoc.isPublic(), constructorDoc.isProtected(), constructorDoc.isPackagePrivate(), constructorDoc.isPrivate()); 432 | } 433 | } 434 | } 435 | 436 | private void mapMethodDependencies() { 437 | for (MethodDoc methodDoc: _classDoc.methods(false)) { 438 | for (Parameter param: methodDoc.parameters()) { 439 | Type type = param.type(); 440 | mapTypeDependency(type, methodDoc.isPublic(), methodDoc.isProtected(), methodDoc.isPackagePrivate(), methodDoc.isPrivate()); 441 | } 442 | Type returnType = methodDoc.returnType(); 443 | mapTypeDependency(returnType, methodDoc.isPublic(), methodDoc.isProtected(), methodDoc.isPackagePrivate(), methodDoc.isPrivate()); 444 | } 445 | } 446 | 447 | private void mapTypeDependency(Type type, boolean isPublic, boolean isProtected, boolean isPackage, boolean isPrivate) { 448 | String typeName = type.qualifiedTypeName(); 449 | // TODO Relationships through collection types. 450 | if (!type.simpleTypeName().equals("void") && !typeName.startsWith("java.lang.") && !type.isPrimitive()) { 451 | ModelClass dest = _model.createClassIfNotExists(type); 452 | 453 | ModelRel.Visibility visibility = null; 454 | if (isPublic) { 455 | visibility = ModelRel.Visibility.PUBLIC; 456 | } else if (isProtected) { 457 | visibility = ModelRel.Visibility.PROTECTED; 458 | } else if (isPackage) { 459 | visibility = ModelRel.Visibility.PACKAGE; 460 | } else if (isPrivate) { 461 | visibility = ModelRel.Visibility.PRIVATE; 462 | } 463 | 464 | // If there is already a dependency with the other class, and it has a visibility 465 | // weaker than this visibility, than replace it with this visibility. 466 | if (this != dest && visibility != null) { 467 | if (hasDependencyWith(dest)) { 468 | ModelRel dependencyWith = dependencyWith(dest); 469 | if (visibility == ModelRel.Visibility.PUBLIC) { 470 | dependencyWith.changeVisibility(visibility); 471 | } else if (visibility == ModelRel.Visibility.PROTECTED) { 472 | if (dependencyWith.destinationVisibility() != ModelRel.Visibility.PUBLIC) { 473 | dependencyWith.changeVisibility(visibility); 474 | } 475 | } else if (visibility == ModelRel.Visibility.PACKAGE) { 476 | if (dependencyWith.destinationVisibility() != ModelRel.Visibility.PUBLIC && 477 | dependencyWith.destinationVisibility() != ModelRel.Visibility.PROTECTED) { 478 | dependencyWith.changeVisibility(visibility); 479 | } 480 | } 481 | } 482 | } 483 | 484 | // Only add if there is no existing relationship with the class. 485 | // Do not add dependency to this class. 486 | if (this != dest && !hasRelationshipWith(dest)) { 487 | ModelRel rel = new ModelRel(ModelRel.Kind.DEPENDENCY, this, dest, visibility); 488 | mapSourceRel(rel); 489 | } 490 | 491 | mapParamDependencies(dest); 492 | } 493 | } 494 | 495 | private void mapParamDependencies(ModelClass modelClass) { 496 | // Is the destination class a parameterized class? If so, there is a dependency on the underlying parameters. 497 | if (modelClass.isParameterized()) { 498 | for (ModelClass param: modelClass.parameterClasses()) { 499 | // In some cases, the generic parameter can be returned as a '?', instead of the actual class. 500 | // I think this can happen with lists to inner classes (that happen in this class, for example). 501 | // Filter them out. 502 | if (!param._type.toString().startsWith("? ")) { 503 | // Do not map a dependency relationship back to this class. 504 | if (!param._type.equals(_type)) { 505 | // Only map the dependency if there is no existing relationship with that class. 506 | if (!hasRelationshipWith(param)) { 507 | ModelRel paramRel = new ModelRel(ModelRel.Kind.DEPENDENCY, this, param); 508 | mapSourceRel(paramRel); 509 | } 510 | } 511 | } 512 | // Relationships through embedded parameter types: 513 | // If the dependent class is also parameterized, expand relationships through the parameters. 514 | mapParamDependencies(param); 515 | } 516 | } 517 | } 518 | 519 | private void mapFields() { 520 | List fields = new ArrayList<>(); 521 | for (FieldDoc fieldDoc: _classDoc.fields(false)) { 522 | Type type = fieldDoc.type(); 523 | String typeName = shortName(type); 524 | Field mappedField = new Field(fieldDoc.name(), typeName, mapVisibility(fieldDoc), fieldDoc.isStatic()); 525 | fields.add(mappedField); 526 | } 527 | orderVisibility(fields, _fields); 528 | } 529 | 530 | private void mapConstructors() { 531 | List constructors = new ArrayList<>(); 532 | for (ConstructorDoc consDoc: _classDoc.constructors(false)) { 533 | List params = new ArrayList<>(); 534 | for (Parameter param: consDoc.parameters()) { 535 | Type paramType = param.type(); 536 | String paramTypeName = shortName(paramType); 537 | params.add(new MethodParameter(paramTypeName, param.name())); 538 | } 539 | Constructor constructor = new Constructor(consDoc.name(), params, mapVisibility(consDoc)); 540 | constructors.add(constructor); 541 | } 542 | orderVisibility(constructors, _constructors); 543 | } 544 | 545 | private void mapMethods() { 546 | List methods = new ArrayList<>(); 547 | for (MethodDoc methodDoc: _classDoc.methods(false)) { 548 | List params = new ArrayList<>(); 549 | for (Parameter param: methodDoc.parameters()) { 550 | Type paramType = param.type(); 551 | String paramTypeName = shortName(paramType); 552 | params.add(new MethodParameter(paramTypeName, param.name())); 553 | } 554 | Type returnType = methodDoc.returnType(); 555 | String returnTypeName = shortName(returnType); 556 | Method method = new Method(methodDoc.name(), 557 | params, 558 | returnTypeName, 559 | mapVisibility(methodDoc), 560 | methodDoc.isAbstract(), 561 | methodDoc.isStatic()); 562 | methods.add(method); 563 | } 564 | orderVisibility(methods, _methods); 565 | } 566 | 567 | private void mapSourceRel(ModelRel rel) { 568 | _rels.add(rel); 569 | // Do not add relationships back to ourselves more than once. 570 | ModelClass dest = rel.destination(); 571 | if (this != dest) { 572 | dest.addRelationship(rel); 573 | } 574 | } 575 | 576 | private Visibility mapVisibility(ProgramElementDoc doc) { 577 | if (doc.isPublic()) { 578 | return Visibility.PUBLIC; 579 | } else if(doc.isProtected()) { 580 | return Visibility.PROTECTED; 581 | } else if(doc.isPrivate()) { 582 | return Visibility.PRIVATE; 583 | } else { 584 | return Visibility.PACKAGE_PRIVATE; 585 | } 586 | } 587 | 588 | private void orderVisibility(List items, List filteredItems) { 589 | filterVisibility(items, filteredItems, Visibility.PUBLIC); 590 | filterVisibility(items, filteredItems, Visibility.PROTECTED); 591 | filterVisibility(items, filteredItems, Visibility.PACKAGE_PRIVATE); 592 | filterVisibility(items, filteredItems, Visibility.PRIVATE); 593 | } 594 | 595 | private void filterVisibility(List items, List filteredItems, Visibility visibility) { 596 | for (VisibilityItem item: items) { 597 | if (item.visibility == visibility) { 598 | ((List)filteredItems).add(item); 599 | } 600 | } 601 | } 602 | 603 | private final Model _model; 604 | private final Type _type; 605 | private final ClassDoc _classDoc; 606 | private final List _params = new ArrayList<>(); 607 | private final List _rels = new ArrayList<>(); 608 | private final List _fields = new ArrayList<>(); 609 | private final List _constructors = new ArrayList<>(); 610 | private final List _methods = new ArrayList<>(); 611 | 612 | private final boolean _isInternal; 613 | } 614 | -------------------------------------------------------------------------------- /src/main/java/info/leadinglight/umljavadoclet/model/ModelPackage.java: -------------------------------------------------------------------------------- 1 | package info.leadinglight.umljavadoclet.model; 2 | 3 | import com.sun.javadoc.PackageDoc; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | /** 8 | * Represents a package containing classes. 9 | */ 10 | public class ModelPackage { 11 | public ModelPackage(Model model, PackageDoc packageDoc) { 12 | _model = model; 13 | _packageDoc = packageDoc; 14 | } 15 | 16 | public void map() { 17 | mapRelationships(); 18 | } 19 | 20 | public String fullName() { 21 | return fullName(_packageDoc); 22 | } 23 | 24 | public String qualifiedName() { 25 | return fullName(_packageDoc); 26 | } 27 | 28 | public List classes() { 29 | return _classes; 30 | } 31 | 32 | public List dependencies() { 33 | return _dependencyPackages; 34 | } 35 | 36 | public List dependents() { 37 | return _dependentPackages; 38 | } 39 | 40 | public static String fullName(PackageDoc packageDoc) { 41 | return packageDoc.name(); 42 | } 43 | 44 | /** 45 | * Is this package an immediate child package of the specified package? 46 | * @param parentPackage Package to check. 47 | * @return Whether or not it is a child package. 48 | */ 49 | public boolean isChildPackage(ModelPackage parentPackage) { 50 | if (parentPackage != this) { 51 | if (qualifiedName().startsWith(parentPackage.qualifiedName())) { 52 | String thisPath = qualifiedName().substring(parentPackage.qualifiedName().length() + 1); 53 | // If the remaining part of the package name does not contain a period, it is an immediate child. 54 | return (!thisPath.contains(".")); 55 | } 56 | return false; 57 | } 58 | return false; 59 | } 60 | 61 | public String parentPackageFullName() { 62 | if(qualifiedName().contains(".")){ 63 | return qualifiedName().substring(0, qualifiedName().lastIndexOf(".")); 64 | } else { 65 | return qualifiedName(); 66 | } 67 | } 68 | 69 | // Update model 70 | 71 | public void addClass(ModelClass modelClass) { 72 | if (!_classes.contains(modelClass)) { 73 | _classes.add(modelClass); 74 | } 75 | } 76 | 77 | // Mapping 78 | 79 | private void mapRelationships() { 80 | for (ModelClass modelClass: _classes) { 81 | for (ModelRel rel: modelClass.relationships()) { 82 | if (rel.source() == modelClass) { 83 | ModelClass dest = rel.destination(); 84 | ModelPackage destPackage = dest.modelPackage(); 85 | // Only packages that are included in the model are modelled. 86 | if (destPackage != null) { 87 | if (destPackage != this && !_dependencyPackages.contains(destPackage)) { 88 | _dependencyPackages.add(destPackage); 89 | } 90 | } 91 | } else { 92 | ModelClass src = rel.source(); 93 | ModelPackage srcPackage = src.modelPackage(); 94 | // Only packages that are included in the model are modelled. 95 | if (srcPackage != null) { 96 | if (srcPackage != this && !_dependentPackages.contains(srcPackage)) { 97 | _dependentPackages.add(srcPackage); 98 | } 99 | } 100 | } 101 | } 102 | } 103 | } 104 | 105 | private final Model _model; 106 | private final PackageDoc _packageDoc; 107 | private final List _classes = new ArrayList<>(); 108 | private final List _dependentPackages = new ArrayList<>(); 109 | private final List _dependencyPackages = new ArrayList<>(); 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/info/leadinglight/umljavadoclet/model/ModelRel.java: -------------------------------------------------------------------------------- 1 | package info.leadinglight.umljavadoclet.model; 2 | 3 | /** 4 | * Representation of a relationship in the model. 5 | * Because of the nature of Java relationship between classes, 6 | * each relationship is directed from the source to the destination. 7 | * Any relationship from the destination to the source is 8 | * represented with a different relationship. 9 | * This is not quite the correct model according to UML, since it 10 | * does not map correctly to associations. However, probably good 11 | * enough for the purpose of Javadoc diagrams. 12 | */ 13 | public class ModelRel { 14 | public ModelRel(Kind kind, ModelClass src, ModelClass dest) { 15 | _kind = kind; 16 | _src = src; 17 | _dest = dest; 18 | _srcRole = null; 19 | _srcCardinality = null; 20 | _srcVisibility = null; 21 | } 22 | 23 | public ModelRel(Kind kind, ModelClass src, ModelClass dest, Visibility srcVisibility) { 24 | _kind = kind; 25 | _src = src; 26 | _dest = dest; 27 | _srcRole = null; 28 | _srcCardinality = null; 29 | _srcVisibility = srcVisibility; 30 | } 31 | 32 | public ModelRel(Kind kind, ModelClass src, ModelClass dest, String srcRole) { 33 | _kind = kind; 34 | _src = src; 35 | _dest = dest; 36 | _srcRole = srcRole; 37 | _srcCardinality = null; 38 | _srcVisibility = null; 39 | } 40 | 41 | public ModelRel(Kind kind, ModelClass src, ModelClass dest, String srcRole, Multiplicity srcCardinality) { 42 | _kind = kind; 43 | _src = src; 44 | _dest = dest; 45 | _srcRole = srcRole; 46 | _srcCardinality = srcCardinality; 47 | _srcVisibility = null; 48 | } 49 | 50 | public Kind kind() { 51 | return _kind; 52 | } 53 | 54 | public ModelClass source() { 55 | return _src; 56 | } 57 | 58 | public ModelClass destination() { 59 | return _dest; 60 | } 61 | 62 | public String destinationRole() { 63 | return _srcRole; 64 | } 65 | 66 | public Multiplicity destinationCardinality() { 67 | return _srcCardinality; 68 | } 69 | 70 | public Visibility destinationVisibility() { 71 | return _srcVisibility; 72 | } 73 | 74 | public void changeVisibility(Visibility visibility) { 75 | _srcVisibility = visibility; 76 | } 77 | 78 | public boolean isVisible(Visibility visibility) { 79 | if (_srcVisibility == null) { 80 | return true; 81 | } else { 82 | if (visibility == Visibility.PUBLIC && 83 | _srcVisibility == Visibility.PUBLIC) { 84 | return true; 85 | } else if (visibility == Visibility.PROTECTED && 86 | (_srcVisibility == Visibility.PUBLIC || _srcVisibility == Visibility.PROTECTED)) { 87 | return true; 88 | } else if (visibility == Visibility.PACKAGE && 89 | (_srcVisibility == Visibility.PUBLIC || _srcVisibility == Visibility.PROTECTED || _srcVisibility == Visibility.PACKAGE)) { 90 | return true; 91 | } else if (visibility == Visibility.PRIVATE) { 92 | return true; 93 | } 94 | return false; 95 | } 96 | } 97 | 98 | public enum Kind { 99 | DIRECTED_ASSOCIATION, GENERALIZATION, REALIZATION, DEPENDENCY; 100 | } 101 | 102 | public enum Multiplicity { 103 | ZERO_OR_ONE, ONE, MANY 104 | } 105 | 106 | public enum Visibility { 107 | PUBLIC, PROTECTED, PACKAGE, PRIVATE 108 | } 109 | 110 | private final Kind _kind; 111 | private final ModelClass _src; 112 | private final ModelClass _dest; 113 | private final String _srcRole; 114 | private final Multiplicity _srcCardinality; 115 | private Visibility _srcVisibility; 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/info/leadinglight/umljavadoclet/model/RelFilter.java: -------------------------------------------------------------------------------- 1 | package info.leadinglight.umljavadoclet.model; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * Lookup of relationships. 8 | */ 9 | public class RelFilter { 10 | public RelFilter() { 11 | _rels = new ArrayList<>(); 12 | } 13 | 14 | public RelFilter(List rels) { 15 | _rels = rels; 16 | } 17 | 18 | public boolean isEmpty() { 19 | return _rels.isEmpty(); 20 | } 21 | 22 | public List all() { 23 | return _rels; 24 | } 25 | 26 | public ModelRel first() { 27 | return _rels.size() > 0 ? _rels.get(0) : null; 28 | } 29 | 30 | public List sourceClasses() { 31 | List classes = new ArrayList<>(); 32 | for (ModelRel rel: _rels) { 33 | classes.add(rel.source()); 34 | } 35 | return classes; 36 | } 37 | 38 | public List destinationClasses() { 39 | List classes = new ArrayList<>(); 40 | for (ModelRel rel: _rels) { 41 | classes.add(rel.destination()); 42 | } 43 | return classes; 44 | } 45 | 46 | public void add(ModelRel rel) { 47 | _rels.add(rel); 48 | } 49 | 50 | public RelFilter source(ModelClass source) { 51 | RelFilter filter = new RelFilter(); 52 | for (ModelRel rel: _rels) { 53 | if (rel.source() == source) { 54 | filter.add(rel); 55 | } 56 | } 57 | return filter; 58 | } 59 | 60 | public RelFilter destination(ModelClass dest) { 61 | RelFilter filter = new RelFilter(); 62 | for (ModelRel rel: _rels) { 63 | if (rel.destination() == dest) { 64 | filter.add(rel); 65 | } 66 | } 67 | return filter; 68 | } 69 | 70 | public RelFilter kind(ModelRel.Kind kind) { 71 | RelFilter filter = new RelFilter(); 72 | for (ModelRel rel: _rels) { 73 | if (rel.kind() == kind) { 74 | filter.add(rel); 75 | } 76 | } 77 | return filter; 78 | } 79 | 80 | public RelFilter between(ModelClass source, ModelClass dest) { 81 | return source(source).destination(dest); 82 | } 83 | 84 | private final List _rels; 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/info/leadinglight/umljavadoclet/printer/ContextDiagramPrinter.java: -------------------------------------------------------------------------------- 1 | package info.leadinglight.umljavadoclet.printer; 2 | 3 | import info.leadinglight.umljavadoclet.model.Model; 4 | import info.leadinglight.umljavadoclet.model.ModelClass; 5 | import info.leadinglight.umljavadoclet.model.ModelRel; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | public class ContextDiagramPrinter extends PumlDiagramPrinter { 10 | public ContextDiagramPrinter(Model model, ModelClass contextClass, DiagramOptions options) { 11 | super(model, options); 12 | _contextClass = contextClass; 13 | } 14 | 15 | public void generate() { 16 | start(); 17 | noPackagesOption(); 18 | addContextClass(_contextClass); 19 | for (ModelRel rel: _contextClass.relationships()) { 20 | addRelationship(rel); 21 | newline(); 22 | } 23 | end(); 24 | } 25 | 26 | // Highlight the class with a different colour. 27 | private void addContextClass(ModelClass modelClass) { 28 | // TODO Show in different color. 29 | String filepath = classFilepath(modelClass, modelClass); 30 | classDefinition(modelClass, true, filepath, null, true, true, true, false, true); 31 | _classes.add(modelClass); 32 | } 33 | 34 | // Put the class on the other side of the relationship on the diagram. 35 | private void addRelationship(ModelRel rel) { 36 | ModelClass otherClass = (rel.source() != _contextClass ? rel.source() : rel.destination()); 37 | boolean isJavaUtilClass = otherClass.fullName().startsWith("java.util."); 38 | // Don't show relationships with java.util (Collection) classes, unless it is as a superclass. 39 | if (!isJavaUtilClass || 40 | (isJavaUtilClass && (rel.kind() == ModelRel.Kind.GENERALIZATION || rel.kind() == ModelRel.Kind.REALIZATION))) { 41 | // Check to see if the class in the relationship is excluded from the diagram, either 42 | // by the name of the class or the name of the package. 43 | if (getOptions().isExcludedPackage(otherClass) || getOptions().isExcludedClass(otherClass)) { 44 | return; 45 | } 46 | 47 | if (isRelationshipVisible(rel)) { 48 | // Only draw the class on the other side of the relationship if it hasn't been added yet. 49 | if (!_classes.contains(otherClass)) { 50 | String filepath = null; 51 | if (otherClass.modelPackage() != null) { 52 | filepath = classFilepath(_contextClass, otherClass); 53 | } 54 | if (otherClass.modelPackage() == _contextClass.modelPackage()) { 55 | classDefinitionNoDetail(otherClass, true, filepath, null); 56 | } else if (otherClass.isInternal()) { 57 | classDefinitionNoDetail(otherClass, true, filepath, "white"); 58 | } else { 59 | classDefinitionNoDetail(otherClass, true, filepath, "lightgrey"); 60 | } 61 | _classes.add(otherClass); 62 | } 63 | 64 | // Draw the relationship with the other class. 65 | relationship(rel); 66 | } 67 | } 68 | } 69 | 70 | private final ModelClass _contextClass; 71 | private final List _classes = new ArrayList<>(); 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/info/leadinglight/umljavadoclet/printer/DiagramOption.java: -------------------------------------------------------------------------------- 1 | package info.leadinglight.umljavadoclet.printer; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class DiagramOption { 7 | public DiagramOption(String name, int length) { 8 | this.name = name; 9 | this.length = length; 10 | // No valid values / default options- free-form option. 11 | this.validValues = null; 12 | this.defaultValue = null; 13 | } 14 | 15 | public DiagramOption(String name, String validValues, String defaultValue, int length) { 16 | this.name = name; 17 | // Valid values are separated by a comma. 18 | this.validValues = new ArrayList<>(); 19 | String[] parts = validValues.split(","); 20 | for (String part: parts) { 21 | this.validValues.add(part.trim()); 22 | } 23 | this.defaultValue = defaultValue; 24 | this.length = length; 25 | } 26 | 27 | public String getName() { 28 | return name; 29 | } 30 | 31 | public void setName(String name) { 32 | this.name = name; 33 | } 34 | 35 | public List getValidValues() { 36 | return validValues; 37 | } 38 | 39 | public boolean isValidValue(String value) { 40 | return validValues != null ? validValues.contains(value) : true; 41 | } 42 | 43 | public void setValidValues(List validValues) { 44 | this.validValues = validValues; 45 | } 46 | 47 | public String getDefaultValue() { 48 | return defaultValue; 49 | } 50 | 51 | public void setDefaultValue(String defaultValue) { 52 | this.defaultValue = defaultValue; 53 | } 54 | 55 | public int getLength() { 56 | return length; 57 | } 58 | 59 | public void setLength(int length) { 60 | this.length = length; 61 | } 62 | 63 | public String getValue() { 64 | return value != null ? value : defaultValue; 65 | } 66 | 67 | public List getCsvValues() { 68 | List values = new ArrayList<>(); 69 | String value = getValue(); 70 | if (value != null && value.length() > 0) { 71 | String[] parts = value.split(","); 72 | for (String part: parts) { 73 | values.add(part); 74 | } 75 | } 76 | return values; 77 | } 78 | 79 | public void setValue(String value) { 80 | if (validValues != null) { 81 | for (String validValue : validValues) { 82 | if (validValue.equalsIgnoreCase(value)) { 83 | this.value = validValue; 84 | return; 85 | } 86 | } 87 | } else { 88 | // Free-form value. 89 | this.value = value; 90 | } 91 | } 92 | 93 | @Override 94 | public String toString() { 95 | return "DiagramOption{" + 96 | "name='" + name + '\'' + 97 | ", validValues=" + validValues + 98 | ", defaultValue='" + defaultValue + '\'' + 99 | ", length=" + length + 100 | ", value='" + value + '\'' + 101 | '}'; 102 | } 103 | 104 | private String name; 105 | private List validValues; 106 | private String defaultValue; 107 | private int length; 108 | private String value; 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/info/leadinglight/umljavadoclet/printer/DiagramOptions.java: -------------------------------------------------------------------------------- 1 | package info.leadinglight.umljavadoclet.printer; 2 | 3 | import info.leadinglight.umljavadoclet.model.ModelClass; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | * Handle options specific to uml-java-doclet. 10 | */ 11 | public class DiagramOptions { 12 | public DiagramOptions() { 13 | addOption(LINETYPE, "polyline,spline,ortho", "ortho", 2); 14 | addOption(DEPENDENCIES, "public,protected,package,private", "public", 2); 15 | addOption(PACKAGE_ORIENTATION, "left-to-right,top-to-bottom", "top-to-bottom", 2); 16 | addOption(OUTPUT_MODEL, "true,false", "false", 2); 17 | addOption(PUML_INCLUDE_FILE, 2); 18 | addOption(EXCLUDE_CLASSES, 2); 19 | addOption(EXCLUDE_PACKAGES, 2); 20 | } 21 | 22 | // Helpers for getting the value for a specific option 23 | 24 | private static final String LINETYPE = "linetype"; 25 | private static final String DEPENDENCIES = "dependencies"; 26 | private static final String PACKAGE_ORIENTATION = "package-orientation"; 27 | private static final String OUTPUT_MODEL = "output-model"; 28 | private static final String PUML_INCLUDE_FILE = "puml-include-file"; 29 | private static final String EXCLUDE_CLASSES = "exclude-classes"; 30 | private static final String EXCLUDE_PACKAGES = "exclude-packages"; 31 | 32 | public enum LineType { SPLINE, POLYLINE, ORTHO }; 33 | public enum Visibility { PUBLIC, PROTECTED, PACKAGE, PRIVATE }; 34 | public enum Orientation { LEFT_TO_RIGHT, TOP_TO_BOTTOM }; 35 | 36 | public LineType getLineType() { 37 | return LineType.valueOf(getOptionEnumValue(LINETYPE)); 38 | } 39 | 40 | public Visibility getDependenciesVisibility() { 41 | return Visibility.valueOf(getOptionEnumValue(DEPENDENCIES)); 42 | } 43 | 44 | public Orientation getPackageOrientation() { 45 | return Orientation.valueOf(getOptionEnumValue(PACKAGE_ORIENTATION)); 46 | } 47 | 48 | public boolean isOutputModel() { 49 | return getOptionValue(OUTPUT_MODEL).equals("true"); 50 | } 51 | 52 | public String getPumlIncludeFile() { 53 | return getOptionValue(PUML_INCLUDE_FILE); 54 | } 55 | 56 | public boolean hasPumlIncludeFile() { 57 | return getPumlIncludeFile() != null && getPumlIncludeFile().length() > 0; 58 | } 59 | 60 | public List getExcludedClasses() { 61 | return getOptionCsvValue(EXCLUDE_CLASSES); 62 | } 63 | 64 | public boolean isExcludedClass(ModelClass modelClass) { 65 | return getExcludedClasses().contains(modelClass.fullNameWithoutParameters()); 66 | } 67 | 68 | public List getExcludedPackages() { 69 | return getOptionCsvValue(EXCLUDE_PACKAGES); 70 | } 71 | 72 | public boolean isExcludedPackage(ModelClass modelClass) { 73 | return getExcludedPackages().contains(modelClass.packageName()); 74 | } 75 | 76 | /** 77 | * Set the options as provided in the strings. 78 | * Invalid options are ignored. 79 | * @param docletOptions Options provided in javadoc format for options: an array of string arrays, each 80 | * array indicating the name of the option (index 0) and the associated value. 81 | */ 82 | public void set(String[][] docletOptions) { 83 | for (String[] docletOption : docletOptions) { 84 | String docletName = docletOption[0]; 85 | DiagramOption option = getOptionForDocletName(docletName); 86 | if (option != null) { 87 | String docletValue = docletOption[1]; 88 | option.setValue(docletValue); 89 | } 90 | } 91 | } 92 | 93 | /** 94 | * Check to see if the specified option is valid. 95 | * @param docletName Name of the option to check. 96 | * @return Whether or not the option is valid. 97 | */ 98 | public boolean isValidOption(String docletName) { 99 | return getOptionForDocletName(docletName) != null; 100 | } 101 | 102 | /** 103 | * Get the number of parameters for the specified option. 104 | * @param docletName Name of option to get parameters for. 105 | * @return Number of parameters. 106 | */ 107 | public int getOptionLength(String docletName) { 108 | DiagramOption option = getOptionForDocletName(docletName); 109 | return option != null ? option.getLength() : 0; 110 | } 111 | 112 | /** 113 | * Check to see if the specified setting is a valid option. 114 | * @param setting Setting to check. 115 | * @return Error associated with the option, null if no error. 116 | */ 117 | public String checkOption(String[] setting) { 118 | String docletName = setting[0]; 119 | DiagramOption option = getOptionForDocletName(docletName); 120 | if (option == null) { 121 | return "Invalid option " + docletName; 122 | } 123 | String value = setting[1]; 124 | if (!option.isValidValue(value)) { 125 | return "Invalid value " + value + " for option " + docletName + "; valid values are " + option.getValidValues(); 126 | } 127 | return null; 128 | } 129 | 130 | public String getOptionValuesAsString() { 131 | String result = ""; 132 | for (DiagramOption option: options) { 133 | result += option.getName() + "=" + option.getValue() + " "; 134 | } 135 | return result; 136 | } 137 | 138 | // Helpers 139 | 140 | private void addOption(String name, int length) { 141 | DiagramOption option = new DiagramOption(name, length); 142 | options.add(option); 143 | } 144 | 145 | private void addOption(String name, String validValues, String defaultValue, int length) { 146 | DiagramOption option = new DiagramOption(name, validValues, defaultValue, length); 147 | options.add(option); 148 | } 149 | 150 | private DiagramOption getOption(String name) { 151 | for (DiagramOption option: options) { 152 | if (option.getName().equals(name)) { 153 | return option; 154 | } 155 | } 156 | return null; 157 | } 158 | 159 | private DiagramOption getOptionForDocletName(String nameWithHyphen) { 160 | String name = nameWithHyphen.substring(1); 161 | return getOption(name); 162 | } 163 | 164 | private String getOptionValue(String name) { 165 | DiagramOption option = getOption(name); 166 | return option != null ? option.getValue() : null; 167 | } 168 | 169 | private List getOptionCsvValue(String name) { 170 | DiagramOption option = getOption(name); 171 | return option != null ? option.getCsvValues() : new ArrayList<>(); 172 | } 173 | 174 | private String getOptionEnumValue(String name) { 175 | String value = getOptionValue(name); 176 | // Any hyphens in the name are treated as _ in the enum value. 177 | return value != null ? value.toUpperCase().replace("-", "_") : null; 178 | } 179 | 180 | @Override 181 | public String toString() { 182 | return "DiagramOptions{" + 183 | "options=" + options + 184 | '}'; 185 | } 186 | 187 | private List options = new ArrayList<>(); 188 | } 189 | -------------------------------------------------------------------------------- /src/main/java/info/leadinglight/umljavadoclet/printer/ModelPrinter.java: -------------------------------------------------------------------------------- 1 | package info.leadinglight.umljavadoclet.printer; 2 | 3 | import info.leadinglight.umljavadoclet.model.Model; 4 | import info.leadinglight.umljavadoclet.model.ModelClass; 5 | import info.leadinglight.umljavadoclet.model.ModelPackage; 6 | import info.leadinglight.umljavadoclet.model.ModelRel; 7 | 8 | public class ModelPrinter extends Printer { 9 | public ModelPrinter(Model model) { 10 | _model = model; 11 | } 12 | 13 | public void generate() { 14 | printClasses(); 15 | printPackages(); 16 | } 17 | 18 | private void printClasses() { 19 | for (ModelClass modelClass: _model.classes()) { 20 | if (modelClass.isInternal()) { 21 | printClass(modelClass); 22 | for (ModelRel rel: modelClass.relationships()) { 23 | printRel(modelClass, rel); 24 | } 25 | } 26 | } 27 | } 28 | 29 | private void printClass(ModelClass modelClass) { 30 | println("Class: " + modelClass.fullName()); 31 | } 32 | 33 | private void printRel(ModelClass modelClass, ModelRel rel) { 34 | indent(1); 35 | boolean fromSource = (rel.source() == modelClass); 36 | printKind(rel.kind(), fromSource); 37 | print(": "); 38 | print(fromSource ? rel.destination().fullName() :rel.source().fullName()); 39 | if (rel.destinationRole() != null) { 40 | print (" " + rel.destinationRole()); 41 | } 42 | if (rel.destinationCardinality() != null) { 43 | print (" "); 44 | printMultiplicity(rel.destinationCardinality()); 45 | } 46 | if (rel.destinationVisibility() != null) { 47 | print(" "); 48 | printVisibility(rel.destinationVisibility()); 49 | } 50 | newline(); 51 | } 52 | 53 | private void printKind(ModelRel.Kind kind, boolean fromSource) { 54 | switch (kind) { 55 | case GENERALIZATION: 56 | print (fromSource ? "extends" : "extended by"); 57 | return; 58 | case REALIZATION: 59 | print (fromSource ? "implements" : "implemented by"); 60 | return; 61 | case DIRECTED_ASSOCIATION: 62 | print (fromSource ? "has" : "had by"); 63 | return; 64 | case DEPENDENCY: 65 | print (fromSource ? "uses" : "used by"); 66 | } 67 | } 68 | 69 | private void printMultiplicity(ModelRel.Multiplicity mult) { 70 | switch (mult) { 71 | case ONE: 72 | print("one"); 73 | return; 74 | case ZERO_OR_ONE: 75 | print("zero or one"); 76 | return; 77 | case MANY: 78 | print("many"); 79 | } 80 | } 81 | 82 | private void printVisibility(ModelRel.Visibility visibility) { 83 | switch (visibility) { 84 | case PUBLIC: 85 | print("public"); 86 | return; 87 | case PROTECTED: 88 | print("protected"); 89 | return; 90 | case PACKAGE: 91 | print("package"); 92 | return; 93 | case PRIVATE: 94 | print("private"); 95 | } 96 | } 97 | 98 | private void printPackages() { 99 | for (ModelPackage modelPackage: _model.packages()) { 100 | printPackage(modelPackage); 101 | } 102 | } 103 | 104 | private void printPackage(ModelPackage modelPackage) { 105 | println("Package: " + modelPackage.fullName()); 106 | for (ModelPackage dependency: modelPackage.dependencies()) { 107 | println(1, "depends on: " + dependency.fullName()); 108 | } 109 | for (ModelPackage dependent: modelPackage.dependents()) { 110 | println(1, "is dependency for: " + dependent.fullName()); 111 | } 112 | for (ModelClass modelClass: modelPackage.classes()) { 113 | indent(1); 114 | printClass(modelClass); 115 | } 116 | } 117 | 118 | private final Model _model; 119 | } 120 | -------------------------------------------------------------------------------- /src/main/java/info/leadinglight/umljavadoclet/printer/OverviewDiagramPrinter.java: -------------------------------------------------------------------------------- 1 | package info.leadinglight.umljavadoclet.printer; 2 | 3 | import info.leadinglight.umljavadoclet.model.Model; 4 | import info.leadinglight.umljavadoclet.model.ModelClass; 5 | import info.leadinglight.umljavadoclet.model.ModelPackage; 6 | 7 | /** 8 | * Diagram showing all packages and all classes. 9 | * No relationships. 10 | */ 11 | public class OverviewDiagramPrinter extends PumlDiagramPrinter { 12 | public OverviewDiagramPrinter(Model model, DiagramOptions options) { 13 | super(model, options); 14 | } 15 | 16 | public void generate() { 17 | start(); 18 | // The layout for packages is really bad. 19 | // Just show the classes within all of the packages in the model. 20 | for (ModelPackage modelPackage: getModel().rootPackages()) { 21 | packageDefinition(modelPackage, packageFilepath(modelPackage), null); 22 | for (ModelClass modelClass: modelPackage.classes()) { 23 | String filepath = null; 24 | if (modelClass.modelPackage() != null) { 25 | filepath = classFilepath(modelClass); 26 | } 27 | classDefinitionNoDetail(modelClass, false, filepath, null); 28 | } 29 | } 30 | end(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/info/leadinglight/umljavadoclet/printer/PackageDiagramPrinter.java: -------------------------------------------------------------------------------- 1 | package info.leadinglight.umljavadoclet.printer; 2 | 3 | import info.leadinglight.umljavadoclet.model.Model; 4 | import info.leadinglight.umljavadoclet.model.ModelClass; 5 | import info.leadinglight.umljavadoclet.model.ModelPackage; 6 | import info.leadinglight.umljavadoclet.model.ModelRel; 7 | 8 | /** 9 | * Generate PUML for package diagram. 10 | */ 11 | public class PackageDiagramPrinter extends PumlDiagramPrinter { 12 | public PackageDiagramPrinter(Model model, ModelPackage modelPackage, DiagramOptions options) { 13 | super(model, options); 14 | _modelPackage = modelPackage; 15 | } 16 | 17 | public void generate() { 18 | start(); 19 | 20 | // Option for displaying the packages diagram left to right. 21 | if (getOptions().getPackageOrientation() == DiagramOptions.Orientation.LEFT_TO_RIGHT) { 22 | leftToRight(); 23 | } 24 | 25 | // Layout of packages is really, really bad. 26 | // Would love to show relationships between packages, but it is just awful. 27 | //noPackagesOption(); 28 | addPackage(_modelPackage, "lightyellow"); 29 | addRelationships(_modelPackage); 30 | // Get all of the sub-packages of the model package and draw them as well. 31 | for (ModelPackage subPackage: getModel().childPackages(_modelPackage)) { 32 | addPackage(subPackage, null); 33 | } 34 | end(); 35 | } 36 | 37 | public void addPackage(ModelPackage modelPackage, String color) { 38 | String filepath = packageFilepath(_modelPackage, modelPackage); 39 | packageDefinition(modelPackage, filepath, color); 40 | for (ModelClass modelClass: modelPackage.classes()) { 41 | filepath = classFilepath(_modelPackage, modelClass); 42 | classDefinitionNoDetail(modelClass, false, filepath, null); 43 | } 44 | } 45 | 46 | public void addRelationships(ModelPackage modelPackage) { 47 | for (ModelClass modelClass: modelPackage.classes()) { 48 | // Only draw the relationships between the classes in the package. 49 | for (ModelRel rel: modelClass.relationships()) { 50 | if (rel.source() == modelClass && rel.destination().modelPackage() == modelPackage) { 51 | relationship(rel); 52 | } 53 | } 54 | } 55 | } 56 | 57 | private final ModelPackage _modelPackage; 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/info/leadinglight/umljavadoclet/printer/Printer.java: -------------------------------------------------------------------------------- 1 | package info.leadinglight.umljavadoclet.printer; 2 | 3 | import java.io.File; 4 | import java.io.FileOutputStream; 5 | import java.io.IOException; 6 | 7 | public abstract class Printer { 8 | public boolean toFile(String filename) { 9 | return dumpToFile(filename, _sb.toString()); 10 | } 11 | 12 | public boolean toFile(File file) { 13 | return dumpToFile(file, _sb.toString()); 14 | } 15 | 16 | @Override 17 | public String toString() { 18 | return _sb.toString(); 19 | } 20 | 21 | public StringBuilder stringBuilder() { 22 | return _sb; 23 | } 24 | 25 | public void print(String str) { 26 | _sb.append(str); 27 | } 28 | 29 | public void print(int level, String str) { 30 | indent(level); 31 | _sb.append(str); 32 | } 33 | 34 | public void println(String str) { 35 | _sb.append(str); 36 | _sb.append("\n"); 37 | } 38 | 39 | public void println(int level, String str) { 40 | indent(level); 41 | _sb.append(str); 42 | _sb.append("\n"); 43 | } 44 | 45 | public void indent() { 46 | indent(1); 47 | } 48 | 49 | public void indent(int level) { 50 | for (int i=0; i 0) { 64 | print(" [["); 65 | print(filepath); 66 | print("{" + modelPackage.fullName() + "}"); 67 | print("]]"); 68 | } 69 | if (color != null && color.length() > 0) { 70 | print(" #" + color); 71 | } 72 | println(" {"); 73 | println("}"); 74 | newline(); 75 | } 76 | 77 | // Displays the class with all details, and full method signatures (if displayed). 78 | public void classDefinition(ModelClass modelClass, 79 | boolean displayPackageName, 80 | String filepath, 81 | String color, 82 | boolean showFields, 83 | boolean showConstructors, 84 | boolean showMethods, 85 | boolean publicMethodsOnly, 86 | boolean includeTypeInfo) { 87 | classDeclaration(modelClass, displayPackageName); 88 | if (filepath != null && filepath.length() > 0) { 89 | print(" [["); 90 | print(filepath); 91 | print("{" + modelClass.fullNameWithoutParameters() + "}"); 92 | print("]]"); 93 | } 94 | if (color != null && color.length() > 0) { 95 | print(" #" + color); 96 | } 97 | println(" {"); 98 | if (showFields) { 99 | for (ModelClass.Field field: modelClass.fields()) { 100 | field(field, includeTypeInfo); 101 | } 102 | } 103 | if (showConstructors) { 104 | for (ModelClass.Constructor cons: modelClass.constructors()) { 105 | if (publicMethodsOnly == false || publicMethodsOnly == true && cons.visibility == ModelClass.Visibility.PUBLIC) { 106 | constructor(cons, includeTypeInfo); 107 | } 108 | } 109 | } 110 | if (showMethods) { 111 | for (ModelClass.Method method: modelClass.methods()) { 112 | if (publicMethodsOnly == false || publicMethodsOnly == true && method.visibility == ModelClass.Visibility.PUBLIC) { 113 | method(method, includeTypeInfo); 114 | } 115 | } 116 | } 117 | println("}"); 118 | newline(); 119 | if (!showFields) { 120 | hideFields(modelClass); 121 | } 122 | if (!showConstructors && !showMethods) { 123 | hideMethods(modelClass); 124 | } 125 | newline(); 126 | } 127 | 128 | public void classDefinitionNoDetail(ModelClass modelClass, boolean displayPackageName, String filepath, String color) { 129 | classDefinition(modelClass, displayPackageName, filepath, color, false, false, false, false, false); 130 | } 131 | 132 | public void classDeclaration(ModelClass modelClass, boolean displayPackageName) { 133 | classType(modelClass); 134 | print(" \""); 135 | print(""); 136 | print(modelClass.shortName()); 137 | print(""); 138 | if (displayPackageName) { 139 | print("\\n"); 140 | print(modelClass.packageName()); 141 | } 142 | print("\" as "); 143 | className(modelClass); 144 | print(" "); 145 | annotations(modelClass); 146 | } 147 | 148 | public void annotations(ModelClass modelClass) { 149 | List annotations = modelClass.annotations(); 150 | if (annotations.size() > 0) { 151 | for (String annotation: modelClass.annotations()) { 152 | print("<<"); 153 | print(annotation); 154 | print(">>"); 155 | } 156 | } 157 | } 158 | 159 | public void className(ModelClass modelClass) { 160 | // Parameterized classes have < and , in the name, which confuses PlantUML. 161 | // Strip out these characters from the name. 162 | // The name will remain unique across the model, and this 163 | // does not affect the way the class is displayed. 164 | print(modelClass.fullNameWithoutParameters()); 165 | } 166 | 167 | public void classType(ModelClass modelClass) { 168 | switch(modelClass.type()) { 169 | case INTERFACE: 170 | print("interface"); 171 | break; 172 | case ENUM: 173 | print("enum"); 174 | break; 175 | case ABSTRACT: 176 | print("abstract"); 177 | break; 178 | default: 179 | print("class"); 180 | } 181 | } 182 | 183 | public void field(ModelClass.Field field, boolean includeTypeInfo) { 184 | if (field.isStatic) { 185 | printStatic(); 186 | } 187 | visibility(field.visibility); 188 | if (includeTypeInfo) { 189 | print(field.type + " "); 190 | } 191 | print(field.name); 192 | newline(); 193 | } 194 | 195 | public void visibility(ModelClass.Visibility visibility) { 196 | switch(visibility) { 197 | case PUBLIC: 198 | print("+"); 199 | break; 200 | case PROTECTED: 201 | print("#"); 202 | break; 203 | case PACKAGE_PRIVATE: 204 | print("~"); 205 | break; 206 | default: 207 | print("-"); 208 | } 209 | } 210 | 211 | public void constructor(ModelClass.Constructor constructor, boolean includeTypeInfo) { 212 | visibility(constructor.visibility); 213 | print(constructor.name); 214 | print("("); 215 | if (includeTypeInfo) { 216 | String sep = ""; 217 | for (ModelClass.MethodParameter param: constructor.parameters) { 218 | print(sep); 219 | print(param.type); 220 | print(" "); 221 | print(param.name); 222 | sep = ","; 223 | } 224 | } 225 | print(")"); 226 | newline(); 227 | } 228 | 229 | public void method(ModelClass.Method method, boolean includeTypeInfo) { 230 | if (method.isStatic) { 231 | printStatic(); 232 | } 233 | if (method.isAbstract) { 234 | printAbstract(); 235 | } 236 | visibility(method.visibility); 237 | if (includeTypeInfo) { 238 | print(method.returnType); 239 | print(" "); 240 | } 241 | print(method.name); 242 | print("("); 243 | if (includeTypeInfo) { 244 | String sep = ""; 245 | for (ModelClass.MethodParameter param: method.parameters) { 246 | print(sep); 247 | print(param.type); 248 | print(" "); 249 | print(param.name); 250 | sep = ","; 251 | } 252 | } 253 | print(")"); 254 | newline(); 255 | } 256 | 257 | public void hideFields(ModelClass modelClass) { 258 | print("hide "); 259 | className(modelClass); 260 | print(" fields"); 261 | newline(); 262 | } 263 | 264 | public void hideMethods(ModelClass modelClass) { 265 | print("hide "); 266 | className(modelClass); 267 | print(" methods"); 268 | newline(); 269 | } 270 | 271 | public void relationship(ModelRel rel) { 272 | if (isRelationshipVisible(rel)) { 273 | switch (rel.kind()) { 274 | case GENERALIZATION: 275 | generalization(rel.source(), rel.destination()); 276 | break; 277 | case DEPENDENCY: 278 | dependency(rel.source(), rel.destination()); 279 | break; 280 | case REALIZATION: 281 | realization(rel.source(), rel.destination()); 282 | break; 283 | case DIRECTED_ASSOCIATION: 284 | association(rel.source(), rel.destination(), rel.destinationRole(), multiplicityLabel(rel.destinationCardinality())); 285 | break; 286 | } 287 | } 288 | } 289 | 290 | public boolean isRelationshipVisible(ModelRel rel) { 291 | // Check to see if the relationship is visible according to diagram options. 292 | if (_options.getDependenciesVisibility() == DiagramOptions.Visibility.PUBLIC) { 293 | return rel.isVisible(ModelRel.Visibility.PUBLIC); 294 | } else if (_options.getDependenciesVisibility() == DiagramOptions.Visibility.PROTECTED) { 295 | return rel.isVisible(ModelRel.Visibility.PROTECTED); 296 | } else if (_options.getDependenciesVisibility() == DiagramOptions.Visibility.PACKAGE) { 297 | return rel.isVisible(ModelRel.Visibility.PACKAGE); 298 | } else if (_options.getDependenciesVisibility() == DiagramOptions.Visibility.PRIVATE) { 299 | return rel.isVisible(ModelRel.Visibility.PRIVATE); 300 | } 301 | return false; 302 | } 303 | 304 | public void generalization(ModelClass src, ModelClass dest) { 305 | printRel(src, "--|>", dest); 306 | } 307 | 308 | public void realization(ModelClass src, ModelClass dest) { 309 | printRel(src, "..|>", dest); 310 | } 311 | 312 | public void dependency(ModelClass src, ModelClass dest) { 313 | printRel(src, "..>", dest); 314 | } 315 | 316 | public void packageDependency(ModelPackage src, ModelPackage dest) { 317 | print(src.fullName()); 318 | print(" ..> "); 319 | print(dest.fullName()); 320 | newline(); 321 | } 322 | 323 | public void association(ModelClass src, ModelClass dest, String destRole, String destCardinality) { 324 | printRel(src, null, null, "-->", dest, destRole, destCardinality); 325 | } 326 | 327 | public String multiplicityLabel(ModelRel.Multiplicity mult) { 328 | if (mult != null) { 329 | switch(mult) { 330 | case ONE: 331 | return "1"; 332 | case ZERO_OR_ONE: 333 | return "0..1"; 334 | case MANY: 335 | return "*"; 336 | default: 337 | return null; 338 | } 339 | } else { 340 | return null; 341 | } 342 | } 343 | 344 | private void printAbstract() { 345 | print("{abstract} "); 346 | } 347 | 348 | private void printStatic() { 349 | print("{static} "); 350 | } 351 | 352 | private void printRel(ModelClass src, String relText, ModelClass dest) { 353 | className(src); 354 | print (" " + relText + " "); 355 | className(dest); 356 | newline(); 357 | } 358 | 359 | private void printRel(ModelClass src, String srcRole, String srcCardinality, String relText, ModelClass dest, String destRole, String destCardinality) { 360 | className(src); 361 | printRelLabel(srcRole, srcCardinality); 362 | print (" " + relText + " "); 363 | printRelLabel(destRole, destCardinality); 364 | className(dest); 365 | newline(); 366 | } 367 | 368 | private void printRelLabel(String role, String cardinality) { 369 | if ((role != null && role.length() > 0) || (cardinality != null && cardinality.length() > 0)) { 370 | print(" \""); 371 | if (role != null && role.length() > 0) { 372 | print(role); 373 | } 374 | if (cardinality != null && cardinality.length() > 0) { 375 | print(" " + cardinality); 376 | } 377 | print("\" "); 378 | } 379 | } 380 | 381 | private void printLineTypeOption() { 382 | if (_options.getLineType() == DiagramOptions.LineType.ORTHO) { 383 | println("skinparam linetype ortho"); 384 | } else if (_options.getLineType() == DiagramOptions.LineType.POLYLINE) { 385 | println("skinparam linetype polyline"); 386 | } 387 | // Otherwise, splines is the default. No skinparam required. 388 | } 389 | 390 | // Filepath 391 | 392 | public String classFilepath(ModelClass modelClass, ModelClass classFile) { 393 | StringBuilder sb = new StringBuilder(); 394 | sb.append(relativePathToRoot(modelClass.packageName())); 395 | sb.append(pathToPackage(classFile.packageName())); 396 | sb.append(classFile.shortNameWithoutParameters()); 397 | sb.append(".html"); 398 | return sb.toString(); 399 | } 400 | 401 | public String packageFilepath(ModelClass modelClass, ModelPackage packageFile) { 402 | StringBuilder sb = new StringBuilder(); 403 | sb.append(relativePathToRoot(modelClass.packageName())); 404 | sb.append(pathToPackage(packageFile.fullName())); 405 | sb.append("pacakge-summary.html"); 406 | return sb.toString(); 407 | } 408 | 409 | public String classFilepath(ModelPackage modelPackage, ModelClass classFile) { 410 | StringBuilder sb = new StringBuilder(); 411 | sb.append(relativePathToRoot(modelPackage.fullName())); 412 | sb.append(pathToPackage(classFile.packageName())); 413 | sb.append(classFile.shortNameWithoutParameters()); 414 | sb.append(".html"); 415 | return sb.toString(); 416 | } 417 | 418 | public String packageFilepath(ModelPackage modelPackage, ModelPackage packageFile) { 419 | StringBuilder sb = new StringBuilder(); 420 | sb.append(relativePathToRoot(modelPackage.fullName())); 421 | sb.append(pathToPackage(packageFile.fullName())); 422 | sb.append("package-summary.html"); 423 | return sb.toString(); 424 | } 425 | 426 | public String classFilepath(ModelClass classFile) { 427 | StringBuilder sb = new StringBuilder(); 428 | sb.append(pathToPackage(classFile.packageName())); 429 | sb.append(classFile.shortNameWithoutParameters()); 430 | sb.append(".html"); 431 | return sb.toString(); 432 | } 433 | 434 | public String packageFilepath(ModelPackage packageFile) { 435 | StringBuilder sb = new StringBuilder(); 436 | sb.append(pathToPackage(packageFile.fullName())); 437 | sb.append("package-summary.html"); 438 | return sb.toString(); 439 | } 440 | 441 | public String relativePathToRoot(String packageName) { 442 | StringBuilder sb = new StringBuilder(); 443 | String[] parts = packageName.split("\\."); 444 | if (parts.length > 0) { 445 | for(int i=0; i { 4 | public T getType() { 5 | return type; 6 | } 7 | 8 | public void setType(T type) { 9 | this.type = type; 10 | } 11 | 12 | private T type; 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/info/leadinglight/umljavadoclet/generics/TestGenerics.java: -------------------------------------------------------------------------------- 1 | package info.leadinglight.umljavadoclet.generics; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.function.BiConsumer; 6 | import java.util.function.Consumer; 7 | 8 | public class TestGenerics { 9 | 10 | Map result = new HashMap<>(); 11 | 12 | public static void main(String[] args[]) { 13 | new TestGenerics().createValue("1").setValue("1VAL").build().createValue("2").setValue("2VAL").build().extendValue("1").setValue("1WAL-Override").build(); 14 | } 15 | 16 | public MyModelBuilder createValue(String key) { 17 | return new MyModelBuilder<>(this, new MyModel(), (p, v) -> p.result.put(v.key, v)).setKey(key); 18 | } 19 | 20 | public MyModelBuilder extendValue(String key) { 21 | return new MyModelBuilder<>(this, result.get(key), (p, v) -> { 22 | }); 23 | } 24 | 25 | public class MyModel { 26 | String key; 27 | String value; 28 | } 29 | 30 | public interface Builder { 31 | T build(); 32 | } 33 | 34 | /** 35 | * Abstract object builder with parent builder. 36 | * 37 | * @param

The parent builder. 38 | * @param The object to build. 39 | * @param The current builder. 40 | */ 41 | abstract public class AbstractBuilder implements Builder

{ 42 | private final P parent; 43 | private final T value; 44 | private final BiConsumer parentBuilder; 45 | 46 | public AbstractBuilder(P parent, T value, BiConsumer parentBuilder) { 47 | this.parent = parent; 48 | this.value = value; 49 | this.parentBuilder = parentBuilder; 50 | } 51 | 52 | @Override 53 | public final P build() { 54 | parentBuilder.accept(parent, value); 55 | return parent; 56 | } 57 | 58 | protected final B make(Consumer mixer) { 59 | mixer.accept(value); 60 | return (B) this; 61 | } 62 | } 63 | 64 | public class MyModelBuilder

extends AbstractBuilder> { 65 | public MyModelBuilder(P parent, MyModel value, BiConsumer parentBuilder) { 66 | super(parent, value, parentBuilder); 67 | } 68 | 69 | protected MyModelBuilder

setKey(String key) { 70 | return make(v -> v.key = key); 71 | } 72 | 73 | public MyModelBuilder

setValue(String value) { 74 | return make(v -> v.value = value); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/test/java/info/leadinglight/umljavadoclet/generics/TestGenerics2.java: -------------------------------------------------------------------------------- 1 | package info.leadinglight.umljavadoclet.generics; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | 6 | public class TestGenerics2 { 7 | public List> getOptionals() { 8 | return optionals; 9 | } 10 | 11 | public void setOptionals(List> optionals) { 12 | this.optionals = optionals; 13 | } 14 | 15 | private List> optionals; 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/info/leadinglight/umljavadoclet/generics/TestGenerics3.java: -------------------------------------------------------------------------------- 1 | package info.leadinglight.umljavadoclet.generics; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | import java.util.Optional; 6 | 7 | public class TestGenerics3 { 8 | public Map>> getMap() { 9 | return map; 10 | } 11 | 12 | public void setMap(Map>> map) { 13 | this.map = map; 14 | } 15 | 16 | private Map>> map; 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/info/leadinglight/umljavadoclet/generics/TestGenerics4.java: -------------------------------------------------------------------------------- 1 | package info.leadinglight.umljavadoclet.generics; 2 | 3 | import java.util.HashMap; 4 | import java.util.List; 5 | 6 | public class TestGenerics4 extends HashMap> { 7 | } 8 | -------------------------------------------------------------------------------- /src/test/java/info/leadinglight/umljavadoclet/generics/TestGenerics5.java: -------------------------------------------------------------------------------- 1 | package info.leadinglight.umljavadoclet.generics; 2 | 3 | import java.util.Optional; 4 | 5 | public class TestGenerics5 extends AbstractGeneric { 6 | } 7 | -------------------------------------------------------------------------------- /src/test/java/info/leadinglight/umljavadoclet/generics/TestGenerics6.java: -------------------------------------------------------------------------------- 1 | package info.leadinglight.umljavadoclet.generics; 2 | 3 | import java.util.Optional; 4 | 5 | public class TestGenerics6 extends AbstractGeneric> { 6 | } 7 | -------------------------------------------------------------------------------- /src/test/java/info/leadinglight/umljavadoclet/generics/TestGenerics7.java: -------------------------------------------------------------------------------- 1 | package info.leadinglight.umljavadoclet.generics; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | 6 | public class TestGenerics7

extends HashMap { 7 | } 8 | -------------------------------------------------------------------------------- /src/test/java/info/leadinglight/umljavadoclet/generics/TestGenerics8.java: -------------------------------------------------------------------------------- 1 | package info.leadinglight.umljavadoclet.generics; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | 7 | public class TestGenerics8

extends HashMap { 8 | public static class ListType {} 9 | // TODO Wildcards not displayed with bounds. 10 | public void addList(List list) { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/test/java/info/leadinglight/umljavadoclet/generics/TestOption.java: -------------------------------------------------------------------------------- 1 | package info.leadinglight.umljavadoclet.generics; 2 | 3 | public class TestOption { 4 | } 5 | --------------------------------------------------------------------------------