我想重构解析器以获得灵活的csv格式,它描述了第一行中的列。根据这些信息,我希望解析器构建具有简单属性的对象,但也要构建复杂的对象,例如List<String>
(空格分隔),例如Thing
s:
import java.util.List;
public class Thing {
protected int foo;
protected String bar
protected List<String> baz;
public Thing(int foo, String bar, List<String> baz) {
this.foo = foo;
this.bar = bar;
this.baz = baz;
}
public String toString() {
return "foo: " + foo + ", bar: " + bar + ", baz: " + baz;
}
}
解析器的输入将是第一行中带有列行(逗号分隔)的文本文件,以及n
下一行(逗号分隔)中的数据。为简化测试,我将Iterator<String>
用于输入行。这个简单的测试应该说明我想要构建的内容:
// prepare example string iterator
List<String> lines = new ArrayList<String>();
lines.add("bar,baz,foo");
lines.add("yay,quux quuux,17");
lines.add("hey,qaax qaaax,42");
// test parsed things
List<Thing> things = ThingBuilder.buildThings(lines.iterator());
assertNotNull(things);
assertEquals(2, things.size());
assertEquals("foo: 17, bar: yay, baz: [quux, quuux]", things.get(0).toString());
assertEquals("foo: 42, bar: hey, baz: [qaax, qaaax]", things.get(1).toString());
i
在列名switch
上执行大else if
/ i
到i
Thing
这种方法的问题是内部开关。在处理完第一行之后,应该清楚如何解析行。
在有闭包的语言中,我会尝试以下方法:
i
调用解析器关闭i
Thing
我为所有三个令牌解析器提供了一个简单的界面。他们应该获得一个令牌并在给定的ThingBuilder
缓存中注入生成的值:
public interface TokenParser {
public void parse(String token, ThingBuilder builder);
}
public class FooParser implements TokenParser {
@Override public void parse(String token, ThingBuilder builder) {
builder.setFoo(Integer.parseInt(token));
}
}
public class BarParser implements TokenParser {
@Override public void parse(String token, ThingBuilder builder) {
builder.setBar(token);
}
}
import java.util.ArrayList;
import java.util.List;
public class BazParser implements TokenParser {
@Override public void parse(String token, ThingBuilder builder) {
List<String> baz = new ArrayList<String>();
for (String s : token.split(" ")) baz.add(s);
builder.setBaz(baz);
}
}
我的ThingBuilder
buildThings
方法是静态的,并在内部创建ThingBuilder
对象,构造函数获取第一行(列)。这也是填充令牌解析器列表的位置。在此之后,隐藏的ThingBuilder
对象已准备就绪,并且使用以下输入行重复调用buildThing
方法以创建Thing
s的列表:
import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;
public class ThingBuilder {
// single column parsers
protected List<TokenParser> columnParsers;
// thing attribute cache
protected int fooCache;
protected String barCache;
protected List<String> bazCache;
// thing attribute cache setter
public void setFoo(int foo) { fooCache = foo; }
public void setBar(String bar) { barCache = bar; }
public void setBaz(List<String> baz) { bazCache = baz; }
// cleanup helper method
protected void cleanup() {
setFoo(0); setBar(null); setBaz(null);
}
// statically build a list of things from given lines
public static List<Thing> buildThings(Iterator<String> lines) {
// prepare builder with the first line
ThingBuilder builder = new ThingBuilder(lines.next());
// parse things
List<Thing> things = new ArrayList<Thing>();
while (lines.hasNext()) {
things.add(builder.buildThing(lines.next()));
}
return things;
}
// prepares a builder to parse thing lines
protected ThingBuilder(String columnLine) {
// split line into columns
String[] columns = columnLine.split(",");
// prepare a parser for each column
columnParsers = new ArrayList<TokenParser>();
for (String column : columns) {
TokenParser parser;
if (column.equals("foo")) parser = new FooParser();
else if (column.equals("bar")) parser = new BarParser();
else if (column.equals("baz")) parser = new BazParser();
else throw new RuntimeException("unknown column: " + column);
columnParsers.add(parser);
}
}
// builds a thing from a string
protected Thing buildThing(String line) {
// split the line in tokens
String[] tokens = line.split(",");
// let the parsers do the work
for (int i = 0; i < tokens.length; i++) {
columnParsers.get(i).parse(tokens[i], this);
}
// hopefully they're done
Thing thing = new Thing(fooCache, barCache, bazCache);
cleanup();
return thing;
}
}
这有效,但是:
TokenParser
填充构建器缓存。int
&#39;该怎么办?我是否必须为每列构建一个解析器类,或者是否可以使用IntegerParser上课多一次?这里的问题是,解析器必须调用正确的缓存设置器方法。提前感谢您的提示!
答案 0 :(得分:2)
就个人而言,我不会走这条路。有一般框架可以很好地完成这类工作,并允许可自定义的扩展点。
例如,考虑一下流行的OpenCSV项目:
http://opencsv.sourceforge.net/#javabean-integration
或者,如果注释是您想要的,请考虑JFileHelpers
答案 1 :(得分:1)
我同意@ btiernay的回答,但是如果你想推出自己的实现,请继续阅读......
公共缓存设置器的事情。只应允许TokenParsers填充构建器缓存。
是的。这是TokenParser
API的结果,以及通过调用ThingBuilder
上的setter“返回”值的方式。事实上,这比你所确定的更糟糕。也就是说:您的TokenParser
API和所有TokenParser
类都特定于一个ThingBuilder
类。它们不可重复使用......
我认为使用这样的API会更好:
public interface TokenParser<T> {
public T parse(String token);
}
如果我有多个带有int的列,该怎么办?我是否必须为每个列构建一个解析器类,或者是否可以使用一次IntegerParser类?这里的问题是,解析器必须调用正确的缓存设置器方法。
烨。
接下来创建一个RowBuilder接口:
public interface RowBuilder<R>
public R buildRow(List<String> tokens);
}
这是一个棘手的问题---创建一个看起来像这样的通用RowBuilder类:
public class GenericRowBuilder<R> implements RowBuilder<R> {
public GenericRowBuilder(Class<R> clazz, TokenParser<?>[] parsers) {
// Extract the return types of the reified parse objects' `parse`
// methods, and use this to locate a matching `Constructor<R>` in
// `clazz`. If there isn't one, throw an exception.
this.clazz = clazz;
this.parsers = parsers;
}
public R parse(List<String> tokens) {
// Check number of tokens matches number of parsers.
// Parse each token with corresponding parsers.
// Use the `Constructor<R>` found above to create the instance of `R`
}
}
现在这一切都非常复杂......并且需要很好地理解你使用Java的反射API ......但最终结果是你可以为你的类实例化一个RowBuilder:
RowBuilder<MyRow> rb = new GenericRowBuilder<MyRow>(MyRow.class,
new TokenParser<?>[]{
new IntTokenParser(), new FloatTokenParser(), new CustomTokenParser});
你有一些东西:
R
,前提是它具有合适的构造函数。