package com.laytonsmith.PureUtilities; 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; /** * Represents a javadoc style comment block, at its lowest level. The rules of smart comment * are that it must start with /** and end with */. Within the comment, the first * whitespace characters, * and optionally a single space after will be removed, so the line " * Text" * would be simply "Text". Annotations are supported, there are two types of annotations, embedded, and normal. * An embedded annotation is a data transformation construct, and a normal annotation is stored separately * from the "body" of the comment, and multiple of the same annotation are allowed. Embedded annotations * are transformed at parse time, and you provide the callback to do the transformation. For instance, the * embedded annotation {@ code myCode} can be configured to return "<code>myCode</code>" * Newlines and spaces are preserved in the body of the comment, but newlines are not stored with annotation * parameters. * */ public class SmartComment { private static final Pattern ANNOTATION = Pattern.compile("@[a-zA-Z][a-zA-Z0-9]*"); private static final Pattern EMBEDDED_ANNOTATION = Pattern.compile("\\{@([a-zA-Z][a-zA-Z0-9]*) +(.*?)\\}"); private static final String LINE_START = "[\\t ]*\\* ?"; private String raw; private String body; private Map<String, List<String>> annotations = new HashMap<String, List<String>>(); private Map<String, Replacement> rplcmnt = new HashMap<String, Replacement>(); /** * Creates a new smart comment. * @param comment The comment to be parsed */ public SmartComment(String comment){ this(comment, null); } /** * Creates a new smart comment. * @param comment The comment to be parsed * @param replacements This is used to replace embedded annotations with some * other text. For instance, if the comment contained { @ code myCode }, (minus spaces) it may be used to * return "<code>myCode</code>". By default, if a particular embedded annotation * has no handler, the embedded text is simply used as is. */ public SmartComment(String comment, Map<String, Replacement> replacements){ if(replacements == null){ replacements = new HashMap<String, Replacement>(); } //Remove the @ at the beginning, if present. for(String key : replacements.keySet()){ rplcmnt.put(key.replaceFirst("@", ""), replacements.get(key)); } comment = comment.trim(); if(comment.startsWith("/**")){ comment = comment.substring(3); } if(comment.endsWith("*/")){ comment = comment.substring(0, comment.length() - 2); } String [] lines = comment.split("\n|\r\n|\n\r"); StringBuilder b = new StringBuilder(); for(String line : lines){ line = line.replaceFirst(LINE_START, ""); b.append("\n").append(line); } raw = replaceEmbedded(b.toString().trim()); String [] words = raw.split(" |\n"); StringBuilder buffer = new StringBuilder(); String lastAnnotation = null; int annotationIndex = -1; for(String word : words){ if(ANNOTATION.matcher(word).matches()){ if(annotationIndex == -1){ Matcher m = ANNOTATION.matcher(raw); m.find(); annotationIndex = m.start(); } processBuffer(lastAnnotation, buffer.toString()); lastAnnotation = word; buffer = new StringBuilder(); } else { buffer.append(" ").append(word); } } processBuffer(lastAnnotation, buffer.toString()); if(annotationIndex == -1){ body = raw; } else { body = raw.substring(0, annotationIndex).trim(); } } private String replaceEmbedded(String string){ //Replace embedded annotations Matcher embedded = EMBEDDED_ANNOTATION.matcher(string); while(embedded.find()){ String key = embedded.group(1); String data = embedded.group(2); if(rplcmnt.containsKey(key)){ string = string.replaceAll(Pattern.quote(embedded.group(0)), rplcmnt.get(key).replace(data)); } else { string = string.replaceAll(Pattern.quote(embedded.group(0)), data); } } return string; } private void processBuffer(String lastAnnotation, String buffer){ if(lastAnnotation != null){ addAnnotation(lastAnnotation, buffer.trim()); } } private void addAnnotation(String name, String value){ if(!annotations.containsKey(name)){ annotations.put(name, new ArrayList<String>()); } annotations.get(name).add(value); } /** * Gets the body of the comment block. * @return */ public String getBody(){ return body; } /** * Gets a list of annotation values for the comment block. * @param annotation * @return */ public List<String> getAnnotations(String annotation){ if(!annotation.startsWith("@")){ annotation = "@" + annotation; } return new ArrayList<String>(annotations.get(annotation)); } public static interface Replacement { /** * Given the matched data in an embedded annotation, returns the transformed data, which * is replaced in the text. * @param data * @return */ public String replace(String data); } }