package org.springframework.roo.classpath.details.comments;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import java.util.List;
/**
* = JavadocComment
*
* Holds a full JavaDoc comment which can be added to a {@link CommentStructure}
* This class is responsible of adding the right JavaDoc syntax in its proper
* place inside the structure. Having this in mind, adding manually any syntax
* *is discouraged*.
*
* Please, avoid using specific system line separators. Use
* `org.apache.commons.io.IOUtils.LINE_SEPARATOR` to specify line breaks.
*
* @author Mike De Haan
* @author Sergio Clares
*/
public class JavadocComment extends AbstractComment {
/**
* The JavaDoc comment main description
*/
private String description;
/**
* The JavaDoc comment parameter info Each item is showed as ` * @param ...`
*/
private List<String> paramsInfo;
/**
* The JavaDoc comment return info, showed as ` * @return ...`
*/
private String returnInfo;
/**
* The JavaDoc comment throws info. Each item is showed as ` * @throws ...`
*/
private List<String> throwsInfo;
/**
* The `StringBuilder` instance which builds the 'comment' field.
*/
private StringBuilder commentBuilder;
//---- Indexes used as 'cache' to locate comment positions faster ----//
private int beginDescriptionIndex;
private int endDescriptionIndex;
private int beginParamsIndex;
private int endParamsIndex;
private int beginReturnIndex;
private int endReturnIndex;
private int beginThrowsIndex;
private int endThrowsIndex;
private boolean initDone = false;
/**
* Default empty constructor.
*/
public JavadocComment() {}
/**
* Used to add the full JavaDoc comment during the instantiation.
* The comment will be checked and formatted with the JavaDoc syntax before
* setting it. Line breaks will be taken in count in order to form the
* syntax.
*
* @param comment the String with the comment to add as JavaDoc syntax.
*/
public JavadocComment(String comment) {
super(comment);
this.setComment(comment);
}
/**
* Constructor used to generate a full Javadoc with description, params,
* return and throws. `null` arguments are accepted. JavaDoc comment block
* syntax will be auto-generated.
*
* ROO-3862: Improve JavaDoc generation for generated methods and constructors
*
* @param description the `String` with the block description to add. Please, use
* `org.apache.commons.io.IOUtils.LINE_SEPARATOR` for line breaks.
* @param paramsInfo the `List<String>` with the parameter info. One entry for
* each parameter. JavaDoc ` * @param` syntax will be automatically
* added.
* @param returnInfo the `String` with the return info. JavaDoc ` * @return`
* syntax will be automatically added.
* @param throwsInfo the `List<String>` with the throws info. One entry for
* each throws type. JavaDoc ` * @throws` syntax will be automatically
* added.
*/
public JavadocComment(final String description, final List<String> paramsInfo,
final String returnInfo, List<String> throwsTypes) {
this.description = description;
this.paramsInfo = paramsInfo;
this.returnInfo = returnInfo;
this.throwsInfo = throwsTypes;
addDescription(this.description);
addParamsInfo(this.paramsInfo);
addReturnInfo(this.returnInfo);
addThrowsInfo(this.throwsInfo);
}
/**
* Adds a new description or changes an existing in the JavadocComment.
*
* @param description the `String` to add as comment description.
*/
private void addDescription(final String description) {
String[] descriptionLines = description.split(IOUtils.LINE_SEPARATOR);
if (this.getComment() == null || this.getComment().isEmpty()) {
// Build a StringBuilder instance
Validate.isTrue(StringUtils.isNotBlank(description),
"The provided comment must be not null, not blank nor empty.");
checkJavadocSyntax(description);
initializeCommentIndexes();
} else {
// Initialize indexes
initializeCommentIndexes();
// Delete existing description
this.commentBuilder.delete(this.beginDescriptionIndex, this.endDescriptionIndex);
// Add each new line with JavaDoc syntax
for (String line : descriptionLines) {
String newLine = (" * ").concat(line).concat(IOUtils.LINE_SEPARATOR);
this.commentBuilder.insert(this.beginDescriptionIndex, newLine);
// Update indexes
int newLineLenght = newLine.length();
this.endDescriptionIndex += newLineLenght;
this.endParamsIndex += newLineLenght;
this.endReturnIndex += newLineLenght;
this.endThrowsIndex += newLineLenght;
}
}
super.setComment(this.commentBuilder.toString());
}
/**
* Adds or changes JavadocComment parameters info. Existing info will be
* replaced by this one.
*
* @param paramsInfo the `List<String>` to add as parameters info. One
* entry for each parameter.
*/
private void addParamsInfo(final List<String> paramsInfo) {
Validate.isTrue(StringUtils.isNotBlank(getComment()),
"JavadocComment needs to have a description value before adding parameters info to it. "
+ "You can do that using 'setDescription(String)' method first, or full constructor.");
if (paramsInfo == null || paramsInfo.isEmpty()) {
return;
}
// Initialize indexes
initializeCommentIndexes();
// Delete existing params info
this.beginParamsIndex = this.endDescriptionIndex;
this.commentBuilder.delete(this.beginParamsIndex, this.endParamsIndex);
// Add each new line with JavaDoc syntax
int nextLineStartingIndex = this.beginParamsIndex;
for (String paramInfo : paramsInfo) {
String[] lines = paramInfo.split(IOUtils.LINE_SEPARATOR);
for (int i = 0; i < lines.length; i++) {
String newLine = "";
if (i == 0) {
newLine = (" * @param ").concat(lines[i]).concat(IOUtils.LINE_SEPARATOR);
} else {
newLine = (" * ").concat("\t\t\t\t").concat(lines[i]).concat(IOUtils.LINE_SEPARATOR);
}
this.commentBuilder.insert(nextLineStartingIndex, newLine);
// Update indexes
int newLineLenght = newLine.length();
nextLineStartingIndex += newLineLenght;
this.endParamsIndex += newLineLenght;
this.endReturnIndex += newLineLenght;
this.endThrowsIndex += newLineLenght;
}
}
super.setComment(this.commentBuilder.toString());
}
/**
* Adds or changes JavadocComment return info. Existing info will be
* replaced by this one.
*
* @param returnInfo the `String` to add as return info.
*/
private void addReturnInfo(final String returnInfo) {
Validate.isTrue(StringUtils.isNotBlank(getComment()),
"JavadocComment needs to have a description value before adding return info to it. "
+ "You can do that using 'setDescription(String)' method first, or full constructor.");
if (StringUtils.isBlank(returnInfo)) {
return;
}
// Initialize indexes
initializeCommentIndexes();
// Delete existing return info
this.beginReturnIndex = this.endParamsIndex;
this.commentBuilder.delete(this.beginReturnIndex, this.endReturnIndex);
// Add each new line with JavaDoc syntax
String[] lines = returnInfo.split(IOUtils.LINE_SEPARATOR);
int nextLineStartingIndex = this.beginReturnIndex;
for (int i = 0; i < lines.length; i++) {
String newLine = "";
if (i == 0) {
newLine = (" * @return ").concat(lines[i]).concat(IOUtils.LINE_SEPARATOR);
} else {
newLine = (" * ").concat("\t\t\t\t").concat(lines[i]).concat(IOUtils.LINE_SEPARATOR);
}
this.commentBuilder.insert(nextLineStartingIndex, newLine);
// Update indexes
int newLineLenght = newLine.length();
nextLineStartingIndex += newLineLenght;
this.endReturnIndex += newLineLenght;
this.endThrowsIndex += newLineLenght;
}
super.setComment(this.commentBuilder.toString());
}
/**
* Adds or changes JavadocComment throws info. Existing info will be
* replaced by this one.
*
* @param throwsInfo the `List<String>` to add as throws info. One entry
* for each throws type.
*/
private void addThrowsInfo(final List<String> throwsInfo) {
Validate.isTrue(StringUtils.isNotBlank(getComment()),
"JavadocComment needs to have a description value before adding throws info to it. "
+ "You can do that using 'setDescription(String)' method first, or full constructor.");
if (throwsInfo == null || throwsInfo.isEmpty()) {
return;
}
// Initialize indexes
initializeCommentIndexes();
// Delete existing throws info
this.beginThrowsIndex = this.endReturnIndex;
this.commentBuilder.delete(this.beginThrowsIndex, this.endThrowsIndex);
// Add each new line with JavaDoc syntax
int nextLineStartingIndex = this.beginThrowsIndex;
for (String entry : throwsInfo) {
String[] lines = entry.split(IOUtils.LINE_SEPARATOR);
for (int i = 0; i < lines.length; i++) {
String newLine = "";
if (i == 0) {
newLine = (" * @throws ").concat(lines[i]).concat(IOUtils.LINE_SEPARATOR);
} else {
newLine = (" * ").concat("\t\t\t\t").concat(lines[i]).concat(IOUtils.LINE_SEPARATOR);
}
this.commentBuilder.insert(nextLineStartingIndex, newLine);
// Update indexes
int newLineLenght = newLine.length();
nextLineStartingIndex += newLineLenght;
this.endThrowsIndex += newLineLenght;
}
}
super.setComment(this.commentBuilder.toString());
}
/**
* Checks if the provided String has the proper JavaDoc syntax and adds it
* if its not present.
*
* @param comment the String to check
* @return a String with the original String or the formatted String if any
* format was applied.
*/
private String checkJavadocSyntax(String comment) {
if (comment.contains("/**")) {
return comment;
}
if (this.commentBuilder == null) {
this.commentBuilder = new StringBuilder();
}
String[] lines = comment.split(IOUtils.LINE_SEPARATOR);
this.commentBuilder.append("/**").append(IOUtils.LINE_SEPARATOR);
this.beginDescriptionIndex = this.commentBuilder.length();
for (String line : lines) {
this.commentBuilder.append(" * ").append(line).append(IOUtils.LINE_SEPARATOR);
}
this.commentBuilder.append(" * ").append(IOUtils.LINE_SEPARATOR);
this.commentBuilder.append(" */").append(IOUtils.LINE_SEPARATOR);
return this.commentBuilder.toString();
}
/**
* Indexes the JavadocComment indexes to know each component location within
* the entire `String` (description, paramsInfo, returnInfo and throwsInfo).
*/
private void initializeCommentIndexes() {
// First, check if initialization has already been done
if (this.initDone) {
return;
}
// Get existing comment
if (this.commentBuilder == null) {
this.commentBuilder = new StringBuilder(this.getComment());
}
// Set description indexes
if (this.beginDescriptionIndex == 0) {
this.beginDescriptionIndex = this.commentBuilder.indexOf(" * ");
}
if (this.endDescriptionIndex == 0) {
this.endDescriptionIndex =
this.commentBuilder.lastIndexOf(" * ".concat(IOUtils.LINE_SEPARATOR)) + 3
+ IOUtils.LINE_SEPARATOR.length();
// Ensure it finds description end when Sring is trimmed
if (this.endDescriptionIndex == -1) {
this.endDescriptionIndex =
this.commentBuilder.lastIndexOf(" *".concat(IOUtils.LINE_SEPARATOR)) + 2
+ IOUtils.LINE_SEPARATOR.length();
}
}
// Set begin indexes, which also indicates when a component does not exist
if (this.beginParamsIndex == 0) {
this.beginParamsIndex = this.commentBuilder.indexOf(" * @param", this.endDescriptionIndex);
}
if (this.beginReturnIndex == 0) {
this.beginReturnIndex = this.commentBuilder.indexOf(" * @return", this.endDescriptionIndex);
}
if (this.beginThrowsIndex == 0) {
this.beginThrowsIndex = this.commentBuilder.indexOf(" * @throws", this.endDescriptionIndex);
}
// Set end of param index
if (this.beginParamsIndex == -1) {
this.endParamsIndex = this.endDescriptionIndex;
} else if (this.beginReturnIndex != -1) {
this.endParamsIndex = this.beginReturnIndex - 1;
} else if (this.beginReturnIndex == -1 && this.beginThrowsIndex != 1) {
this.endParamsIndex = this.beginThrowsIndex - 1;
} else {
this.endParamsIndex = this.commentBuilder.indexOf(" */", this.endDescriptionIndex) - 1;
}
// Set end of return index
if (this.beginReturnIndex == -1) {
this.endReturnIndex = this.endParamsIndex;
} else if (this.beginThrowsIndex != -1) {
this.endReturnIndex = this.endThrowsIndex - 1;
} else {
this.endReturnIndex = this.commentBuilder.indexOf(" */", this.endDescriptionIndex) - 1;
}
// Set end of throws index
if (this.beginThrowsIndex == -1) {
this.endThrowsIndex = this.endReturnIndex;
} else {
this.endThrowsIndex = this.commentBuilder.indexOf(" */", this.endDescriptionIndex) - 1;
}
this.initDone = true;
}
@Override
public void setComment(String comment) {
Validate.isTrue(StringUtils.isNotBlank(comment),
"You must add a not empty, not null nor blank String as a comment");
super.setComment(checkJavadocSyntax(comment));
initializeCommentIndexes();
}
public String getDescription() {
// This check is needed in case that 'comment' field had been filled directly
if (this.beginDescriptionIndex == -1) {
return null;
}
if (StringUtils.isBlank(this.description)) {
return null;
}
return this.description;
}
public void setDescription(String description) {
this.description = description;
addDescription(description);
}
public List<String> getParamsInfo() {
// This check is needed in case that 'comment' field had been filled directly
if (this.beginParamsIndex == -1) {
return null;
}
if (this.paramsInfo == null || this.paramsInfo.isEmpty()) {
return null;
}
return this.paramsInfo;
}
public void setParamsInfo(List<String> paramsInfo) {
this.paramsInfo = paramsInfo;
addParamsInfo(paramsInfo);
}
public String getReturnInfo() {
// This check is needed in case that 'comment' field had been filled directly
if (this.beginReturnIndex == -1) {
return null;
}
if (StringUtils.isBlank(this.returnInfo)) {
return null;
}
return this.returnInfo;
}
public void setReturnInfo(String returnInfo) {
this.returnInfo = returnInfo;
addReturnInfo(returnInfo);
}
public List<String> getThrowsInfo() {
// This check is needed in case that 'comment' field had been filled directly
if (this.beginThrowsIndex == -1) {
return null;
}
if (this.throwsInfo == null || this.throwsInfo.isEmpty()) {
return null;
}
return this.throwsInfo;
}
public void setThrowsInfo(List<String> throwsInfo) {
this.throwsInfo = throwsInfo;
addThrowsInfo(throwsInfo);
}
}