工具简介
该工具是由一名国外工程师jeyben 发布到github上。目前star是41,属于非常小众。但是功能非常具体,十分好用。推荐给大家!
工具用途
用于定长的文本做format成具体的java DTO,以及java DTO做parse成为固定长度的文本,文本中各个属性又是定长的。
工具核心
一、重要的注解
- @Record 用于标识需要做转化的类,进行扫描;
- @Feild 用于标识哪些字段需要做转化,并且有两个重要属性:offset(标识字段位于文本的第N个位置)、length(标识当前字段的长度)、align(标识字段从左到右解析还是从右到左解析,接收LEFT和RIGHT枚举值)、paddingChar(标识字段由什么字符来做填充,一般用于数字以及字符,例如数字填充0,字符填充空格等等)
- @FixedFormatPattern 用于时间格式的Pattern,其他类型没有做尝试。
二、支持的类型
类型 |
---|
String |
Character / char |
Long / long |
Integer / int |
Double / double |
Float /float |
Boolean / boolean |
Date |
BigDecimal |
三、代码亮点
- 大量结合注解+反射实现对不同类型的load和parse
/**
* @inheritDoc
*/
public <T> T load(Class<T> fixedFormatRecordClass, String data) {
HashMap<String, Object> foundData = new HashMap<String, Object>();
HashMap<String, Class<?>> methodClass = new HashMap<String, Class<?>>();
//assert the record is marked with a Record
getAndAssertRecordAnnotation(fixedFormatRecordClass);
//根据传入class解析为对应实例
T instance = createRecordInstance(fixedFormatRecordClass);
//根据当前类的所有方法做循环,切割出对应的field的string做后续的解析
Method[] allMethods = fixedFormatRecordClass.getMethods();
for (Method method : allMethods) {
String methodName = stripMethodPrefix(method.getName());
Field fieldAnnotation = method.getAnnotation(Field.class);
Fields fieldsAnnotation = method.getAnnotation(Fields.class);
if (fieldAnnotation != null) {
readFieldData(fixedFormatRecordClass, data, foundData, methodClass, method, methodName, fieldAnnotation);
} else if (fieldsAnnotation != null) {
//@Feilds 集合的特殊判空处理
if (fieldsAnnotation.value() == null || fieldsAnnotation.value().length == 0) {
throw new FixedFormatException(format("%s annotation must contain minimum one %s annotation", Fields.class.getName(), Field.class.getName()));
}
readFieldData(fixedFormatRecordClass, data, foundData, methodClass, method, methodName, fieldsAnnotation.value()[0]);
}
}
//将所有找到的切片string做循环
Set<String> keys = foundData.keySet();
for (String key : keys) {
String setterMethodName = "set" + key;
Object foundDataObj = foundData.get(key);
if (foundDataObj != null) {
Class datatype = methodClass.get(key);
Method method;
try {
method = fixedFormatRecordClass.getMethod(setterMethodName, datatype);
} catch (NoSuchMethodException e) {
throw new FixedFormatException(format("setter method named %s.%s(%s) does not exist", fixedFormatRecordClass.getName(), setterMethodName, datatype));
}
try {
//反射设置属性
method.invoke(instance, foundData.get(key));
} catch (Exception e) {
throw new FixedFormatException(format("could not invoke method %s.%s(%s)", fixedFormatRecordClass.getName(), setterMethodName, datatype), e);
}
}
}
return instance;
}
/**
* 这里使用到了递归export中for循环调用exportDataAccordingFieldAnnotation
* @inheritDoc
*/
public <T> String export(String template, T fixedFormatRecord) {
StringBuffer result = new StringBuffer(template);
Record record = getAndAssertRecordAnnotation(fixedFormatRecord.getClass());
Class fixedFormatRecordClass = fixedFormatRecord.getClass();
HashMap<Integer, String> foundData = new HashMap<Integer, String>();
//通过传入的类,反向解析offset,realvalue这些数据,将数据填充进foundData这个offset做key的集合
Method[] allMethods = fixedFormatRecordClass.getMethods();
for (Method method : allMethods) {
Field fieldAnnotation = method.getAnnotation(Field.class);
Fields fieldsAnnotation = method.getAnnotation(Fields.class);
if (fieldAnnotation != null) {
String exportedData = exportDataAccordingFieldAnnotation(fixedFormatRecord, method, fieldAnnotation);
foundData.put(fieldAnnotation.offset(), exportedData);
} else if (fieldsAnnotation != null) {
Field[] fields = fieldsAnnotation.value();
for (Field field : fields) {
String exportedData = exportDataAccordingFieldAnnotation(fixedFormatRecord, method, field);
foundData.put(field.offset(), exportedData);
}
}
}
//读取foundData,将map转化为最终返回的文本
Set<Integer> sortedoffsets = foundData.keySet();
for (Integer offset : sortedoffsets) {
String data = foundData.get(offset);
appendData(result, record.paddingChar(), offset, data);
}
if (record.length() != -1) { //pad with paddingchar
while (result.length() < record.length()) {
result.append(record.paddingChar());
}
}
return result.toString();
}
/*
**单个feild 的读取
*/
@SuppressWarnings({"unchecked"})
private <T> String exportDataAccordingFieldAnnotation(T fixedFormatRecord, Method method, Field fieldAnno) {
String result;
Class datatype = getDatatype(method, fieldAnno);
FormatContext<T> context = getFormatContext(datatype, fieldAnno);
FixedFormatter formatter = getFixedFormatterInstance(context.getFormatter(), context);
FormatInstructions formatdata = getFormatInstructions(method, fieldAnno);
Object valueObject;
try {
valueObject = method.invoke(fixedFormatRecord);
} catch (Exception e) {
throw new FixedFormatException(format("could not invoke method %s.%s(%s)", fixedFormatRecord.getClass().getName(), method.getName(), datatype), e);
}
//recursivly follow if the valueObject is annotated as a record
if (valueObject != null && valueObject.getClass().getAnnotation(Record.class) != null) {
result = export(valueObject);
} else {
result = formatter.format(valueObject, formatdata);
}
if (LOG.isDebugEnabled()) {
LOG.debug(format("exported %s ", result));
}
return result;
}
- 巧妙的设计思路
①定义注解,功能性和使用性的巧妙设计
②多重继承,值得学习
③主类FixedFormatManagerImpl的各个方法的设计
④广泛的使用泛型
⑤提供拓展的接口,目前只支持上述一些类型。像是java8的datetime这些目前好像还不支持,可以方便大家去扩充。毕竟这个工具项目已经有10多年历史了。
工具使用
public class TestFixedFormatManagerImpl extends TestCase {
private static final Log LOG = LogFactory.getLog(TestFixedFormatManagerImpl.class);
private static String STR = "some text ";
public static final String MY_RECORD_DATA = "some text 0012320080514CT001100000010350000002056-0012 01200000002056";
public static final String MULTIBLE_RECORD_DATA = "some 2008101320081013 0100";
public static final String MULTIBLE_RECORD_DATA_X_PADDED = "some 2008101320081013xxxxxxxxxxxxxxxxxxxxxxx0100";
FixedFormatManager manager = null;
@Override
protected void setUp() throws Exception {
super.setUp();
manager = new FixedFormatManagerImpl();
}
public void testLoadRecord() {
MyRecord loadedRecord = manager.load(MyRecord.class, MY_RECORD_DATA);
Assert.assertNotNull(loadedRecord);
Assert.assertEquals(STR, loadedRecord.getStringData());
Assert.assertTrue(loadedRecord.isBooleanData());
}
public void testLoadMultibleFieldsRecord() {
//when reading data having multible field annotations the first field will decide what data to return
Calendar someDay = Calendar.getInstance();
someDay.set(2008, 9, 13, 0, 0, 0);
someDay.set(Calendar.MILLISECOND, 0);
MultibleFieldsRecord loadedRecord = manager.load(MultibleFieldsRecord.class, MULTIBLE_RECORD_DATA);
Assert.assertNotNull(loadedRecord);
Assert.assertEquals("some ", loadedRecord.getStringData());
Assert.assertEquals(someDay.getTime(), loadedRecord.getDateData());
}
public void testExportRecordObject() {
MyRecord myRecord = createMyRecord();
Assert.assertEquals("wrong record exported", MY_RECORD_DATA, manager.export(myRecord));
}
public void testExportNestedRecordObject() {
MyRecord myRecord = createMyRecord();
MyOtherRecord myOtherRecord = new MyOtherRecord(myRecord);
Assert.assertEquals("wrong record exported", MY_RECORD_DATA, manager.export(myOtherRecord));
myOtherRecord = new MyOtherRecord((MyRecord) null);
Assert.assertEquals("wrong record exported", "", manager.export(myOtherRecord));
}
private MyRecord createMyRecord() {
Calendar someDay = Calendar.getInstance();
someDay.set(2008, 4, 14, 0, 0, 0);
someDay.set(Calendar.MILLISECOND, 0);
MyRecord myRecord = new MyRecord();
myRecord.setBooleanData(true);
myRecord.setCharData('C');
myRecord.setDateData(someDay.getTime());
myRecord.setDoubleData(10.35);
myRecord.setFloatData(20.56F);
myRecord.setLongData(11L);
myRecord.setIntegerData(123);
myRecord.setStringData("some text ");
myRecord.setBigDecimalData(new BigDecimal(-12.012));
myRecord.setSimpleFloatData(20.56F);
return myRecord;
}
public void testExportMultibleFieldRecordObject() {
Calendar someDay = Calendar.getInstance();
someDay.set(2008, 9, 13, 0, 0, 0);
someDay.set(Calendar.MILLISECOND, 0);
MultibleFieldsRecord multibleFieldsRecord = new MultibleFieldsRecord();
multibleFieldsRecord.setDateData(someDay.getTime());
multibleFieldsRecord.setStringData("some ");
multibleFieldsRecord.setIntegerdata(100);
manager.export(multibleFieldsRecord);
Assert.assertEquals("wrong record exported", MULTIBLE_RECORD_DATA, manager.export(multibleFieldsRecord));
}
public void testExportIntoExistingString() {
Calendar someDay = Calendar.getInstance();
someDay.set(2008, 9, 13, 0, 0, 0);
someDay.set(Calendar.MILLISECOND, 0);
MultibleFieldsRecord multibleFieldsRecord = new MultibleFieldsRecord();
multibleFieldsRecord.setDateData(someDay.getTime());
multibleFieldsRecord.setStringData("some ");
multibleFieldsRecord.setIntegerdata(100);
String exportedString = manager.export("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", multibleFieldsRecord);
Assert.assertEquals("wrong record exported", MULTIBLE_RECORD_DATA_X_PADDED, exportedString);
}
public void testLoadNonRecordAnnotatedClass() {
try {
manager.load(String.class, "some");
} catch (FixedFormatException e) {
//expected
}
}
public void testExportAnnotatedNestedClass() {
MyRecord.MyStaticNestedClass myStaticNestedClass = new MyRecord.MyStaticNestedClass();
myStaticNestedClass.setStringData("xyz");
String exportedString = manager.export(myStaticNestedClass);
Assert.assertEquals("xyz ", exportedString);
NoDefaultConstructorClass.MyStaticNestedClass myStaticNestedClass2 = new NoDefaultConstructorClass.MyStaticNestedClass();
myStaticNestedClass2.setStringData("xyz");
String exportedString2 = manager.export(myStaticNestedClass2);
Assert.assertEquals("xyz ", exportedString2);
}
public void testExportAnnotatedInnerClass() {
MyRecord myRecord = new MyRecord();
MyRecord.MyInnerClass myInnerClass = myRecord.new MyInnerClass();
myInnerClass.setStringData("xyz");
String exportedString = manager.export(myInnerClass);
Assert.assertEquals("xyz ", exportedString);
NoDefaultConstructorClass noDefaultConstructorClass = new NoDefaultConstructorClass("foobar");
NoDefaultConstructorClass.MyInnerClass myInnerClass2 = noDefaultConstructorClass.new MyInnerClass();
myInnerClass2.setStringData("xyz");
exportedString = manager.export(myInnerClass2);
Assert.assertEquals("xyz ", exportedString);
}
public void testImportAnnotatedNestedClass() {
MyRecord.MyStaticNestedClass staticNested = manager.load(MyRecord.MyStaticNestedClass.class, "xyz ");
Assert.assertEquals("xyz", staticNested.getStringData());
NoDefaultConstructorClass.MyStaticNestedClass staticNested2 = manager.load(NoDefaultConstructorClass.MyStaticNestedClass.class, "xyz ");
Assert.assertEquals("xyz", staticNested2.getStringData());
}
public void testImportAnnotatedInnerClass() {
MyRecord.MyInnerClass inner = manager.load(MyRecord.MyInnerClass.class, "xyz ");
Assert.assertEquals("xyz", inner.getStringData());
try {
manager.load(NoDefaultConstructorClass.MyInnerClass.class, "xyz ");
fail(String.format("expected an %s exception to be thrown", FixedFormatException.class.getName()));
} catch (FixedFormatException e) {
//expected this
}
}
public void testParseFail() {
try {
manager.load(MyRecord.class, "foobarfoobarfoobarfoobar");
fail("expected parse exception");
} catch (ParseException e) {
if (LOG.isDebugEnabled()) {
LOG.debug("bu", e);
}
//expected
}
}
}
- 如果你只是着急用的话,可以直接读官网上面的样例用完之后,你应该就懂了。
- 如果你需要做一些BigDecial四舍五入,Double带有正负数这些,我建议你下载github的项目,项目中test有大量的测试用例,当然如果你需要拓展,建议去看看用例,方便知道如何在内部进行测试。