View Javadoc

1   /*******************************************************************************
2    * Copyright (c) 2015 LegSem.
3    * All rights reserved. This program and the accompanying materials
4    * are made available under the terms of the GNU Lesser Public License v2.1
5    * which accompanies this distribution, and is available at
6    * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
7    * 
8    * Contributors:
9    *     LegSem - initial API and implementation
10   ******************************************************************************/
11  package com.legstar.coxb.convert.simple;
12  
13  import com.legstar.coxb.CobolContext;
14  import com.legstar.coxb.ICobolArrayPackedDecimalBinding;
15  import com.legstar.coxb.ICobolPackedDecimalBinding;
16  import com.legstar.coxb.convert.ICobolPackedDecimalConverter;
17  import com.legstar.coxb.convert.CobolConversionException;
18  import com.legstar.coxb.host.HostData;
19  import com.legstar.coxb.host.HostException;
20  
21  import java.math.BigDecimal;
22  import java.util.ArrayList;
23  import java.util.List;
24  
25  /**
26   * This is a concrete implementation of marshal/unmarshal operations of java
27   * numerics to cobol packed decimals.
28   * 
29   * @author Fady Moussallam
30   * 
31   */
32  public class CobolPackedDecimalSimpleConverter extends CobolSimpleConverter
33          implements ICobolPackedDecimalConverter {
34  
35      /** Java characters corresponding to digits. */
36      private static final char[] JAVA_DIGITS = new char[] { '0',
37              '1', '2', '3', '4', '5', '6', '7', '8', '9' };
38  
39      /**
40       * Assumes no packed decimal will have more than this number of digits +
41       * signs.
42       */
43      private static final int MAX_PACKED_CHARS = 128;
44  
45      /**
46       * @param cobolContext the Cobol compiler parameters in effect
47       */
48      public CobolPackedDecimalSimpleConverter(final CobolContext cobolContext) {
49          super(cobolContext);
50      }
51  
52      /** {@inheritDoc} */
53      public int toHost(
54              final ICobolPackedDecimalBinding ce,
55              final byte[] hostTarget,
56              final int offset)
57              throws HostException {
58          int newOffset = 0;
59          try {
60              newOffset = toHostSingle(ce.getBigDecimalValue(),
61                      ce.getByteLength(),
62                      ce.getTotalDigits(),
63                      ce.getFractionDigits(),
64                      ce.isSigned(),
65                      hostTarget,
66                      offset);
67          } catch (CobolConversionException e) {
68              throwHostException(ce, e);
69          }
70          return newOffset;
71      }
72  
73      /** {@inheritDoc} */
74      public int toHost(
75              final ICobolArrayPackedDecimalBinding ce,
76              final byte[] hostTarget,
77              final int offset,
78              final int currentOccurs)
79              throws HostException {
80          int newOffset = offset;
81          try {
82              for (BigDecimal javaSource : ce.getBigDecimalList()) {
83                  newOffset = toHostSingle(javaSource,
84                          ce.getItemByteLength(),
85                          ce.getTotalDigits(),
86                          ce.getFractionDigits(),
87                          ce.isSigned(),
88                          hostTarget,
89                          newOffset);
90              }
91              /* If necessary, fill in the array with missing items */
92              for (int i = ce.getBigDecimalList().size(); i < currentOccurs; i++) {
93                  newOffset = toHostSingle(BigDecimal.ZERO,
94                          ce.getItemByteLength(),
95                          ce.getTotalDigits(),
96                          ce.getFractionDigits(),
97                          ce.isSigned(),
98                          hostTarget,
99                          newOffset);
100             }
101         } catch (CobolConversionException e) {
102             throwHostException(ce, e);
103         }
104         return newOffset;
105     }
106 
107     /** {@inheritDoc} */
108     public int fromHost(
109             final ICobolPackedDecimalBinding ce,
110             final byte[] hostSource,
111             final int offset)
112             throws HostException {
113         int newOffset = offset;
114         try {
115             BigDecimal javaDecimal = fromHostSingle(ce.getByteLength(),
116                     ce.getTotalDigits(),
117                     ce.getFractionDigits(),
118                     hostSource,
119                     newOffset);
120             ce.setBigDecimalValue(javaDecimal);
121             newOffset += ce.getByteLength();
122         } catch (CobolConversionException e) {
123             throwHostException(ce, e);
124         }
125         return newOffset;
126     }
127 
128     /** {@inheritDoc} */
129     public int fromHost(
130             final ICobolArrayPackedDecimalBinding ce,
131             final byte[] hostSource,
132             final int offset,
133             final int currentOccurs)
134             throws HostException {
135         List < BigDecimal > lArray = new ArrayList < BigDecimal >();
136         int newOffset = offset;
137         try {
138             for (int i = 0; i < currentOccurs; i++) {
139                 BigDecimal javaDecimal = fromHostSingle(ce.getItemByteLength(),
140                         ce.getTotalDigits(),
141                         ce.getFractionDigits(),
142                         hostSource,
143                         newOffset);
144                 lArray.add(javaDecimal);
145                 newOffset += ce.getItemByteLength();
146             }
147             ce.setBigDecimalList(lArray);
148         } catch (CobolConversionException e) {
149             throwHostException(ce, e);
150         }
151         return newOffset;
152     }
153 
154     /**
155      * Converts a Java BigDecimal to a host packed decimal.
156      * 
157      * @param javaDecimal java decimal to convert
158      * @param cobolByteLength host byte length
159      * @param totalDigits Cobol element total number of digits
160      * @param fractionDigits Cobol element number of fractional digits
161      * @param isSigned Cobol element is signed or unsigned
162      * @param hostTarget target host buffer
163      * @param offset offset in target host buffer
164      * @return offset after host buffer is updated
165      * @throws CobolConversionException if conversion fails
166      */
167     public static final int toHostSingle(
168             final BigDecimal javaDecimal,
169             final int cobolByteLength,
170             final int totalDigits,
171             final int fractionDigits,
172             final boolean isSigned,
173             final byte[] hostTarget,
174             final int offset)
175             throws CobolConversionException {
176 
177         /* Check that we are still within the host target range */
178         int lastOffset = offset + cobolByteLength;
179         if (lastOffset > hostTarget.length) {
180             throw (new CobolConversionException(
181                     "Attempt to write past end of host source buffer",
182                     new HostData(hostTarget), offset, cobolByteLength));
183         }
184 
185         /* Provide a default if input is null */
186         BigDecimal localDecimal = javaDecimal;
187         if (localDecimal == null) {
188             localDecimal = BigDecimal.ZERO;
189         }
190 
191         /* Get a string representation of the decimal value */
192         String sDecimal = localDecimal.toString();
193 
194         /*
195          * if the Java decimal has a different scale than target cobol field,
196          * adjust scale
197          */
198         if (localDecimal.scale() != fractionDigits) {
199             sDecimal = localDecimal.setScale(
200                     fractionDigits, BigDecimal.ROUND_DOWN).toString();
201         }
202 
203         /* Determine the number of digits that the java decimal holds */
204         int javaDigits = 0;
205         for (int i = 0; i < sDecimal.length(); i++) {
206             if (Character.isDigit(sDecimal.charAt(i))) {
207                 javaDigits++;
208             }
209         }
210 
211         if (javaDigits > totalDigits) {
212             throw (new CobolConversionException(
213                     "BigDecimal value too large for target Cobol field",
214                     new HostData(hostTarget), offset, cobolByteLength));
215         }
216 
217         /*
218          * Number of digits that are needed to pad the java value if it has
219          * less digits than the target cobol field
220          */
221         int pad = totalDigits - javaDigits;
222 
223         /**
224          * As we encounter digits in the string representation of the
225          * BigDecimal, we group them by 2 and create a byte value where the
226          * first digit occupies the left half-byte and the second digit goes
227          * into the right half-byte.
228          */
229         int iTarget = offset; /* points to current byte in host data */
230         boolean flip = false; /*
231                                * indicates when it is time to add a byte
232                                * to host data
233                                */
234         int bByte = 0; /*
235                         * represents the byte value to be appended
236                         * to host data
237                         */
238 
239         /*
240          * If the number of digits is even, we need to add a leading 0 value
241          * half-byte.
242          */
243         if (totalDigits % 2 == 0) {
244             flip = true;
245         }
246 
247         /* Start by padding to the left with zeroes as necessary */
248         for (int i = 0; i < pad; i++) {
249             if (flip) {
250                 hostTarget[iTarget] = (byte) 0x00;
251                 iTarget++;
252                 flip = false;
253             } else {
254                 flip = true;
255             }
256         }
257 
258         /*
259          * Translate digit characters into there numeric value
260          * and populate right and left half bytes
261          */
262         for (int i = 0; i < sDecimal.length(); i++) {
263             char sC = sDecimal.charAt(i);
264             if (Character.isDigit(sC)) {
265                 int digit = Character.digit(sC, 10);
266                 if (flip) {
267                     bByte += digit;
268                     hostTarget[iTarget] = (byte) bByte;
269                     iTarget++;
270                     flip = false;
271                 } else {
272                     bByte = digit * 16;
273                     flip = true;
274                 }
275             }
276         }
277 
278         /*
279          * The last half-byte is the sign. The rule is 0xF for unsigned
280          * decimals otherwise 0xD is negative and 0xC is positive
281          */
282         if (!isSigned) {
283             bByte += 0x0F;
284         } else {
285             if (localDecimal.signum() == -1) {
286                 bByte += 0x0D;
287             } else {
288                 bByte += 0x0C;
289             }
290         }
291         hostTarget[iTarget] = (byte) bByte;
292         iTarget++;
293 
294         return iTarget;
295     }
296 
297     /**
298      * Converts a host packed decimal to a Java BigDecimal.
299      * 
300      * @param cobolByteLength host byte length
301      * @param totalDigits Cobol element total number of digits
302      * @param fractionDigits Cobol element number of fractional digits
303      * @param hostSource source host buffer
304      * @param offset offset in source host buffer
305      * @return offset after host buffer is read
306      * @throws CobolConversionException if conversion fails
307      */
308     public static final BigDecimal fromHostSingle(
309             final int cobolByteLength,
310             final int totalDigits,
311             final int fractionDigits,
312             final byte[] hostSource,
313             final int offset)
314             throws CobolConversionException {
315 
316         /*
317          * To initialize the BigDecimal, we construct a char array that
318          * represents the decimal value held in the Cobol packed decimal
319          */
320         char[] sDecimal = new char[MAX_PACKED_CHARS];
321 
322         int lastOffset = offset + cobolByteLength;
323         int pos = 0;
324 
325         /*
326          * Check that we are still within the host source range.
327          * If not, consider the host optimized its payload by truncating
328          * trailing nulls in which case, we just need to initialize and return.
329          * TODO: This situation should not happen as packed decimals cannot
330          * end with a binary zero (the last byte always holds a sign half byte)
331          */
332         if (lastOffset > hostSource.length) {
333             return new BigDecimal(0).setScale(fractionDigits);
334         }
335         if (lastOffset < 1) {
336             throw (new CobolConversionException(
337                     "Invalid host byte length",
338                     new HostData(hostSource), offset, cobolByteLength));
339         }
340 
341         /* The leading sign is derived from the last byte */
342         int s = (hostSource[lastOffset - 1] & 0x0F);
343         if (s == 0x0d) {
344             sDecimal[pos++] = '-';
345         } else {
346             if (s != 0x0c && s != 0x0f) {
347                 throw (new CobolConversionException(
348                         "Host data last byte is not a valid packed decimal byte",
349                         new HostData(hostSource), offset, cobolByteLength));
350             }
351         }
352 
353         /* Each byte holds 2 digits except for the last one. */
354         int integerPart = 0;
355         int[] d = new int[2];
356         for (int i = offset; i < lastOffset; i++) {
357 
358             d[0] = (hostSource[i] & 0xF0) >>> 4;
359             d[1] = hostSource[i] & 0x0F;
360 
361             for (int j = 0; j < 2; j++) {
362                 /*
363                  * Insert left digit unless this is the first byte of an even
364                  * number of digits in which case we can ignore that first
365                  * half-byte.
366                  * Insert right digit unless this is the last byte because it
367                  * contains the sign which was already processed.
368                  */
369                 if ((j == 0 && (i != offset || (totalDigits % 2 != 0)))
370                         || (j == 1 && i != (lastOffset - 1))) {
371 
372                     /* Insert a decimal point when needed */
373                     if (integerPart == (totalDigits - fractionDigits)) {
374                         sDecimal[pos++] = '.';
375                     }
376 
377                     /* Make sure this is a valid digit */
378                     if (d[j] >= JAVA_DIGITS.length) {
379                         throw (new CobolConversionException(
380                                 "Host data contains a byte that is not a valid packed decimal byte",
381                                 new HostData(hostSource), offset,
382                                 cobolByteLength));
383                     }
384 
385                     sDecimal[pos++] = JAVA_DIGITS[d[j]];
386                     integerPart++;
387                 }
388 
389             }
390         }
391 
392         return new BigDecimal(sDecimal, 0, pos);
393     }
394 
395 }