In perl, there is that format facility for console output. I thought it might be good to practice some library design instead of algorithms once in a while. This may be compared to a GUI library: you start from an empty window, add some lines to it, add labels and other stuff to each line and so on. At this point I have this:
FormatWindow.java:
package net.coderodde.textui;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* This class defines a formatted text window.
*
* @author Rodion "rodde" Efremov
* @version 1.6 (Apr 13, 2016)
*/
public class FormatWindow {
private final List<FormatWindowLine> formattedLineList = new ArrayList<>();
private final Map<String, String> fieldNameToValueMap = new HashMap<>();
/**
* Adds another line to this formatter.
*
* @param formatWindowLine a new line descriptor.
*/
public void addFormatWindowLine(final FormatWindowLine formatWindowLine) {
if (formatWindowLine != null) {
formattedLineList.add(formatWindowLine);
}
}
public void setFieldValue(final String fieldName, final String value) {
if (fieldName != null) {
fieldNameToValueMap.put(fieldName, value);
}
}
@Override
public String toString() {
if (formattedLineList.isEmpty()) {
return "";
}
StringBuilder sb = new StringBuilder()
.append(formattedLineList.get(0).toText(fieldNameToValueMap));
for (int i = 1; i < formattedLineList.size(); ++i) {
sb.append("\n")
.append(formattedLineList.get(i).toText(fieldNameToValueMap));
}
return sb.toString();
}
}
FormatWindowLine.java:
package net.coderodde.textui;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* This class implements a descriptor for formatted text output on a single
* line.
*
* @author Rodion "rodde" Efremov
* @version 1.6 (Apr 13, 2016)
*/
public class FormatWindowLine {
private final List<AbstractFormatWindowField> formatWindowFieldList =
new ArrayList<>();
public FormatWindowLine addFormatWindowField(
final AbstractFormatWindowField formatWindowField) {
if (formatWindowField != null) {
formatWindowFieldList.add(formatWindowField);
}
return this;
}
public FormatWindowLine addFormatWindowLine(FormatWindowLine other) {
if (other != null) {
other.formatWindowFieldList
.forEach((field) -> { addFormatWindowField(field); });
}
return this;
}
String toText(final Map<String, String> mapFieldNameToValue) {
StringBuilder sb = new StringBuilder();
for (AbstractFormatWindowField field : formatWindowFieldList) {
sb.append(field.toText(mapFieldNameToValue.get(field.getName())));
}
return sb.toString();
}
}
FormatWindowFieldAlignment.java:
package net.coderodde.textui;
/**
* This enumeration specifies all possible ways of aligning a text token within
* a {@link net.coderodde.textui.FormatWindowField}.
*
* @author Rodion "rodde" Efremov
* @version 1.6 (Apr 13, 2016)
*/
public enum FormatWindowFieldAlignment {
LEFT,
CENTER,
RIGHT,
}
AbstractFormatWindowField.java:
package net.coderodde.textui;
/**
* This class defines a text field. A field is specified by its offset from the
* start of the line, its maximum allowed length and its alignment.
*
* @author Rodion "rodde" Efremov
* @version 1.6 (Apr 13, 2016)
*/
public abstract class AbstractFormatWindowField {
public abstract String toText(String value);
public abstract String getName();
protected void checkNotNegative(int number, String errorMessage) {
if (number < 0) {
throw new IllegalArgumentException(errorMessage + ": " + number);
}
}
protected void checkPositive(int number, String errorMessage) {
if (number <= 0) {
throw new IllegalArgumentException(errorMessage);
}
}
protected void checkMaximumLength(int maximumLength) {
checkNotNegative(maximumLength, "The maximum field length is negative");
if (maximumLength == 0) {
throw new IllegalArgumentException(
"The maximum field length is zero.");
}
}
}
BarField.java:
package net.coderodde.textui.support;
import net.coderodde.textui.AbstractFormatWindowField;
/**
* This class implements a fixed-length character bar. A bar is a sequence of
* only one character.
*
* @author Rodion "rodde" Efremov
* @version 1.6 (Apr 13, 2016)
*/
public class BarField extends AbstractFormatWindowField {
private final char barCharacter;
private final int length;
public BarField(final char barCharacter, final int length) {
checkPositive(length, "The bar length must be a positive number");
this.barCharacter = barCharacter;
this.length = length;
}
@Override
public String toText(String value) {
StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; ++i) {
sb.append(barCharacter);
}
return sb.toString();
}
@Override
public String getName() {
return null;
}
}
StringField.java:
package net.coderodde.textui.support;
import java.util.Objects;
import net.coderodde.textui.AbstractFormatWindowField;
/**
* This class implements a simple field consisting of a string.
*
* @author Rodion "rodde" Efremov
* @version 1.6 (Apr 13, 2016)
*/
public class StringField extends AbstractFormatWindowField {
private final String string;
public StringField(final String string) {
this.string = Objects.requireNonNull(string,
"The input string is null.");
}
@Override
public String toText(String value) {
return string;
}
@Override
public String getName() {
return null;
}
}
TextField.java:
package net.coderodde.textui.support;
import java.util.Objects;
import net.coderodde.textui.AbstractFormatWindowField;
import net.coderodde.textui.FormatWindowFieldAlignment;
/**
* This class defines a simple text field.
* @author Rodion "rodde" Efremov
* @version 1.6 (Apr 13, 2016)
*/
public class TextField extends AbstractFormatWindowField {
private static final FormatWindowFieldAlignment DEFAULT_ALIGNMENT =
FormatWindowFieldAlignment.CENTER;
private static final char DEFAULT_PAD_CHARACTER = ' ';
private final String name;
private final char padCharacter;
private final FormatWindowFieldAlignment alignment;
private final int maximumLength;
public TextField(String name,
char padCharacter,
FormatWindowFieldAlignment alignment,
int maximumLength) {
this.name = Objects.requireNonNull(
name,
"The name of the field may not be null.");
checkMaximumLength(maximumLength);
this.padCharacter = padCharacter;
this.alignment = alignment == null ? DEFAULT_ALIGNMENT : alignment;
this.maximumLength = maximumLength;
}
public TextField(String name,
FormatWindowFieldAlignment alignment,
int maximumLength) {
this(name, DEFAULT_PAD_CHARACTER, alignment, maximumLength);
}
public TextField(String name, int maximumLength) {
this(name, FormatWindowFieldAlignment.CENTER, maximumLength);
}
public String getName() {
return name;
}
@Override
public String toText(String value) {
value = value == null ? "" : value;
if (value.length() > maximumLength) {
return value.substring(0, maximumLength);
}
switch (alignment) {
case LEFT:
return toTextOnLeft(value);
case CENTER:
return toTextAtCenter(value);
case RIGHT:
return toTextOnRight(value);
default:
throw new IllegalStateException("Unknown enumeration.");
}
}
private String toTextOnLeft(String value) {
StringBuilder sb = new StringBuilder(maximumLength);
sb.append(value);
while (sb.length() < maximumLength) {
sb.append(padCharacter);
}
return sb.toString();
}
private String toTextAtCenter(String value) {
StringBuilder sb = new StringBuilder(maximumLength);
int leftPaddingLength = (maximumLength - value.length()) / 2;
int rightPaddingLength = maximumLength - leftPaddingLength
- value.length();
for (int i = 0; i < leftPaddingLength; ++i) {
sb.append(padCharacter);
}
sb.append(value);
for (int i = 0; i < rightPaddingLength; ++i) {
sb.append(padCharacter);
}
return sb.toString();
}
private String toTextOnRight(String value) {
StringBuilder sb = new StringBuilder(maximumLength);
int leftPaddingLength = maximumLength - value.length();
for (int i = 0; i < leftPaddingLength; ++i) {
sb.append(padCharacter);
}
return sb.append(value).toString();
}
}
Demo.java:
import java.util.Arrays;
import net.coderodde.textui.support.BarField;
import net.coderodde.textui.FormatWindow;
import net.coderodde.textui.FormatWindowFieldAlignment;
import net.coderodde.textui.FormatWindowLine;
import net.coderodde.textui.support.StringField;
import net.coderodde.textui.support.TextField;
public class Demo {
public static void main(String[] args) {
FormatWindow window = new FormatWindow();
FormatWindowLine[] lines = new FormatWindowLine[]{
new FormatWindowLine(),
new FormatWindowLine(),
new FormatWindowLine(),
new FormatWindowLine(),
new FormatWindowLine(),
new FormatWindowLine()
};
BarField horizontalLine = new BarField('*', 80);
BarField innerLine = new BarField(' ', 78);
lines[0].addFormatWindowField(horizontalLine);
lines[1].addFormatWindowField(new StringField("*"))
.addFormatWindowField(innerLine)
.addFormatWindowField(new StringField("*"));
lines[2].addFormatWindowField(new StringField("* First name: "))
.addFormatWindowField(
new TextField("firstName",
'_',
FormatWindowFieldAlignment.CENTER,
20))
.addFormatWindowField(new BarField(' ', 43))
.addFormatWindowField(new StringField("*"));
lines[3].addFormatWindowField(new StringField("* Last name: "))
.addFormatWindowField(
new TextField("lastName",
'_',
FormatWindowFieldAlignment.RIGHT,
20))
.addFormatWindowField(new BarField(' ', 43))
.addFormatWindowField(new StringField("*"));
lines[4].addFormatWindowLine(lines[1]);
lines[5].addFormatWindowLine(lines[0]);
Arrays.stream(lines).forEach(window::addFormatWindowLine);
// Manipulating window content is easy:
window.setFieldValue("firstName", "Ken");
window.setFieldValue("lastName", "Thompson");
System.out.println(window);
}
}
The output is
******************************************************************************** * * * First name: ________Ken_________ * * Last name: ____________Thompson * * * ********************************************************************************
Any critique is much appreciated!