• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007-2010 Júlio Vilmar Gesser.
3  * Copyright (C) 2011, 2013-2016 The JavaParser Team.
4  *
5  * This file is part of JavaParser.
6  *
7  * JavaParser can be used either under the terms of
8  * a) the GNU Lesser General Public License as published by
9  *     the Free Software Foundation, either version 3 of the License, or
10  *     (at your option) any later version.
11  * b) the terms of the Apache License
12  *
13  * You should have received a copy of both licenses in LICENCE.LGPL and
14  * LICENCE.APACHE. Please refer to those files for details.
15  *
16  * JavaParser is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU Lesser General Public License for more details.
20  */
21 
22 package com.github.javaparser;
23 
24 import com.github.javaparser.ast.comments.JavadocComment;
25 import com.github.javaparser.javadoc.Javadoc;
26 import com.github.javaparser.javadoc.JavadocBlockTag;
27 import com.github.javaparser.javadoc.description.JavadocDescription;
28 import java.util.Arrays;
29 import java.util.Collections;
30 import java.util.List;
31 import java.util.regex.Pattern;
32 import java.util.stream.Collectors;
33 
34 import static com.github.javaparser.utils.Utils.*;
35 import static com.github.javaparser.utils.Utils.nextWord;
36 
37 /**
38  * The class responsible for parsing the content of JavadocComments and produce JavadocDocuments.
39  */
40 class JavadocParser {
41 
42     private static String BLOCK_TAG_PREFIX = "@";
43     private static Pattern BLOCK_PATTERN = Pattern.compile("^" + BLOCK_TAG_PREFIX, Pattern.MULTILINE);
44 
parse(JavadocComment comment)45     public static Javadoc parse(JavadocComment comment) {
46         return parse(comment.getContent());
47     }
48 
parse(String commentContent)49     public static Javadoc parse(String commentContent) {
50         List<String> cleanLines = cleanLines(normalizeEolInTextBlock(commentContent, EOL));
51         int indexOfFirstBlockTag = cleanLines.stream()
52                 .filter(JavadocParser::isABlockLine)
53                 .map(cleanLines::indexOf)
54                 .findFirst()
55                 .orElse(-1);
56         List<String> blockLines;
57         String descriptionText;
58         if (indexOfFirstBlockTag == -1) {
59             descriptionText = trimRight(String.join(EOL, cleanLines));
60             blockLines = Collections.emptyList();
61         } else {
62             descriptionText = trimRight(String.join(EOL, cleanLines.subList(0, indexOfFirstBlockTag)));
63 
64             //Combine cleaned lines, but only starting with the first block tag till the end
65             //In this combined string it is easier to handle multiple lines which actually belong together
66             String tagBlock = cleanLines.subList(indexOfFirstBlockTag, cleanLines.size())
67                 .stream()
68                 .collect(Collectors.joining(EOL));
69 
70             //Split up the entire tag back again, considering now that some lines belong to the same block tag.
71             //The pattern splits the block at each new line starting with the '@' symbol, thus the symbol
72             //then needs to be added again so that the block parsers handles everything correctly.
73             blockLines = BLOCK_PATTERN
74                 .splitAsStream(tagBlock)
75                 .filter(STRING_NOT_EMPTY)
76                 .map(s -> BLOCK_TAG_PREFIX + s)
77                 .collect(Collectors.toList());
78         }
79         Javadoc document = new Javadoc(JavadocDescription.parseText(descriptionText));
80         blockLines.forEach(l -> document.addBlockTag(parseBlockTag(l)));
81         return document;
82     }
83 
parseBlockTag(String line)84     private static JavadocBlockTag parseBlockTag(String line) {
85         line = line.trim().substring(1);
86         String tagName = nextWord(line);
87         String rest = line.substring(tagName.length()).trim();
88         return new JavadocBlockTag(tagName, rest);
89     }
90 
isABlockLine(String line)91     private static boolean isABlockLine(String line) {
92         return line.trim().startsWith(BLOCK_TAG_PREFIX);
93     }
94 
trimRight(String string)95     private static String trimRight(String string) {
96         while (!string.isEmpty() && Character.isWhitespace(string.charAt(string.length() - 1))) {
97             string = string.substring(0, string.length() - 1);
98         }
99         return string;
100     }
101 
cleanLines(String content)102     private static List<String> cleanLines(String content) {
103         String[] lines = content.split(EOL);
104         List<String> cleanedLines = Arrays.stream(lines).map(l -> {
105             int asteriskIndex = startsWithAsterisk(l);
106             if (asteriskIndex == -1) {
107                 return l;
108             } else {
109                 // if a line starts with space followed by an asterisk drop to the asterisk
110                 // if there is a space immediately after the asterisk drop it also
111                 if (l.length() > (asteriskIndex + 1)) {
112 
113                     char c = l.charAt(asteriskIndex + 1);
114                     if (c == ' ' || c == '\t') {
115                         return l.substring(asteriskIndex + 2);
116                     }
117                 }
118                 return l.substring(asteriskIndex + 1);
119             }
120         }).collect(Collectors.toList());
121         // lines containing only whitespace are normalized to empty lines
122         cleanedLines = cleanedLines.stream().map(l -> l.trim().isEmpty() ? "" : l).collect(Collectors.toList());
123         // if the first starts with a space, remove it
124         if (!cleanedLines.get(0).isEmpty() && (cleanedLines.get(0).charAt(0) == ' ' || cleanedLines.get(0).charAt(0) == '\t')) {
125             cleanedLines.set(0, cleanedLines.get(0).substring(1));
126         }
127         // drop empty lines at the beginning and at the end
128         while (cleanedLines.size() > 0 && cleanedLines.get(0).trim().isEmpty()) {
129             cleanedLines = cleanedLines.subList(1, cleanedLines.size());
130         }
131         while (cleanedLines.size() > 0 && cleanedLines.get(cleanedLines.size() - 1).trim().isEmpty()) {
132             cleanedLines = cleanedLines.subList(0, cleanedLines.size() - 1);
133         }
134         return cleanedLines;
135     }
136 
137     // Visible for testing
startsWithAsterisk(String line)138     static int startsWithAsterisk(String line) {
139         if (line.startsWith("*")) {
140             return 0;
141         } else if ((line.startsWith(" ") || line.startsWith("\t")) && line.length() > 1) {
142             int res = startsWithAsterisk(line.substring(1));
143             if (res == -1) {
144                 return -1;
145             } else {
146                 return 1 + res;
147             }
148         } else {
149             return -1;
150         }
151     }
152 }
153