Formatting string content xtext 2.14 - java

Given a grammar (simplified version below) where I can enter arbitrary text in a section of the grammar, is it possible to format the content of the arbitrary text? I understand how to format the position of the arbitrary text in relation to the rest of the grammar, but not whether it is possible to format the content string itself?
Sample grammar
Model:
'content' content=RT
terminal RT: // (returns ecore::EString:)
'RT>>' -> '<<RT';
Sample content
content RT>>
# Some sample arbitrary text
which I would like to format
<<RT

you can add custom ITextReplacer to the region of the string.
assuming you have a grammar like
Model:
greetings+=Greeting*;
Greeting:
'Hello' name=STRING '!';
you can do something like the follow in the formatter
def dispatch void format(Greeting model, extension IFormattableDocument document) {
model.prepend[newLine]
val region = model.regionFor.feature(MyDslPackage.Literals.GREETING__NAME)
val r = new AbstractTextReplacer(document, region) {
override createReplacements(ITextReplacerContext it) {
val text = region.text
var int index = text.indexOf(SPACE);
val offset = region.offset
while (index >=0){
it.addReplacement(region.textRegionAccess.rewriter.createReplacement(offset+index, SPACE.length, "\n"))
index = text.indexOf(SPACE, index+SPACE.length()) ;
}
it
}
}
addReplacer(r)
}
this will turn this model
Hello "A B C"!
into
Hello "A
B
C"!
of course you need to come up with a more sophisticated formatter logic.
see How to define different indentation levels in the same document with Xtext formatter too

Related

How can I create a Locale with a specific script code?

I'm trying to convert this String az_AZ_#Latn, found here, to a Locale but I'm unable to parse the #Latn part.
If I do new Locale("az_AZ_#Latn") I lose the #Latn part (the Script code).
I've tried as well using the LocaleUtils from commons-lang but I get an error saying that it's an invalid format.
As written in the docs:
It is not possible to set a script code on a Locale object in a
release earlier than JDK 7.
But you can use the Locale builder to make it like this:
Locale locale = new Locale.Builder().setLanguage("az").setRegion("AZ").setScript("Latn").build();
You can get the Script it by calling locale.getScript()
Edit:
Here's a method I made for converting a string into a locale (doesn't work for extensions):
public static Locale stringToLocale(String locale){
if(locale == null || locale.isEmpty()) return null;
String[] parts = locale.split("_");
if(parts.length == 1) return new Locale(parts[0]);
if(parts.length == 2) return new Locale(parts[0],parts[1]);
if(parts.length == 3)
if(parts[2].charAt(0) != '#') return new Locale(parts[0],parts[1],parts[2]);
else return new Locale.Builder().setLanguage(parts[0]).setRegion(parts[1]).setScript(parts[2].substring(1)).build();
if(parts.length == 4) return new Locale.Builder().setLanguage(parts[0]).setRegion(parts[1]).setVariant(parts[2]).setScript(parts[3].charAt(0)=='#'? parts[3].substring(1):null).build();
return null;
}
//works for the toString output expect for extensions. test: for(Locale l: Locale.getAvailableLocales()) System.out.println(l.equals(stringToLocale(l.toString())));
// output : true true true...
usage:
Locale l = stringToLocale("az_AZ_#Latn");
Locale.Builder is able to handle script information for locales. The documentation of the Builder class also includes this example code:
Locale aLocale = new Locale.Builder().setLanguage("sr")
.setScript("Latn")
.setRegion("RS")
.build();
By using the builder you would have to do the splitting of the string yourself and also removal of any unsupported characters like #.
Using the 3-arg contructor java.util.Locale.Locale(String, String, String) is not correct since you probably don't intend to specify a variant using Latn but a script instead.
If the format is consistent (your input looks always the same) you can split by # and by _ and get the parts.
See the following example:
var input = "az_AZ#Latn"
var lns = input.split("#")
var l = lns[0].split("_")
var locale = new Locale.Builder()
.setLanguage(l[0])
.setRegion(l[1])
.setScript(lns[1])
.build()
locale.getLanguage() // ==> "az"
locale.getCountry() // ==> "AZ"
locale.getScript() //==> "Latn"
That #Latn refers to the script which in this case is Latin.
From java documentation:
script
ISO 15924 alpha-4 script code. You can find a full list of valid script codes in the IANA Language Subtag Registry (search for "Type: script"). The script field is case insensitive, but Locale always canonicalizes to title case (the first letter is upper case and the rest of the letters are lower case).
Well-formed script values have the form [a-zA-Z]{4}
Example: "Latn" (Latin), "Cyrl" (Cyrillic)
If you want to create a Locale using the script you can use its builder.
For instance:
Locale locale = new Locale.Builder()
.setLanguage("az")
.setRegion("AZ")
.setScript("Latn")
.build();

