/**
* Copyright © 2006-2016 Web Cohesion (info@webcohesion.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
package com.webcohesion.enunciate.javac.javadoc;
import com.webcohesion.enunciate.javac.decorations.DecoratedProcessingEnvironment;
import com.webcohesion.enunciate.javac.decorations.element.DecoratedElement;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static java.lang.Math.min;
public class JavaDoc extends HashMap<String, JavaDoc.JavaDocTagList> {
public static final JavaDoc EMPTY = new JavaDoc("");
private static final Pattern INLINE_TAG_PATTERN = Pattern.compile("\\{@([^\\} ]+) ?(.*?)\\}");
private static final Pattern INHERITDOC_PATTERN = Pattern.compile("\\{@inheritDoc(.*?)\\}");
private static final char[] WHITESPACE_CHARS = new char[]{' ', '\t', '\n', 0x0B, '\f', '\r'};
protected String value;
private JavaDoc(String value) {
this.value = value;
}
public JavaDoc(String docComment, JavaDocTagHandler tagHandler, DecoratedElement context, DecoratedProcessingEnvironment env) {
init(docComment, tagHandler, context, env);
}
protected void init(String docComment, JavaDocTagHandler tagHandler, DecoratedElement context, DecoratedProcessingEnvironment env) {
if (docComment == null) {
value = "";
}
else {
BufferedReader reader = new BufferedReader(new StringReader(docComment));
StringWriter currentValue = new StringWriter();
PrintWriter out = new PrintWriter(currentValue);
String currentTag = null;
boolean preformatting = false;
try {
String line = reader.readLine();
while (line != null) {
if (!preformatting) {
line = line.trim();
}
if (line.startsWith("@")) { //it's a javadoc block tag.
//push and clear our current value.
pushValue(currentTag, currentValue.toString());
int spaceIndex = indexOfFirstWhitespace(line);
currentTag = line.substring(1, spaceIndex);
String value = "";
if ((spaceIndex + 1) < line.length()) {
value = line.substring(spaceIndex + 1);
}
currentValue = new StringWriter();
out = new PrintWriter(currentValue);
out.println(value);
}
else {
out.println(line);
}
preformatting = (line.contains("<pre") || preformatting) && !line.contains("</pre");
line = reader.readLine();
}
//push the last value.
pushValue(currentTag, currentValue.toString());
}
catch (IOException e) {
//fall through.
}
}
assumeInheritedComments(context, env, tagHandler);
if (tagHandler != null) {
this.value = resolveJavaDocSemantics(null, this.value, tagHandler, context);
for (Map.Entry<String, JavaDocTagList> entry : entrySet()) {
JavaDocTagList tagValues = entry.getValue();
for (int i = 0; i < tagValues.size(); i++) {
String value = tagValues.get(i);
tagValues.set(i, resolveJavaDocSemantics(entry.getKey(), value, tagHandler, context));
}
}
}
}
public static int indexOfFirstWhitespace(String line) {
int result = line.length();
for (char ws : WHITESPACE_CHARS) {
int spaceIndex = line.indexOf(ws);
spaceIndex = spaceIndex == -1 ? result : spaceIndex;
result = min(spaceIndex, result);
}
return result;
}
public static int indexOfWhitespaceFrom(String line, int from) {
int result = line.length();
for (char ws : WHITESPACE_CHARS) {
int spaceIndex = line.indexOf(ws, from);
spaceIndex = spaceIndex == -1 ? result : spaceIndex;
result = min(spaceIndex, result);
}
return result;
}
/**
* Handles all the semantic tokens of the JavaDoc.
*
* @param section The section name (null for main description).
* @param value The value.
* @param handler The handler.
* @param context The context of the tags.
* @return The replacement value.
*/
private String resolveJavaDocSemantics(String section, String value, JavaDocTagHandler handler, DecoratedElement context) {
//first pass through the inline tags...
StringBuilder builder = new StringBuilder();
Matcher matcher = INLINE_TAG_PATTERN.matcher(value);
int lastStart = 0;
while (matcher.find()) {
builder.append(value.substring(lastStart, matcher.start()));
String replacement = handler.onInlineTag(matcher.group(1), matcher.group(2), context);
if (replacement != null) {
builder.append(replacement);
}
else {
builder.append(value.substring(matcher.start(), matcher.end()));
}
lastStart = matcher.end();
}
builder.append(value.substring(lastStart, value.length()));
return handler.onBlockTag(section, builder.toString(), context);
}
/**
* Pushes a value onto a tag.
*
* @param tag The tag onto which to push the value. (null indicates no tag.)
* @param value The value of the tag.
*/
private void pushValue(String tag, String value) {
value = value.trim(); //trim the value.
if (tag == null) {
this.value = value;
}
else {
JavaDocTagList tagList = get(tag);
if (tagList == null) {
tagList = new JavaDocTagList(value);
put(tag, tagList);
}
else {
tagList.add(value);
}
}
}
private void assumeInheritedComments(DecoratedElement context, DecoratedProcessingEnvironment env, JavaDocTagHandler tagHandler) {
//algorithm defined per http://docs.oracle.com/javase/6/docs/technotes/tools/solaris/javadoc.html#inheritingcomments
if (context instanceof TypeElement) {
assumeInheritedTypeComments((TypeElement) context, tagHandler);
}
else if (context instanceof ExecutableElement) {
assumeInheritedExecutableComments((ExecutableElement) context, env, tagHandler);
}
}
private void assumeInheritedExecutableComments(ExecutableElement context, DecoratedProcessingEnvironment env, JavaDocTagHandler tagHandler) {
if (assumeInheritedExecutableComments(context, EMPTY)) {
//all comments have already been assumed.
return;
}
Element el = context.getEnclosingElement();
if (el instanceof TypeElement) {
TypeElement typeElement = (TypeElement) el;
List<? extends TypeMirror> interfaces = typeElement.getInterfaces();
for (TypeMirror iface : interfaces) {
Element superType = iface instanceof DeclaredType ? ((DeclaredType) iface).asElement() : null;
if (superType != null) {
List<ExecutableElement> methods = ElementFilter.methodsIn(superType.getEnclosedElements());
for (ExecutableElement candidate : methods) {
if (env.getElementUtils().overrides(context, candidate, typeElement) && candidate instanceof DecoratedElement) {
JavaDoc inheritedDocs = ((DecoratedElement) candidate).getJavaDoc(tagHandler);
if (assumeInheritedExecutableComments(context, inheritedDocs)) {
return;
}
}
}
}
}
TypeMirror superclass = typeElement.getSuperclass();
if (superclass != null && superclass instanceof DeclaredType) {
Element superType = ((DeclaredType) superclass).asElement();
if (superType != null) {
List<ExecutableElement> methods = ElementFilter.methodsIn(superType.getEnclosedElements());
for (ExecutableElement candidate : methods) {
if (env.getElementUtils().overrides(context, candidate, typeElement) && candidate instanceof DecoratedElement) {
JavaDoc inheritedDocs = ((DecoratedElement) candidate).getJavaDoc(tagHandler);
assumeInheritedExecutableComments(context, inheritedDocs);
return;
}
}
}
}
}
}
private boolean assumeInheritedExecutableComments(ExecutableElement context, JavaDoc inherited) {
boolean assumed = true;
if (valueInherits(this.value)) {
String inheritedValue = inherited.toString();
if (!inheritedValue.isEmpty()) {
if (this.value.isEmpty()) {
this.value = inheritedValue;
}
else {
this.value = INHERITDOC_PATTERN.matcher(this.value).replaceAll(inheritedValue);
}
}
else {
assumed = false;
}
}
if (context.getReturnType() != null && context.getReturnType().getKind() != TypeKind.VOID) {
//need a return tag.
JavaDocTagList returnTag = get("return");
String returnValue = returnTag == null ? "" : returnTag.toString();
if (valueInherits(returnValue)) {
JavaDocTagList inheritedTag = inherited.get("return");
String inheritedValue = inheritedTag == null ? "" : inheritedTag.toString();
if (!inheritedValue.isEmpty()) {
if (returnValue.isEmpty()) {
put("return", new JavaDocTagList(inheritedValue));
}
else {
returnValue = INHERITDOC_PATTERN.matcher(returnValue).replaceAll(inheritedValue);
put("return", new JavaDocTagList(returnValue));
}
}
else {
assumed = false;
}
}
}
List<? extends VariableElement> parameterNames = context.getParameters();
if (parameterNames != null && !parameterNames.isEmpty()) {
JavaDocTagList paramTags = get("param");
for (VariableElement param : parameterNames) {
String paramName = param.getSimpleName().toString();
String paramValue = "";
int paramIndex = -1;
if (paramTags != null) {
for (int i = 0; i < paramTags.size(); i++) {
String paramTag = paramTags.get(i);
if (paramName.equals(paramTag.substring(0, indexOfFirstWhitespace(paramTag)))) {
paramValue = paramTag;
paramIndex = i;
break;
}
}
}
if (valueInherits(paramValue)) {
JavaDocTagList inheritedTags = inherited.get("param");
String inheritedValue = "";
if (inheritedTags != null) {
for (String inheritedTag : inheritedTags) {
if (paramName.equals(inheritedTag.substring(0, indexOfFirstWhitespace(inheritedTag)))) {
inheritedValue = inheritedTag;
break;
}
}
}
if (!inheritedValue.isEmpty()) {
if (paramIndex < 0) {
pushValue("param", inheritedValue);
}
else {
paramValue = INHERITDOC_PATTERN.matcher(paramValue).replaceAll(inheritedValue);
paramTags.set(paramIndex, paramValue);
}
}
else {
assumed = false;
}
}
}
}
//todo: inherit 'throws'.
return assumed;
}
private void assumeInheritedTypeComments(TypeElement e, JavaDocTagHandler tagHandler) {
if (valueInherits(this.value)) {
String inheritedValue = "";
List<? extends TypeMirror> interfaces = e.getInterfaces();
for (TypeMirror iface : interfaces) {
Element el = iface instanceof DeclaredType ? ((DeclaredType)iface).asElement() : null;
if (el instanceof DecoratedElement) {
inheritedValue = ((DecoratedElement) el).getJavaDoc(tagHandler).toString();
if (!inheritedValue.isEmpty()) {
break;
}
}
}
if (inheritedValue.isEmpty()) {
TypeMirror superclass = e.getSuperclass();
if (superclass instanceof DeclaredType) {
Element el = ((DeclaredType) superclass).asElement();
if (el instanceof DecoratedElement) {
inheritedValue = ((DecoratedElement) el).getJavaDoc().toString();
}
}
}
if (!inheritedValue.isEmpty()) {
if (this.value.isEmpty()) {
this.value = inheritedValue;
}
else {
this.value = INHERITDOC_PATTERN.matcher(this.value).replaceAll(inheritedValue);
}
}
}
}
private boolean valueInherits(String value) {
return value == null || value.isEmpty() || INHERITDOC_PATTERN.matcher(value).find();
}
public void setValue(String value) {
this.value = value;
}
public String toString() {
return value;
}
/**
* A list of values for a javadoc tag.
*/
public static class JavaDocTagList extends ArrayList<String> {
/**
* To construct a tag list, at least one value must be supplied.
*
* @param firstValue The first value.
*/
public JavaDocTagList(String firstValue) {
add(firstValue);
}
/**
* @return The first value in the list.
*/
public String toString() {
return get(0);
}
}
}