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.ICobolArrayBinaryBinding;
15  import com.legstar.coxb.ICobolBinaryBinding;
16  import com.legstar.coxb.convert.ICobolBinaryConverter;
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.math.BigInteger;
23  import java.util.ArrayList;
24  import java.util.List;
25  
26  /**
27   * This is a concrete implementation of marshal/unmarshal operations of java
28   * numerics to cobol zoned decimals.
29   *
30   * @author Fady Moussallam
31   * 
32   */
33  public class CobolBinarySimpleConverter extends CobolSimpleConverter 
34  implements ICobolBinaryConverter {
35  
36      /**
37       * @param cobolContext the Cobol compiler parameters in effect
38       */
39      public CobolBinarySimpleConverter(final CobolContext cobolContext) {
40          super(cobolContext);
41      }
42  
43      /** {@inheritDoc} */
44      public int toHost(
45              final ICobolBinaryBinding ce,
46              final byte[] hostTarget,
47              final int offset)
48      throws HostException {
49          int newOffset = 0;
50          try {
51              newOffset = toHostSingle(ce.getBigDecimalValue(),
52                      ce.getByteLength(),
53                      ce.isSigned(),
54                      hostTarget,
55                      offset);
56          } catch (CobolConversionException e) {
57              throwHostException(ce, e);
58          }
59          return newOffset;
60      }
61  
62      /** {@inheritDoc} */
63      public int toHost(
64              final ICobolArrayBinaryBinding ce,
65              final byte[] hostTarget,
66              final int offset,
67              final int currentOccurs) 
68      throws HostException {
69          int newOffset = offset;
70          try {
71              for (BigDecimal javaSource : ce.getBigDecimalList()) {
72                  newOffset = toHostSingle(javaSource,
73                          ce.getItemByteLength(),
74                          ce.isSigned(),
75                          hostTarget,
76                          newOffset);
77              }
78              /* If necessary, fill in the array with missing items */
79              for (int i = ce.getBigDecimalList().size();
80              i < currentOccurs; i++) {
81                  newOffset = toHostSingle(BigDecimal.ZERO,
82                          ce.getItemByteLength(),
83                          ce.isSigned(),
84                          hostTarget,
85                          newOffset);
86              }
87          } catch (CobolConversionException e) {
88              throwHostException(ce, e);
89          }
90          return newOffset;
91      }
92  
93      /** {@inheritDoc} */
94      public int fromHost(
95              final ICobolBinaryBinding ce,
96              final byte[] hostSource,
97              final int offset)
98      throws HostException {
99          int newOffset = offset;
100         try {
101             BigDecimal javaDecimal = fromHostSingle(ce.getByteLength(),
102                     ce.isSigned(),
103                     ce.getTotalDigits(),
104                     ce.getFractionDigits(),
105                     hostSource,
106                     newOffset);
107             ce.setBigDecimalValue(javaDecimal);
108             newOffset += ce.getByteLength();
109         } catch (CobolConversionException e) {
110             throwHostException(ce, e);
111         }
112         return newOffset;
113     }
114 
115     /** {@inheritDoc} */
116     public int fromHost(
117             final ICobolArrayBinaryBinding ce,
118             final byte[] hostSource,
119             final int offset,
120             final int currentOccurs)
121     throws HostException {
122         List < BigDecimal > lArray = new ArrayList < BigDecimal >();
123         int newOffset = offset;
124         try {
125             for (int i = 0; i < currentOccurs; i++) {
126                 BigDecimal javaDecimal = fromHostSingle(ce.getItemByteLength(),
127                         ce.isSigned(),
128                         ce.getTotalDigits(),
129                         ce.getFractionDigits(),
130                         hostSource,
131                         newOffset);
132                 lArray.add(javaDecimal);
133                 newOffset += ce.getItemByteLength();
134             }
135             ce.setBigDecimalList(lArray);
136 
137         } catch (CobolConversionException e) {
138             throwHostException(ce, e);
139         }
140         return newOffset;
141     }
142 
143     /**
144      *  Converts a Java BigDecimal to a host binary.
145      * 
146      * @param javaDecimal java decimal to convert
147      * @param cobolByteLength host byte length
148      * @param isSigned Cobol element is signed or unsigned
149      * @param hostTarget target host buffer
150      * @param offset offset in target host buffer
151      * @return offset after host buffer is updated
152      * @throws CobolConversionException if conversion fails
153      */
154     public static final int toHostSingle(
155             final BigDecimal javaDecimal,
156             final int cobolByteLength,
157             final boolean isSigned,
158             final byte[] hostTarget,
159             final int offset) throws CobolConversionException {
160 
161         /* Check that we are still within the host target range */
162         int lastOffset = offset + cobolByteLength;
163         if (lastOffset > hostTarget.length) {
164             throw (
165                     new CobolConversionException(
166                             "Attempt to write past end of host source buffer",
167                             new HostData(hostTarget), offset, cobolByteLength));
168         }
169 
170         /* Since binary items are not sensible to scale, we start by unscaling
171          * the BigDecimal */
172         BigInteger javaInteger;
173         if (javaDecimal == null) {
174             javaInteger = BigInteger.ZERO;
175         } else {
176             javaInteger = javaDecimal.unscaledValue();
177         }
178 
179         /**
180          * BigIntegers values are accessed as a byte array here. Usually the
181          * number of byte items in this array will be identical or smaller
182          * than the target cobol element. There is an exception though when
183          * the target cobol element is unsigned: because there is no sign bit,
184          * a smaller number of cobol byte items can actually hold a larger
185          * value than the same number of items in the corresponding java
186          * BigInteger byte array.
187          * For instance, values between "2^31 + 1" and "2^32" are stored on 5
188          * bytes in java with the most significant byte holding value 0.
189          */
190         byte[] javaBytes = javaInteger.toByteArray();
191         if (!isSigned) {
192             if (javaBytes.length > cobolByteLength + 1) {
193                 throw (new CobolConversionException(
194                         "Java binary too large for Cobol element",
195                         new HostData(hostTarget), offset, cobolByteLength));
196             } else {
197                 if ((javaBytes.length == cobolByteLength + 1)
198                         && (javaBytes[0] != 0)) {
199                     throw (new CobolConversionException(
200                             "Java binary too large for Cobol element",
201                             new HostData(hostTarget), offset, cobolByteLength));
202                 }
203             }
204         } else {
205             if (javaBytes.length > cobolByteLength) {
206                 throw (new CobolConversionException(
207                         "Java binary too large for Cobol element",
208                         new HostData(hostTarget), offset, cobolByteLength));
209             }
210         }
211 
212         /**
213          * Byte ordering is big-endian for both java and Cobol for z/os so no
214          * reordering is necessary.
215          * Java binaries are always signed when Cobol ones might be unsigned.
216          * This leads to situations where Java needs one additional byte to
217          * store the same value. That additional byte has a 0 value which can
218          * be safely ignored.
219          */
220         int j = offset + cobolByteLength;
221         for (int i = javaBytes.length; i > 0; i--) {
222             if (j > offset) {
223                 hostTarget[j - 1] = javaBytes[i - 1];
224                 j--;
225             }
226         }
227         /* If cobol field most significant bytes were not filled, we need to
228            set their value according to the binary value sign */
229         while (j > offset) {
230             if (javaInteger.signum() == -1) {
231                 hostTarget[j - 1] = -0x01;
232             } else {
233                 hostTarget[j - 1] = 0;
234             }
235             j--;
236         }
237         return offset + cobolByteLength;
238     }
239 
240     /** Converts a host binary to a Java BigDecimal.
241      * 
242      * @param cobolByteLength host byte length
243      * @param isSigned Cobol element is signed or unsigned
244      * @param totalDigits Cobol element total number of digits
245      * @param fractionDigits Cobol element number of fractional digits
246      * @param hostSource source host buffer
247      * @param offset offset in source host buffer
248      * @return offset after host buffer is read
249      * @throws CobolConversionException if conversion fails
250      */
251     public static final BigDecimal fromHostSingle(
252             final int cobolByteLength,
253             final boolean isSigned,
254             final int totalDigits,
255             final int fractionDigits,
256             final byte[] hostSource,
257             final int offset) throws CobolConversionException {
258 
259         /* Construct a byte array with same byte ordering. The only difference
260          * is that java might need an extra byte with zero value to represent
261          * a positive integer */
262         byte[] javaBytes;
263         int j;
264         if (isSigned) {
265             javaBytes = new byte[cobolByteLength];
266             j = cobolByteLength;
267         } else {
268             javaBytes = new byte[cobolByteLength + 1];
269             j = cobolByteLength + 1;
270         }
271 
272         for (int i = (offset + cobolByteLength); i > offset; i--) {
273             if (j > 0) {
274                 if (i > hostSource.length) {
275                     javaBytes[j - 1] = 0x00;
276                 } else {
277                     javaBytes[j - 1] = hostSource[i - 1];
278                 }
279                 j--;
280             } else {
281                 /* Java binaries are always signed when Cobol ones might be
282                  * unsigned. This leads to situations where Java needs one
283                  * additional byte to store the same value. That additional
284                  * byte has a 0 value.*/
285                 javaBytes[0] = 0;
286             }
287         }
288 
289         BigInteger result = new BigInteger(javaBytes);
290 
291         /* Scale the result according to number of fractional digits
292          * required */
293         BigDecimal bDecimal = new BigDecimal(result, fractionDigits);
294 
295         return bDecimal;
296     }
297 
298 }