Building XML document in lowercase or TitleCase - flag based

In my current project, we are in the process of re-factoring a java class that constructs an XML document. In previous versions of the product delivered to the customer, the XML document is built with lower case elements and attributes:
<rootElement attr = "abc">
<childElement childAttr = "xyz"/>
</rootElement>
But now we have a requirement to build the XML document with TitleCase element and attributes. The user will set a flag in a properties file to indicate whether the document should be built in lower case or title case. If the flag is configured to build the document in TitleCase, the resultant document will look like:
<RootElement Attr = "abc">
<ChildElement ChildAttr = "xyz">
</RootElement>
Various approaches to solve the problem:
1. Plugging in a transformer to convert lowercase XML document to TitleCase XML document. But this will impact the overall performance, as we deal with huge XML files spanning more than 10,000 lines.
2. Create two separate maps with corr. XML elements and attributes.
For eg:
lowercase map: rootelement -> rootElement, attr -> attr ....
TitelCase map: rootlement -> RootElement, attr -> Attr ....
Based on the property set by the user, the corr. map will be chosen and XML element/attributes from this map will be used to build the XML document.
3. Using enum to define constants and its corr. values.
public enum XMLConstants {
ROOTELEMENT("rootElement", "RootElement"),
ATTRIBUTE("attr", "Attr");
private String lowerCase;
private String titleCase;
private XMLConstants(String aLowerCase, String aTitleCase){
titleCase = aTitleCase;
lowerCase = aLowerCase;
}
public String getValue(boolean isLowerCase){
if(isLowerCase){
return lowerCase;
} else {
return titleCase;
}
}
}
--------------------------------------------------------------
// XML document builder
if(propertyFlag){
isLowerCase = false;
} else {
isLowerCase = true;
}
....
....
createRootElement(ROOTELEMENT.getValue(isLowerCase));
createAttribute(ATTRIBUTE.getValue(isLowerCase));
Please help me choose the right option keeping in mind the performance aspect of the entire solution. If you have any other suggestions, please let me know.
// set before generate XML
boolean isUpperCase;
// use function for each tag/attribute name instead of string constant
// smth. like getInCase("rootElement")
String getInCase(String initialName) {
String intialFirstCharacter = initialName.substring(0, 1);
String actualFirstCharacter;
if (isUpperCase) {
actualFirstCharacter = intialFirstCharacter.toUpperCase();
} else {
actualFirstCharacter = intialFirstCharacter.toLowerCase();
}
return actualFirstCharacter + initialName.substring(1);
}

How to write data into a .SYLK File Or Convert SLK File data

