Skip to content

Instantly share code, notes, and snippets.

@josefbetancourt
Last active October 13, 2015 05:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save josefbetancourt/4149648 to your computer and use it in GitHub Desktop.
Save josefbetancourt/4149648 to your computer and use it in GitHub Desktop.
Very simple data file in Java source with embedded JUnit test. Presented in "Simple Java Data File" blog post.
/** Vsdf.java */
package com.octodecillion.vsdf;
import static com.octodecillion.vsdf.Vsdf.EventType.END;
import static org.hamcrest.core.DescribedAs.describedAs;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.hamcrest.core.IsNull.notNullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Callable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
*
* Very Simple Data File Support.
*
* An extension of INI files. More like external HEREDOCS
* with subdivisions.
*
* Written for Java 1.7 runtime.
*
* @see "http://octodecillion.com/blog/simple-java-data-file-in-java/"
* @see "http://octodecillion.com/blog/very-simple-data-file-format/"
*
* What is an alternative parsing approach?
* <a href="http://www.antlr4.org/">Antlr</a>,
* <a href="https://github.com/sirthias/parboiled/wiki">Parboiled</a>
*
* @author Josef Betancourt
*
*/
public class Vsdf {
private BufferedReader reader; // when is it closed?
private Reader sourceReader;
private File file;
private int lineCounter;
private int sectionCounter;
private Event data;
private boolean reusable;
@SuppressWarnings("javadoc")
public enum EventType {
COMMENT, SECTION, EMPTY, END
}
@SuppressWarnings("javadoc")
public enum DataType {
TEXT, PROPERTIES, LIST, MAP, SET, DOCUMENT;
}
/** Default constructor */
public Vsdf() {
}
/**
*
* @param inixData
* sectioned data
*/
public Vsdf(final String inixData) {
sourceReader = new StringReader(inixData);
reader = new BufferedReader(sourceReader);
}
/**
* @param reader
*/
public Vsdf(final Reader reader) {
this();
if (reader == null) {
throw new NullPointerException("reader is null");
}
sourceReader = reader;
this.reader = new BufferedReader(reader);
}
/**
* @param file
* @throws FileNotFoundException
*/
public Vsdf(final File file) throws FileNotFoundException {
if (file == null) {
throw new NullPointerException("file is null");
}
this.file = file;
sourceReader = new FileReader(file);
reader = new BufferedReader(sourceReader);
reusable = true;
}
/**
* If the input is a file, allow reuse of Vsdf instance.
*
* Side effect: If a file is being used, a new reader is created.
*
* @return true if reader will be recreated.
* @throws VsdfException
*/
protected boolean reusable() {
if (!reusable) {
return false;
}
try {
reader = new BufferedReader(new FileReader(file));
} catch (FileNotFoundException ex) {
throw new VsdfException(ex.getMessage(), ex);
}
return true;
}
/**
* Load a section's data.
*
* Example:
*
* <pre>
* String data = new Vsdf(getFile(TEST_FILE)).load(id);
* </pre>
*
* @param id
* @return data in section
*
* @throws IOException
*/
public String load(final String id) throws IOException {
reusable();
String data = "";
EventType eventType;
while ((eventType = next()) != EventType.END) {
if ((eventType == EventType.SECTION) && match(id, "")) {
data = getEvent().text;
break;
}
}
reader.close();
return data;
}
/**
* Load a section's data.
*
* Example:
*
* <pre>
* String data = new Vsdf(getFile(TEST_FILE)).load(id, subsection);
* </pre>
*
* @param id
* @param subsection
* @return data in section
*
* @throws IOException
*/
public String load(final String id, final String subsection)
throws IOException {
if (isNullOrBlank(id)) {
throw new IllegalArgumentException(String.format(
"id, '%s' is null or blank", id));
}
if (null == subsection) {
throw new IllegalArgumentException("subsection is null");
}
reusable();
String data = "";
EventType eventType;
while ((eventType = next()) != EventType.END) {
if ((eventType == EventType.SECTION) && match(id, subsection)) {
data = getEvent().text;
break;
}
}
reader.close();
return data;
}
/**
* See {@link #loadAs(DataType, String, String)}
*/
@SuppressWarnings("javadoc")
public <T> T loadAs(final Class<T> type, final String id)
throws ClassCastException {
return loadAs(type, id, BLANK);
}
/**
* See {@link #loadAs(DataType, String, String)}
*/
@SuppressWarnings("javadoc")
public Object loadAs(final DataType type, final String id)
throws ClassCastException {
return loadAs(type, id, BLANK);
}
/**
* See {@link #loadAs(DataType, String, String)}
*/
@SuppressWarnings({ "unchecked", "javadoc" })
public <T> T loadAs(final Class<T> type, final String id,
final String subsection) {
String typeName = type.getSimpleName().toUpperCase(Locale.ENGLISH);
DataType dType = DataType.valueOf(typeName);
return (T) loadAs(dType, id, subsection);
}
/**
*
* Load a section from inix file and parse according to the datatype.
*
* @param type
* @param id
* @param subsection
* @return parsed section data
* @returns the object or null
* @throws VsdfException
*/
public Object loadAs(final DataType type, final String id,
final String subsection) {
reusable();
Object data = null;
try {
String text = load(id, subsection);
if (!isNullOrBlank(text)) {
switch (type) {
case PROPERTIES:
data = new PropertiesConverter().parse(text);
break;
case LIST:
data = new ListConverter().parse(text);
break;
case DOCUMENT:
data = new DocumentConverter().parse(text);
break;
default:
break;
}
}
} catch (Exception ex) {
throw new VsdfException(ex.getMessage(), ex);
}
return data;
}
/**
*
* Add a new section to inix file;
*
* @param id
* @param subsection
* @param text
* @return true if section was added
* @throws IOException
*/
public boolean addSection(final String id, final String subsection,
final String text) throws IOException {
reusable();
boolean result = true;
boolean found = false;
if (!found) {
// TODO: write the section to end of file
// may have to write to temp file, etc.
}
return result;
}
private class ListConverter implements Converter {
public ListConverter() {
//
}
@SuppressWarnings("unchecked")
@Override
public List<String> parse(final String text) throws Exception {
return Arrays.asList(text.split(LINESEP));
}
}
private class PropertiesConverter implements Converter {
public PropertiesConverter() {
//
}
@SuppressWarnings("unchecked")
@Override
public Properties parse(final String data) throws Exception {
Properties p = new Properties();
p.load(new StringReader(data));
return p;
}
}
private class DocumentConverter implements Converter {
public DocumentConverter() {
//
}
@SuppressWarnings("unchecked")
@Override
public Document parse(final String text) throws Exception {
return parseXML(text);
}
}
/**
* @author jbetancourt
*
*/
public static interface Converter {
/**
* @param text
* @return the data converter to data type
* @throws Exception
*/
public <T> T parse(String text) throws Exception;
}
/**
* Parse xml String.
*
* @param xml
* @return a Document
* @throws SAXException
* @throws IOException
* @throws ParserConfigurationException
*/
Document parseXML(final String xml) throws SAXException,
IOException, ParserConfigurationException {
DocumentBuilder builder = DocumentBuilderFactory.newInstance()
.newDocumentBuilder();
return builder.parse(new InputSource(new StringReader(xml)));
}
/**
* Read all the sections into a map.
*
* Just a convenience method, not recommended.
*
* Example:
* <code>
* Map&lt;String, String&gt; map = new Vsdf(getFile(TEST_FILE)).load();
* </code>
*
*
* @return a map of section header string to section content as line
* separted string.
* @throws IOException
*/
public Map<String, String> load() throws IOException {
reusable();
Map<String, String> map = new HashMap<String, String>();
EventType eventType;
while ((eventType = next()) != EventType.END) {
if (eventType == EventType.SECTION) {
Event ev = getEvent();
map.put(ev.header, ev.text);
}
}
return map;
}
/**
*
*
* @return list of of array id, subsection.
* @throws IOException
*/
public List<String[]> loadIndex() throws IOException {
reusable();
List<String[]> list = new ArrayList<String[]>();
EventType eventType;
while ((eventType = next()) != EventType.END) {
if (eventType == EventType.SECTION) {
Event ev = getEvent();
String[] arr = new String[2];
arr[0] = ev.id;
arr[1] = ev.subsection;
list.add(arr);
}
}
return list;
}
/**
* Read the data base and report on configuration.
* @return human readable report string
*
* @throws IOException
*/
@SuppressWarnings("boxing")
public String testData() throws IOException {
reusable();
EventType eventType;
int sectionCount = 0;
int blankSections = 0;
while ((eventType = next()) != EventType.END) {
if (eventType == EventType.SECTION) {
Event ev = getEvent();
sectionCount++;
if((ev.text != null) && (ev.text.trim().length() == 0)) {
blankSections++;
}
}
}
return String.format("#sections=%d,#blank sections=%d",
sectionCount, blankSections);
}
/**
* Pull event style processing.
*
* @return event type
* @throws IOException
*/
public EventType next() throws IOException {
if (reader == null) {
throw new NullPointerException(READER_IS_NULL);
}
String line = reader.readLine();
lineCounter++;
data = new Event();
EventType eventType = null;
data.lineNum = lineCounter;
if (line == null) {
eventType = EventType.END;
} else if (isBlank(line)) {
eventType = EventType.EMPTY;
} else if (line.matches(COMMENT_REGEX)) {
eventType = EventType.COMMENT;
data.text = line;
data.event = EventType.COMMENT;
data.lineNum = lineCounter;
} else if (line.matches(SECTION_START_REGEX)) {
eventType = EventType.SECTION;
sectionCounter++;
processSection(line.trim(), data);
}
return eventType;
}
/**
* Push event style processing.
*
* Read the file and push events via a runnable interface.
*
* @param reader
* BufferedReader
* @param callable
* @throws Exception
*
*/
public <T> void parse(final BufferedReader reader,
final Callable<T> callable) throws Exception {
try {
setReader(reader);
while (next() != END) {
@SuppressWarnings("unused")
T result = callable.call();
}
} finally {
reader.close();
}
}
/**
* Push event style processing.
*
* See {@link #parse(BufferedReader, Callable)}
*
* @param filePath
* @param runnable
* @throws Exception
*
*/
public void parse(final String filePath, final Runnable runnable)
throws Exception {
reader = new BufferedReader(new FileReader(new File(filePath)));
// Since parse requires a Callable, we wrap the runnable. yuck.
parse(reader, new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
runnable.run();
return Boolean.TRUE;
}
});
reader.close();
}
/**
* Get info about the section.
*
* Fill in the Event data structure
*
* @param line
* @param dataEvent
* @throws IOException
*/
private void processSection(final String line, final Event dataEvent)
throws IOException {
dataEvent.event = EventType.SECTION;
dataEvent.sectionNum = sectionCounter;
Matcher m;
if ((m = headerPattern.matcher(line)).find()) {
dataEvent.header = m.group(1);
} else {
throw new IOException("Could not extract header from: '" + line
+ "'");
}
Matcher secMatcher = sectionStartPattern.matcher(line);
String mString = secMatcher.matches() ? secMatcher.group(1) : BLANK;
parseStartTag(dataEvent, mString.trim());
dataEvent.text = readContent();
}
/**
* Get the id and subsection from header string.
*
* @param dataEvent
* @param headerContent
* */
private void parseStartTag(final Event dataEvent, final String headerContent) {
dataEvent.id = find(headerContent, idPattern);
if (isNullOrBlank(dataEvent.id)) {
throw new IllegalArgumentException(ID_MISSING + headerContent);
}
String type = find(headerContent, typePattern);
dataEvent.type = valueOf(type, DataType.TEXT.name());
dataEvent.subsection = find(headerContent, subsectionPattern);
}
/**
* Find first matching group in string.
*
* @param current
* @param pattern
* @return string found or blank
*
*/
private String find(final String current, final Pattern pattern) {
String found = "";
Matcher matcher = pattern.matcher(current);
if (matcher.find()) {
found = matcher.group(1);
}
return found != null ? found : BLANK;
}
/**
* Read the data in the section.
*
* All lines will be included as data.
*
* @return the data as String
* @throws IOException
*/
private String readContent() throws IOException {
StringBuilder buffer = new StringBuilder(8 * 1024);
while (true) {
String line = reader.readLine();
lineCounter++;
if ((line == null) || line.trim().matches(TERMINAL_REGEX)) {
break;
}
if (line.trim().matches(ANY_SECTION_REGEX)) {
throw new IllegalStateException(UNTERMINATED_SECTION + " "
+ (data != null ? data.header : ""));
}
buffer.append(line + LINESEP);
}
return buffer.toString();
}
/**
* Does current event id match.
*
* Invokes {@link #match(String, String)} with blank subsection.
*
* @param id
* @return true if match
*/
public boolean match(final String id) {
return match(id, BLANK);
}
/**
* section match?
*
* @param id
* @param subsection
* @return true if match
*/
public boolean match(final String id, final String subsection) {
if (isNullOrBlank(id)) {
throw new IllegalArgumentException(String.format(
ID_ARG_MATCH_ERROR, id));
}
if (null == subsection) {
throw new IllegalArgumentException(String.format(
SUB_ARG_MATCH_ERROR, subsection));
}
boolean idMatch = data.id.equals(id);
String sub = data.subsection == null ? BLANK : data.subsection;
boolean subMatch = sub.equals(subsection);
return idMatch && subMatch;
}
/**
* Event struct for parsed sections.
*/
@SuppressWarnings("javadoc")
public class Event {
public EventType event;
public String type;
public String subsection;
public String id;
public String text;
public int lineNum;
public int sectionNum;
public String header;
@SuppressWarnings("boxing")
@Override
public String toString() {
return String.format(EVENT_TO_STRING, event, id, subsection, type,
header, lineNum);
}
}
/**
* Get current parse event.
*
* @return current event
*/
public Event getEvent() {
return data;
}
/**
* @param reader
* the reader to set
*/
public void setReader(final BufferedReader reader) {
if (reader == null) {
throw new NullPointerException("reader is null!");
}
this.reader = reader;
}
/**
* @return the reader
*/
public BufferedReader getReader() {
return reader;
}
/**
* Use string or default string.
*
* @param inString
* @param name
* @return inString or if blank or null, returns a blank
*/
private String valueOf(final String inString, final String name) {
return isNullOrBlank(inString) ? name : inString;
}
/**
* Is string blank?
*
* @param obj
* @return true if so
*/
private boolean isBlank(final String obj) {
if (obj == null) {
throw new NullPointerException("obj is null");
}
return obj.trim().length() == 0;
}
/**
* Is object null, or string blank.
*
* @param obj
* @return true if it is
*/
private boolean isNullOrBlank(final Object obj) {
if (obj == null) {
return true;
}
if (obj instanceof String) {
return ((String) obj).trim().length() == 0;
}
return false;
}
/**
*
*
* @author jbetancourt
*
*/
public class VsdfException extends RuntimeException {
/** */
private static final long serialVersionUID = 1L;
/**
* See {@link RuntimeException#RuntimeException()}
*/
public VsdfException() {
super();
}
/**
* See {@link RuntimeException#RuntimeException(String, Throwable) }
*/
public VsdfException(final String message, final Throwable cause) {
super(message, cause);
}
/**
* See {@link RuntimeException#RuntimeException(String) }.
*/
public VsdfException(final String message) {
super(message);
}
/**
* See {@link RuntimeException#RuntimeException(Throwable)}.
*/
public VsdfException(final Throwable cause) {
super(cause);
}
}
private static final String BLANK = "";
@SuppressWarnings("javadoc")
static final String LINESEP = System.getProperty("line.separator");
private static final String EVENT_TO_STRING = "event=%s,id=%s, subsection=%s, type=%s, header=%s, lineNum=%s";
private static final String READER_IS_NULL = "reader is null";
private static final String UNTERMINATED_SECTION = "Unterminated section: ";
private static final String ID_ARG_MATCH_ERROR = "id arg to match is null or blank: '%s'";
private static final String SUB_ARG_MATCH_ERROR = "subsection arg to match is null: '%s'";
private static final String ID_MISSING = "id missing in section: ";
private static final String ANY_SECTION_REGEX = "^\\s*\\[>.*\\]\\s*";
private static final String TERMINAL_REGEX = "^\\s*\\[<.*\\]\\s*";
private static final String COMMENT_REGEX = "^\\s*[#!].*";
private static final String HEADER_REGEX = "\\[>(.*)\\]\\s*";
private static final Pattern headerPattern = Pattern.compile(HEADER_REGEX);
private static final String ID_REGEX = "^(?:.*:)?(.*?)(?:/.*)?$";
private static final Pattern idPattern = Pattern.compile(ID_REGEX);
private static final String TYPE_REGEX = "^(.*):";
private static final Pattern typePattern = Pattern.compile(TYPE_REGEX);
private static final String SECTION_START_REGEX = "^\\s*\\[>(.*)\\]\\s*";
private static final Pattern sectionStartPattern = Pattern
.compile(SECTION_START_REGEX);
private static final String SUBSECTION_REGEX = "^(?:.*:)?.*?/(.*)$";
private static final Pattern subsectionPattern = Pattern
.compile(SUBSECTION_REGEX);
@SuppressWarnings("boxing")
@Override
public String toString() {
return String.format("lineCounter=%s, sectionCounter=%s, " + "data=%s",
lineCounter, sectionCounter, data);
}
// ====================================================================
// ====================================================================
// Embedded JUnit test
// ====================================================================
// ====================================================================
/**
* Test {@link Vsdf}.
*
* @author Josef Betancourt
*
*/
@RunWith(JUnit4.class)
public static class VsdfTest {
private static final String TEST_FILE = "TestDataTestFile.inix";
/**
* the tested object.
*/
protected Vsdf app;
/**
* @throws Exception
*/
@Before
public void setUp() throws Exception {
app = new Vsdf(getFileInClassPath(TEST_FILE));
}
/**
* @throws IOException
*
*/
@SuppressWarnings("boxing")
@Test
public void should_loadIndex() throws IOException {
List<String[]> list = app.loadIndex();
assertThat(list.size(), is(9));
if (app.isReusable()) {
list = app.loadIndex();
assertThat(list.size(), is(9));
}
}
/**
* @throws IOException
*
*/
@SuppressWarnings("boxing")
@Test
public void should_loadIndex_ofStringSrc() throws IOException {
Path path = Paths.get(getFileInClassPath(TEST_FILE)
.getAbsolutePath());
String testData = new String(Files.readAllBytes(path));
app = new Vsdf(testData);
List<String[]> list = app.loadIndex();
assertThat("First attempt to loadIndex", list.size(), is(9));
if (app.isReusable()) {
list = app.loadIndex();
assertThat("Second attempt to loadIndex", list.size(), is(9));
}
}
/**
* Test {@link "Vsdf#parseStartTag(Event, String)"}.
*
* @throws Exception
*/
@Test
public void should_parseStartTag() throws Exception {
Method parseStartTag = getMethod("parseStartTag", Event.class,
String.class);
Vsdf vsdf = new Vsdf();
Event event = vsdf.new Event();
parseStartTag.invoke(vsdf, event, "x/y");
assertThat(event.id, is(equalTo("x")));
assertThat(event.subsection, is(equalTo("y")));
}
/**
* @throws Exception
* @throws InvocationTargetException
* @throws IllegalAccessException
* @throws IllegalArgumentException
*
*/
@Test
public void should_parseStartTag_fail() throws Exception {
try {
getMethod("parseStartTag", Event.class, String.class).invoke(
app, app.new Event(), "");
} catch (Exception e) {
assertThat(e.getCause(), is(IllegalArgumentException.class));
}
}
/**
* Test {@link Vsdf#load(String, String)}.
*
* @throws Exception
* @throws FileNotFoundException
*
*/
@Test
public void shouldLoadSection() throws FileNotFoundException, Exception {
String actual = new Vsdf(getFileInClassPath(TEST_FILE)).load(
"credit", "alerts");
String expected = "[\"one\",\"two\",\"three\"]" + LINESEP;
assertThat(actual, is(equalTo(expected)));
}
/**
* @throws FileNotFoundException
* @throws Exception
*/
@Test
public void shouldLoad_with_id() throws FileNotFoundException,
Exception {
String actual = new Vsdf(getFileInClassPath(TEST_FILE))
.load("messages");
String expected = "one=a one" + LINESEP + "two=a two" + LINESEP;
assertThat(actual, is(equalTo(expected)));
}
/**
* @throws FileNotFoundException
* @throws Exception
*/
@Test(expected = IllegalArgumentException.class)
public void shouldLoad_null_id() throws FileNotFoundException,
Exception {
new Vsdf(getFileInClassPath(TEST_FILE)).load(null,
"alerts");
}
/**
* @throws FileNotFoundException
* @throws Exception
*/
@Test(expected = IllegalArgumentException.class)
public void shouldLoad_null_Section() throws FileNotFoundException,
Exception {
new Vsdf(getFileInClassPath(TEST_FILE)).load(
"credit", null);
}
/**
* Test {@link Vsdf#loadAs(Class, String, String)}.
*
* @throws Exception
* @throws FileNotFoundException
*
*/
@Test
public void shouldLoadProperties() throws FileNotFoundException,
Exception {
Properties actual = new Vsdf(getFileInClassPath(TEST_FILE)).loadAs(
Properties.class, "credit", "config");
Properties expected = new Properties();
expected.put("one", "alpha");
expected.put("two", "beta");
expected.put("three", "charlie");
assertThat(actual, is(equalTo(expected)));
}
/**
* Test {@link Vsdf#loadAs(Class, String)}.
*
* @throws Exception
* @throws FileNotFoundException
*
*/
@Test
public void shouldLoadProperties2() throws FileNotFoundException,
Exception {
Properties actual = new Vsdf(getFileInClassPath(TEST_FILE)).loadAs(
Properties.class, "messages");
Properties expected = new Properties();
expected.put("one", "a one");
expected.put("two", "a two");
assertThat(actual,
describedAs("messages properties", is(equalTo(expected))));
}
/**
* Test {@link Vsdf#loadAs(DataType, String, String)}.
*
* @throws Exception
* @throws FileNotFoundException
*
*/
@Test
public void shouldLoadPropertiesType() throws FileNotFoundException,
Exception {
Properties actual = (Properties) new Vsdf(
getFileInClassPath(TEST_FILE)).loadAs(DataType.PROPERTIES,
"credit", "config");
Properties expected = new Properties();
expected.put("one", "alpha");
expected.put("two", "beta");
expected.put("three", "charlie");
assertThat(actual, is(equalTo(expected)));
}
/**
*
* Test {@link Vsdf#loadAs(Class, String)}.
*
* @throws FileNotFoundException
* @throws Exception
*/
@SuppressWarnings("unchecked")
@Test
public void shouldLoadList() throws FileNotFoundException, Exception {
List<String> actual = new Vsdf(getFileInClassPath(TEST_FILE))
.loadAs(List.class, "credit");
List<String> expected = new ArrayList<String>();
expected.add("one");
expected.add("two");
expected.add("three");
assertThat(actual, is(equalTo(expected)));
}
/**
* Test {@link Vsdf#loadAs(DataType, String)}.
*
* @throws FileNotFoundException
* @throws Exception
*/
@Test
public void shouldLoadListType() throws FileNotFoundException,
Exception {
@SuppressWarnings("unchecked")
List<String> actual = (List<String>) new Vsdf(
getFileInClassPath(TEST_FILE)).loadAs(DataType.LIST,
"credit");
List<String> expected = new ArrayList<String>();
expected.add("one");
expected.add("two");
expected.add("three");
assertThat(actual, is(equalTo(expected)));
}
/**
*
* Test {@link Vsdf#loadAs(Class, String, String)}.
*
* @throws FileNotFoundException
* @throws Exception
*/
@Test
public void shouldLoadXML() throws FileNotFoundException, Exception {
Document actualDom = new Vsdf(getFileInClassPath(TEST_FILE))
.loadAs(Document.class, "credit", "beans");
String actual = getItems(actualDom);
String xml = "<description><item>one</item><item>two</item><item>three</item></description>";
Document expectedDom = (Document) invokeMethod("parseXML",
new Class<?>[] { String.class }, xml);
String expected = getItems(expectedDom);
assertThat(actual, is(equalTo(expected)));
}
/**
* Test {@link Vsdf#loadAs(DataType, String, String)}.
*
* @throws FileNotFoundException
* @throws Exception
*/
@Test
public void shouldLoadXMLType() throws FileNotFoundException, Exception {
Document actualDom = (Document) new Vsdf(
getFileInClassPath(TEST_FILE)).loadAs(DataType.DOCUMENT,
"credit", "beans");
String actual = getItems(actualDom);
String xml = "<description><item>one</item><item>two</item><item>three</item></description>";
Document expectedDom = (Document) invokeMethod("parseXML",
new Class<?>[] { String.class }, xml);
String expected = getItems(expectedDom);
assertThat(actual, is(equalTo(expected)));
}
/**
*
*/
@Test(expected = VsdfException.class)
public void should_fail_loadXML() {
String data = "[>first]\nabc\n[<]\n";
app = new Vsdf(data);
app.loadAs(Document.class, "first");
}
/**
* Get items elements text content and concatenates into String.
*
* @see {@link #shouldLoadXML()}
*
* @param actualDom
* @return concatenates items
* @throws XPathExpressionException
*/
private String getItems(final Document actualDom)
throws XPathExpressionException {
NodeList nodes = (NodeList) XPathFactory.newInstance().newXPath()
.compile("/description/item")
.evaluate(actualDom, XPathConstants.NODESET);
StringBuffer buf = new StringBuffer();
for (int i = 0; i < nodes.getLength(); i++) {
buf.append(nodes.item(i).getTextContent());
}
String actual = buf.toString();
return actual;
}
/**
*
* Test {@link Vsdf#load()}.
*
* @throws Exception
* @throws FileNotFoundException
*
*/
@Test
public void shouldLoad() throws FileNotFoundException, Exception {
Map<String, String> map = new Vsdf(getFileInClassPath(TEST_FILE))
.load();
assertThat(map.get("credit/alerts"), notNullValue());
assertThat(map.get("credit/report"), notNullValue());
}
/**
* @throws IOException
*/
@Test
public void should_testData() throws IOException {
String actual = app.testData();
String expected = "#sections=9,#blank sections=1";
assertThat(actual, is(expected));
}
/**
* Test {@link Vsdf#next()}.
*
* @throws Exception
*/
@Test
public void should_get_credit_sect2() throws Exception {
BufferedReader reader = new BufferedReader(new FileReader(
getFileInClassPath(TEST_FILE)));
try {
app.setReader(reader);
EventType ev;
while ((ev = app.next()) != END) {
if (ev == EventType.SECTION) {
if (app.match("credit")) {
break;
}
}
}
} finally {
reader.close();
}
}
/**
* @throws Exception
* @throws InvocationTargetException
* @throws IllegalAccessException
* @throws IllegalArgumentException
*
*/
@Test
public void should_fail_unterminated_section() throws Exception {
String data = "[>abc]\n123\n456";
app.setReader(new BufferedReader(new StringReader(data)));
try {
getMethod("readContent").invoke(app);
} catch (Exception e) {
assertThat(e.getCause(), is(IllegalStateException.class));
}
}
/**
* Test {@link Vsdf#next()}.
*
* @throws IOException
*/
@Test(expected = NullPointerException.class)
public void should_not_parse_with_null_reader() throws IOException {
app.setReader(null);
app.next();
}
/**
*
*/
@Test(expected = IllegalArgumentException.class)
public void matchWithNull_fails() {
Vsdf v = new Vsdf();
v.match(null);
}
/**
*
*/
@Test(expected = IllegalArgumentException.class)
public void matchWithNull_null_fails() {
Vsdf v = new Vsdf();
v.match(null, null);
}
/**
*
*/
@Test(expected = NullPointerException.class)
public void setReader_with_null() {
app.setReader(null);
}
/**
* @throws Exception
*/
@SuppressWarnings("boxing")
@Test
public void valid_isNullOrBlank() throws Exception {
Vsdf vsdf = new Vsdf();
Method method = getMethod("isNullOrBlank", Object.class);
boolean actual = (Boolean) method.invoke(vsdf, Boolean.TRUE);
assertThat(actual, is(false));
}
/**
*
*/
@Test
public void toString_event() {
Event ev = new Vsdf().new Event();
ev.toString();
}
/**
*
*/
@Test
public void toString_Vsdf() {
app.toString();
}
/**
* Test {@link Vsdf#parse(BufferedReader, Callable)}.
*
* @throws Exception
*/
@SuppressWarnings("boxing")
@Test
public void should_get_credtest_sect() throws Exception {
@SuppressWarnings("resource")
BufferedReader reader = new BufferedReader(new FileReader(
getFileInClassPath(TEST_FILE)));
app.parse(reader, new Callable<Event>() {
@Override
public Event call() {
Event ev = app.getEvent();
if (ev.event == EventType.SECTION) {
if (app.match("credit", "tests")) {
String[] data = ev.text.split(LINESEP);
assertThat(data.length, is(3));
assertThat("one", is(data[0]));
assertThat("two", is(data[1]));
assertThat("three", is(data[2]));
}
}
return ev;
}
});
}
/**
* Test {@link Vsdf#parse(BufferedReader, Callable)}.
*
* @throws Exception
*/
@Test
public void should_get_credit_sect() throws Exception {
BufferedReader reader = new BufferedReader(new FileReader(
getFileInClassPath(TEST_FILE)));
app.parse(reader, new Callable<Event>() {
@SuppressWarnings("boxing")
@Override
public Event call() {
Event ev = app.getEvent();
if (ev.event == EventType.SECTION) {
if (app.match("credit")) {
String[] data = ev.text.split(LINESEP);
assertThat(data.length, is(3));
assertThat("one".compareTo(data[0]), is(0));
assertThat("two".compareTo(data[1]), is(0));
assertThat("three", is(equalTo(data[2])));
assertThat("TEXT", is(equalTo(ev.type)));
}
}
return ev;
}
});
reader.close();
}
/**
* Test {@link Vsdf#parse(BufferedReader, Callable)}.
*
* @throws Exception
*/
@Test
public void should_get_credit_sect_with_runnable() throws Exception {
BufferedReader reader = new BufferedReader(new FileReader(
getFileInClassPath(TEST_FILE)));
app.parse(getFileInClassPath(TEST_FILE).getAbsolutePath(),
new Runnable() {
@Override
@SuppressWarnings("boxing")
public void run() {
Event ev = app.getEvent();
if (ev.event == EventType.SECTION) {
if (app.match("credit")) {
String[] data = ev.text.split(LINESEP);
assertThat(data.length, is(3));
assertThat("one".compareTo(data[0]), is(0));
assertThat("two".compareTo(data[1]), is(0));
assertThat("three", is(equalTo(data[2])));
assertThat("TEXT", is(equalTo(ev.type)));
}
}
}
});
reader.close();
}
/**
* Just for coverage completeness.
*
* @throws FileNotFoundException
*/
@Test
public void constructWithFile() throws FileNotFoundException {
app = new Vsdf(getFileInClassPath(TEST_FILE));
assertThat(app.getReader(), is(notNullValue()));
}
/**
* @throws FileNotFoundException
*/
@Test(expected = NullPointerException.class)
public void constructWithFile_null() throws FileNotFoundException {
File file = null;
app = new Vsdf(file);
}
/**
* @throws FileNotFoundException
*/
@Test(expected = FileNotFoundException.class)
public void constructWithFile_non_existent()
throws FileNotFoundException {
File file = new File("xyz\\abc\\zzz.123");
app = new Vsdf(file);
}
/**
* @throws Exception
*/
@SuppressWarnings("resource")
@Test
public void constructWithReader() throws Exception {
app = new Vsdf(new FileReader(getFileInClassPath(TEST_FILE)));
assertThat(app.getReader(), is(notNullValue()));
Reader f = (Reader) getField("sourceReader");
assertThat(f, is(notNullValue()));
}
/**
* @throws Exception
*/
@Test(expected = NullPointerException.class)
public void constructWithReader_null() throws Exception {
Reader r = null;
app = new Vsdf(r);
}
/**
* @throws FileNotFoundException
*/
@Test
public void constructWithString() throws FileNotFoundException {
Vsdf v = new Vsdf("[>x]abc[<]");
assertThat(v.getReader(), is(notNullValue()));
}
/**
* @throws FileNotFoundException
*/
@Test(expected = NullPointerException.class)
public void constructWithString_null() throws FileNotFoundException {
String s = null;
new Vsdf(s);
}
/**
* @throws Exception
*/
@Test
public final void reg1() throws Exception {
regTest("list:credit/tests", "typePattern", "list");
regTest("list:credit/tests", "idPattern", "credit");
regTest("list:credit/tests", "subsectionPattern", "tests");
}
/**
* @throws Exception
*/
@Test
public final void reg2() throws Exception {
regTest("map:/junk", "typePattern", "map");
regTest("map:/junk", "idPattern", BLANK);
regTest("map:/junk", "subsectionPattern", "junk");
}
/**
* @throws Exception
*/
@Test
public final void reg3() throws Exception {
regTest("list:credit", "typePattern", "list");
regTest("list:credit", "idPattern", "credit");
regTest("list:credit", "subsectionPattern", BLANK);
}
/**
* @throws Exception
*/
@Test
public final void reg4() throws Exception {
regTest("credit", "typePattern", BLANK);
regTest("credit", "idPattern", "credit");
regTest("credit", "subsectionPattern", BLANK);
}
/**
* @throws Exception
*/
@Test
public final void reg5() throws Exception {
regTest(BLANK, "idPattern", BLANK);
}
/**
* Invoke private method to test regex.
*
* @param line
* @param patternName
* @param expected
* @throws Exception
*
*/
private void regTest(final String line, final String patternName,
final String expected) throws Exception {
Pattern pattern = (Pattern) getField(patternName);
String actual = (String) invokeMethod("find", new Class<?>[] {
String.class, Pattern.class }, line, pattern);
assertTrue(String.format("%s failed for input: %s, pattern=%s",
patternName, line, pattern),
actual.compareTo(expected) == 0);
}
/**
* Test {@link "Vsdf#isBlank(String)"}.
*
* @throws Exception
*/
@SuppressWarnings("boxing")
@Test
public final void testIsBlank() throws Exception {
Vsdf vsdf = new Vsdf();
Method method = getMethod("isBlank", String.class);
boolean actual = (Boolean) method.invoke(vsdf, "");
assertThat(actual, is(true));
actual = (Boolean) method.invoke(vsdf, " ");
assertThat(actual, is(true));
actual = (Boolean) method.invoke(vsdf, "x");
assertThat(actual, is(false));
try {
actual = (Boolean) method.invoke(vsdf, new Object[] { null });
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
assertThat(cause, is(NullPointerException.class));
}
}
/**
* @throws Exception
*/
@Test
public void processSection_with_null() throws Exception {
Method method = getMethod("processSection", String.class,
Event.class);
String line = "xxxx";
Event event = app.new Event();
try {
method.invoke(app, line, event);
} catch (Exception e) {
Throwable cause = e.getCause();
assertThat(cause, is(IOException.class));
}
}
/**
* Test {@link Vsdf#isNullOrBlank(Object)}.
*
* @throws Exception
*/
@SuppressWarnings({ "boxing", "javadoc" })
@Test
public final void testIsNullOrBlank() throws Exception {
Vsdf vsdf = new Vsdf();
Method method = getMethod("isNullOrBlank", Object.class);
boolean actual = (Boolean) method.invoke(vsdf, "");
assertThat(actual, is(true));
actual = (Boolean) method.invoke(vsdf, " ");
assertThat(actual, is(true));
actual = (Boolean) method.invoke(vsdf, (Object) null);
assertThat(actual, is(true));
actual = (Boolean) method.invoke(vsdf, "x");
assertThat(actual, is(false));
}
/**
*
* Get method and invoke it.
*
* @author jbetancourt
*
* @param name
* @param types
* @param args
* @return
* @throws Exception
*/
private final Object invokeMethod(final String name,
final Class<?>[] types, final Object... args) throws Exception {
return getMethod(name, types).invoke(app, args);
}
/**
* Get private method util.
*
* @param name
* @param parameterTypes
* @return the method
* @throws Exception
*
*
*/
private final Method getMethod(final String name,
final Class<?>... parameterTypes) throws Exception {
Method method = Vsdf.class.getDeclaredMethod(name, parameterTypes);
method.setAccessible(true);
return method;
}
/**
* Get field by reflection.
*
* @param name
* @return the value of the field.
* @throws Exception
*
*/
private Object getField(final String name) throws Exception {
Field field = app.getClass().getDeclaredField(name);
field.setAccessible(true);
return field.get(app);
}
/**
*
* Get File in classpath.
*
* @param fileName
* @return the file
* @throws FileNotFoundException
*/
private File getFileInClassPath(final String fileName)
throws FileNotFoundException {
URL url = this.getClass().getResource(fileName);
if (url == null) {
throw new FileNotFoundException("Resource not found: '"
+ fileName + "' ");
}
return new File(url.getFile());
}
}
/**
* @return the reusable
*/
public boolean isReusable() {
return reusable;
}
// Test file TestDataFile.inix contains:
/* @formatter:off
# Example very simple data file
! Another comment
#[>map:/junk]
#[<]
[>list:credit/tests]
one
two
three
[<]
[>credit/report]
one,two,three
[<]
[>properties:credit/config]
one=alpha
two=beta
three=charlie
[<]
[>xml:credit/beans]
<description>
<item>one</item>
<item>two</item>
<item>three</item>
</description>
[<]
[>notype/sub]
[<]
[>credit/alerts]
["one","two","three"]
[<]
[>credit]
one
two
three
[<]
[>credit/coverages]
["one","two","three"]
[<]
[>messages]
one=a one
two=a two
[<messages]
*/
} // end of class Vsdf
@josefbetancourt
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment