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