I have a code that contains a function taking columns and rows and exporting it to an SLK file. It uses a specific style-sheet ( for example dates are shown in a certain way, and prices in another one). However, the file's representation has been written using a long string and is undocumented. I need to write the same function using java and wanted to know if there is a better way for re-writing this code , for instance using apache HSSFWorkbook class.
However , i don't know what my current string representation means.
So my questions are:
Is there other open source classes that i can use writing this code
Is there any site that can show me what each of this string represents ( which style ), so i can initilize the write one using HSSFWorkbook class
Thanks Ido
my current function starts like this and doesn't look good ( it's in javascript and I need to re-write it in java):
function exportToSlk(columns, rows,filename, $a) {
var formatMaps = {
"$#,###.##":"F;P34;F$2G;",
"$#0.00":"F;P34;F$2G;",
"$#0.000" : "F;P34;F$2G;",
"#,###":"F;P39;FI0G;",
"%#0.00":"F;P14;F%2G;",
"date": "F;P19;FG0G;"
};
var header = 'ID;PWXL;N;E\nO;L\nP;PGeneral\nP;P0\nP;P0.00\nP;P#,##0\nP;P#,##0.00\nP;P#,##0_);;\\(#,##0\\)\nP;P#,##0_);;[Red]\\(#,##0\\)\nP;P#,##0.00_);;\\(#,##0.00\\)\nP;P#,##0.00_);;[Red]\\(#,##0.00\\)\nP;P"$"#,##0_);;\\("$"#,##0\\)\nP;P"$"#,##0_);;[Red]\\("$"#,##0\\)\nP;P"$"#,##0.00_);;\\("$"#,##0.00\\)\nP;P"$"#,##0.00_);;[Red]\\("$"#,##0.00\\)\nP;P0%\nP;P0.00%\nP;P0.00E+00\nP;P##0.0E+0\nP;P#\\ ?/?\nP;P#\\ ??/??\nP;Pm/d/yyyy\nP;Pd\\-mmm\\-yy\nP;Pd\\-mmm\nP;Pmmm\\-yy\nP;Ph:mm\\ AM/PM\nP;Ph:mm:ss\\ AM/PM\nP;Ph:mm\nP;Ph:mm:ss\nP;Pm/d/yyyy\\ h:mm\nP;Pmm:ss\nP;Pmm:ss.0\nP;P#\nP;P[h]:mm:ss\nP;P_("$"* #,##0_);;_("$"* \\(#,##0\\);;_("$"* "-"_);;_(#_)\nP;P_(* #,##0_);;_(* \\(#,##0\\);;_(* "-"_);;_(#_)\nP;P_("$"* #,##0.00_);;_("$"* \\(#,##0.00\\);;_("$"* "-"??_);;_(#_)\nP;P_(* #,##0.00_);;_(* \\(#,##0.00\\);;_(* "-"??_);;_(#_)\nP;P_(* #,##0.000_);;_(* \\(#,##0.000\\);;_(* "-"??_);;_(#_)\nP;P_(* #,##0.0000_);;_(* \\(#,##0.0000\\);;_(* "-"??_);;_(#_)\nP;P_(* #,##0.0_);;_(* \\(#,##0.0\\);;_(* "-"??_);;_(#_)\nP;P_(* #,##0_);;_(* \\(#,##0\\);;_(* "-"??_);;_(#_)\nP;Pmmm\\-yyyy\nP;FCalibri;M220;L9\nP;FCalibri;M220;L9\nP;FCalibri;M220;L9\nP;FCalibri;M220;L9\nP;ECalibri;M220;L9\nP;ECambria;M360;SB;L57\nP;ECalibri;M300;SB;L57\nP;ECalibri;M260;SB;L57\nP;ECalibri;M220;SB;L57\nP;ECalibri;M220;L18\nP;ECalibri;M220;L21\nP;ECalibri;M220;L61\nP;ECalibri;M220;L63\nP;ECalibri;M220;SB;L64\nP;ECalibri;M220;SB;L53\nP;ECalibri;M220;L53\nP;ECalibri;M220;SB;L10\nP;ECalibri;M220;L11\nP;ECalibri;M220;SI;L24\nP;ECalibri;M220;SB;L9\nP;ECalibri;M220;L10\nP;ECalibri;M220;L9\nP;ECambria;M360;SB;L57\nP;ECalibri;M300;SB;L57\nP;ECalibri;M260;SB;L57\nP;ECalibri;M220;SB;L57\nP;ECalibri;M220;L18\nP;ECalibri;M220;L21\nP;ECalibri;M220;L61\nP;ECalibri;M220;L63\nP;ECalibri;M220;SB;L64\nP;ECalibri;M220;SB;L53\nP;ECalibri;M220;L53\nP;ECalibri;M220;SB;L10\nP;ECalibri;M220;L11\nP;ECalibri;M220;SI;L24\nP;ECalibri;M220;SB;L9\nP;ECalibri;M220;L10\nF;P0;DG0G8;M300\n';
var exported = header;
//now handle col width
for(var i=1; i<=columns.length; i++) {
exported += "F;W"+i+" "+i+" 15\n";
}
...
}

How to use formatted strings together with placeholders in Android?

