All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.phoenixnap.oss.ramlapisync.javadoc.JavaDocEntry Maven / Gradle / Ivy

Go to download

Components including the parsing of RAML documents and Spring MVC Annotations to create RAML models

There is a newer version: 0.9.1
Show newest version
/*
 * Copyright 2002-2017 the original author or authors.
 * 
 * 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.phoenixnap.oss.ramlapisync.javadoc;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import com.phoenixnap.oss.ramlapisync.naming.NamingHelper;

/**
 * A class which will accept a raw chunk of javadoc as a string, clean away useless characters and provide easy access
 * to portions of it
 * 
 * @author Kurt Paris
 * @since 0.0.1
 *
 */
public class JavaDocEntry {
	private static final Logger logger = LoggerFactory.getLogger(JavaDocEntry.class);

	/**
	 * Regular expression used to identify any comment text relating to the Class or Method being documented
	 */
	private static final Pattern COMMENT_BLOCK = Pattern.compile(
			"([^@]+)@(return|param|throws|exception|since|version|author)", Pattern.CASE_INSENSITIVE);

	/**
	 * Regular expression used to identify any parameter blocks within the java doc
	 */
	private static final Pattern PARAM_BLOCK = Pattern.compile("(?m)^[\\s|\\*]*@param([^@]+)(\n|\\Z)",
			Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
	
	/**
	 * Regular expression used to identify any link blocks within the java doc
	 */
	private static final Pattern LINK_BLOCK = Pattern.compile("\\{[\\s]*@[a-zA-Z]+[\\s]*([^@]+)\\}",
			Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);

	/**
	 * Regular Expression used to identify the return portion of the java doc
	 */
	private static final Pattern RETURN_BLOCK = Pattern.compile("(?m)^[\\s|\\*]*@return([^@]+)\n",
			Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);

	/**
	 * The extracted main comment block
	 */
	private String comment;

	/**
	 * Map of method parameters extracted javadoc keyed by parameter name
	 */
	private Map parameterComments = new LinkedHashMap();

	/**
	 * The extracted javadoc of the return type
	 */
	private String returnTypeComment;

	/**
	 * The extracted javadoc of each exception.//TODO link
	 */
	private Map errorComments = new LinkedHashMap();

	/**
	 * Constructor which accepts a raw unprocessed chunk of javadoc and extracts meaningful portions which are stored
	 * for RAML generation
	 * 
	 * @param rawJavaDoc The String representing an raw chunck of javadoc for a class or method
	 */
	public JavaDocEntry(String rawJavaDoc) {
		rawJavaDoc = cleanLinks(rawJavaDoc);
		buildMainComment(rawJavaDoc);
		buildParameterComments(rawJavaDoc);
		buildReturnComments(rawJavaDoc);
	}

	/**
	 * Match any @return entries in the javadoc and extract the documentation relating to the returned object for use
	 * within the method returns
	 * 
	 * @param rawJavaDoc
	 * @param paramMatcher
	 */
	private void buildReturnComments(String rawJavaDoc) {
		Matcher returnMatcher = RETURN_BLOCK.matcher(rawJavaDoc);
		if (returnMatcher.find()) {
			try {
				String rawParam = returnMatcher.group(1);
				returnTypeComment = NamingHelper.cleanLeadingAndTrailingNewLineAndChars(rawParam);

			} catch (Exception ex) {
				logger.warn("****WARNING: Error processing javadoc return type for: " + rawJavaDoc, ex);
			}
		}
	}

	/**
	 * Matches @param entries within a Javadoc block and stores them in a map keyed by parameter name
	 * 
	 * @param rawJavaDoc
	 * @param paramMatcher
	 */
	private void buildParameterComments(String rawJavaDoc) {
		Matcher paramMatcher = PARAM_BLOCK.matcher(rawJavaDoc);

		while (paramMatcher.find()) {
			try {
				String rawParam = paramMatcher.group(1);

				rawParam = NamingHelper.cleanLeadingAndTrailingNewLineAndChars(rawParam);

				if (rawParam != null && rawParam.contains(" ")) {
					String key = NamingHelper.cleanLeadingAndTrailingNewLineAndChars(rawParam.substring(0,
							rawParam.indexOf(" ")));
					String value = NamingHelper.cleanLeadingAndTrailingNewLineAndChars(rawParam.substring(rawParam
							.indexOf(" ")));
					if (StringUtils.hasText(key)) {
						parameterComments.put(key, value);
					}
				}
			} catch (Exception ex) {
				logger.warn("Error processing javadoc parameters for: " + rawJavaDoc, ex);
			}
		}
	}

	/**
	 * Extracts the main comment from a Method or Class Javadoc entry.
	 * 
	 * @param rawJavaDoc
	 */
	private void buildMainComment(String rawJavaDoc) {
		Matcher commentMatcher = COMMENT_BLOCK.matcher(rawJavaDoc);
		// Build the main comment first
		commentMatcher.find();
		try {
			comment = commentMatcher.group(1);
		} catch (Exception ex) {
			comment = rawJavaDoc;
		}
		comment = NamingHelper.cleanLeadingAndTrailingNewLineAndChars(comment).replaceAll("\\n *\\* *", "\n ");
	}

	/**
	 * Returns the Main comment
	 * @return String
	 */
	public String getComment() {
		return comment;
	}

	/**
	 * Returns the Parameter Comments
	 * @return Map of Parameter comments keyed by Parameter name
	 */
	public Map getParameterComments() {
		return parameterComments;
	}

	/**
	 * returns the comment for the return type
	 * @return String with comment for return type
	 */
	public String getReturnTypeComment() {
		return returnTypeComment;
	}

	/**
	 * Gets the Exception comments //TODO
	 * @return Empty Map for now
	 */
	public Map getErrorComments() {
		return errorComments;
	}

	public String toString() {
		String out = "Comment: || " + comment + " || Params: ";
		for (Entry entry : parameterComments.entrySet()) {
			out += entry.getKey() + " : " + entry.getValue() + ", ";
		}
		out += "|| Return Comment: || " + returnTypeComment + " || Errors: ||";
		for (Entry entry : errorComments.entrySet()) {
			out += entry.getKey() + " : " + entry.getValue() + ", ";
		}
		return out + " ||";
	}

	/**
	 * Removes special characters and other meaningless characters from a string in an attempt to identify how much
	 * meaningful content it contains. this score is returned as a numeric value where higher numbers mean more
	 * meaningful content
	 * 
	 * @param comment The comment to evaluate
	 * @return The score of the comment. Higher scores imply more semantic value.
	 */
	private int getStringScore(String comment) {
		// Empty strings are not really meaningful.
		if (!StringUtils.hasText(comment)) {
			return 0;
		}

		// Ignore Case
		String modifiedString = comment.toLowerCase();

		// remove @see blocks
		modifiedString = modifiedString.replaceAll(
				"@see(\\n| |\\t)+[a-z|0-9|.]*(\\#[a-z|0-9|.]*){0,1}(\\([a-z|0-9|.]*\\)){0,1}", "");

		// remove other crap words
		modifiedString = modifiedString.replaceAll("[^\\w]{0,3}non[^\\w]javadoc[^\\w]{0,3}", "");

		// remove other crap words
		modifiedString = modifiedString.replaceAll("[^\\w]{0,3}inheritdoc[^\\w]{0,3}", "");
		modifiedString = (" " + modifiedString);
		boolean loop = false;

		// loop and remove semantically null words
		do {
			String tempString = modifiedString.replaceAll(
					"[\\s](all|helper|common|functionality|to|useful|toolkit|parent|super)[\\s]", " ");
			if (!tempString.equals(modifiedString)) {
				loop = true;
				modifiedString = tempString;
			} else {
				loop = false;
			}
		} while (loop);

		// Numbers and other characters are less likely to be meaningful
		// remove all non word chars (destructive)
		modifiedString = modifiedString.replaceAll("[^\\w]", "");

		// user the remaining number of characters as the score
		return modifiedString.length();
	}

	/**
	 * We'll check the string scores of both strings and if the newer scores higher we will return true so that the
	 * proposed string will replace the current one
	 * 
	 * @param current The string we currently reference in the Doc Store
	 * @param proposed The proposed string 
	 * @return If true, then the new string is semantically better than the current and should be replaced
	 */
	private boolean shouldReplace(String current, String proposed) {
		if (getStringScore(current) > getStringScore(proposed)) {
			return false;
		} else {
			return true;
		}
	}
	
	/**
	 * Removes {@link or other {@ notation from the javadoc and retains the enclosed data
	 * 
	 * @param target The target comment to clean
	 * @return The comment cleaned from the target characters
	 */
	private String cleanLinks(String target) {
		Matcher linkMatcher = LINK_BLOCK.matcher(target);
		// Build the main comment first
		while (linkMatcher.find()) {
			try {
				target = target.substring(0, linkMatcher.start(0)) + linkMatcher.group(1) + target.substring(linkMatcher.end(1)+1);
			} catch (Exception ex) {
				//do nothing
			}
		}
		return target;
	}

	/**
	 * Due to inheritance we can have multiple java doc entries for the same entry. As such we need to combine or
	 * overwrite and keep the most meaningful set of comments we can for this particular entry
	 * 
	 * Different areas of javadoc (comment, parameter comment, etc) are evaluated seperately so that we keep the most
	 * meaningful fragment.
	 * 
	 * @param entry The JavaDocEntry to be used and integrated into this entry
	 */
	public void merge(JavaDocEntry entry) {
		if (entry != null) {
			if (shouldReplace(this.comment, entry.comment)) {
				this.comment = entry.comment;
			}
			if (shouldReplace(this.returnTypeComment, entry.returnTypeComment)) {
				this.returnTypeComment = entry.returnTypeComment;
			}
			for (Entry parameterCommentEntry : entry.parameterComments.entrySet()) {
				if (!this.parameterComments.containsKey(parameterCommentEntry.getKey())
						|| (shouldReplace(this.parameterComments.get(parameterCommentEntry.getKey()),
								parameterCommentEntry.getValue()))) {
					this.parameterComments.put(parameterCommentEntry.getKey(), parameterCommentEntry.getValue());
				}
			}
			for (Entry errorCommentEntry : entry.errorComments.entrySet()) {
				if (!this.errorComments.containsKey(errorCommentEntry.getKey())
						|| (shouldReplace(this.errorComments.get(errorCommentEntry.getKey()),
								errorCommentEntry.getValue()))) {
					this.errorComments.put(errorCommentEntry.getKey(), errorCommentEntry.getValue());
				}
			}
		}
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy