public class Image extends Link {

    public Image(Object text, String url) {
        super(text, url);
    }

    public Image(String url) {
        this(url, url);
    }

    @Override
    public String serialize() throws MarkdownSerializationException {
        return "!" + super.serialize();
    }

} public class Link extends MarkdownElement {

    private Object text;
    private String url;

    public Link(Object text, String url) {
        this.text = text;
        this.url = url;
    }

    public Link(String url) {
        this(url, url);
    }

    @Override
    public String serialize() throws MarkdownSerializationException {
        StringBuilder sb = new StringBuilder();
        sb.append("[").append(text).append("]");
        sb.append("(").append(url).append(")");
        return sb.toString();
    }

    public Object getText() {
        return text;
    }

    public void setText(Object text) {
        this.text = text;
        invalidateSerialized();
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
        invalidateSerialized();
    }

} public class TaskList extends UnorderedList<TaskListItem> {

    public TaskList() {
        this.items = new ArrayList<>();
    }

    public TaskList(List<TaskListItem> items) {
        this.items = items;
    }

} public class TaskListItem extends UnorderedListItem {

    private boolean checked;

    public TaskListItem(Object value) {
        this(value, false);
    }

    public TaskListItem(Object value, boolean checked) {
        super(value);
        this.checked = checked;
    }

    @Override
    public String getPredecessor() {
        return "- " + getCheckedIndicator(checked) + " ";
    }

    public static String getCheckedIndicator(boolean checked) {
        return checked ? "[x]" : "[ ]";
    }

} public class UnorderedList<T extends Object> extends MarkdownElement {

    protected List<T> items;
    protected int indentationLevel = 0;

    public UnorderedList() {
        this.items = new ArrayList<>();
    }

    public UnorderedList(List<T> items) {
        this.items = items;
    }

    @Override
    public String serialize() throws MarkdownSerializationException {
        StringBuilder sb = new StringBuilder();
        for (int itemIndex = 0; itemIndex < items.size(); itemIndex++) {
            T item = items.get(itemIndex);

            if (itemIndex > 0) {
                sb.append(StringUtil.fillUpLeftAligned("", "  ", indentationLevel * 2));
            } else if (indentationLevel > 0) {
                sb.append("  ");
            }

            if (item instanceof UnorderedListItem) {
                sb.append(item);
            } else if (item instanceof UnorderedList) {
                UnorderedList unorderedList = (UnorderedList) item;
                unorderedList.setIndentationLevel(indentationLevel + 1);
                sb.append(unorderedList);
            } else {
                sb.append(new UnorderedListItem(item));
            }

            if (itemIndex < items.size() - 1) {
                sb.append(System.lineSeparator());
            }
        }
        return sb.toString();
    }

    public List<T> getItems() {
        return items;
    }

    public void setItems(List<T> items) {
        this.items = items;
        invalidateSerialized();
    }

    public int getIndentationLevel() {
        return indentationLevel;
    }

    public void setIndentationLevel(int indentationLevel) {
        this.indentationLevel = indentationLevel;
        invalidateSerialized();
    }

    public void incrementIndentationLevel() {
        indentationLevel = indentationLevel + 1;
        invalidateSerialized();
    }

} public class ProgressBar extends MarkdownElement {

    public static final int LENGTH_SMALL = 15;
    public static final int LENGTH_NORMAL = 20;
    public static final int LENGTH_LARGE = 50;

    public static final char OPENING_CHAR_DEFAULT = '[';
    public static final char CLOSING_CHAR_DEFAULT = ']';

    public static final char FILL_CHAR_DEFAULT = '=';
    public static final char EMPTY_CHAR_DEFAULT = '-';

    private char openingChar = OPENING_CHAR_DEFAULT;
    private char closingChar = CLOSING_CHAR_DEFAULT;

    private char fillChar = FILL_CHAR_DEFAULT;
    private char emptyChar = EMPTY_CHAR_DEFAULT;

    private int length = LENGTH_NORMAL;
    private boolean appendValue = false;
    private boolean appendPercentage = false;

    private double value;
    private double minimumValue = 0;
    private double maximumValue = 1;

    public ProgressBar(double value) {
        this.value = value;
    }

    public ProgressBar(double value, int length) {
        this.value = value;
        this.length = length;
    }

    private void trimValue() {
        value = Math.max(minimumValue, Math.min(maximumValue, value));
    }

    @Override
    public String serialize() throws MarkdownSerializationException {
        trimValue();
        StringBuilder sb = new StringBuilder().append(openingChar);
        int filledCharsCount = calculateFilledCharsCount(value, minimumValue, maximumValue, length);
        for (int charIndex = 0; charIndex < length; charIndex++) {
            sb.append((charIndex < filledCharsCount) ? fillChar : emptyChar);
        }
        sb.append(closingChar);
        if (appendValue) {
            String readableValue = getReadableValue(value);
            readableValue = StringUtil.fillUpRightAligned(readableValue, " ", 7);
            sb.append(" ").append(readableValue);
        }
        if (appendPercentage) {
            String readablePercentage = getReadablePercentage(value, minimumValue, maximumValue);
            readablePercentage = StringUtil.fillUpRightAligned(readablePercentage, " ", 4);
            sb.append(" (").append(readablePercentage).append(")");
        }
        return sb.toString();
    }

    public static int calculateFilledCharsCount(double value, double minimumValue, double maximumValue, int length) {
        try {
            return (int) Math.round((length * value) / (maximumValue - minimumValue));
        } catch (ArithmeticException e) {
            return 0;
        }
    }

    public static String getReadableValue(double value) {
        if (value >= 1 || value == 0) {
            return String.valueOf(value);
        } else {
            return String.format("%.4f", value);
        }
    }

    public static String getReadablePercentage(double value, double minimumValue, double maximumValue) {
        int percentage = calculateFilledCharsCount(value, minimumValue, maximumValue, 100);
        return percentage + "%";
    }

    public char getOpeningChar() {
        return openingChar;
    }

    public void setOpeningChar(char openingChar) {
        this.openingChar = openingChar;
        invalidateSerialized();
    }

    public char getClosingChar() {
        return closingChar;
    }

    public void setClosingChar(char closingChar) {
        this.closingChar = closingChar;
        invalidateSerialized();
    }

    public char getFillChar() {
        return fillChar;
    }

    public void setFillChar(char fillChar) {
        this.fillChar = fillChar;
        invalidateSerialized();
    }

    public char getEmptyChar() {
        return emptyChar;
    }

    public void setEmptyChar(char emptyChar) {
        this.emptyChar = emptyChar;
        invalidateSerialized();
    }

    public int getLength() {
        return length;
    }

    public void setLength(int length) {
        this.length = length;
        invalidateSerialized();
    }

    public double getValue() {
        return value;
    }

    public void setValue(double value) {
        this.value = value;
        invalidateSerialized();
    }

    public double getMinimumValue() {
        return minimumValue;
    }

    public void setMinimumValue(double minimumValue) {
        this.minimumValue = minimumValue;
        invalidateSerialized();
    }

    public double getMaximumValue() {
        return maximumValue;
    }

    public void setMaximumValue(double maximumValue) {
        this.maximumValue = maximumValue;
        invalidateSerialized();
    }

    public boolean isAppendingValue() {
        return appendValue;
    }

    public void setAppendValue(boolean appendValue) {
        this.appendValue = appendValue;
    }

    public boolean isAppendingPercentage() {
        return appendPercentage;
    }

    public void setAppendPercentage(boolean appendPercentage) {
        this.appendPercentage = appendPercentage;
        invalidateSerialized();
    }
} public class HorizontalRule extends MarkdownElement {

    public static final char HYPHEN = '-';
    public static final char ASTERISK = '*';
    public static final char UNDERSCORE = '_';

    public static final int MINIMUM_LENGTH = 3;

    private int length;
    private char character = HYPHEN;

    public HorizontalRule() {
        this.length = MINIMUM_LENGTH;
    }

    public HorizontalRule(int length) {
        this.length = Math.max(MINIMUM_LENGTH, length);
    }

    public HorizontalRule(int length, char character) {
        this(length);
        this.character = character;
    }

    @Override
    public String serialize() throws MarkdownSerializationException {
        return StringUtil.fillUpLeftAligned("", "" + character, length);
    }

} public class Text extends MarkdownElement implements MarkdownCascadable {

    protected Object value;

    public Text(Object value) {
        this.value = value;
    }

    @Override
    public String serialize() throws MarkdownSerializationException {
        if (value == null) {
            throw new MarkdownSerializationException("Value is null");
        }
        return getPredecessor() + value.toString() + getSuccessor();
    }

    @Override
    public String getPredecessor() {
        return "";
    }

    @Override
    public String getSuccessor() {
        return getPredecessor();
    }

    public Object getValue() {
        return value;
    }

    public void setValue(Object value) {
        this.value = value;
        invalidateSerialized();
    }

} public class Code extends Text {

    public Code(Object value) {
        super(value);
    }

    @Override
    public String getPredecessor() {
        return "`";
    }

} public class CodeBlock extends Text {

    public static final String LANGUAGE_UNKNOWN = "";
    public static final String LANGUAGE_JAVA = "java";
    public static final String LANGUAGE_MARKDOWN = "markdown";

    private String language = LANGUAGE_UNKNOWN;

    public CodeBlock(Object value) {
        this(value, "");
    }

    public CodeBlock(Object value, String language) {
        super(value);
        this.language = language;
    }

    @Override
    public String getPredecessor() {
        return "```" + language + System.lineSeparator();
    }

    @Override
    public String getSuccessor() {
        return System.lineSeparator() + "```";
    }

    public String getLanguage() {
        return language;
    }

    public void setLanguage(String language) {
        this.language = language;
        invalidateSerialized();
    }
} public class BoldText extends Text {

    public BoldText(Object value) {
        super(value);
    }

    @Override
    public String getPredecessor() {
        return "**";
    }

} public class ItalicText extends Text {

    public ItalicText(Object value) {
        super(value);
    }

    @Override
    public String getPredecessor() {
        return "_";
    }

} public class StrikeThroughText extends Text {

    public StrikeThroughText(Object value) {
        super(value);
    }

    @Override
    public String getPredecessor() {
        return "~~";
    }

} public class Heading extends Text {

    public static final int MINIMUM_LEVEL = 1;
    public static final int MAXIMUM_LEVEL = 6;

    public static final char UNDERLINE_CHAR_1 = '=';
    public static final char UNDERLINE_CHAR_2 = '-';

    private int level;
    boolean underlineStyle = true;

    public Heading(Object value) {
        super(value);
        this.level = MINIMUM_LEVEL;
    }

    public Heading(Object value, int level) {
        super(value);
        this.level = level;
        trimLevel();
    }

    @Override
    public String getPredecessor() {
        if (underlineStyle && level < 3) {
            return "";
        }
        return StringUtil.fillUpRightAligned("", "#", level) + " ";
    }

    @Override
    public String getSuccessor() {
        if (underlineStyle && level < 3) {
            char underlineChar = (level == 1) ? UNDERLINE_CHAR_1 : UNDERLINE_CHAR_2;
            return System.lineSeparator() + StringUtil.fillUpLeftAligned("", "" + underlineChar, value.toString().length());
        }
        return "";
    }

    private void trimLevel() {
        level = Math.min(MAXIMUM_LEVEL, Math.max(MINIMUM_LEVEL, level));
    }

    public int getLevel() {
        return level;
    }

    public void setLevel(int level) {
        this.level = level;
        trimLevel();
        invalidateSerialized();
    }

    public boolean isUnderlineStyle() {
        return underlineStyle;
    }

    public void setUnderlineStyle(boolean underlineStyle) {
        this.underlineStyle = underlineStyle;
        invalidateSerialized();
    }

} public class Quote extends Text {

    public Quote(Object value) {
        super(value);
    }

    @Override
    public String serialize() throws MarkdownSerializationException {
        if (value == null) {
            throw new MarkdownSerializationException("Value is null");
        }
        StringBuilder sb = new StringBuilder();
        String lines[] = value.toString().split("\\r?\\n");
        for (int lineIndex = 0; lineIndex < lines.length; lineIndex++) {
            sb.append("> ").append(lines[lineIndex]);
            if (lineIndex < lines.length - 1) {
                sb.append(System.lineSeparator());
            }
        }
        return sb.toString();
    }

}
import net.steppschuh.markdowngenerator.image.Image;
import net.steppschuh.markdowngenerator.link.Link;
import net.steppschuh.markdowngenerator.list.TaskList;
import net.steppschuh.markdowngenerator.list.TaskListItem;
import net.steppschuh.markdowngenerator.list.UnorderedList;
import net.steppschuh.markdowngenerator.progress.ProgressBar;
import net.steppschuh.markdowngenerator.rule.HorizontalRule;
import net.steppschuh.markdowngenerator.text.Text;
import net.steppschuh.markdowngenerator.text.code.Code;
import net.steppschuh.markdowngenerator.text.code.CodeBlock;
import net.steppschuh.markdowngenerator.text.emphasis.BoldText;
import net.steppschuh.markdowngenerator.text.emphasis.ItalicText;
import net.steppschuh.markdowngenerator.text.emphasis.StrikeThroughText;
import net.steppschuh.markdowngenerator.text.heading.Heading;
import net.steppschuh.markdowngenerator.text.quote.Quote;
import java.util.Arrays;
package net.steppschuh.markdowngenerator;



/**
 * Created by steppschuh on 23/12/2016.
 */

public abstract class Markdown {

    // Heading

    public static Heading heading(String value, int level) {
        return new Heading(value, level);
    }

    public static Heading heading(String value) {
        return heading(value, 1);
    }

    public static Heading subHeading(String value) {
        return heading(value, 2);
    }

    // Rule

    public static HorizontalRule rule() {
        return new HorizontalRule();
    }

    public static HorizontalRule rule(int length) {
        return new HorizontalRule(length);
    }

    // Emphasis

    public static Text text(String value) {
        return new Text(value);
    }

    public static BoldText bold(String value) {