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

cdc.issues.StructuredDescription Maven / Gradle / Ivy

There is a newer version: 0.62.0
Show newest version
package cdc.issues;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import cdc.util.strings.StringUtils;

/**
 * Class used to build simple structured descriptions.
 * 

* It can have an optional header, followed by optional sections.
* Each section has a title and a content.
* A section is marked as {@code "# name"} where name is the section name (title).
* Header and sections contents can be structured as lists of ordered or unordered items.
* Unordered list items are generated as {@code "- item"}.
* Ordered list items are generated as {@code "N) item"}, where {@code N} is a number.
* List items can be indented.
* * @author Damien Carbonne */ public class StructuredDescription { private static final Pattern SECTION_PATTERN = Pattern.compile("(\\R|^)# (.+)(\\R|$)"); private static final Pattern TAIL_NL_PATTERN = Pattern.compile("\\R$"); private static final Pattern ITEM_START_PATTERN = Pattern.compile("(\\R|^) *(- |\\d+\\) )"); private static Pattern sectionPattern(String section) { return Pattern.compile("(\\R|^)# " + section + "(\\R|$)"); } private static String removeTailNL(String s) { return TAIL_NL_PATTERN.matcher(s).replaceFirst(""); } private final String text; protected StructuredDescription(Builder builder) { this.text = builder.text.toString(); } /** * @return The text of the description. */ public String getText() { return text; } /** * @return The description header (text before first section). */ public String getHeader() { final Matcher first = SECTION_PATTERN.matcher(text); if (first.find()) { return removeTailNL(text.substring(0, first.start())); } else { return text; } } /** * @return A list of declared sections. */ public List getSections() { return SECTION_PATTERN.matcher(text) .results() .map(mr -> mr.group(2)) .toList(); } /** * @param section The section name. * @return {@code true} if a section named {@code section} exists in this description. */ public boolean hasSection(String section) { final Pattern pattern = sectionPattern(section); return pattern.matcher(text).find(); } /** * @param section The section name. * @return The content of the section named {@code section}, * or an empty string if no such section exists. * */ public String getSectionContent(String section) { final Matcher start = sectionPattern(section).matcher(text); if (start.find()) { final String tail = text.substring(start.end()); final Matcher end = SECTION_PATTERN.matcher(tail); final String content; if (end.find()) { // There is an additional NL in some cases content = removeTailNL(tail.substring(0, end.start())); } else { content = tail; } return content; } else { return ""; } } /** * @param section The section name. * @return A list of items declared in section named {@code section}. * The returned list is empty if no such section exists. */ public List getSectionItems(String section) { final String content = getSectionContent(section); if (content != null) { final List list = new ArrayList<>(); final String[] items = ITEM_START_PATTERN.split(content); for (final String item : items) { if (!StringUtils.isNullOrEmpty(item)) { list.add(item); } } return list; } else { return Collections.emptyList(); } } @Override public String toString() { return getText(); } public static Builder builder() { return new Builder<>(); } /** *  {@link StructuredDescription} builder. * * @author Damien Carbonne * @param The Builder type. */ public static class Builder> { private final StringBuilder text = new StringBuilder(); /** The indentation String. */ public static final String INDENT = " "; protected Builder() { } protected static String indent(int level) { final StringBuilder builder = new StringBuilder(); for (int i = 0; i < level; i++) { builder.append(INDENT); } return builder.toString(); } protected B self() { @SuppressWarnings("unchecked") final B x = (B) this; return x; } /** * Adds text to description. * * @param text The text to add. * @return This builder. */ public B text(String text) { this.text.append(text); return self(); } /** * Appends: {@code text}. * * @param text The header text. * @return This Builder. */ public B header(String text) { return text(text); } /** * Starts a section. * * @param section The section title. * @return This builder. */ public B section(String section) { if (this.text.length() > 0) { this.text.append("\n\n# ") .append(section); } else { this.text.append("# ") .append(section); } return self(); } /** * Adds 1 unordered list {@code item} at indentation {@code level}. * * @param level The indentation level. * @param item The list item. * @return This builder. */ public B uItem(int level, String item) { this.text.append("\n"); if (level > 0) { this.text.append(indent(level)); } this.text.append("- ") .append(item); return self(); } /** * Adds 1 unordered list {@code item} at indentation {@code level 0}. * * @param item The list item. * @return This builder. */ public B uItem(String item) { return uItem(0, item); } /** * Adds unordered list {@code items} at indentation {@code level}. * * @param level The indentation level. * @param items The list items. * @return This builder. */ public B uItems(int level, String... items) { for (final String item : items) { uItem(level, item); } return self(); } /** * Adds unordered list {@code items} at indentation {@code level 0}. * * @param items The list items. * @return This builder. */ public B uItems(String... items) { return uItems(0, items); } /** * Adds unordered list {@code items} at indentation {@code level}. * * @param level The indentation level. * @param items The list items. * @return This builder. */ public B uItems(int level, Collection items) { for (final String item : items) { uItem(level, item); } return self(); } /** * Adds unordered list {@code items} at indentation {@code level 0}. * * @param items The list items. * @return This builder. */ public B uItems(Collection items) { return uItems(0, items); } /** * Adds 1 ordered list {@code item} at indentation {@code level} and with {@code number}. * * @param level The indentation level. * @param number The number. * @param item The list item. * @return This builder. */ public B oItem(int level, int number, String item) { this.text.append("\n"); if (level > 0) { this.text.append(indent(level)); } this.text.append(Integer.toString(number)) .append(") ") .append(item); return self(); } /** * Adds 1 ordered list {@code item} at indentation {@code level 0} and with {@code number}. * * @param number The number. * @param item The list item. * @return This builder. */ public B oItem(int number, String item) { return oItem(0, number, item); } /** * Adds unordered list {@code items} at indentation {@code level}, starting at {@code number}. * * @param level The indentation level. * @param number The first number. * @param items The list items. * @return This builder. */ public B oItems(int level, int number, String... items) { for (final String item : items) { oItem(level, number, item); number++; } return self(); } /** * Adds unordered list {@code items} at indentation {@code level 0}, starting at {@code number}. * * @param number The first number. * @param items The list items. * @return This builder. */ public B oItems(int number, String... items) { return oItems(0, number, items); } /** * Adds unordered list {@code items} at indentation {@code level}, starting at {@code number}. * * @param level The indentation level. * @param number The first number. * @param items The list items. * @return This builder. */ public B oItems(int level, int number, Collection items) { for (final String item : items) { oItem(level, item); } return self(); } /** * Adds unordered list {@code items} at indentation {@code level °}, starting at {@code number}. * * @param number The first number. * @param items The list items. * @return This builder. */ public B oItems(int number, Collection items) { return oItems(0, number, items); } /** * Appends a violation explanation. * * @param explanation The violation explanation. * @return This Builder. */ public B violation(String explanation) { return text("\n").text(explanation); } public B value(String value) { return text("\n'").text(value) .text("'"); } public B justification(int number, String justification) { return oItem(number, justification); } public B justifications(String... justifications) { return oItems(1, justifications); } public StructuredDescription build() { return new StructuredDescription(this); } } }