com.phoenixnap.oss.ramlapisync.javadoc.JavaDocEntry.java Source code

Java tutorial

Introduction

Here is the source code for com.phoenixnap.oss.ramlapisync.javadoc.JavaDocEntry.java

Source

/*
 * Copyright 2002-2016 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<String, String> parameterComments = new LinkedHashMap<String, String>();

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

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

    /**
     * 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<String, String> 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<Integer, String> getErrorComments() {
        return errorComments;
    }

    public String toString() {
        String out = "Comment: || " + comment + " || Params: ";
        for (Entry<String, String> entry : parameterComments.entrySet()) {
            out += entry.getKey() + " : " + entry.getValue() + ", ";
        }
        out += "|| Return Comment: || " + returnTypeComment + " || Errors: ||";
        for (Entry<Integer, String> 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<String, String> 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<Integer, String> 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());
                }
            }
        }
    }

}