In Android it is possible to use placeholders in strings, such as:
<string name="number">My number is %1$d</string>
and then in Java code (inside a subclass of Activity):
String res = getString(R.string.number);
String formatted = String.format(res, 5);
or even simpler:
String formatted = getString(R.string.number, 5);
It is also possible to use some HTML tags in Android string resources:
<string name="underline"><u>Underline</u> example</string>
Since the String itself cannot hold any information about formatting, one should use getText(int) instead of getString(int) method:
CharSequence formatted = getText(R.string.underline);
The returned CharSequence can be then passed to Android widgets, such as TextView, and the marked phrase will be underlined.
However, I could not find how to combine these two methodes, using formatted string together with placeholders:
<string name="underlined_number">My number is <u>%1$d</u></string>
How to process above resource in the Java code to display it in a TextView, substituting %1$d with an integer?
Finally I managed to find a working solution and wrote my own method for replacing placeholders, preserving formatting:
public static CharSequence getText(Context context, int id, Object... args) {
for(int i = 0; i < args.length; ++i)
args[i] = args[i] instanceof String? TextUtils.htmlEncode((String)args[i]) : args[i];
return Html.fromHtml(String.format(Html.toHtml(new SpannedString(context.getText(id))), args));
}
This approach does not require to escape HTML tags manually neither in a string being formatted nor in strings that replace placeholders.
Kotlin extension function that
works with all API versions
handles multiple arguments
Example usage
textView.text = context.getText(R.string.html_formatted, "Hello in bold")
HTML string resource wrapped in a CDATA section
<string name="html_formatted"><![CDATA[ bold text: <B>%1$s</B>]]></string>
Result
bold text: Hello in bold
Code
/**
* Create a formatted CharSequence from a string resource containing arguments and HTML formatting
*
* The string resource must be wrapped in a CDATA section so that the HTML formatting is conserved.
*
* Example of an HTML formatted string resource:
* <string name="html_formatted"><![CDATA[ bold text: <B>%1$s</B> ]]></string>
*/
fun Context.getText(#StringRes id: Int, vararg args: Any?): CharSequence =
HtmlCompat.fromHtml(String.format(getString(id), *args), HtmlCompat.FROM_HTML_MODE_COMPACT)
<resources>
<string name="welcome_messages">Hello, %1$s! You have <b>%2$d new messages</b>.</string>
</resources>
Resources res = getResources();
String text = String.format(res.getString(R.string.welcome_messages), username, mailCount);
CharSequence styledText = Html.fromHtml(text);
More Infos here: http://developer.android.com/guide/topics/resources/string-resource.html
For the simple case where you want to replace a placeholder without number formatting (i.e. leading zeros, numbers after comma) you can use Square Phrase library.
The usage is very simple: first you have to change the placeholders in your string resource to this simpler format:
<string name="underlined_number">My number is <u> {number} </u></string>
then you can make the replacement like this:
CharSequence formatted = Phrase.from(getResources(), R.string.underlined_number)
.put("number", 5)
.format()
The formatted CharSequence is also styled. If you need to format your numbers, you can always pre-format them using String.format("%03d", 5) and then use the resulting string in the .put() function.
This is the code that finally worked for me
strings.xml
<string name="launch_awaiting_instructions">Contact <b>our</b> team on %1$s to activate.</string>
<string name="support_contact_phone_number"><b>555 555 555</b> Opt <b>3</b></string>
Kotlin code
fun Spanned.toHtmlWithoutParagraphs(): String {
return HtmlCompat.toHtml(this, HtmlCompat.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE)
.substringAfter("<p dir=\"ltr\">").substringBeforeLast("</p>")
}
fun Resources.getText(#StringRes id: Int, vararg args: Any): CharSequence {
val escapedArgs = args.map {
if (it is Spanned) it.toHtmlWithoutParagraphs() else it
}.toTypedArray()
val resource = SpannedString(getText(id))
val htmlResource = resource.toHtmlWithoutParagraphs()
val formattedHtml = String.format(htmlResource, *escapedArgs)
return HtmlCompat.fromHtml(formattedHtml, HtmlCompat.FROM_HTML_MODE_LEGACY)
}
Using this I was able to render styled text on Android with styled placeholders too
Output
Contact our team on 555 555 555 Opt 3 to activate.
I was then able to expand on this solution to create the following Compose methods.
Jetpack Compose UI
#Composable
fun annotatedStringResource(#StringRes id: Int, vararg formatArgs: Any): AnnotatedString {
val resources = LocalContext.current.resources
return remember(id) {
val text = resources.getText(id, *formatArgs)
spannableStringToAnnotatedString(text)
}
}
#Composable
fun annotatedStringResource(#StringRes id: Int): AnnotatedString {
val resources = LocalContext.current.resources
return remember(id) {
val text = resources.getText(id)
spannableStringToAnnotatedString(text)
}
}
private fun spannableStringToAnnotatedString(text: CharSequence): AnnotatedString {
return if (text is Spanned) {
val spanStyles = mutableListOf<AnnotatedString.Range<SpanStyle>>()
spanStyles.addAll(text.getSpans(0, text.length, UnderlineSpan::class.java).map {
AnnotatedString.Range(
SpanStyle(textDecoration = TextDecoration.Underline),
text.getSpanStart(it),
text.getSpanEnd(it)
)
})
spanStyles.addAll(text.getSpans(0, text.length, StyleSpan::class.java).map {
AnnotatedString.Range(
SpanStyle(fontWeight = FontWeight.Bold),
text.getSpanStart(it),
text.getSpanEnd(it)
)
})
AnnotatedString(text.toString(), spanStyles = spanStyles)
} else {
AnnotatedString(text.toString())
}
}
Similar to the accepted answer, I attempted to write a Kotlin extension method for this.
Here's the accepted answer in Kotlin
#Suppress("DEPRECATION")
fun Context.getText(id: Int, vararg args: Any): CharSequence {
val escapedArgs = args.map {
if (it is String) TextUtils.htmlEncode(it) else it
}.toTypedArray()
return Html.fromHtml(String.format(Html.toHtml(SpannedString(getText(id))), *escapedArgs))
}
The problem with the accepted answer, is that it doesn't seem to work when the format arguments themselves are styled (i.e. Spanned, not String). By experiment, it seems to do weird things, possibly to do with the fact that we're not escaping non-String CharSequences. I'm seeing that if I call
context.getText(R.id.my_format_string, myHelloSpanned)
where R.id.my_format_string is:
<string name="my_format_string">===%1$s===</string>
and myHelloSpanned is a Spanned that looks like <b>hello</b> (i.e. it would have HTML <i><b>hello</b></i>) then I get ===hello=== (i.e. HTML ===<b>hello</b>===).
That is wrong, I should get ===<b>hello</b>===.
I tried to fix this by converting all CharSequences to HTML before applying String.format, and here is my resulting code.
#Suppress("DEPRECATION")
fun Context.getText(#StringRes resId: Int, vararg formatArgs: Any): CharSequence {
// First, convert any styled Spanned back to HTML strings before applying String.format. This
// converts the styling to HTML and also does HTML escaping.
// For other CharSequences, just do HTML escaping.
// (Leave any other args alone.)
val htmlFormatArgs = formatArgs.map {
if (it is Spanned) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Html.toHtml(it, Html.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE)
} else {
Html.toHtml(it)
}
} else if (it is CharSequence) {
Html.escapeHtml(it)
} else {
it
}
}.toTypedArray()
// Next, get the format string, and do the same to that.
val formatString = getText(resId);
val htmlFormatString = if (formatString is Spanned) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Html.toHtml(formatString, Html.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE)
} else {
Html.toHtml(formatString)
}
} else {
Html.escapeHtml(formatString)
}
// Now apply the String.format
val htmlResultString = String.format(htmlFormatString, *htmlFormatArgs)
// Convert back to a CharSequence, recovering any of the HTML styling.
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Html.fromHtml(htmlResultString, Html.FROM_HTML_MODE_LEGACY)
} else {
Html.fromHtml(htmlResultString)
}
}
However, this didn't quite work because when you call Html.toHtml it puts <p> tags around everything even when that extra padding wasn't in the input. Said another way, Html.fromHtml(Html.toHtml(myHelloSpanned)) is not equal to myHelloSpanned - it's got extra padding. I didn't know how to resolve this nicely.
Update: this answer https://stackoverflow.com/a/56944152/6007104 has been updated, and is now the preferred answer
Here's a more readable Kotlin extension which doesn't use deprecated APIs, works on all Android versions, and doesn't require strings to be wrapped in CDATA sections:
fun Context.getText(id: Int, vararg args: Any): CharSequence {
val escapedArgs = args.map {
if (it is String) TextUtils.htmlEncode(it) else it
}.toTypedArray()
val resource = SpannedString(getText(id))
val htmlResource = HtmlCompat.toHtml(resource, HtmlCompat.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE)
val formattedHtml = String.format(htmlResource, *escapedArgs)
return HtmlCompat.fromHtml(formattedHtml, HtmlCompat.FROM_HTML_MODE_LEGACY)
}
You can add an alias as as extension of Fragment - just remember to spread the args in between:
fun Fragment.getText(id: Int, vararg args: Any) = requireContext().getText(id, *args)
You can use java.lang.String for string formatting in Kotlin
fun main(args : Array<String>) {
var value1 = 1
var value2 = "2"
var value3 = 3.0
println(java.lang.String.format("%d, %s, %6f", value1, value2, value3))
}

