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 java.math.BigDecimal;
14  import java.util.ArrayList;
15  import java.util.List;
16  
17  import com.legstar.coxb.CobolContext;
18  import com.legstar.coxb.ICobolArrayZonedDecimalBinding;
19  import com.legstar.coxb.ICobolZonedDecimalBinding;
20  import com.legstar.coxb.convert.CobolConversionException;
21  import com.legstar.coxb.convert.ICobolZonedDecimalConverter;
22  import com.legstar.coxb.host.HostContext;
23  import com.legstar.coxb.host.HostData;
24  import com.legstar.coxb.host.HostException;
25  
26  /**
27   * This is a concrete implementation of marshal/unmarshal operations of java
28   * numerics to cobol zoned decimals.
29   * <p/>
30   * Zoned decimals, also known as external decimals, are defined with PIC 9(n) or
31   * PIC S9(n) with the implicit or explicit DISPLAY usage. They contain character
32   * representation of digits and signs apart from a special case where the
33   * numeric is defined as PIC S9(n) without the SIGN IS SEPARATE clause. In this
34   * case, the sign info shares a byte with one of the digits (either the first
35   * one if SIGN IS LEADING or the last one if SIGN is TRAILING).
36   * <p/>
37   * This version does not use code pages anymore when converting from host to
38   * java. Using the Charset conversion is very CPU intensive. Rather we use a
39   * limited set of digits, either EBCDIC or ASCII.
40   * 
41   */
42  public class CobolZonedDecimalSimpleConverter extends CobolSimpleConverter
43          implements ICobolZonedDecimalConverter {
44  
45      /**
46       * @param cobolContext the Cobol compiler parameters in effect
47       */
48      public CobolZonedDecimalSimpleConverter(final CobolContext cobolContext) {
49          super(cobolContext);
50      }
51  
52      /** {@inheritDoc} */
53      public int toHost(final ICobolZonedDecimalBinding ce,
54              final byte[] hostTarget, final int offset) throws HostException {
55          int newOffset = 0;
56          try {
57              newOffset = toHostSingle(ce.getBigDecimalValue(),
58                      ce.getByteLength(), ce.getTotalDigits(),
59                      ce.getFractionDigits(), ce.isSigned(), ce.isSignSeparate(),
60                      ce.isSignLeading(), hostTarget, offset, getCobolContext()
61                              .getHostIntegerSigns());
62          } catch (CobolConversionException e) {
63              throwHostException(ce, e);
64          }
65          return newOffset;
66      }
67  
68      /** {@inheritDoc} */
69      public int toHost(final ICobolArrayZonedDecimalBinding ce,
70              final byte[] hostTarget, final int offset, final int currentOccurs)
71              throws HostException {
72          int newOffset = offset;
73          try {
74              for (BigDecimal javaSource : ce.getBigDecimalList()) {
75                  newOffset = toHostSingle(javaSource, ce.getItemByteLength(),
76                          ce.getTotalDigits(), ce.getFractionDigits(),
77                          ce.isSigned(), ce.isSignSeparate(), ce.isSignLeading(),
78                          hostTarget, newOffset, getCobolContext()
79                                  .getHostIntegerSigns());
80              }
81              /* If necessary, fill in the array with missing items */
82              for (int i = ce.getBigDecimalList().size(); i < currentOccurs; i++) {
83                  newOffset = toHostSingle(BigDecimal.ZERO,
84                          ce.getItemByteLength(), ce.getTotalDigits(),
85                          ce.getFractionDigits(), ce.isSignSeparate(),
86                          ce.isSignLeading(), ce.isSigned(), hostTarget,
87                          newOffset, getCobolContext().getHostIntegerSigns());
88              }
89          } catch (CobolConversionException e) {
90              throwHostException(ce, e);
91          }
92          return newOffset;
93      }
94  
95      /** {@inheritDoc} */
96      public int fromHost(final ICobolZonedDecimalBinding ce,
97              final byte[] hostSource, final int offset) throws HostException {
98          int newOffset = offset;
99          try {
100             BigDecimal javaDecimal = fromHostSingle(ce.getByteLength(),
101                     ce.getTotalDigits(), ce.getFractionDigits(), ce.isSigned(),
102                     ce.isSignSeparate(), ce.isSignLeading(), hostSource,
103                     newOffset, getCobolContext().getHostIntegerSigns());
104             ce.setBigDecimalValue(javaDecimal);
105             newOffset += ce.getByteLength();
106         } catch (CobolConversionException e) {
107             throwHostException(ce, e);
108         }
109         return newOffset;
110     }
111 
112     /** {@inheritDoc} */
113     public int fromHost(final ICobolArrayZonedDecimalBinding ce,
114             final byte[] hostSource, final int offset, final int currentOccurs)
115             throws HostException {
116         List < BigDecimal > lArray = new ArrayList < BigDecimal >();
117         int newOffset = offset;
118         try {
119             for (int i = 0; i < currentOccurs; i++) {
120                 BigDecimal javaDecimal = fromHostSingle(ce.getItemByteLength(),
121                         ce.getTotalDigits(), ce.getFractionDigits(),
122                         ce.isSigned(), ce.isSignSeparate(), ce.isSignLeading(),
123                         hostSource, newOffset, getCobolContext()
124                                 .getHostIntegerSigns());
125                 lArray.add(javaDecimal);
126                 newOffset += ce.getItemByteLength();
127             }
128             ce.setBigDecimalList(lArray);
129         } catch (CobolConversionException e) {
130             throwHostException(ce, e);
131         }
132         return newOffset;
133     }
134 
135     /**
136      * Converts a Java BigDecimal to a host zoned decimal.
137      * 
138      * @param javaDecimal java decimal to convert
139      * @param cobolByteLength host byte length
140      * @param totalDigits Cobol element total number of digits
141      * @param fractionDigits Cobol element number of fractional digits
142      * @param isSigned Cobol element is signed or unsigned
143      * @param isSignSeparate Cobol element sign occupies own byte
144      * @param isSignLeading Cobol element sign in first byte
145      * @param hostTarget target host buffer
146      * @param offset offset in target host buffer
147      * @return offset after host buffer is updated
148      * @param hostIntegerSigns the integer characters in the target host charset
149      * @throws CobolConversionException if conversion fails
150      */
151     public static final int toHostSingle(final BigDecimal javaDecimal,
152             final int cobolByteLength, final int totalDigits,
153             final int fractionDigits, final boolean isSigned,
154             final boolean isSignSeparate, final boolean isSignLeading,
155             final byte[] hostTarget, final int offset,
156             final byte[] hostIntegerSigns) throws CobolConversionException {
157 
158         /* Check that we are still within the host target range */
159         int lastOffset = offset + cobolByteLength;
160         if (lastOffset > hostTarget.length) {
161             throw (new CobolConversionException(
162                     "Attempt to write past end of host source buffer",
163                     new HostData(hostTarget), offset, cobolByteLength));
164         }
165 
166         /* Leave the first host char available for sign if needed. */
167         int iTarget = offset
168                 + ((isSigned && isSignSeparate && isSignLeading) ? 1 : 0);
169 
170         int intHostDigits = totalDigits - fractionDigits;
171 
172         /* Process the java decimal character by character. */
173         char[] source = new char[] { '0' };
174         /* Fraction digits in the java decimal. */
175         int fractionJavaDigits = 0;
176         /* Integer digits in the java decimal. */
177         int intJavaPrecision = 0;
178         /* Only case where java will explicitly contain a sign. */
179         boolean isNegative = false;
180 
181         if (javaDecimal != null) {
182             source = javaDecimal.toPlainString().toCharArray();
183             fractionJavaDigits = javaDecimal.scale();
184             intJavaPrecision = javaDecimal.precision();
185             isNegative = javaDecimal.signum() == -1;
186         }
187 
188         /* Evaluate the java entire and fractional digits we got. */
189         int totalJavaDigits = (intJavaPrecision > fractionJavaDigits) ? intJavaPrecision
190                 : fractionJavaDigits + 1;
191 
192         /* Java decimal is too large. */
193         if (totalJavaDigits > totalDigits) {
194             throw new CobolConversionException(
195                     "BigDecimal value too large for target Cobol field",
196                     new HostData(hostTarget), offset, cobolByteLength);
197         }
198 
199         int intJavaDigits = totalJavaDigits - fractionJavaDigits;
200 
201         /* Truncate left java digits if they won't fit in the host. */
202         int iSource = (intJavaDigits > intHostDigits) ? (intJavaDigits - intHostDigits)
203                 : 0;
204 
205         /* Skip java sign for now, it will be processed at the end. */
206         if (isNegative) {
207             iSource++;
208         }
209 
210         /* Place integer part, left padding with zeroes if needed */
211         for (int i = 0; i < intHostDigits; i++) {
212             if (i < (intHostDigits - intJavaDigits)) {
213                 hostTarget[iTarget] = hostIntegerSigns[0];
214             } else {
215                 hostTarget[iTarget] = hostIntegerSigns[source[iSource]
216                         - (int) '0'];
217                 iSource++;
218             }
219             iTarget++;
220         }
221 
222         /* Skip the java decimal point */
223         iSource++;
224 
225         /* Place fraction part, right padding with zeroes if needed */
226         for (int i = 0; i < fractionDigits; i++) {
227             if (i >= fractionJavaDigits) {
228                 hostTarget[iTarget] = hostIntegerSigns[0];
229             } else {
230                 hostTarget[iTarget] = hostIntegerSigns[source[iSource]
231                         - (int) '0'];
232                 iSource++;
233             }
234             iTarget++;
235         }
236 
237         /*
238          * If the sign is separate and trailing we need to advance one more
239          * position
240          */
241         if (isSigned && isSignSeparate && !isSignLeading) {
242             iTarget++;
243         }
244 
245         /*
246          * Place the sign. It can be separate or overpunched into the first or
247          * last byte.
248          */
249         if (isSigned) {
250             if (isSignSeparate) {
251                 if (isSignLeading) {
252                     if (isNegative) {
253                         hostTarget[offset] = hostIntegerSigns[13];
254                     } else {
255                         hostTarget[offset] = hostIntegerSigns[12];
256                     }
257                 } else {
258                     if (isNegative) {
259                         hostTarget[iTarget - 1] = hostIntegerSigns[13];
260                     } else {
261                         hostTarget[iTarget - 1] = hostIntegerSigns[12];
262                     }
263                 }
264             } else {
265                 if (isSignLeading) {
266                     if (isNegative) {
267                         hostTarget[offset] = (byte) (hostTarget[offset] - 0x20);
268                     } else {
269                         hostTarget[offset] = (byte) (hostTarget[offset] - 0x30);
270                     }
271                 } else {
272                     if (isNegative) {
273                         hostTarget[iTarget - 1] = (byte) (hostTarget[iTarget - 1] - 0x20);
274                     } else {
275                         hostTarget[iTarget - 1] = (byte) (hostTarget[iTarget - 1] - 0x30);
276                     }
277                 }
278             }
279         }
280 
281         return iTarget;
282     }
283 
284     /**
285      * Converts a host packed decimal to a Java BigDecimal.
286      * 
287      * @param cobolByteLength host byte length
288      * @param totalDigits Cobol element total number of digits
289      * @param fractionDigits Cobol element number of fractional digits
290      * @param isSigned Cobol element is signed or unsigned
291      * @param isSignSeparate Cobol element sign occupies own byte
292      * @param isSignLeading Cobol element sign in first byte
293      * @param hostSource source host buffer
294      * @param offset offset in source host buffer
295      * @param hostIntegerSigns the integer characters in the target host charset
296      * @return offset after host buffer is read
297      * @throws CobolConversionException if conversion fails
298      */
299     public static final BigDecimal fromHostSingle(final int cobolByteLength,
300             final int totalDigits, final int fractionDigits,
301             final boolean isSigned, final boolean isSignSeparate,
302             final boolean isSignLeading, final byte[] hostSource,
303             final int offset, final byte[] hostIntegerSigns)
304             throws CobolConversionException {
305 
306         int lastOffset = offset + cobolByteLength;
307 
308         /*
309          * Check that we are still within the host source range. If not,
310          * consider the host optimized its payload by truncating trailing nulls
311          * in which case, we just need to initialize and return.
312          */
313         if (lastOffset > hostSource.length) {
314             return new BigDecimal(0).setScale(fractionDigits);
315         }
316         if (lastOffset < 1) {
317             throw (new CobolConversionException("Invalid host byte length",
318                     new HostData(hostSource), offset, cobolByteLength));
319         }
320 
321         int sourceSize = totalDigits;
322         if (isSigned) {
323             sourceSize++;
324         }
325         char[] workDecimal = new char[sourceSize];
326 
327         /*
328          * Transfer source bytes to work byte array. The objective is that the
329          * work byte array contains only valid digits ready for code page
330          * translation. The first and last source bytes might be sign bytes.
331          * These happen for signed decimals with overpunch sign (not separate).
332          * Such a byte encodes both the sign (high order 4 bits) and a digit
333          * (low order 4 bits) so it results in 2 bytes in the work byte array.
334          */
335 
336         /*
337          * reserve first java character for sign when it cannot be determined
338          * right away from the host byte.
339          */
340         int i = (isSigned && (!isSignLeading || (isSignLeading && !isSignSeparate))) ? 1
341                 : 0;
342         byte hostByte;
343         for (int iSource = offset; iSource < lastOffset; iSource++) {
344             hostByte = hostSource[iSource];
345             if (isSigned) {
346                 if (iSource == offset && isSignLeading && !isSignSeparate) {
347                     setFromOverPunch(workDecimal, hostByte, i, hostIntegerSigns);
348                     i++;
349                     continue;
350                 }
351 
352                 if (iSource == lastOffset - 1 && !isSignLeading) {
353                     if (isSignSeparate) {
354                         workDecimal[0] = toJavaChar(hostByte, hostIntegerSigns);
355                     } else {
356                         setFromOverPunch(workDecimal, hostByte, i,
357                                 hostIntegerSigns);
358                     }
359                     i++;
360                     continue;
361                 }
362 
363             }
364             workDecimal[i] = toJavaChar(hostByte, hostIntegerSigns);
365             i++;
366         }
367 
368         /* Turn the char array into a decimal with the required scale */
369         BigDecimal result;
370         try {
371             result = new BigDecimal(workDecimal);
372         } catch (NumberFormatException e) {
373             throw (new CobolConversionException(
374                     "Host data contains a byte that is not a valid zoned"
375                             + " decimal byte", new HostData(hostSource),
376                     offset, cobolByteLength));
377         }
378         if (fractionDigits == 0) {
379             return result;
380         } else {
381             return result.movePointLeft(fractionDigits);
382         }
383     }
384 
385     /**
386      * Sets the java sign as the first character, based on the zoned decimal
387      * byte that holds the sign as an overpunch character the second half byte
388      * of the overpunch byte is the actual digit.
389      * 
390      * @param workDecimal the Java decimal being built
391      * @param hostByte the host byte with overpunch sign
392      * @param i the current index in the java decimal being built
393      */
394     protected static void setFromOverPunch(char[] workDecimal, byte hostByte,
395             int i, final byte[] hostIntegerSigns) {
396         int signCode = hostByte & 0xF0;
397         if (signCode == 0xd0) {
398             workDecimal[0] = '-';
399         } else {
400             workDecimal[0] = '+';
401         }
402         workDecimal[i] = toJavaChar((byte) ((hostByte & 0x0F) + 0xF0),
403                 hostIntegerSigns);
404     }
405 
406     /**
407      * Lookup a host numeric character and translate to java.
408      * <p/>
409      * A host white space is translated to a java zero digit. On the host,
410      * numerics are frequently left padded with spaces but java does not like
411      * that.
412      * <p/>
413      * Similarly, host might use low values for padding which again does not
414      * suit java so we switch that to zeroes.
415      * 
416      * @param hostByte the host numeric character
417      * @param hostIntegerSigns the list of host integer characters
418      * @return the corresponding java numeric character or 0 if not found
419      */
420     public static char toJavaChar(final byte hostByte,
421             final byte[] hostIntegerSigns) {
422         for (int i = 0; i < hostIntegerSigns.length; i++) {
423             if (hostByte == hostIntegerSigns[i]) {
424                 char javaChar = HostContext.getIntegerSigns().charAt(i);
425                 return javaChar == ' ' ? '0' : (javaChar == '\0' ? '0'
426                         : javaChar);
427             }
428         }
429         return '\0';
430     }
431 
432 }