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