← joda-money  /  src/main/java/org/joda/money/Ser.java

1
/*
2
 *  Copyright 2009-present, Stephen Colebourne
3
 *
4
 *  Licensed under the Apache License, Version 2.0 (the "License");
5
 *  you may not use this file except in compliance with the License.
6
 *  You may obtain a copy of the License at
7
 *
8
 *      http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 *  Unless required by applicable law or agreed to in writing, software
11
 *  distributed under the License is distributed on an "AS IS" BASIS,
12
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 *  See the License for the specific language governing permissions and
14
 *  limitations under the License.
15
 */
16
package org.joda.money;
17
18
import java.io.Externalizable;
19
import java.io.IOException;
20
import java.io.InvalidClassException;
21
import java.io.InvalidObjectException;
22
import java.io.ObjectInput;
23
import java.io.ObjectOutput;
24
import java.io.StreamCorruptedException;
25
import java.math.BigDecimal;
26
import java.math.BigInteger;
27
28
/**
29
 * A package scoped class used to manage serialization efficiently.
30
 * <p>
31
 * This class is mutable and intended for use by a single thread.
32
 */
33
final class Ser implements Externalizable {
34
35
    /** Type for BigMoney. */
36
    static final byte BIG_MONEY = 'B';
37
    /** Type for Money. */
38
    static final byte MONEY = 'M';
39
    /** Type for CurrencyUnit. */
40
    static final byte CURRENCY_UNIT = 'C';  // not in use yet
41
42
    /** The type. */
43
    private byte type;
44
    /** The data object. */
45
    private Object object;
46
47
    /**
48
     * Constructor for serialization.
49
     */
50
    public Ser() {
51
    }
52
53
    /**
54
     * Constructor for package.
55
     *
56
     * @param type  the type
57
     * @param object  the object
58
     */
59
    Ser(byte type, Object object) {
60
        this.type = type;
61
        this.object = object;
62
    }
63
64
    //-----------------------------------------------------------------------
65
    /**
66
     * Outputs the data.
67
     *
68
     * @serialData One byte type code, then data specific to the type.
69
     * @param out  the output stream
70
     * @throws IOException if an error occurs
71
     */
72
    @Override
73
    public void writeExternal(ObjectOutput out) throws IOException {
74
        out.writeByte(type);
75
        switch (type) {
76
            case BIG_MONEY -> {
77
                var obj = (BigMoney) object;
78
                writeBigMoney(out, obj);
79
            }
80
            case MONEY -> {
81
                var obj = (Money) object;
82
                writeBigMoney(out, obj.toBigMoney());
83
            }
84
            case CURRENCY_UNIT -> {
85
                var obj = (CurrencyUnit) object;
86
                writeCurrency(out, obj);
87
            }
88
            default -> throw new InvalidClassException("Joda-Money bug: Serialization broken");
89
        }
90
    }
91
92
    private void writeBigMoney(ObjectOutput out, BigMoney obj) throws IOException {
93
        writeCurrency(out, obj.getCurrencyUnit());
94
        var bytes = obj.getAmount().unscaledValue().toByteArray();
95
        out.writeInt(bytes.length);
96
        out.write(bytes);
97
        out.writeInt(obj.getScale());
98
    }
99
100
    private void writeCurrency(ObjectOutput out, CurrencyUnit obj) throws IOException {
101
        out.writeUTF(obj.getCode());
102
        out.writeShort(obj.getNumericCode());
103
        out.writeShort(obj.getDecimalPlaces());
104
    }
105
106
    /**
107
     * Outputs the data.
108
     *
109
     * @param in  the input stream
110
     * @throws IOException if an error occurs
111
     */
112
    @Override
113
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
114
        type = in.readByte();
115
        switch (type) {
116
            case BIG_MONEY -> {
117
                object = readBigMoney(in);
118
            }
119
            case MONEY -> {
120
                object = new Money(readBigMoney(in));
121
            }
122
            case CURRENCY_UNIT -> {
123
                object = readCurrency(in);
124
            }
125
            default -> throw new StreamCorruptedException("Serialization input has invalid type");
126
        }
127
    }
128
129
    private BigMoney readBigMoney(ObjectInput in) throws IOException {
130
        var currency = readCurrency(in);
131
        var bytes = new byte[in.readInt()];
132
        in.readFully(bytes);
133
        var bd = new BigDecimal(new BigInteger(bytes), in.readInt());
134
        var bigMoney = new BigMoney(currency, bd);
135
        return bigMoney;
136
    }
137
138
    private CurrencyUnit readCurrency(ObjectInput in) throws IOException {
139
        var code = in.readUTF();
140
        var singletonCurrency = CurrencyUnit.of(code);
141
        if (singletonCurrency.getNumericCode() != in.readShort()) {
142
            throw new InvalidObjectException("Deserialization found a mismatch in the numeric code for currency " + code);
143
        }
144
        if (singletonCurrency.getDecimalPlaces() != in.readShort()) {
145
            throw new InvalidObjectException("Deserialization found a mismatch in the decimal places for currency " + code);
146
        }
147
        return singletonCurrency;
148
    }
149
150
    /**
151
     * Returns the object that will replace this one.
152
     *
153
     * @return the read object, should never be null
154
     */
155
    private Object readResolve() {
156
        return object;
157
    }
158
159
}
160