Is there an smart way to write a fixed length flat file?

Is there any framework/library to help writing fixed length flat files in java?
I want to write a collection of beans/entities into a flat file without worrying with convertions, padding, alignment, fillers, etcs
For example, I'd like to parse a bean like:
public class Entity{
String name = "name"; // length = 10; align left; fill with spaces
Integer id = 123; // length = 5; align left; fill with spaces
Integer serial = 321 // length = 5; align to right; fill with '0'
Date register = new Date();// length = 8; convert to yyyyMMdd
}
... into ...
name 123 0032120110505
mikhas 5000 0122120110504
superuser 1 0000120101231
...
You're not likely to encounter a framework that can cope with a "Legacy" system's format. In most cases, Legacy systems don't use standard formats, but frameworks expect them. As a maintainer of legacy COBOL systems and Java/Groovy convert, I encounter this mismatch frequently. "Worrying with conversions, padding, alignment, fillers, etcs" is primarily what you do when dealing with a legacy system. Of course, you can encapsulate some of it away into handy helpers. But most likely, you'll need to get real familiar with java.util.Formatter.
For example, you might use the Decorator pattern to create decorators to do the conversion. Below is a bit of groovy (easily convertible into Java):
class Entity{
String name = "name"; // length = 10; align left; fill with spaces
Integer id = 123; // length = 5; align left; fill with spaces
Integer serial = 321 // length = 5; align to right; fill with '0'
Date register = new Date();// length = 8; convert to yyyyMMdd
}
class EntityLegacyDecorator {
Entity d
EntityLegacyDecorator(Entity d) { this.d = d }
String asRecord() {
return String.format('%-10s%-5d%05d%tY%<tm%<td',
d.name,d.id,d.serial,d.register)
}
}
def e = new Entity(name: 'name', id: 123, serial: 321, register: new Date('2011/05/06'))
assert new EntityLegacyDecorator(e).asRecord() == 'name 123 0032120110506'
This is workable if you don't have too many of these and the objects aren't too complex. But pretty quickly the format string gets intolerable. Then you might want decorators for Date, like:
class DateYMD {
Date d
DateYMD(d) { this.d = d }
String toString() { return d.format('yyyyMMdd') }
}
so you can format with %s:
String asRecord() {
return String.format('%-10s%-5d%05d%s',
d.name,d.id,d.serial,new DateYMD(d.register))
}
But for significant number of bean properties, the string is still too gross, so you want something that understands columns and lengths that looks like the COBOL spec you were handed, so you'll write something like this:
class RecordBuilder {
final StringBuilder record
RecordBuilder(recordSize) {
record = new StringBuilder(recordSize)
record.setLength(recordSize)
}
def setField(pos,length,String s) {
record.replace(pos - 1, pos + length, s.padRight(length))
}
def setField(pos,length,Date d) {
setField(pos,length, new DateYMD(d).toString())
}
def setField(pos,length, Integer i, boolean padded) {
if (padded)
setField(pos,length, String.format("%0" + length + "d",i))
else
setField(pos,length, String.format("%-" + length + "d",i))
}
String toString() { record.toString() }
}
class EntityLegacyDecorator {
Entity d
EntityLegacyDecorator(Entity d) { this.d = d }
String asRecord() {
RecordBuilder record = new RecordBuilder(28)
record.setField(1,10,d.name)
record.setField(11,5,d.id,false)
record.setField(16,5,d.serial,true)
record.setField(21,8,d.register)
return record.toString()
}
}
After you've written enough setField() methods to handle you legacy system, you'll briefly consider posting it on GitHub as a "framework" so the next poor sap doesn't have to to it again. But then you'll consider all the ridiculous ways you've seen COBOL store a "date" (MMDDYY, YYMMDD, YYDDD, YYYYDDD) and numerics (assumed decimal, explicit decimal, sign as trailing separate or sign as leading floating character). Then you'll realize why nobody has produced a good framework for this and occasionally post bits of your production code into SO as an example... ;)
If you are still looking for a framework, check out BeanIO at http://www.beanio.org
uniVocity-parsers goes a long way to support tricky fixed-width formats, including lines with different fields, paddings, etc.
Check out this example to write imaginary client & accounts details. This uses a lookahead value to identify which format to use when writing a row:
FixedWidthFields accountFields = new FixedWidthFields();
accountFields.addField("ID", 10); //account ID has length of 10
accountFields.addField("Bank", 8); //bank name has length of 8
accountFields.addField("AccountNumber", 15); //etc
accountFields.addField("Swift", 12);
//Format for clients' records
FixedWidthFields clientFields = new FixedWidthFields();
clientFields.addField("Lookahead", 5); //clients have their lookahead in a separate column
clientFields.addField("ClientID", 15, FieldAlignment.RIGHT, '0'); //let's pad client ID's with leading zeroes.
clientFields.addField("Name", 20);
FixedWidthWriterSettings settings = new FixedWidthWriterSettings();
settings.getFormat().setLineSeparator("\n");
settings.getFormat().setPadding('_');
//If a record starts with C#, it's a client record, so we associate "C#" with the client format.
settings.addFormatForLookahead("C#", clientFields);
//Rows starting with #A should be written using the account format
settings.addFormatForLookahead("A#", accountFields);
StringWriter out = new StringWriter();
//Let's write
FixedWidthWriter writer = new FixedWidthWriter(out, settings);
writer.writeRow(new Object[]{"C#",23234, "Miss Foo"});
writer.writeRow(new Object[]{"A#23234", "HSBC", "123433-000", "HSBCAUS"});
writer.writeRow(new Object[]{"A#234", "HSBC", "222343-130", "HSBCCAD"});
writer.writeRow(new Object[]{"C#",322, "Mr Bar"});
writer.writeRow(new Object[]{"A#1234", "CITI", "213343-130", "CITICAD"});
writer.close();
System.out.println(out.toString());
The output will be:
C#___000000000023234Miss Foo____________
A#23234___HSBC____123433-000_____HSBCAUS_____
A#234_____HSBC____222343-130_____HSBCCAD_____
C#___000000000000322Mr Bar______________
A#1234____CITI____213343-130_____CITICAD_____
This is just a rough example. There are many other options available, including support for annotated java beans, which you can find here.
Disclosure: I'm the author of this library, it's open-source and free (Apache 2.0 License)
The library Fixedformat4j is a pretty neat tool to do exactly this: http://fixedformat4j.ancientprogramming.com/
Spring Batch has a FlatFileItemWriter, but that won't help you unless you use the whole Spring Batch API.
But apart from that, I'd say you just need a library that makes writing to files easy (unless you want to write the whole IO code yourself).
Two that come to mind are:
Guava
Files.write(stringData, file, Charsets.UTF_8);
Commons / IO
FileUtils.writeStringToFile(file, stringData, "UTF-8");
Don't know of any frame work but you can just use RandomAccessFile. You can position the file pointer to anywhere in the file to do your reads and writes.
I've just find a nice library that I'm using:
http://sourceforge.net/apps/trac/ffpojo/wiki
Very simple to configurate with XML or annotations!
A simple way to write beans/entities to a flat file is to use ObjectOutputStream.
public static void writeToFile(File file, Serializable object) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(object);
oos.close();
}
You can write to a fixed length flat file with
FileUtils.writeByteArrayToFile(new File(filename), new byte[length]);
You need to be more specific about what you want to do with the file. ;)
Try FFPOJO API as it has everything which you need to create a flat file with fixed lengths and also it will convert a file to an object and vice versa.
#PositionalRecord
public class CFTimeStamp {
String timeStamp;
public CFTimeStamp(String timeStamp) {
this.timeStamp = timeStamp;
}
#PositionalField(initialPosition = 1, finalPosition = 26, paddingAlign = PaddingAlign.RIGHT, paddingCharacter = '0')
public String getTimeStamp() {
return timeStamp;
}
#Override
public String toString() {
try {
FFPojoHelper ffPojo = FFPojoHelper.getInstance();
return ffPojo.parseToText(this);
} catch (FFPojoException ex) {
trsLogger.error(ex.getMessage(), ex);
}
return null;
}
}

Categories

Resources