1 package net.sf.cobol2j;
2
3 import org.apache.commons.logging.Log;
4 import org.apache.commons.logging.LogFactory;
5
6 import java.io.IOException;
7 import java.io.InputStream;
8
9 import java.math.BigDecimal;
10 import java.math.BigInteger;
11
12 import java.nio.ByteBuffer;
13 import java.nio.ByteOrder;
14
15 import java.util.ArrayList;
16 import java.util.HashMap;
17 import java.util.Iterator;
18 import java.util.List;
19 import java.util.Map;
20
21
22 public class RecordSet {
23 private static Log log = LogFactory.getLog(RecordSet.class);
24 private InputStream stream;
25 private long bytesProcessed = 0L;
26 private String charset;
27 private FileFormat fileFormat;
28 private long recNr = 0L;
29
30 public RecordSet(InputStream is, FileFormat ff) {
31 stream = is;
32 fileFormat = ff;
33 charset = ff.getConversionTable();
34 }
35
36 public void setInputStream(InputStream byteStream) {
37 stream = byteStream;
38 }
39
40 public void setFileFormat(FileFormat fFormat) {
41 fileFormat = fFormat;
42 }
43
44 public boolean hasNext() throws IOException {
45 return (stream.available() == 0) ? false : true;
46 }
47
48 /***
49 * @return List of next record's fields values. Elements may contain String, BigDecimal, BigInteger, Double or Float.
50 * @throws IOException
51 * @throws FileFormatException
52 */
53 public List next()
54 throws IOException, FileFormatException, RecordParseException {
55 String dfv = "";
56 RecordFormat rf;
57 ArrayList fields = new ArrayList();
58 int dfsz = fileFormat.getDistinguishFieldSize().intValue();
59 Map recDef = new RecordsMap(fileFormat);
60
61 if (dfsz < 0) {
62 throw new FileFormatException(
63 "Negative distinguish field size is invalid.");
64 }
65
66 recNr++;
67
68 if (dfsz > 0) {
69 try {
70 dfv = readText(dfsz);
71 } catch (FieldParseException ex) {
72 log.error("Total bytes processed before error: " +
73 bytesProcessed);
74 throw new RecordParseException(
75 "Unexpected EOF when reading distinguished field of record nr: " +
76 recNr + ".", null, fields, ex);
77 }
78
79 rf = (RecordFormat) recDef.get(dfv);
80 } else {
81 rf = (RecordFormat) recDef.get("0");
82 }
83
84 if (rf == null) {
85 throw new FileFormatException("No such record format : " + dfv);
86 }
87
88 try {
89 getFieldsValues(rf.getFieldFormatOrFieldsGroup(), fields, dfv);
90 } catch (FieldParseException ex) {
91 String printable = new String(ex.getOryginalData()).replaceAll("//p{Cntrl}",
92 ".");
93 FieldFormat ff = ex.getFieldFormat();
94 log.error("Cannot parse field: " + ff.getName() + ". Data: '" +
95 printable + "', Picture: " + ff.getPicture() + ", Type: " +
96 ff.getType() + ", Size: " + ff.getSize());
97 log.error("Total bytes processed before error: " + bytesProcessed);
98
99 String msg;
100
101 if (ex.getOryginalData().length == ff.getSize().intValue()) {
102 msg = "Couldn't parse record nr: ";
103 } else {
104 msg = "Unexpected EOF while reading record nr: ";
105 }
106
107 throw new RecordParseException(msg + recNr + ".",
108 rf.getFieldFormatOrFieldsGroup(), fields, ex);
109 }
110
111
112 try {
113 readText(fileFormat.getNewLineSize().intValue());
114 } catch (FieldParseException ex) {
115 log.error("Total bytes processed before error: " + bytesProcessed);
116 throw new RecordParseException(
117 "Unexpected EOF while reading new line separator of record nr: " +
118 recNr + ".", rf.getFieldFormatOrFieldsGroup(), fields, ex);
119 }
120
121 return fields;
122 }
123
124 private List getFieldsValues(List fieldsList, List values, String dfv)
125 throws IOException, FileFormatException, FieldParseException {
126 HashMap potentialDepOn = new HashMap();
127 Iterator i = fieldsList.iterator();
128
129 while (i.hasNext()) {
130 Object o = i.next();
131
132 if (o instanceof FieldFormat) {
133 int occurs = 1;
134 FieldFormat fF = (FieldFormat) o;
135 String dependingon = fF.getDependingOn();
136
137 if (dependingon.length() > 0) {
138 BigDecimal bd = (BigDecimal) potentialDepOn.get(dependingon);
139 occurs = bd.intValue();
140 } else {
141 occurs = fF.getOccurs().intValue();
142 }
143
144 char fType = fF.getType().charAt(0);
145
146 while (occurs-- > 0) {
147 BigDecimal v;
148
149 switch (fType) {
150 case 'X':
151
152 if (dfv.length() > 0) {
153 values.add(dfv);
154 dfv = "";
155 } else {
156 values.add(readText(fF));
157 }
158
159 break;
160
161 case '1':
162 values.add(readComp1(fF));
163
164 break;
165
166 case '2':
167 values.add(readComp2(fF));
168
169 break;
170
171 case '3':
172 v = readPacked(fF);
173 values.add(v);
174 potentialDepOn.put(fF.getName(), v);
175
176 break;
177
178 case '7':
179 values.add(readComp7(fF));
180
181 break;
182
183 case '8':
184 values.add(readComp8(fF));
185
186 break;
187
188 case '9':
189 v = readZoned(fF);
190 values.add(v);
191 potentialDepOn.put(fF.getName(), v);
192
193 break;
194
195 case 'D':
196
197
198
199
200
201
202
203
204 break;
205
206 case 'B':
207
208 BigDecimal bd = readBinary(fF);
209 values.add(bd);
210 potentialDepOn.put(fF.getName(), bd);
211
212 break;
213
214 case 'T':
215 values.add(readTransparent(fF));
216
217 break;
218
219 case 'H':
220 values.add(readByteAsHex(fF));
221
222 break;
223
224 default:
225 throw new FileFormatException(
226 "Invalid field type definition: " + fType);
227 }
228 }
229 }
230
231 if (o instanceof FieldsGroup) {
232 int occurs = 1;
233 FieldsGroup fG = (FieldsGroup) o;
234 String dependingon = fG.getDependingOn();
235
236 if (dependingon.length() > 0) {
237 BigDecimal bd = (BigDecimal) potentialDepOn.get(dependingon);
238 occurs = bd.intValue();
239 } else {
240 occurs = fG.getOccurs().intValue();
241 }
242
243 while (occurs-- > 0) {
244 getFieldsValues(fG.getFieldFormatOrFieldsGroup(), values,
245 dfv);
246 }
247 }
248 }
249
250 return values;
251 }
252
253 String readText(int len) throws IOException, FieldParseException {
254 byte[] buf = new byte[len];
255 int actuallyRead = stream.read(buf);
256
257 if (actuallyRead == len) {
258 bytesProcessed += actuallyRead;
259 } else {
260 if (actuallyRead < 0) {
261 actuallyRead = 0;
262 }
263
264 byte[] buf2 = new byte[actuallyRead];
265
266 for (int i = 0; i < actuallyRead; i++)
267 buf2[i] = buf[i];
268
269 throw new FieldParseException(buf2, null, null);
270 }
271
272 return new String(buf, charset);
273 }
274
275 String readText(FieldFormat ff) throws IOException, FieldParseException {
276 int len = getByteSize(ff);
277 String retValue;
278
279 try {
280 retValue = readText(len);
281 } catch (FieldParseException ex) {
282 throw new FieldParseException(ex.getOryginalData(), ff, ex);
283 }
284
285 return retValue;
286 }
287
288 Float readComp1(FieldFormat ff) throws IOException, FieldParseException {
289 int len = getByteSize(ff);
290 byte[] b = new byte[len];
291 float f;
292 int actuallyRead = stream.read(b);
293
294 if (actuallyRead == len) {
295 bytesProcessed += actuallyRead;
296 } else {
297 throw new FieldParseException(b, ff, null);
298 }
299
300 try {
301 f = ByteBuffer.wrap(b).order(ByteOrder.BIG_ENDIAN).getFloat();
302 } catch (Exception ex) {
303 throw new FieldParseException(b, ff, ex);
304 }
305
306 return new Float(f);
307 }
308
309 Double readComp2(FieldFormat ff) throws IOException, FieldParseException {
310 int len = getByteSize(ff);
311 byte[] b = new byte[len];
312 double d;
313 int actuallyRead = stream.read(b);
314
315 if (actuallyRead == len) {
316 bytesProcessed += actuallyRead;
317 } else {
318 throw new FieldParseException(b, ff, null);
319 }
320
321 try {
322 d = ByteBuffer.wrap(b).order(ByteOrder.BIG_ENDIAN).getDouble();
323 } catch (Exception ex) {
324 throw new FieldParseException(b, ff, ex);
325 }
326
327 return new Double(d);
328 }
329
330 Float readComp7(FieldFormat ff) throws IOException, FieldParseException {
331 int len = getByteSize(ff);
332 byte[] b = new byte[len];
333 float f;
334 int actuallyRead = stream.read(b);
335
336 if (actuallyRead == len) {
337 bytesProcessed += actuallyRead;
338 } else {
339 throw new FieldParseException(b, ff, null);
340 }
341
342 try {
343 f = ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN).getFloat();
344 } catch (Exception ex) {
345 throw new FieldParseException(b, ff, ex);
346 }
347
348 return new Float(f);
349 }
350
351 Double readComp8(FieldFormat ff) throws IOException, FieldParseException {
352 int len = getByteSize(ff);
353 byte[] b = new byte[len];
354 double d;
355 int actuallyRead = stream.read(b);
356
357 if (actuallyRead == len) {
358 bytesProcessed += actuallyRead;
359 } else {
360 throw new FieldParseException(b, ff, null);
361 }
362
363 try {
364 d = ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN).getDouble();
365 } catch (Exception ex) {
366 throw new FieldParseException(b, ff, ex);
367 }
368
369 return new Double(d);
370 }
371
372 BigDecimal readZoned(FieldFormat ff)
373 throws IOException, FieldParseException {
374 int len = getByteSize(ff);
375 int dec = ff.getDecimal().intValue();
376 byte[] b = new byte[len];
377 byte c;
378 int idx;
379 char[] pos = { 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9 };
380 char[] neg = { 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9 };
381 String positives = new String(pos);
382 String negatives = new String(neg);
383 String failoverpositives = "{ABCDEFGHI";
384 String failovernegatives = "}JKLMNOPQR";
385 int actuallyRead = stream.read(b);
386
387 if (actuallyRead == len) {
388 bytesProcessed += actuallyRead;
389 } else {
390 throw new FieldParseException(b, ff, null);
391 }
392
393
394
395
396
397
398
399
400 StringBuffer buf = new StringBuffer(new String(b, charset));
401 int lastidx = buf.length() - 1;
402 char lastbyte = buf.charAt(lastidx);
403 boolean positive = true;
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418 idx = positives.indexOf(lastbyte);
419
420 if (idx > -1) {
421 buf.replace(lastidx, lastidx + 1, new Integer(idx).toString());
422 positive = true;
423 }
424
425 idx = negatives.indexOf(lastbyte);
426
427 if (idx > -1) {
428 buf.replace(lastidx, lastidx + 1, new Integer(idx).toString());
429 positive = false;
430 }
431
432 idx = failoverpositives.indexOf(lastbyte);
433
434 if (idx > -1) {
435 buf.replace(lastidx, lastidx + 1, new Integer(idx).toString());
436 positive = true;
437
438
439
440
441
442 }
443
444 idx = failovernegatives.indexOf(lastbyte);
445
446 if (idx > -1) {
447 buf.replace(lastidx, lastidx + 1, new Integer(idx).toString());
448 positive = false;
449
450
451
452
453
454
455 }
456
457 if ((dec > 0) && ff.isImpliedDecimal()) {
458 buf.insert(buf.length() - dec, '.');
459 }
460
461 BigDecimal retVal;
462
463 try {
464 retVal = new BigDecimal(buf.toString());
465 } catch (NumberFormatException ex) {
466 throw new FieldParseException(buf.toString().getBytes(), ff, ex);
467 }
468
469 if (!positive) {
470 retVal = retVal.negate();
471 }
472
473 return retVal;
474 }
475
476 /***
477 * TODO Need to check if nibble is 0-9 and throw exception if A-F.
478 * @return
479 * @throws IOException
480 */
481 BigDecimal readPacked(FieldFormat ff)
482 throws IOException, FieldParseException {
483 int len = getByteSize(ff);
484 int dec = ff.getDecimal().intValue();
485 byte[] buf = new byte[len];
486 StringBuffer strbuf = new StringBuffer();
487 int tmp;
488 int tmp1;
489 int tmp2;
490 int actuallyRead = stream.read(buf);
491
492 if (actuallyRead == len) {
493 bytesProcessed += actuallyRead;
494 } else {
495 throw new FieldParseException(buf, ff, null);
496 }
497
498 for (int i = 0; i < len; i++) {
499 tmp = buf[i];
500 tmp1 = tmp & 0xF0;
501 tmp2 = tmp1 >> 4;
502 strbuf.append(tmp2);
503
504 if (i < (len - 1)) {
505 tmp = buf[i];
506 tmp1 = tmp & 0x0F;
507 strbuf.append(tmp1);
508 }
509 }
510
511 if ((dec > 0) && ff.isImpliedDecimal()) {
512 strbuf.insert(strbuf.length() - dec, '.');
513 }
514
515 BigDecimal retVal;
516
517 try {
518 retVal = new BigDecimal(strbuf.toString());
519 } catch (NumberFormatException ex) {
520 throw new FieldParseException(strbuf.toString().getBytes(), ff, ex);
521 }
522
523 tmp = buf[len - 1];
524 tmp1 = tmp & 0x0F;
525
526 if ((tmp1 == 0x0F) || (tmp1 == 0x0C)) {
527 return retVal;
528 } else if (tmp1 == 0x0D) {
529 return retVal.negate();
530 } else {
531 log.debug(
532 "Packed field sign nibble auto-correction. Field parsed but sign nibble not equal to 0xC, 0xD or 0xF. Assuming NOT negative. Field name: " +
533 ff.getName());
534
535 return retVal;
536 }
537 }
538
539 String readDateZoned(FieldFormat ff)
540 throws IOException, FieldParseException {
541 StringBuffer buf = new StringBuffer(readText(ff));
542 char lastbyte = buf.charAt(buf.length() - 1);
543
544 if (lastbyte == 0xC6) {
545 ;
546 }
547
548 return buf.toString();
549 }
550
551 /***
552 * TODO: Check if no scientific notation.
553 * @return String representation of date.
554 * @throws IOException
555 */
556 String readDatePacked(FieldFormat ff)
557 throws IOException, FieldParseException {
558 return readPacked(ff).toString();
559 }
560
561 BigDecimal readBinary(FieldFormat ff)
562 throws IOException, FieldParseException {
563
564
565 int len = getByteSize(ff);
566 byte[] buf;
567 buf = new byte[len];
568
569 BigDecimal retVal;
570 int actuallyRead = stream.read(buf);
571
572 if (actuallyRead == len) {
573 bytesProcessed += actuallyRead;
574 } else {
575 throw new FieldParseException(buf, ff, null);
576 }
577
578 try {
579 retVal = new BigDecimal(new BigInteger(buf),
580 ff.getDecimal().intValue());
581 } catch (NumberFormatException ex) {
582 throw new FieldParseException(buf, ff, ex);
583 }
584
585 return retVal;
586 }
587
588 byte[] readTransparent(FieldFormat ff)
589 throws IOException, FieldParseException {
590 int len = getByteSize(ff);
591 byte[] buf;
592 buf = new byte[len];
593
594 int actuallyRead = stream.read(buf);
595
596 if (actuallyRead == len) {
597 bytesProcessed += actuallyRead;
598 } else {
599 throw new FieldParseException(buf, ff, null);
600 }
601
602 return buf;
603 }
604
605 String readByteAsHex(FieldFormat ff)
606 throws IOException, FieldParseException {
607 int len = getByteSize(ff);
608 byte[] buf;
609 buf = new byte[len];
610
611 int actuallyRead = stream.read(buf);
612
613 if (actuallyRead == len) {
614 bytesProcessed += actuallyRead;
615 } else {
616 throw new FieldParseException(buf, ff, null);
617 }
618
619 return byteArrayToHexString(buf);
620 }
621
622 private int getByteSize(FieldFormat fieldF) {
623 int sz = fieldF.getSize().intValue();
624
625 if (fieldF.getType().equals("3")) {
626 if ((sz % 2) != 0) {
627 return (sz / 2) + 1;
628 } else {
629 return sz / 2;
630 }
631 } else {
632 return sz;
633 }
634 }
635
636 /***
637 * @deprecated Use next() and combine with commas,tabs,etc yourselve.
638 * @see next()
639 * @return
640 */
641 public String getNextRecordAsPlainString(String fieldSeparator)
642 throws IOException, FileFormatException, RecordParseException,
643 FieldParseException {
644 StringBuffer row = new StringBuffer();
645 Iterator i = next().iterator();
646 boolean afterfirst = false;
647
648 while (i.hasNext()) {
649 if (afterfirst) {
650 row.append(fieldSeparator);
651 }
652
653 row.append(i.next().toString());
654 afterfirst = true;
655 }
656
657 return row.toString();
658 }
659
660 /***
661 * Convert a byte[] array to readable string format. This makes the "hex" readable!
662 * @return result String buffer in String format
663 * @param in byte[] buffer to convert to string format
664 */
665 private String byteArrayToHexString(byte[] in) {
666 byte ch = 0x00;
667
668 if ((in == null) || (in.length <= 0)) {
669 return null;
670 }
671
672 String[] pseudo = {
673 "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C",
674 "D", "E", "F"
675 };
676 StringBuffer out = new StringBuffer(in.length * 2);
677
678 for (int i = 0; i < in.length; i++) {
679 ch = (byte) (in[i] & 0xF0);
680
681
682 ch = (byte) (ch >>> 4);
683
684
685 ch = (byte) (ch & 0x0F);
686
687
688 out.append(pseudo[(int) ch]);
689
690
691 ch = (byte) (in[i] & 0x0F);
692
693
694 out.append(pseudo[(int) ch]);
695
696
697 }
698
699 String rslt = new String(out);
700
701 return rslt;
702 }
703 }