| import com.sun.source.doctree.*; | |
| import com.sun.source.util.DocTrees; | |
| import java.util.HashMap; | |
| import java.util.Map.Entry; | |
| import java.util.Map; | |
| import java.util.Set; | |
| import java.util.Iterator; | |
| import java.io.BufferedWriter; | |
| import java.io.OutputStreamWriter; | |
| import java.io.FileOutputStream; | |
| import java.io.IOException; | |
| import java.util.*; | |
| import java.util.spi.ToolProvider; | |
| import javax.lang.model.*; | |
| import javax.lang.model.element.*; | |
| import javax.lang.model.util.*; | |
| import jdk.javadoc.doclet.*; | |
| public class CommentParser implements Doclet { | |
| private static Map<String, String> m_parsedComments = new HashMap<String, String>(); | |
| // We need to implement these base class pure virtual methods. | |
| @Override | |
| public void init(Locale locale, Reporter reporter) { | |
| } | |
| @Override | |
| public Set<? extends Option> getSupportedOptions() { | |
| return new HashSet<>(); | |
| } | |
| @Override | |
| public SourceVersion getSupportedSourceVersion() { | |
| return SourceVersion.latest(); | |
| } | |
| @Override | |
| public String getName() { | |
| return "CommentParser"; | |
| } | |
| // Element name must be the fully qualified name of the element. | |
| // | |
| // If there is no comment associated with this element, simply do nothing. | |
| private void storeCommentFor(DocTrees docTrees, String fullName, Element e) { | |
| DocCommentTree docCommentTree = docTrees.getDocCommentTree(e); | |
| if (docCommentTree == null) | |
| return; | |
| StringBuilder name = new StringBuilder(fullName); | |
| // We must use signature in the key for methods for compatibility with | |
| // the existing tests and to allow distinguishing between overloaded | |
| // methods. | |
| if (e instanceof ExecutableElement) { | |
| ExecutableElement ex = (ExecutableElement)e; | |
| name.append("("); | |
| boolean firstParam = true; | |
| for (VariableElement p : ex.getParameters()) { | |
| if (firstParam) { | |
| firstParam = false; | |
| } else { | |
| name.append(", "); | |
| } | |
| name.append(p.asType().toString()); | |
| } | |
| name.append(")"); | |
| } | |
| // For some reason the comment in the source is split into "body" and | |
| // "block tags" parts, so we need to concatenate them back together. | |
| StringBuilder comment = new StringBuilder(); | |
| for (DocTree d : docCommentTree.getFullBody()) { | |
| comment.append(d.toString()); | |
| comment.append("\n"); | |
| } | |
| boolean firstBlockTag = true; | |
| for (DocTree d : docCommentTree.getBlockTags()) { | |
| if (firstBlockTag) { | |
| firstBlockTag = false; | |
| comment.append("\n"); | |
| } | |
| comment.append(d.toString()); | |
| comment.append("\n"); | |
| } | |
| m_parsedComments.put(name.toString(), comment.toString()); | |
| } | |
| @Override | |
| public boolean run(DocletEnvironment docEnv) { | |
| /* | |
| * This method is called by 'javadoc' and gets the whole parsed java | |
| * file, we get comments and store them | |
| */ | |
| DocTrees docTrees = docEnv.getDocTrees(); | |
| for (TypeElement t : ElementFilter.typesIn(docEnv.getIncludedElements())) { | |
| String typeName = t.getQualifiedName().toString(); | |
| storeCommentFor(docTrees, typeName, t); | |
| for (Element e : t.getEnclosedElements()) { | |
| // Omit the method name for ctors: this is a bit weird, but | |
| // this is what the existing tests expect. | |
| String fullName = typeName; | |
| if (e.getKind() != ElementKind.CONSTRUCTOR) { | |
| fullName = fullName + "." + e.getSimpleName(); | |
| } | |
| storeCommentFor(docTrees, fullName, e); | |
| } | |
| } | |
| return true; | |
| } | |
| public static int check(Map<String, String> wantedComments) { | |
| int errorCount=0; | |
| Iterator<Entry<String, String>> it = m_parsedComments.entrySet().iterator(); | |
| while (it.hasNext()) { | |
| Entry<String, String> e = it.next(); | |
| String actualStr = e.getValue(); | |
| String wantedStr = wantedComments.get(e.getKey()); | |
| // this may be weird, but I don't know any more effective solution | |
| actualStr = actualStr.replace(" ", ""); | |
| actualStr = actualStr.replaceAll("\t", ""); | |
| actualStr = actualStr.replace("\n", ""); | |
| // Removing of <br> is temporary solution, since adding of | |
| // <br> tag requires changes in all tests. However, <br> | |
| // tag should be added more selectively and when this is | |
| // implemented, tests should be updated. | |
| actualStr = actualStr.replace("<br>", ""); | |
| if (wantedStr != null) { | |
| wantedStr = wantedStr.replace(" ", ""); | |
| wantedStr = wantedStr.replace("\t", ""); | |
| wantedStr = wantedStr.replace("\n", ""); | |
| wantedStr = wantedStr.replace("<br>", ""); | |
| } | |
| /* The following lines replace multiple whitespaces with a single one. | |
| Although this would be more exact testing, it would also require | |
| more work on test maintenance. | |
| actualStr = actualStr.replace('\t', ' '); | |
| actualStr = actualStr.replaceAll(" +", " "); | |
| // actualStr = actualStr.replace("\n", ""); | |
| if (wantedStr != null) { | |
| wantedStr = wantedStr.replace('\t', ' '); | |
| wantedStr = wantedStr.replaceAll(" +", " "); | |
| // wantedStr = wantedStr.replace("\n", ""); | |
| } */ | |
| if (!actualStr.equals(wantedStr)) { | |
| System.out.println("\n\n////////////////////////////////////////////////////////////////////////"); | |
| System.out.println("Documentation comments for '" + e.getKey() + "' do not match!"); | |
| String expectedFileName = "expected.txt"; | |
| String gotFileName = "got.txt"; | |
| System.out.println("Output is also saved to files '" + expectedFileName + | |
| "' and '" + gotFileName + "'"); | |
| // here we print original strings, for nicer output | |
| System.out.println("\n\n---\nexpected:\n" + wantedStr); | |
| System.out.println("\n\n---\ngot:\n" + e.getValue()); | |
| try { | |
| // write expected string to file | |
| BufferedWriter expectedFile = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(expectedFileName))); | |
| if (wantedStr != null) | |
| expectedFile.write(wantedStr); | |
| expectedFile.close(); | |
| // write translated string to file | |
| BufferedWriter gotFile = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(gotFileName))); | |
| gotFile.write(e.getValue().replace("<br>", "")); | |
| gotFile.close(); | |
| } catch (IOException ex) { | |
| System.out.println("Error when writing output to file: " + ex); | |
| } | |
| errorCount++; | |
| } | |
| } | |
| if (m_parsedComments.size() != wantedComments.size()) { | |
| System.out.println("Mismatch in the number of comments!\n Expected: " + | |
| wantedComments.size() + "\n Parsed: " + | |
| m_parsedComments.size()); | |
| System.out.println("Expected keys: "); | |
| printKeys(wantedComments); | |
| System.out.println("Parsed keys: "); | |
| printKeys(m_parsedComments); | |
| errorCount++; | |
| } | |
| return errorCount > 0 ? 1 : 0; | |
| } | |
| private static void printKeys(Map<String, String> map) { | |
| Set<String> keys = map.keySet(); | |
| for (String key : keys) { | |
| System.out.println(" " + key); | |
| } | |
| } | |
| public static void printCommentListForJavaSource() { | |
| Iterator< Entry<String, String> > it = m_parsedComments.entrySet().iterator(); | |
| while (it.hasNext()) { | |
| Entry<String, String> e = it.next(); | |
| String commentText = e.getValue(); | |
| commentText = commentText.replace("\\", "\\\\"); | |
| commentText = commentText.replace("\"", "\\\""); | |
| commentText = commentText.replace("\n", "\\n\" +\n\t\t\""); | |
| System.out.format("wantedComments.put(\"%s\",\n\t\t\"%s\");\n", e.getKey(), commentText); | |
| } | |
| } | |
| public static void parse(String sourcefile) { | |
| ToolProvider javadoc = ToolProvider.findFirst("javadoc").orElseThrow(); | |
| int result = javadoc.run(System.out, System.err, new String[]{"-quiet", "-doclet", "CommentParser", sourcefile}); | |
| if (result != 0) { | |
| System.err.println("Executing javadoc failed."); | |
| System.exit(result); | |
| } | |
| } | |
| public static void main(String argv[]) { | |
| if (argv.length<1) { | |
| System.out.format("Usage:\n\tCommentParser <package to parse>\n"); | |
| System.exit(1); | |
| } | |
| parse(argv[0]); | |
| // if we are run as standalone app, print the list of found comments as it would appear in java source | |
| printCommentListForJavaSource(); | |
| } | |
| } |