001package Torello.JSON;
002
003import Torello.Java.Function.FloatConsumer;
004
005import java.util.function.IntConsumer;
006import java.util.function.Function;
007import java.util.function.Predicate;
008import java.util.function.DoubleConsumer;
009import java.util.function.ObjIntConsumer;
010
011import java.math.BigDecimal;
012
013import javax.json.JsonNumber;
014
015import static javax.json.JsonValue.ValueType.NUMBER;
016
017
018// This class in invoked ONLY once.  The SettingsRec<T, U> Constructor calls it.
019// Once this class a returned a "Json-Number Handler", only the selected handler
020// itself will ever be invoked during the Main-Loop-Processing
021// 
022// Though this may look like the most ascinine thing that has ever been written
023// in Java.  Over a period of three years it has, indeed, been testing and retested
024// to the point of being extremely feasible.
025//
026// All this class does is generate an "Object-Int-Consumer".
027// This "Consumer" is just a method that accepts:
028// 
029// 1) JsonNumber
030// 2) Array-Index as an Integer (the 'int i' you see in the Lambda-Expressions)
031// 
032// All this conumer does is generate a Valid Java-Number, and insert it into the
033// user's consumer.  If the user has actually requested a "Stream<SomeNumberType>",
034// Then the Stream.Builder<SomeType> is converted into a consumer and used by the
035// Lambda's inside this method.
036// 
037// 
038// *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
039// The 'ja' instance declared in class "SettingsRec" **IS NOT** declared final.  It can be reset !
040// *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
041// 
042// IMPORTANT NOTE: The 'ja' that is passed to the Functional-Interface Implementatons which are 
043// constructed in this method is "de-referenced" from the SettingsRec -- It **IS NOT** passed as
044// a parameter to this method.  If it **WERE NOT** retrieved from the 'SettingsRec', then 'sr'
045// instance, then the 'ja' that would be used would be permanently-solidified to the 'ja' 
046// referenced that were passed right at the beginning, upon construction of the SettingsRec
047// instance.
048
049@Torello.JavaDoc.Annotations.StaticFunctional
050public class ChooseNumberHandler
051{
052    private ChooseNumberHandler() {}
053
054
055    // handlerNumber: **ONLY USED BY** ProcessJsonArray.numericToJava
056    // 
057    // This makes the method ProcessJsonArray.numericJsonArrayTo look a million times better
058    // It is also slightly more efficient this way... slightly - since the 'if-statements'
059    // are "outside" of the lambda's, not inside.
060
061    @SuppressWarnings({"unchecked"})
062    // Consumer<Number> c = (Consumer<Number>) this.acceptor;
063    static <T> ObjIntConsumer<JsonNumber> choose(
064            final SettingsRec<T, ?> sr,
065            final ACCEPTOR<T>       acceptor,
066            final byte              whichType,
067
068            // BASIC_TYPES: bt.whichType,
069            final boolean refOrPrim,
070
071            // BASIC_TYPES: bt.referenceOrPrimitive,
072            final Function<Number, T> numberConverter,
073
074            // BASIC_TYPES: bt.numberConverter,
075            final Function<BigDecimal, T> numberConverterExThrow,
076
077            // BASIC_TYPES: bt.jsonNumWillFit
078            final Predicate<JsonNumber> jsonNumWillFit,
079
080            // BASIC_TYPES: bt.numberConverterExThrow,
081            final boolean RJA_AEX,
082
083            final IntConsumer handlerAEX
084        )
085    {
086        // The Type-Checking Downside... There is no way I can 'prove' that this
087        // Consumer *REALLY* is a 'Number' consumer... but it must be!
088
089        if (whichType == BASIC_TYPES.NUMBER) return (JsonNumber jn, int i) -> 
090            ((ACCEPTOR<Number>) acceptor).accept(ReadNumberJSON.convertToNumber(jn), i);
091
092        else if (RJA_AEX)
093            return (JsonNumber jn, int i) ->
094                acceptor.accept(numberConverter.apply(jn.numberValue()), i);
095
096        else if (handlerAEX == null)
097        {
098            if (whichType == BASIC_TYPES.FLOAT) return (JsonNumber jn, int i) ->
099            {
100                // IMPORTANT NOTE: See the comment at the top of this class regarding the 'sr.ja'
101                // versus 'ja' (being passed as a parameter to this method).
102
103                if (! handleFloat(acceptor, jn, i)) throw new JsonArithmeticArrException(
104                    ChooseNumberHandler.getAEX(jn, numberConverterExThrow),
105                    sr.ja, i, NUMBER, /* used to be 'jv' */ jn, sr.CLASS
106                );
107            };
108
109            else if (whichType == BASIC_TYPES.DOUBLE) return (JsonNumber jn, int i) ->
110            {
111                // IMPORTANT NOTE: See the comment at the top of this class regarding the 'sr.ja'
112                // versus 'ja' (being passed as a parameter to this method).
113
114                if (! handleDouble(acceptor, jn, i)) throw new JsonArithmeticArrException(
115                    ChooseNumberHandler.getAEX(jn, numberConverterExThrow),
116                    sr.ja, i, NUMBER, jn, sr.CLASS
117                );
118            };
119
120            else return (JsonNumber jn, int i) ->
121            {
122                if (jsonNumWillFit.test(jn))
123                    acceptor.accept(numberConverter.apply(jn.numberValue()), i);
124
125
126                // IMPORTANT NOTE: See the comment at the top of this class regarding the 'sr.ja'
127                // versus 'ja' (being passed as a parameter to this method).
128
129                else throw new JsonArithmeticArrException(
130                    ChooseNumberHandler.getAEX(jn, numberConverterExThrow),
131                    sr.ja, i, NUMBER, /* used to be 'jv' */ jn, sr.CLASS
132                );
133            };
134        }
135
136        else 
137        {
138            if (whichType == BASIC_TYPES.FLOAT) return (JsonNumber jn, int i) ->
139            { if (! handleFloat(acceptor, jn, i)) handlerAEX.accept(i); };
140
141            else if (whichType == BASIC_TYPES.DOUBLE) return (JsonNumber jn, int i) ->
142            { if (! handleDouble(acceptor, jn, i)) handlerAEX.accept(i); };
143
144            else return (JsonNumber jn, int i) ->
145            {
146                if (jsonNumWillFit.test(jn))
147                    acceptor.accept(numberConverter.apply(jn.numberValue()), i);
148                else 
149                    handlerAEX.accept(i);
150            };
151        }
152    }
153
154
155    // As a part of the Java-HTML experience is the need to make sure that the types
156    // which are converted do not accidentally do any "rounding" or "truncation" unless the user 
157    // has explicitly requested it.
158    // 
159    // This method simply puts a JsonNumber into a Double-Consumer....
160    // **HOWEVER** It checks that no rounding & truncation is necesary before doing this.
161    // 
162    // NOTE: The Class "PREDICATES" hasL
163    //  * shortTypePred
164    //  * longTypePred
165    //  * byteTypePred
166    // 
167    // Note that the *ONLY WAY* to actually check if the `BigDecimal.doubleValue` is actually 
168    // returning a number that has been properly fit into a Valid Double-Value is to go ahead and
169    // generate the `Double`, and the check (using the static BigDecimal.valueOf(d) Method) to 
170    // convert it *BACK INTO A BigDecimal INSTANCE*.
171    // 
172    // As a Result, since the 'double' is generated during the processing of checking whether the
173    // double is a Valid-Conversion or not, it would be somewhat inefficient to do this conversion
174    // **TWICE** - Once in the class PREDICATES, and then a Second-Time (dircectly) above in the 
175    // 'choose(..)' method.
176
177    @SuppressWarnings({"rawtypes", "unchecked"})
178    private static boolean handleDouble
179        (final ACCEPTOR acceptor, final JsonNumber jn, final int i)
180    {
181        final BigDecimal    bd  = jn.bigDecimalValue();
182        final double        d   = bd.doubleValue();
183
184        if ((!Double.isInfinite(d)) && BigDecimal.valueOf(d).compareTo(bd) == 0)
185        {
186            ((ACCEPTOR<Double>) acceptor).accept(d, i);
187            return true;
188        }
189
190        return false;
191    }
192
193    // Same as above, but for Float's instead of Double's.
194    @SuppressWarnings({"rawtypes", "unchecked"})
195    private static boolean handleFloat
196        (final ACCEPTOR acceptor, final JsonNumber jn, final int i)
197    {
198        final BigDecimal    bd = jn.bigDecimalValue();
199        final float         f   = bd.floatValue();
200
201        if ((!Float.isInfinite(f)) && BigDecimal.valueOf(f).compareTo(bd) == 0)
202        {
203            ((ACCEPTOR<Float>) acceptor).accept(f, i);
204            return true;
205        }
206
207        return false;
208    }
209
210    // NOTE: It is **A LOT** Smarter to check for the exception instead of using the try-catch
211    //       since I have read that generating an exception is one of the most costly-expensive
212    //       things the JRE does.  It isn't the Exception-Constructor that costs a lot - it is the
213    //       creating of the **STACK-TRACE** that costs well over 100 to 1,000 times the number of
214    //       of CPU-Cycles that the cost of a simple constructor.  This is an "Only if abolutely"
215    //       necessary things.
216
217    private static <T extends Number> ArithmeticException getAEX
218        (JsonNumber jn, Function<BigDecimal, ?> converter)
219    {
220        try
221            { converter.apply(jn.bigDecimalValue()); }
222
223        catch (ArithmeticException aex)
224            { return aex; }
225
226        throw new Torello.Java.UnreachableError();
227    }
228}
229