View Javadoc
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         // Skip new line byte(s) if any
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                                                                         if (!fF.getPacked()) {
199                                                                                 values.add(readDateZoned(fF));
200                                                                         } else {
201                                                                                 values.add(readDatePacked(fF));
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                 } // while end
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         // We want last byte not converted
394         // 2008-09-02
395         // No need to save last byte. We convert then take care
396         // Messages about failover removed from log
397         //        c = b[len - 1];
398         //        fileFormat.getConversionTable().convert(b);
399         //        b[len - 1] = c;
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         //                 In case most significant digit is over punched with sign
407                                         idx = failoverpositives.indexOf(buf.charAt(0));
408                         if (idx > -1) {
409                                 buf.replace(0, 1, new Integer(idx).toString());
410                                 positive = true;
411                                         }
412                         idx = failovernegatives.indexOf(buf.charAt(0));
413                         if (idx > -1) {
414                                 buf.replace(0, 1, new Integer(idx).toString());
415                                 positive = false;
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             /*            log.info("Zoned field auto-correction. Last byte value : \'" +
439                                             failoverpositives.charAt(idx) + "\' . Field name: " +
440                                             ff.getName());
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                                     log.info("Zoned field auto-correction. Last byte value : \'" +
452                                             failovernegatives.charAt(idx) + "\' . Field name: " +
453                                             ff.getName());
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         // TODO size should be rather computed here depending on cobol impl.
564         // cb2xml2cobol2j xsl assumes IBM mainframe binary type rep.
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             // Strip off high nibble
682             ch = (byte) (ch >>> 4);
683 
684             // shift the bits down
685             ch = (byte) (ch & 0x0F);
686 
687             // must do this is high order bit is on!
688             out.append(pseudo[(int) ch]);
689 
690             // convert the  nibble to a String Character
691             ch = (byte) (in[i] & 0x0F);
692 
693             // Strip off  low nibble
694             out.append(pseudo[(int) ch]);
695 
696             // convert the  nibble to a String Character
697         }
698 
699         String rslt = new String(out);
700 
701         return rslt;
702     }
703 }