diff --git a/build.gradle.kts b/build.gradle.kts index 9df5b7f987..9451d2550c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -79,6 +79,7 @@ dependencies { testImplementation("org.hamcrest:hamcrest:2.2") testImplementation("pl.pragmatists:JUnitParams:1.1.1") testImplementation("com.google.code.tempus-fugit:tempus-fugit:1.1") + testImplementation("com.github.luben:zstd-jni:1.5.6-5") } group = "com.amazon.ion" diff --git a/config/spotbugs/baseline.xml b/config/spotbugs/baseline.xml index 656a32390c..b5121577fc 100644 --- a/config/spotbugs/baseline.xml +++ b/config/spotbugs/baseline.xml @@ -1,15 +1,15 @@ - + Possible null pointer dereference on branch that might be infeasible Possible null pointer dereference of Timestamp._fraction on branch that might be infeasible in com.amazon.ion.Timestamp.equals(Timestamp) - - At Timestamp.java:[lines 74-2884] + + At Timestamp.java:[lines 75-2962] In class com.amazon.ion.Timestamp - + In method com.amazon.ion.Timestamp.equals(Timestamp) @@ -18,29 +18,29 @@ Value contained in com.amazon.ion.Timestamp._fraction - - Dereferenced at Timestamp.java:[line 2808] + + Dereferenced at Timestamp.java:[line 2886] - - Known null at Timestamp.java:[line 2804] + + Known null at Timestamp.java:[line 2882] - Comparaison de références suspecte - Comparaison suspecte des références com.amazon.ion.Timestamp._offset dans com.amazon.ion.Timestamp.make_localtime() + Suspicious reference comparison + Suspicious comparison of Integer references in com.amazon.ion.Timestamp.make_localtime() - - At Timestamp.java:[lines 74-2884] + + At Timestamp.java:[lines 75-2962] In class com.amazon.ion.Timestamp - + In method com.amazon.ion.Timestamp.make_localtime() - - At Integer.java:[lines 71-1872] + + At Integer.java:[lines 52-1590] Actual type Integer @@ -50,64 +50,26 @@ Value loaded from field com.amazon.ion.Timestamp._offset - - At Timestamp.java:[line 1249] - - - - Suspicious reference comparison to constant - Suspicious comparison of a Integer reference to constant in com.amazon.ion.Timestamp.print(Appendable, Timestamp) - - - At Timestamp.java:[lines 74-2884] - - In class com.amazon.ion.Timestamp - - - - In method com.amazon.ion.Timestamp.print(Appendable, Timestamp) - - - - At Integer.java:[lines 71-1872] - - Actual type Integer - - - - In Timestamp.java - - Value loaded from field com.amazon.ion.Timestamp.UNKNOWN_OFFSET - - - At Timestamp.java:[line 2171] - - - Another occurrence at Timestamp.java:[line 2179] - - - Another occurrence at Timestamp.java:[line 2187] - - - Another occurrence at Timestamp.java:[line 2205] + + At Timestamp.java:[line 1262] Suspicious reference comparison to constant Suspicious comparison of a Integer reference to constant in com.amazon.ion.Timestamp.printZ(Appendable) - - At Timestamp.java:[lines 74-2884] + + At Timestamp.java:[lines 75-2962] In class com.amazon.ion.Timestamp - + In method com.amazon.ion.Timestamp.printZ(Appendable) - - At Integer.java:[lines 71-1872] + + At Integer.java:[lines 52-1590] Actual type Integer @@ -117,26 +79,26 @@ Value loaded from field com.amazon.ion.Timestamp.UNKNOWN_OFFSET - - At Timestamp.java:[line 2131] + + At Timestamp.java:[line 2189] Suspicious reference comparison to constant Suspicious comparison of a Integer reference to constant in com.amazon.ion.Timestamp.set_fields_from_calendar(Calendar, Timestamp$Precision, boolean) - - At Timestamp.java:[lines 74-2884] + + At Timestamp.java:[lines 75-2962] In class com.amazon.ion.Timestamp - + In method com.amazon.ion.Timestamp.set_fields_from_calendar(Calendar, Timestamp$Precision, boolean) - - At Integer.java:[lines 71-1872] + + At Integer.java:[lines 52-1590] Actual type Integer @@ -146,105 +108,105 @@ Value loaded from field com.amazon.ion.Timestamp.UNKNOWN_OFFSET - - At Timestamp.java:[line 436] + + At Timestamp.java:[line 449] - Un switch comporte un cas qui déborde sur le suivant - Un switch de new com.amazon.ion.Timestamp(BigDecimal, Timestamp$Precision, Integer) comporte un cas qui déborde sur le suivant + Switch statement found where one case falls through to the next case + Switch statement found in new com.amazon.ion.Timestamp(BigDecimal, Timestamp$Precision, Integer) where one case falls through to the next case - - At Timestamp.java:[lines 74-2884] + + At Timestamp.java:[lines 75-2962] In class com.amazon.ion.Timestamp - + In method new com.amazon.ion.Timestamp(BigDecimal, Timestamp$Precision, Integer) - - At Timestamp.java:[lines 757-759] + + At Timestamp.java:[lines 770-772] - - Another occurrence at Timestamp.java:[lines 759-761] + + Another occurrence at Timestamp.java:[lines 772-774] - - Another occurrence at Timestamp.java:[lines 762-764] + + Another occurrence at Timestamp.java:[lines 775-777] - Un switch comporte un cas qui déborde sur le suivant - Un switch de com.amazon.ion.Timestamp.clearUnusedPrecision() comporte un cas qui déborde sur le suivant + Switch statement found where one case falls through to the next case + Switch statement found in com.amazon.ion.Timestamp.clearUnusedPrecision() where one case falls through to the next case - - At Timestamp.java:[lines 74-2884] + + At Timestamp.java:[lines 75-2962] In class com.amazon.ion.Timestamp - + In method com.amazon.ion.Timestamp.clearUnusedPrecision() - - At Timestamp.java:[lines 2367-2369] + + At Timestamp.java:[lines 2445-2447] - - Another occurrence at Timestamp.java:[lines 2369-2371] + + Another occurrence at Timestamp.java:[lines 2447-2449] - - Another occurrence at Timestamp.java:[lines 2372-2374] + + Another occurrence at Timestamp.java:[lines 2450-2452] Switch statement found where default case is missing Switch statement found in new com.amazon.ion.Timestamp(BigDecimal, Timestamp$Precision, Integer) where default case is missing - - At Timestamp.java:[lines 74-2884] + + At Timestamp.java:[lines 75-2962] In class com.amazon.ion.Timestamp - + In method new com.amazon.ion.Timestamp(BigDecimal, Timestamp$Precision, Integer) - - At Timestamp.java:[lines 754-764] + + At Timestamp.java:[lines 767-777] Switch statement found where default case is missing Switch statement found in com.amazon.ion.Timestamp.calendarValue() where default case is missing - - At Timestamp.java:[lines 74-2884] + + At Timestamp.java:[lines 75-2962] In class com.amazon.ion.Timestamp - + In method com.amazon.ion.Timestamp.calendarValue() - - At Timestamp.java:[lines 1594-1607] + + At Timestamp.java:[lines 1607-1620] Switch statement found where default case is missing Switch statement found in com.amazon.ion.Timestamp.clearUnusedPrecision() where default case is missing - - At Timestamp.java:[lines 74-2884] + + At Timestamp.java:[lines 75-2962] In class com.amazon.ion.Timestamp - + In method com.amazon.ion.Timestamp.clearUnusedPrecision() - - At Timestamp.java:[lines 2365-2375] + + At Timestamp.java:[lines 2443-2453] @@ -265,8 +227,8 @@ - La méthode peut ne pas fermer un flux - La méthode new com.amazon.ion.impl.BlockedBuffer(InputStream) peut ne pas fermer un flux + Method may fail to close stream + new com.amazon.ion.impl.BlockedBuffer(InputStream) may fail to close stream At BlockedBuffer.java:[lines 38-1041] @@ -278,8 +240,8 @@ In method new com.amazon.ion.impl.BlockedBuffer(InputStream) - - At OutputStream.java:[lines 52-198] + + At OutputStream.java:[lines 46-152] Need to close java.io.OutputStream @@ -288,8 +250,8 @@ - Une méthode expose sa représentation interne en incorporant une référence à un objet modifiable - La méthode new com.amazon.ion.impl.BlockedBuffer$BlockedByteOutputStream(BlockedBuffer) risque d'exposer sa représentation interne en stockant un objet externe modifiable dans com.amazon.ion.impl.BlockedBuffer$BlockedByteOutputStream._buf + May expose internal representation by incorporating reference to mutable object + new com.amazon.ion.impl.BlockedBuffer$BlockedByteOutputStream(BlockedBuffer) may expose internal representation by storing an externally mutable object into BlockedBuffer$BlockedByteOutputStream._buf At BlockedBuffer.java:[lines 1310-1656] @@ -314,8 +276,8 @@ - Une méthode expose sa représentation interne en incorporant une référence à un objet modifiable - La méthode new com.amazon.ion.impl.BlockedBuffer$BlockedByteOutputStream(BlockedBuffer, int) risque d'exposer sa représentation interne en stockant un objet externe modifiable dans com.amazon.ion.impl.BlockedBuffer$BlockedByteOutputStream._buf + May expose internal representation by incorporating reference to mutable object + new com.amazon.ion.impl.BlockedBuffer$BlockedByteOutputStream(BlockedBuffer, int) may expose internal representation by storing an externally mutable object into BlockedBuffer$BlockedByteOutputStream._buf At BlockedBuffer.java:[lines 1310-1656] @@ -340,8 +302,8 @@ - Une méthode expose sa représentation interne en incorporant une référence à un objet modifiable - La méthode new com.amazon.ion.impl.BlockedBuffer$BufferedOutputStream(BlockedBuffer) risque d'exposer sa représentation interne en stockant un objet externe modifiable dans com.amazon.ion.impl.BlockedBuffer$BufferedOutputStream._buffer + May expose internal representation by incorporating reference to mutable object + new com.amazon.ion.impl.BlockedBuffer$BufferedOutputStream(BlockedBuffer) may expose internal representation by storing an externally mutable object into BlockedBuffer$BufferedOutputStream._buffer At BlockedBuffer.java:[lines 1674-1776] @@ -472,8 +434,8 @@ - Une méthode peut exposer sa représentation interne en renvoyant une référence à un objet modifiable - La méthode com.amazon.ion.impl.IonBinary$BufferManager.buffer() risque d'exposer sa représentation interne en renvoyant com.amazon.ion.impl.IonBinary$BufferManager._buf + May expose internal representation by returning reference to mutable object + com.amazon.ion.impl.IonBinary$BufferManager.buffer() may expose internal representation by returning IonBinary$BufferManager._buf At IonBinary.java:[lines 322-437] @@ -495,8 +457,8 @@ - Une méthode peut exposer sa représentation interne en renvoyant une référence à un objet modifiable - La méthode com.amazon.ion.impl.IonBinary$BufferManager.openReader() risque d'exposer sa représentation interne en renvoyant com.amazon.ion.impl.IonBinary$BufferManager._reader + May expose internal representation by returning reference to mutable object + com.amazon.ion.impl.IonBinary$BufferManager.openReader() may expose internal representation by returning IonBinary$BufferManager._reader At IonBinary.java:[lines 322-437] @@ -518,8 +480,8 @@ - Une méthode peut exposer sa représentation interne en renvoyant une référence à un objet modifiable - La méthode com.amazon.ion.impl.IonBinary$BufferManager.openWriter() risque d'exposer sa représentation interne en renvoyant com.amazon.ion.impl.IonBinary$BufferManager._writer + May expose internal representation by returning reference to mutable object + com.amazon.ion.impl.IonBinary$BufferManager.openWriter() may expose internal representation by returning IonBinary$BufferManager._writer At IonBinary.java:[lines 322-437] @@ -541,8 +503,8 @@ - Une méthode peut exposer sa représentation interne en renvoyant une référence à un objet modifiable - La méthode com.amazon.ion.impl.IonBinary$BufferManager.reader() risque d'exposer sa représentation interne en renvoyant com.amazon.ion.impl.IonBinary$BufferManager._reader + May expose internal representation by returning reference to mutable object + com.amazon.ion.impl.IonBinary$BufferManager.reader() may expose internal representation by returning IonBinary$BufferManager._reader At IonBinary.java:[lines 322-437] @@ -564,8 +526,8 @@ - Une méthode peut exposer sa représentation interne en renvoyant une référence à un objet modifiable - La méthode com.amazon.ion.impl.IonBinary$BufferManager.reader(int) risque d'exposer sa représentation interne en renvoyant com.amazon.ion.impl.IonBinary$BufferManager._reader + May expose internal representation by returning reference to mutable object + com.amazon.ion.impl.IonBinary$BufferManager.reader(int) may expose internal representation by returning IonBinary$BufferManager._reader At IonBinary.java:[lines 322-437] @@ -587,8 +549,8 @@ - Une méthode peut exposer sa représentation interne en renvoyant une référence à un objet modifiable - La méthode com.amazon.ion.impl.IonBinary$BufferManager.writer() risque d'exposer sa représentation interne en renvoyant com.amazon.ion.impl.IonBinary$BufferManager._writer + May expose internal representation by returning reference to mutable object + com.amazon.ion.impl.IonBinary$BufferManager.writer() may expose internal representation by returning IonBinary$BufferManager._writer At IonBinary.java:[lines 322-437] @@ -610,8 +572,8 @@ - Une méthode peut exposer sa représentation interne en renvoyant une référence à un objet modifiable - La méthode com.amazon.ion.impl.IonBinary$BufferManager.writer(int) risque d'exposer sa représentation interne en renvoyant com.amazon.ion.impl.IonBinary$BufferManager._writer + May expose internal representation by returning reference to mutable object + com.amazon.ion.impl.IonBinary$BufferManager.writer(int) may expose internal representation by returning IonBinary$BufferManager._writer At IonBinary.java:[lines 322-437] @@ -633,8 +595,8 @@ - Une méthode expose sa représentation interne en incorporant une référence à un objet modifiable - La méthode new com.amazon.ion.impl.IonBinary$BufferManager(BlockedBuffer) risque d'exposer sa représentation interne en stockant un objet externe modifiable dans com.amazon.ion.impl.IonBinary$BufferManager._buf + May expose internal representation by incorporating reference to mutable object + new com.amazon.ion.impl.IonBinary$BufferManager(BlockedBuffer) may expose internal representation by storing an externally mutable object into IonBinary$BufferManager._buf At IonBinary.java:[lines 322-437] @@ -659,8 +621,8 @@ - Un switch comporte un cas qui déborde sur le suivant - Un switch de com.amazon.ion.impl.IonBinary$Reader.readIntAsInt(int) comporte un cas qui déborde sur le suivant + Switch statement found where one case falls through to the next case + Switch statement found in com.amazon.ion.impl.IonBinary$Reader.readIntAsInt(int) where one case falls through to the next case At IonBinary.java:[lines 942-1815] @@ -710,8 +672,8 @@ - Masques binaires incompatibles - Des masques binaires incompatibles renvoient un résultat constant dans com.amazon.ion.impl.IonBinary$Writer.writeIntValue(long) + Incompatible bit masks + Incompatible bit masks in (e & 0x80 == 0x40) yields a constant result in com.amazon.ion.impl.IonBinary$Writer.writeIntValue(long) At IonBinary.java:[lines 1819-2917] @@ -733,327 +695,327 @@ - La méthode concatène des chaînes au moyen de + en boucle - La méthode com.amazon.ion.impl.IonReaderTextRawTokensX.peekNullTypeSymbolUndo(int[], int) concatène des chaînes au moyen de + en boucle + Method concatenates strings using + in a loop + com.amazon.ion.impl.IonReaderTextRawTokensX.peekNullTypeSymbolUndo(int[], int) concatenates strings using + in a loop - - At IonReaderTextRawTokensX.java:[lines 66-2915] + + At IonReaderTextRawTokensX.java:[lines 53-2902] In class com.amazon.ion.impl.IonReaderTextRawTokensX - + In method com.amazon.ion.impl.IonReaderTextRawTokensX.peekNullTypeSymbolUndo(int[], int) - - At IonReaderTextRawTokensX.java:[line 429] + + At IonReaderTextRawTokensX.java:[line 416] - Un switch comporte un cas qui déborde sur le suivant - Un switch de com.amazon.ion.impl.IonReaderTextRawTokensX.load_double_quoted_string(StringBuilder, boolean) comporte un cas qui déborde sur le suivant + Switch statement found where one case falls through to the next case + Switch statement found in com.amazon.ion.impl.IonReaderTextRawTokensX.load_double_quoted_string(StringBuilder, boolean) where one case falls through to the next case - - At IonReaderTextRawTokensX.java:[lines 66-2915] + + At IonReaderTextRawTokensX.java:[lines 53-2902] In class com.amazon.ion.impl.IonReaderTextRawTokensX - + In method com.amazon.ion.impl.IonReaderTextRawTokensX.load_double_quoted_string(StringBuilder, boolean) - - At IonReaderTextRawTokensX.java:[lines 2057-2059] + + At IonReaderTextRawTokensX.java:[lines 2044-2046] - Un switch comporte un cas qui déborde sur le suivant - Un switch de com.amazon.ion.impl.IonReaderTextRawTokensX.load_fixed_digits(StringBuilder, int) comporte un cas qui déborde sur le suivant + Switch statement found where one case falls through to the next case + Switch statement found in com.amazon.ion.impl.IonReaderTextRawTokensX.load_fixed_digits(StringBuilder, int) where one case falls through to the next case - - At IonReaderTextRawTokensX.java:[lines 66-2915] + + At IonReaderTextRawTokensX.java:[lines 53-2902] In class com.amazon.ion.impl.IonReaderTextRawTokensX - + In method com.amazon.ion.impl.IonReaderTextRawTokensX.load_fixed_digits(StringBuilder, int) - - At IonReaderTextRawTokensX.java:[lines 1725-1728] + + At IonReaderTextRawTokensX.java:[lines 1712-1715] - - Another occurrence at IonReaderTextRawTokensX.java:[lines 1730-1733] + + Another occurrence at IonReaderTextRawTokensX.java:[lines 1717-1720] - - Another occurrence at IonReaderTextRawTokensX.java:[lines 1735-1738] + + Another occurrence at IonReaderTextRawTokensX.java:[lines 1722-1725] - Un switch comporte un cas qui déborde sur le suivant - Un switch de com.amazon.ion.impl.IonReaderTextRawTokensX.load_single_quoted_string(StringBuilder, boolean) comporte un cas qui déborde sur le suivant + Switch statement found where one case falls through to the next case + Switch statement found in com.amazon.ion.impl.IonReaderTextRawTokensX.load_single_quoted_string(StringBuilder, boolean) where one case falls through to the next case - - At IonReaderTextRawTokensX.java:[lines 66-2915] + + At IonReaderTextRawTokensX.java:[lines 53-2902] In class com.amazon.ion.impl.IonReaderTextRawTokensX - + In method com.amazon.ion.impl.IonReaderTextRawTokensX.load_single_quoted_string(StringBuilder, boolean) - - At IonReaderTextRawTokensX.java:[lines 1973-1977] + + At IonReaderTextRawTokensX.java:[lines 1960-1964] - Un switch comporte un cas qui déborde sur le suivant - Un switch de com.amazon.ion.impl.IonReaderTextRawTokensX.read_base64_byte_helper() comporte un cas qui déborde sur le suivant + Switch statement found where one case falls through to the next case + Switch statement found in com.amazon.ion.impl.IonReaderTextRawTokensX.read_base64_byte_helper() where one case falls through to the next case - - At IonReaderTextRawTokensX.java:[lines 66-2915] + + At IonReaderTextRawTokensX.java:[lines 53-2902] In class com.amazon.ion.impl.IonReaderTextRawTokensX - + In method com.amazon.ion.impl.IonReaderTextRawTokensX.read_base64_byte_helper() - - At IonReaderTextRawTokensX.java:[lines 2579-2582] + + At IonReaderTextRawTokensX.java:[lines 2566-2569] - - Another occurrence at IonReaderTextRawTokensX.java:[lines 2583-2586] + + Another occurrence at IonReaderTextRawTokensX.java:[lines 2570-2573] - Un switch comporte un cas qui déborde sur le suivant - Un switch de com.amazon.ion.impl.IonReaderTextRawTokensX.skip_double_quoted_string_helper() comporte un cas qui déborde sur le suivant + Switch statement found where one case falls through to the next case + Switch statement found in com.amazon.ion.impl.IonReaderTextRawTokensX.skip_double_quoted_string_helper() where one case falls through to the next case - - At IonReaderTextRawTokensX.java:[lines 66-2915] + + At IonReaderTextRawTokensX.java:[lines 53-2902] In class com.amazon.ion.impl.IonReaderTextRawTokensX - + In method com.amazon.ion.impl.IonReaderTextRawTokensX.skip_double_quoted_string_helper() - - At IonReaderTextRawTokensX.java:[lines 2019-2024] + + At IonReaderTextRawTokensX.java:[lines 2006-2011] - - Another occurrence at IonReaderTextRawTokensX.java:[lines 2024-2026] + + Another occurrence at IonReaderTextRawTokensX.java:[lines 2011-2013] - Un switch comporte un cas qui déborde sur le suivant - Un switch de com.amazon.ion.impl.IonReaderTextRawTokensX.skip_over_container(int) comporte un cas qui déborde sur le suivant + Switch statement found where one case falls through to the next case + Switch statement found in com.amazon.ion.impl.IonReaderTextRawTokensX.skip_over_container(int) where one case falls through to the next case - - At IonReaderTextRawTokensX.java:[lines 66-2915] + + At IonReaderTextRawTokensX.java:[lines 53-2902] In class com.amazon.ion.impl.IonReaderTextRawTokensX - + In method com.amazon.ion.impl.IonReaderTextRawTokensX.skip_over_container(int) - - At IonReaderTextRawTokensX.java:[lines 1225-1229] + + At IonReaderTextRawTokensX.java:[lines 1212-1216] - Un switch comporte un cas qui déborde sur le suivant - Un switch de com.amazon.ion.impl.IonReaderTextRawTokensX.skip_single_quoted_string(UnifiedSavePointManagerX$SavePoint) comporte un cas qui déborde sur le suivant + Switch statement found where one case falls through to the next case + Switch statement found in com.amazon.ion.impl.IonReaderTextRawTokensX.skip_single_quoted_string(UnifiedSavePointManagerX$SavePoint) where one case falls through to the next case - - At IonReaderTextRawTokensX.java:[lines 66-2915] + + At IonReaderTextRawTokensX.java:[lines 53-2902] In class com.amazon.ion.impl.IonReaderTextRawTokensX - + In method com.amazon.ion.impl.IonReaderTextRawTokensX.skip_single_quoted_string(UnifiedSavePointManagerX$SavePoint) - - At IonReaderTextRawTokensX.java:[lines 1937-1939] + + At IonReaderTextRawTokensX.java:[lines 1924-1926] - Un switch comporte un cas qui déborde sur le suivant - Un switch de com.amazon.ion.impl.IonReaderTextRawTokensX.skip_triple_quoted_string(UnifiedSavePointManagerX$SavePoint, IonReaderTextRawTokensX$CommentStrategy) comporte un cas qui déborde sur le suivant + Switch statement found where one case falls through to the next case + Switch statement found in com.amazon.ion.impl.IonReaderTextRawTokensX.skip_triple_quoted_string(UnifiedSavePointManagerX$SavePoint, IonReaderTextRawTokensX$CommentStrategy) where one case falls through to the next case - - At IonReaderTextRawTokensX.java:[lines 66-2915] + + At IonReaderTextRawTokensX.java:[lines 53-2902] In class com.amazon.ion.impl.IonReaderTextRawTokensX - + In method com.amazon.ion.impl.IonReaderTextRawTokensX.skip_triple_quoted_string(UnifiedSavePointManagerX$SavePoint, IonReaderTextRawTokensX$CommentStrategy) - - At IonReaderTextRawTokensX.java:[lines 2155-2157] + + At IonReaderTextRawTokensX.java:[lines 2142-2144] Switch statement found where default case is missing Switch statement found in com.amazon.ion.impl.IonReaderTextRawTokensX.read_escaped_char_content_helper(int, boolean) where default case is missing - - At IonReaderTextRawTokensX.java:[lines 66-2915] + + At IonReaderTextRawTokensX.java:[lines 53-2902] In class com.amazon.ion.impl.IonReaderTextRawTokensX - + In method com.amazon.ion.impl.IonReaderTextRawTokensX.read_escaped_char_content_helper(int, boolean) - - At IonReaderTextRawTokensX.java:[lines 2490-2509] + + At IonReaderTextRawTokensX.java:[lines 2477-2496] Switch statement found where default case is missing Switch statement found in com.amazon.ion.impl.IonReaderTextRawTokensX.skip_block_comment() where default case is missing - - At IonReaderTextRawTokensX.java:[lines 66-2915] + + At IonReaderTextRawTokensX.java:[lines 53-2902] In class com.amazon.ion.impl.IonReaderTextRawTokensX - + In method com.amazon.ion.impl.IonReaderTextRawTokensX.skip_block_comment() - - At IonReaderTextRawTokensX.java:[lines 940-953] + + At IonReaderTextRawTokensX.java:[lines 927-940] Switch statement found where default case is missing Switch statement found in com.amazon.ion.impl.IonReaderTextRawTokensX.skip_double_quoted_string_helper() where default case is missing - - At IonReaderTextRawTokensX.java:[lines 66-2915] + + At IonReaderTextRawTokensX.java:[lines 53-2902] In class com.amazon.ion.impl.IonReaderTextRawTokensX - + In method com.amazon.ion.impl.IonReaderTextRawTokensX.skip_double_quoted_string_helper() - - At IonReaderTextRawTokensX.java:[lines 2017-2028] + + At IonReaderTextRawTokensX.java:[lines 2004-2015] Switch statement found where default case is missing Switch statement found in com.amazon.ion.impl.IonReaderTextRawTokensX.skip_single_quoted_string(UnifiedSavePointManagerX$SavePoint) where default case is missing - - At IonReaderTextRawTokensX.java:[lines 66-2915] + + At IonReaderTextRawTokensX.java:[lines 53-2902] In class com.amazon.ion.impl.IonReaderTextRawTokensX - + In method com.amazon.ion.impl.IonReaderTextRawTokensX.skip_single_quoted_string(UnifiedSavePointManagerX$SavePoint) - - At IonReaderTextRawTokensX.java:[lines 1936-1944] + + At IonReaderTextRawTokensX.java:[lines 1923-1931] Switch statement found where default case is missing Switch statement found in com.amazon.ion.impl.IonReaderTextRawTokensX.skip_triple_quoted_string(UnifiedSavePointManagerX$SavePoint, IonReaderTextRawTokensX$CommentStrategy) where default case is missing - - At IonReaderTextRawTokensX.java:[lines 66-2915] + + At IonReaderTextRawTokensX.java:[lines 53-2902] In class com.amazon.ion.impl.IonReaderTextRawTokensX - + In method com.amazon.ion.impl.IonReaderTextRawTokensX.skip_triple_quoted_string(UnifiedSavePointManagerX$SavePoint, IonReaderTextRawTokensX$CommentStrategy) - - At IonReaderTextRawTokensX.java:[lines 2153-2176] + + At IonReaderTextRawTokensX.java:[lines 2140-2163] Condition has no effect Useless condition: it's known that c != -1 at this point - - At IonReaderTextRawTokensX.java:[lines 66-2915] + + At IonReaderTextRawTokensX.java:[lines 53-2902] In class com.amazon.ion.impl.IonReaderTextRawTokensX - + In method com.amazon.ion.impl.IonReaderTextRawTokensX.read_base64_getchar_helper(int) Value c != -1 - - Unreachable code at IonReaderTextRawTokensX.java:[line 2595] + + Unreachable code at IonReaderTextRawTokensX.java:[line 2582] - - At IonReaderTextRawTokensX.java:[line 2594] + + At IonReaderTextRawTokensX.java:[line 2581] Condition has no effect Useless condition: it's known that c != 125 ('}') at this point - - At IonReaderTextRawTokensX.java:[lines 66-2915] + + At IonReaderTextRawTokensX.java:[lines 53-2902] In class com.amazon.ion.impl.IonReaderTextRawTokensX - + In method com.amazon.ion.impl.IonReaderTextRawTokensX.read_base64_getchar_helper(int) Value c != 125 ('}') - - Unreachable code at IonReaderTextRawTokensX.java:[line 2595] + + Unreachable code at IonReaderTextRawTokensX.java:[line 2582] - - At IonReaderTextRawTokensX.java:[line 2594] + + At IonReaderTextRawTokensX.java:[line 2581] Condition has no effect Useless condition: it's known that len <= 0 at this point - - At IonReaderTextRawTokensX.java:[lines 66-2915] + + At IonReaderTextRawTokensX.java:[lines 53-2902] In class com.amazon.ion.impl.IonReaderTextRawTokensX - + In method com.amazon.ion.impl.IonReaderTextRawTokensX.read_hex_escape_sequence_value(int) Value len <= 0 - - Unreachable code at IonReaderTextRawTokensX.java:[line 2528] + + Unreachable code at IonReaderTextRawTokensX.java:[line 2515] - - At IonReaderTextRawTokensX.java:[line 2527] + + At IonReaderTextRawTokensX.java:[line 2514] - Un switch comporte un cas qui déborde sur le suivant - Un switch de com.amazon.ion.impl.IonReaderTextRawX.parseSymbolToken(String, StringBuilder, int) comporte un cas qui déborde sur le suivant + Switch statement found where one case falls through to the next case + Switch statement found in com.amazon.ion.impl.IonReaderTextRawX.parseSymbolToken(String, StringBuilder, int) where one case falls through to the next case At IonReaderTextRawX.java:[lines 72-1446] @@ -1061,10 +1023,10 @@ In class com.amazon.ion.impl.IonReaderTextRawX - + In method com.amazon.ion.impl.IonReaderTextRawX.parseSymbolToken(String, StringBuilder, int) - + At IonReaderTextRawX.java:[lines 756-758] @@ -1112,8 +1074,8 @@ - Un switch comporte un cas qui déborde sur le suivant - Un switch de com.amazon.ion.impl.IonTokenReader.readEscapedCharacter(PushbackReader, boolean) comporte un cas qui déborde sur le suivant + Switch statement found where one case falls through to the next case + Switch statement found in com.amazon.ion.impl.IonTokenReader.readEscapedCharacter(PushbackReader, boolean) where one case falls through to the next case At IonTokenReader.java:[lines 41-1620] @@ -1121,13 +1083,13 @@ In class com.amazon.ion.impl.IonTokenReader - + In method com.amazon.ion.impl.IonTokenReader.readEscapedCharacter(PushbackReader, boolean) - + At IonTokenReader.java:[lines 1167-1171] - + Another occurrence at IonTokenReader.java:[lines 1179-1183] @@ -1149,8 +1111,8 @@ - Comparaison d'objets String utilisant == ou != - Comparaison d'objets String utilisant == ou != dans com.amazon.ion.impl.IonTokenReader$Type.setNumericValue(IonTokenReader, String) + Comparison of String objects using == or != + Comparison of String objects using == or != in com.amazon.ion.impl.IonTokenReader$Type.setNumericValue(IonTokenReader, String) At IonTokenReader.java:[lines 54-209] @@ -1162,8 +1124,8 @@ In method com.amazon.ion.impl.IonTokenReader$Type.setNumericValue(IonTokenReader, String) - - At String.java:[lines 140-4657] + + At String.java:[lines 111-3141] Actual type String @@ -1179,8 +1141,8 @@ - Nom de classe devant commencer par une majuscule - Le nom de la classe com.amazon.ion.impl.IonTokenReader$Type$timeinfo ne commence pas par une majusucle + Class names should start with an upper case letter + The class name com.amazon.ion.impl.IonTokenReader$Type$timeinfo doesn't start with an upper case letter At IonTokenReader.java:[lines 132-143] @@ -1192,8 +1154,8 @@ - Ou binaire d'un octet signé - Ou binaire d'un octet signé calculé dans com.amazon.ion.impl.IonUTF8.getAs4BytesReversed(int) + Bitwise OR of signed byte value + Bitwise OR of signed byte value computed in com.amazon.ion.impl.IonUTF8.getAs4BytesReversed(int) At IonUTF8.java:[lines 35-406] @@ -1215,8 +1177,8 @@ - Ou binaire d'un octet signé - Ou binaire d'un octet signé calculé dans com.amazon.ion.impl.IonUTF8.packBytesAfter1(int, int) + Bitwise OR of signed byte value + Bitwise OR of signed byte value computed in com.amazon.ion.impl.IonUTF8.packBytesAfter1(int, int) At IonUTF8.java:[lines 35-406] @@ -1252,16 +1214,16 @@ - Test de nullité d'une valeur préalablement déréférencée - Test de nullité dans com.amazon.ion.impl.IonWriterSystemBinary._patch d'une valeur préalablement déréférencée dans com.amazon.ion.impl.IonWriterSystemBinary.closeValue() + Nullcheck of value previously dereferenced + Nullcheck of IonWriterSystemBinary._patch at line 495 of value previously dereferenced in com.amazon.ion.impl.IonWriterSystemBinary.closeValue() - - At IonWriterSystemBinary.java:[lines 40-1049] + + At IonWriterSystemBinary.java:[lines 27-1025] In class com.amazon.ion.impl.IonWriterSystemBinary - + In method com.amazon.ion.impl.IonWriterSystemBinary.closeValue() @@ -1270,16 +1232,16 @@ Value loaded from field com.amazon.ion.impl.IonWriterSystemBinary._patch - - At IonWriterSystemBinary.java:[line 518] + + At IonWriterSystemBinary.java:[line 494] - - Redundant null check at IonWriterSystemBinary.java:[line 519] + + Redundant null check at IonWriterSystemBinary.java:[line 495] - Synchronisation incohérente - Synchronisation incohérente de com.amazon.ion.impl.LocalSymbolTable.mySymbolNames; verrouillée à 58% + Inconsistent synchronization + Inconsistent synchronization of com.amazon.ion.impl.LocalSymbolTable.mySymbolNames; locked 58% of time At LocalSymbolTable.java:[lines 52-863] @@ -1333,8 +1295,8 @@ - Synchronisation incohérente - Synchronisation incohérente de com.amazon.ion.impl.LocalSymbolTable.mySymbolsCount; verrouillée à 81% + Inconsistent synchronization + Inconsistent synchronization of com.amazon.ion.impl.LocalSymbolTable.mySymbolsCount; locked 81% of time At LocalSymbolTable.java:[lines 52-863] @@ -1385,8 +1347,8 @@ - Méthode passant null à un paramètre déréférencé inconditionnellement - L'appel de méthode dans com.amazon.ion.impl.SimpleByteBuffer$SimpleByteReader.readTimestamp(int) passe null à un paramètre de com.amazon.ion.Timestamp.createFromUtcFields(Timestamp$Precision, int, int, int, int, int, int, BigDecimal, Integer) déréférencé de façon inconditionnelle + Method call passes null for non-null parameter + Null passed for non-null parameter of com.amazon.ion.Timestamp.createFromUtcFields(Timestamp$Precision, int, int, int, int, int, int, BigDecimal, Integer) in com.amazon.ion.impl.SimpleByteBuffer$SimpleByteReader.readTimestamp(int) At SimpleByteBuffer.java:[lines 135-660] @@ -1398,7 +1360,7 @@ In method com.amazon.ion.impl.SimpleByteBuffer$SimpleByteReader.readTimestamp(int) - + Called method com.amazon.ion.Timestamp.createFromUtcFields(Timestamp$Precision, int, int, int, int, int, int, BigDecimal, Integer) @@ -1419,8 +1381,8 @@ - Un switch comporte un cas qui déborde sur le suivant - Un switch de com.amazon.ion.impl.SimpleByteBuffer$SimpleByteWriter.writeVarInt(int, int, boolean) comporte un cas qui déborde sur le suivant + Switch statement found where one case falls through to the next case + Switch statement found in com.amazon.ion.impl.SimpleByteBuffer$SimpleByteWriter.writeVarInt(int, int, boolean) where one case falls through to the next case At SimpleByteBuffer.java:[lines 841-1383] @@ -1510,8 +1472,8 @@ - Une méthode expose sa représentation interne en incorporant une référence à un objet modifiable - La méthode new com.amazon.ion.impl.UnifiedDataPageX$Bytes(byte[], int, int) risque d'exposer sa représentation interne en stockant un objet externe modifiable dans com.amazon.ion.impl.UnifiedDataPageX$Bytes._bytes + May expose internal representation by incorporating reference to mutable object + new com.amazon.ion.impl.UnifiedDataPageX$Bytes(byte[], int, int) may expose internal representation by storing an externally mutable object into UnifiedDataPageX$Bytes._bytes At UnifiedDataPageX.java:[lines 168-204] @@ -1536,8 +1498,8 @@ - Une méthode expose sa représentation interne en incorporant une référence à un objet modifiable - La méthode new com.amazon.ion.impl.UnifiedDataPageX$Chars(char[], int, int) risque d'exposer sa représentation interne en stockant un objet externe modifiable dans com.amazon.ion.impl.UnifiedDataPageX$Chars._characters + May expose internal representation by incorporating reference to mutable object + new com.amazon.ion.impl.UnifiedDataPageX$Chars(char[], int, int) may expose internal representation by storing an externally mutable object into UnifiedDataPageX$Chars._characters At UnifiedDataPageX.java:[lines 210-251] @@ -1562,8 +1524,8 @@ - Une méthode expose sa représentation interne en incorporant une référence à un objet modifiable - La méthode new com.amazon.ion.impl._Private_FastAppendableDecorator(_Private_FastAppendable) risque d'exposer sa représentation interne en stockant un objet externe modifiable dans com.amazon.ion.impl._Private_FastAppendableDecorator.myOutput + May expose internal representation by incorporating reference to mutable object + new com.amazon.ion.impl._Private_FastAppendableDecorator(_Private_FastAppendable) may expose internal representation by storing an externally mutable object into _Private_FastAppendableDecorator.myOutput At _Private_FastAppendableDecorator.java:[lines 31-100] @@ -1588,8 +1550,8 @@ - Une méthode peut exposer sa représentation interne en renvoyant une référence à un objet modifiable - La méthode com.amazon.ion.impl._Private_IonBinaryWriterBuilder.getInitialSymbolTable() risque d'exposer sa représentation interne en renvoyant com.amazon.ion.impl._Private_IonBinaryWriterBuilder.myInitialSymbolTable + May expose internal representation by returning reference to mutable object + com.amazon.ion.impl._Private_IonBinaryWriterBuilder.getInitialSymbolTable() may expose internal representation by returning _Private_IonBinaryWriterBuilder.myInitialSymbolTable At _Private_IonBinaryWriterBuilder.java:[lines 39-414] @@ -1611,8 +1573,8 @@ - Une méthode expose sa représentation interne en incorporant une référence à un objet modifiable - La méthode com.amazon.ion.impl._Private_IonBinaryWriterBuilder.setInitialSymbolTable(SymbolTable) risque d'exposer sa représentation interne en stockant un objet externe modifiable dans com.amazon.ion.impl._Private_IonBinaryWriterBuilder.myInitialSymbolTable + May expose internal representation by incorporating reference to mutable object + com.amazon.ion.impl._Private_IonBinaryWriterBuilder.setInitialSymbolTable(SymbolTable) may expose internal representation by storing an externally mutable object into _Private_IonBinaryWriterBuilder.myInitialSymbolTable At _Private_IonBinaryWriterBuilder.java:[lines 39-414] @@ -1637,8 +1599,8 @@ - Un champ est un tableau modifiable - com.amazon.ion.impl._Private_IonConstants.BINARY_VERSION_MARKER_1_0 est un tableau modifiable + Field is a mutable array + com.amazon.ion.impl._Private_IonConstants.BINARY_VERSION_MARKER_1_0 is a mutable array At _Private_IonConstants.java:[lines 23-282] @@ -1656,8 +1618,8 @@ - Un champ est un tableau modifiable - com.amazon.ion.impl._Private_IonTextAppender.ZERO_PADDING est un tableau modifiable + Field is a mutable array + com.amazon.ion.impl._Private_IonTextAppender.ZERO_PADDING is a mutable array At _Private_IonTextAppender.java:[lines 43-1032] @@ -1670,13 +1632,13 @@ Field com.amazon.ion.impl._Private_IonTextAppender.ZERO_PADDING - + At _Private_IonTextAppender.java:[line 111] - Un champ devrait être package protected - com.amazon.ion.impl._Private_IonTextAppender.OPERATOR_CHAR_FLAGS devrait être package protected + Field should be package protected + com.amazon.ion.impl._Private_IonTextAppender.OPERATOR_CHAR_FLAGS should be package protected At _Private_IonTextAppender.java:[lines 43-1032] @@ -1711,11 +1673,11 @@ - Un champ n'est pas final alors qu'il devrait l'être - com.amazon.ion.impl._Private_IonTextWriterBuilder.STANDARD n'est pas final mais devrait l'être + Field isn't final but should be + com.amazon.ion.impl._Private_IonTextWriterBuilder.STANDARD isn't final but should be - - At _Private_IonTextWriterBuilder.java:[lines 33-329] + + At _Private_IonTextWriterBuilder.java:[lines 33-336] In class com.amazon.ion.impl._Private_IonTextWriterBuilder @@ -1730,8 +1692,8 @@ - Une méthode peut exposer sa représentation interne en renvoyant une référence à un objet modifiable - La méthode com.amazon.ion.impl._Private_MarkupCallback.getAppendable() risque d'exposer sa représentation interne en renvoyant com.amazon.ion.impl._Private_MarkupCallback.myAppendable + May expose internal representation by returning reference to mutable object + com.amazon.ion.impl._Private_MarkupCallback.getAppendable() may expose internal representation by returning _Private_MarkupCallback.myAppendable At _Private_MarkupCallback.java:[lines 190-351] @@ -1753,8 +1715,8 @@ - Une méthode expose sa représentation interne en incorporant une référence à un objet modifiable - La méthode new com.amazon.ion.impl._Private_MarkupCallback(_Private_FastAppendable) risque d'exposer sa représentation interne en stockant un objet externe modifiable dans com.amazon.ion.impl._Private_MarkupCallback.myAppendable + May expose internal representation by incorporating reference to mutable object + new com.amazon.ion.impl._Private_MarkupCallback(_Private_FastAppendable) may expose internal representation by storing an externally mutable object into _Private_MarkupCallback.myAppendable At _Private_MarkupCallback.java:[lines 190-351] @@ -1779,8 +1741,8 @@ - Un champ n'est pas final alors qu'il devrait l'être - com.amazon.ion.impl._Private_ScalarConversions.FNID_identity n'est pas final mais devrait l'être + Field isn't final but should be + com.amazon.ion.impl._Private_ScalarConversions.FNID_identity isn't final but should be At _Private_ScalarConversions.java:[lines 30-333] @@ -1798,8 +1760,8 @@ - Un champ n'est pas final alors qu'il devrait l'être - com.amazon.ion.impl._Private_ScalarConversions.FNID_no_conversion n'est pas final mais devrait l'être + Field isn't final but should be + com.amazon.ion.impl._Private_ScalarConversions.FNID_no_conversion isn't final but should be At _Private_ScalarConversions.java:[lines 30-333] @@ -1817,8 +1779,8 @@ - Transtypage non vérifié/non confirmé - Transtypage non vérifié/non confirmé de com.amazon.ion.Decimal vers value dans com.amazon.ion.impl._Private_ScalarConversions$ValueVariant.addValue(BigDecimal) + Unchecked/unconfirmed cast + Unchecked/unconfirmed cast from java.math.BigDecimal to com.amazon.ion.Decimal in com.amazon.ion.impl._Private_ScalarConversions$ValueVariant.addValue(BigDecimal) At _Private_ScalarConversions.java:[lines 373-873] @@ -1830,8 +1792,8 @@ In method com.amazon.ion.impl._Private_ScalarConversions$ValueVariant.addValue(BigDecimal) - - At BigDecimal.java:[lines 305-5815] + + At BigDecimal.java:[lines 224-5315] Actual type java.math.BigDecimal @@ -1849,8 +1811,8 @@ - Une méthode peut exposer sa représentation interne en renvoyant une référence à un objet modifiable - La méthode com.amazon.ion.impl._Private_ScalarConversions$ValueVariant.getDate() risque d'exposer sa représentation interne en renvoyant com.amazon.ion.impl._Private_ScalarConversions$ValueVariant._date_value + May expose internal representation by returning reference to mutable object + com.amazon.ion.impl._Private_ScalarConversions$ValueVariant.getDate() may expose internal representation by returning _Private_ScalarConversions$ValueVariant._date_value At _Private_ScalarConversions.java:[lines 373-873] @@ -1872,8 +1834,8 @@ - Une méthode peut exposer sa représentation interne en renvoyant une référence à un objet modifiable - La méthode com.amazon.ion.impl._Private_ScalarConversions$ValueVariant.getDecimal() risque d'exposer sa représentation interne en renvoyant com.amazon.ion.impl._Private_ScalarConversions$ValueVariant._decimal_value + May expose internal representation by returning reference to mutable object + com.amazon.ion.impl._Private_ScalarConversions$ValueVariant.getDecimal() may expose internal representation by returning _Private_ScalarConversions$ValueVariant._decimal_value At _Private_ScalarConversions.java:[lines 373-873] @@ -1895,8 +1857,8 @@ - Une méthode peut exposer sa représentation interne en renvoyant une référence à un objet modifiable - La méthode com.amazon.ion.impl._Private_ScalarConversions$ValueVariant.getTimestamp() risque d'exposer sa représentation interne en renvoyant com.amazon.ion.impl._Private_ScalarConversions$ValueVariant._timestamp_value + May expose internal representation by returning reference to mutable object + com.amazon.ion.impl._Private_ScalarConversions$ValueVariant.getTimestamp() may expose internal representation by returning _Private_ScalarConversions$ValueVariant._timestamp_value At _Private_ScalarConversions.java:[lines 373-873] @@ -1918,8 +1880,8 @@ - Une méthode expose sa représentation interne en incorporant une référence à un objet modifiable - La méthode com.amazon.ion.impl._Private_ScalarConversions$ValueVariant.addValue(Decimal) risque d'exposer sa représentation interne en stockant un objet externe modifiable dans com.amazon.ion.impl._Private_ScalarConversions$ValueVariant._decimal_value + May expose internal representation by incorporating reference to mutable object + com.amazon.ion.impl._Private_ScalarConversions$ValueVariant.addValue(Decimal) may expose internal representation by storing an externally mutable object into _Private_ScalarConversions$ValueVariant._decimal_value At _Private_ScalarConversions.java:[lines 373-873] @@ -1944,8 +1906,8 @@ - Une méthode expose sa représentation interne en incorporant une référence à un objet modifiable - La méthode com.amazon.ion.impl._Private_ScalarConversions$ValueVariant.addValue(Timestamp) risque d'exposer sa représentation interne en stockant un objet externe modifiable dans com.amazon.ion.impl._Private_ScalarConversions$ValueVariant._timestamp_value + May expose internal representation by incorporating reference to mutable object + com.amazon.ion.impl._Private_ScalarConversions$ValueVariant.addValue(Timestamp) may expose internal representation by storing an externally mutable object into _Private_ScalarConversions$ValueVariant._timestamp_value At _Private_ScalarConversions.java:[lines 373-873] @@ -1970,8 +1932,8 @@ - Une méthode expose sa représentation interne en incorporant une référence à un objet modifiable - La méthode com.amazon.ion.impl._Private_ScalarConversions$ValueVariant.addValue(BigDecimal) risque d'exposer sa représentation interne en stockant un objet externe modifiable dans com.amazon.ion.impl._Private_ScalarConversions$ValueVariant._decimal_value + May expose internal representation by incorporating reference to mutable object + com.amazon.ion.impl._Private_ScalarConversions$ValueVariant.addValue(BigDecimal) may expose internal representation by storing an externally mutable object into _Private_ScalarConversions$ValueVariant._decimal_value At _Private_ScalarConversions.java:[lines 373-873] @@ -1996,8 +1958,8 @@ - Une méthode expose sa représentation interne en incorporant une référence à un objet modifiable - La méthode com.amazon.ion.impl._Private_ScalarConversions$ValueVariant.addValue(Date) risque d'exposer sa représentation interne en stockant un objet externe modifiable dans com.amazon.ion.impl._Private_ScalarConversions$ValueVariant._date_value + May expose internal representation by incorporating reference to mutable object + com.amazon.ion.impl._Private_ScalarConversions$ValueVariant.addValue(Date) may expose internal representation by storing an externally mutable object into _Private_ScalarConversions$ValueVariant._date_value At _Private_ScalarConversions.java:[lines 373-873] @@ -2022,8 +1984,8 @@ - Une méthode expose sa représentation interne en incorporant une référence à un objet modifiable - La méthode com.amazon.ion.impl._Private_ScalarConversions$ValueVariant.setValue(Decimal) risque d'exposer sa représentation interne en stockant un objet externe modifiable dans com.amazon.ion.impl._Private_ScalarConversions$ValueVariant._decimal_value + May expose internal representation by incorporating reference to mutable object + com.amazon.ion.impl._Private_ScalarConversions$ValueVariant.setValue(Decimal) may expose internal representation by storing an externally mutable object into _Private_ScalarConversions$ValueVariant._decimal_value At _Private_ScalarConversions.java:[lines 373-873] @@ -2048,8 +2010,8 @@ - Une méthode expose sa représentation interne en incorporant une référence à un objet modifiable - La méthode com.amazon.ion.impl._Private_ScalarConversions$ValueVariant.setValue(Timestamp) risque d'exposer sa représentation interne en stockant un objet externe modifiable dans com.amazon.ion.impl._Private_ScalarConversions$ValueVariant._timestamp_value + May expose internal representation by incorporating reference to mutable object + com.amazon.ion.impl._Private_ScalarConversions$ValueVariant.setValue(Timestamp) may expose internal representation by storing an externally mutable object into _Private_ScalarConversions$ValueVariant._timestamp_value At _Private_ScalarConversions.java:[lines 373-873] @@ -2074,8 +2036,8 @@ - Une méthode expose sa représentation interne en incorporant une référence à un objet modifiable - La méthode com.amazon.ion.impl._Private_ScalarConversions$ValueVariant.setValue(Date) risque d'exposer sa représentation interne en stockant un objet externe modifiable dans com.amazon.ion.impl._Private_ScalarConversions$ValueVariant._date_value + May expose internal representation by incorporating reference to mutable object + com.amazon.ion.impl._Private_ScalarConversions$ValueVariant.setValue(Date) may expose internal representation by storing an externally mutable object into _Private_ScalarConversions$ValueVariant._date_value At _Private_ScalarConversions.java:[lines 373-873] @@ -2100,8 +2062,8 @@ - Une méthode expose sa représentation interne en incorporant une référence à un objet modifiable - La méthode com.amazon.ion.impl._Private_SymtabExtendsCache.symtabsCompat(SymbolTable, SymbolTable) risque d'exposer sa représentation interne en stockant un objet externe modifiable dans com.amazon.ion.impl._Private_SymtabExtendsCache.myReaderSymtab + May expose internal representation by incorporating reference to mutable object + com.amazon.ion.impl._Private_SymtabExtendsCache.symtabsCompat(SymbolTable, SymbolTable) may expose internal representation by storing an externally mutable object into _Private_SymtabExtendsCache.myReaderSymtab At _Private_SymtabExtendsCache.java:[lines 27-64] @@ -2126,8 +2088,8 @@ - Une méthode expose sa représentation interne en incorporant une référence à un objet modifiable - La méthode com.amazon.ion.impl._Private_SymtabExtendsCache.symtabsCompat(SymbolTable, SymbolTable) risque d'exposer sa représentation interne en stockant un objet externe modifiable dans com.amazon.ion.impl._Private_SymtabExtendsCache.myWriterSymtab + May expose internal representation by incorporating reference to mutable object + com.amazon.ion.impl._Private_SymtabExtendsCache.symtabsCompat(SymbolTable, SymbolTable) may expose internal representation by storing an externally mutable object into _Private_SymtabExtendsCache.myWriterSymtab At _Private_SymtabExtendsCache.java:[lines 27-64] @@ -2202,32 +2164,9 @@ At IonManagedBinaryWriter.java:[lines 499-505] - - Une méthode peut exposer sa représentation interne en renvoyant une référence à un objet modifiable - La méthode com.amazon.ion.impl.bin.utf8.PoolableByteBuffer.getBuffer() risque d'exposer sa représentation interne en renvoyant com.amazon.ion.impl.bin.utf8.PoolableByteBuffer.buffer - - - At PoolableByteBuffer.java:[lines 12-31] - - In class com.amazon.ion.impl.bin.utf8.PoolableByteBuffer - - - - In method com.amazon.ion.impl.bin.utf8.PoolableByteBuffer.getBuffer() - - - - In PoolableByteBuffer.java - - Field com.amazon.ion.impl.bin.utf8.PoolableByteBuffer.buffer - - - At PoolableByteBuffer.java:[line 31] - - - Une méthode peut exposer sa représentation interne en renvoyant une référence à un objet modifiable - La méthode com.amazon.ion.impl.bin.utf8.Utf8StringEncoder$Result.getBuffer() risque d'exposer sa représentation interne en renvoyant com.amazon.ion.impl.bin.utf8.Utf8StringEncoder$Result.buffer + May expose internal representation by returning reference to mutable object + com.amazon.ion.impl.bin.utf8.Utf8StringEncoder$Result.getBuffer() may expose internal representation by returning Utf8StringEncoder$Result.buffer At Utf8StringEncoder.java:[lines 128-148] @@ -2249,8 +2188,8 @@ - Une méthode expose sa représentation interne en incorporant une référence à un objet modifiable - La méthode new com.amazon.ion.impl.bin.utf8.Utf8StringEncoder$Result(int, byte[]) risque d'exposer sa représentation interne en stockant un objet externe modifiable dans com.amazon.ion.impl.bin.utf8.Utf8StringEncoder$Result.buffer + May expose internal representation by incorporating reference to mutable object + new com.amazon.ion.impl.bin.utf8.Utf8StringEncoder$Result(int, byte[]) may expose internal representation by storing an externally mutable object into Utf8StringEncoder$Result.buffer At Utf8StringEncoder.java:[lines 128-148] @@ -2298,8 +2237,8 @@ - Une méthode peut exposer sa représentation interne en renvoyant une référence à un objet modifiable - La méthode com.amazon.ion.impl.lite.IonContainerLite$SequenceContentIterator.next() risque d'exposer sa représentation interne en renvoyant com.amazon.ion.impl.lite.IonContainerLite$SequenceContentIterator.__current + May expose internal representation by returning reference to mutable object + com.amazon.ion.impl.lite.IonContainerLite$SequenceContentIterator.next() may expose internal representation by returning IonContainerLite$SequenceContentIterator.__current At IonContainerLite.java:[lines 275-463] @@ -2321,8 +2260,8 @@ - Une méthode peut exposer sa représentation interne en renvoyant une référence à un objet modifiable - La méthode com.amazon.ion.impl.lite.IonContainerLite$SequenceContentIterator.previous() risque d'exposer sa représentation interne en renvoyant com.amazon.ion.impl.lite.IonContainerLite$SequenceContentIterator.__current + May expose internal representation by returning reference to mutable object + com.amazon.ion.impl.lite.IonContainerLite$SequenceContentIterator.previous() may expose internal representation by returning IonContainerLite$SequenceContentIterator.__current At IonContainerLite.java:[lines 275-463] @@ -2344,8 +2283,8 @@ - Une méthode expose sa représentation interne en incorporant une référence à un objet modifiable - La méthode new com.amazon.ion.impl.lite.IonContainerLite$SequenceContentIterator(IonContainerLite, int, boolean) risque d'exposer sa représentation interne en stockant un objet externe modifiable dans com.amazon.ion.impl.lite.IonContainerLite$SequenceContentIterator.this$0 + May expose internal representation by incorporating reference to mutable object + new com.amazon.ion.impl.lite.IonContainerLite$SequenceContentIterator(IonContainerLite, int, boolean) may expose internal representation by storing an externally mutable object into IonContainerLite$SequenceContentIterator.this$0 At IonContainerLite.java:[lines 275-463] @@ -2370,8 +2309,8 @@ - Auto-alimentation d'un champs - Auto-alimentation du champs com.amazon.ion.impl.lite.IonContainerLite$SequenceContentIterator.__pos dans com.amazon.ion.impl.lite.IonContainerLite$SequenceContentIterator.force_position_sync_helper() + Self assignment of field + Self assignment of field IonContainerLite$SequenceContentIterator.__pos in com.amazon.ion.impl.lite.IonContainerLite$SequenceContentIterator.force_position_sync_helper() At IonContainerLite.java:[lines 275-463] @@ -2393,16 +2332,16 @@ - Une méthode expose sa représentation interne en incorporant une référence à un objet modifiable - La méthode new com.amazon.ion.impl.lite.IonDatagramLite$SystemContentIterator(IonDatagramLite, boolean) risque d'exposer sa représentation interne en stockant un objet externe modifiable dans com.amazon.ion.impl.lite.IonDatagramLite$SystemContentIterator.this$0 + May expose internal representation by incorporating reference to mutable object + new com.amazon.ion.impl.lite.IonDatagramLite$SystemContentIterator(IonDatagramLite, boolean) may expose internal representation by storing an externally mutable object into IonDatagramLite$SystemContentIterator.this$0 - - At IonDatagramLite.java:[lines 585-834] + + At IonDatagramLite.java:[lines 572-821] In class com.amazon.ion.impl.lite.IonDatagramLite$SystemContentIterator - + In method new com.amazon.ion.impl.lite.IonDatagramLite$SystemContentIterator(IonDatagramLite, boolean) @@ -2414,21 +2353,21 @@ Local variable named this$0 - - At IonDatagramLite.java:[line 595] + + At IonDatagramLite.java:[line 582] - Test de nullité d'une valeur préalablement déréférencée - Test de nullité dans com.amazon.ion.impl.lite.IonDatagramLite$SystemIteratorPosition.__local_values d'une valeur préalablement déréférencée dans com.amazon.ion.impl.lite.IonDatagramLite$SystemIteratorPosition.push_system_value(IonValueLite) + Nullcheck of value previously dereferenced + Nullcheck of IonDatagramLite$SystemIteratorPosition.__local_values at line 1062 of value previously dereferenced in com.amazon.ion.impl.lite.IonDatagramLite$SystemIteratorPosition.push_system_value(IonValueLite) - - At IonDatagramLite.java:[lines 837-1138] + + At IonDatagramLite.java:[lines 824-1125] In class com.amazon.ion.impl.lite.IonDatagramLite$SystemIteratorPosition - + In method com.amazon.ion.impl.lite.IonDatagramLite$SystemIteratorPosition.push_system_value(IonValueLite) @@ -2437,52 +2376,52 @@ Value loaded from field com.amazon.ion.impl.lite.IonDatagramLite$SystemIteratorPosition.__local_values - - At IonDatagramLite.java:[line 1074] + + At IonDatagramLite.java:[line 1061] - - Redundant null check at IonDatagramLite.java:[line 1075] + + Redundant null check at IonDatagramLite.java:[line 1062] - Test de nullité d'une valeur préalablement déréférencée - Test de nullité dans curr d'une valeur préalablement déréférencée dans com.amazon.ion.impl.lite.IonDatagramLite$SystemIteratorPosition.count_system_values(IonSystem, SymbolTable, SymbolTable) + Nullcheck of value previously dereferenced + Nullcheck of curr at line 1121 of value previously dereferenced in com.amazon.ion.impl.lite.IonDatagramLite$SystemIteratorPosition.count_system_values(IonSystem, SymbolTable, SymbolTable) - - At IonDatagramLite.java:[lines 837-1138] + + At IonDatagramLite.java:[lines 824-1125] In class com.amazon.ion.impl.lite.IonDatagramLite$SystemIteratorPosition - + In method com.amazon.ion.impl.lite.IonDatagramLite$SystemIteratorPosition.count_system_values(IonSystem, SymbolTable, SymbolTable) Value loaded from curr - - At IonDatagramLite.java:[line 1129] + + At IonDatagramLite.java:[line 1116] - - Redundant null check at IonDatagramLite.java:[line 1134] + + Redundant null check at IonDatagramLite.java:[line 1121] - Comparaison d'objets String utilisant == ou != - Comparaison d'objets String utilisant == ou != dans com.amazon.ion.impl.lite.IonStructLite.validate() + Comparison of String objects using == or != + Comparison of String objects using == or != in com.amazon.ion.impl.lite.IonStructLite.validate() - - At IonStructLite.java:[lines 38-753] + + At IonStructLite.java:[lines 25-740] In class com.amazon.ion.impl.lite.IonStructLite - + In method com.amazon.ion.impl.lite.IonStructLite.validate() - - At String.java:[lines 140-4657] + + At String.java:[lines 111-3141] Actual type String @@ -2492,51 +2431,51 @@ Value loaded from error - - At IonStructLite.java:[line 230] + + At IonStructLite.java:[line 217] - Chargement d'une valeur connue pour être à null - Chargement d'une valeur connue pour être à null dans com.amazon.ion.impl.lite.IonStructLite.add(SymbolToken, IonValue) + Load of known null value + Load of known null value in com.amazon.ion.impl.lite.IonStructLite.add(SymbolToken, IonValue) - - At IonStructLite.java:[lines 38-753] + + At IonStructLite.java:[lines 25-740] In class com.amazon.ion.impl.lite.IonStructLite - + In method com.amazon.ion.impl.lite.IonStructLite.add(SymbolToken, IonValue) - + Value loaded from text - - At IonStructLite.java:[line 500] + + At IonStructLite.java:[line 487] - La méthode concatène des chaînes au moyen de + en boucle - La méthode com.amazon.ion.impl.lite.IonStructLite.validate() concatène des chaînes au moyen de + en boucle + Method concatenates strings using + in a loop + com.amazon.ion.impl.lite.IonStructLite.validate() concatenates strings using + in a loop - - At IonStructLite.java:[lines 38-753] + + At IonStructLite.java:[lines 25-740] In class com.amazon.ion.impl.lite.IonStructLite - + In method com.amazon.ion.impl.lite.IonStructLite.validate() - - At IonStructLite.java:[line 226] + + At IonStructLite.java:[line 213] - Décalage à droite non signé et transtypage short/byte - Décalage à droite non signé et transtypage vers un short/byte dans com.amazon.ion.impl.lite.ReverseBinaryEncoder.writeVarInt(int) + Unsigned right shift cast to short/byte + Unsigned right shift cast to short/byte in com.amazon.ion.impl.lite.ReverseBinaryEncoder.writeVarInt(int) At ReverseBinaryEncoder.java:[lines 90-1464] @@ -2558,8 +2497,8 @@ - Décalage à droite non signé et transtypage short/byte - Décalage à droite non signé et transtypage vers un short/byte dans com.amazon.ion.impl.lite.ReverseBinaryEncoder.writeVarUInt(int) + Unsigned right shift cast to short/byte + Unsigned right shift cast to short/byte in com.amazon.ion.impl.lite.ReverseBinaryEncoder.writeVarUInt(int) At ReverseBinaryEncoder.java:[lines 90-1464] @@ -2583,17 +2522,40 @@ Another occurrence at ReverseBinaryEncoder.java:[line 513] + + May expose internal representation by returning reference to mutable object + com.amazon.ion.system.IonReaderBuilder.getInputStreamInterceptors() may expose internal representation by returning IonReaderBuilder.DETECTED_STREAM_INTERCEPTORS + + + At IonReaderBuilder.java:[lines 40-361] + + In class com.amazon.ion.system.IonReaderBuilder + + + + In method com.amazon.ion.system.IonReaderBuilder.getInputStreamInterceptors() + + + + In IonReaderBuilder.java + + Field com.amazon.ion.system.IonReaderBuilder.DETECTED_STREAM_INTERCEPTORS + + + At IonReaderBuilder.java:[line 338] + + - Une méthode peut exposer sa représentation interne en renvoyant une référence à un objet modifiable - La méthode com.amazon.ion.system.IonSystemBuilder.getIonBinaryWriterBuilder() risque d'exposer sa représentation interne en renvoyant com.amazon.ion.system.IonSystemBuilder.binaryWriterBuilder + May expose internal representation by returning reference to mutable object + com.amazon.ion.system.IonSystemBuilder.getIonBinaryWriterBuilder() may expose internal representation by returning IonSystemBuilder.binaryWriterBuilder - - At IonSystemBuilder.java:[lines 86-465] + + At IonSystemBuilder.java:[lines 73-452] In class com.amazon.ion.system.IonSystemBuilder - + In method com.amazon.ion.system.IonSystemBuilder.getIonBinaryWriterBuilder() @@ -2602,21 +2564,21 @@ Field com.amazon.ion.system.IonSystemBuilder.binaryWriterBuilder - - At IonSystemBuilder.java:[line 345] + + At IonSystemBuilder.java:[line 332] - Une méthode peut exposer sa représentation interne en renvoyant une référence à un objet modifiable - La méthode com.amazon.ion.system.IonSystemBuilder.getIonTextWriterBuilder() risque d'exposer sa représentation interne en renvoyant com.amazon.ion.system.IonSystemBuilder.textWriterBuilder + May expose internal representation by returning reference to mutable object + com.amazon.ion.system.IonSystemBuilder.getIonTextWriterBuilder() may expose internal representation by returning IonSystemBuilder.textWriterBuilder - - At IonSystemBuilder.java:[lines 86-465] + + At IonSystemBuilder.java:[lines 73-452] In class com.amazon.ion.system.IonSystemBuilder - + In method com.amazon.ion.system.IonSystemBuilder.getIonTextWriterBuilder() @@ -2625,21 +2587,21 @@ Field com.amazon.ion.system.IonSystemBuilder.textWriterBuilder - - At IonSystemBuilder.java:[line 292] + + At IonSystemBuilder.java:[line 279] - Une méthode peut exposer sa représentation interne en renvoyant une référence à un objet modifiable - La méthode com.amazon.ion.system.IonSystemBuilder.getReaderBuilder() risque d'exposer sa représentation interne en renvoyant com.amazon.ion.system.IonSystemBuilder.readerBuilder + May expose internal representation by returning reference to mutable object + com.amazon.ion.system.IonSystemBuilder.getReaderBuilder() may expose internal representation by returning IonSystemBuilder.readerBuilder - - At IonSystemBuilder.java:[lines 86-465] + + At IonSystemBuilder.java:[lines 73-452] In class com.amazon.ion.system.IonSystemBuilder - + In method com.amazon.ion.system.IonSystemBuilder.getReaderBuilder() @@ -2648,21 +2610,21 @@ Field com.amazon.ion.system.IonSystemBuilder.readerBuilder - - At IonSystemBuilder.java:[line 398] + + At IonSystemBuilder.java:[line 385] - Une méthode expose sa représentation interne en incorporant une référence à un objet modifiable - La méthode com.amazon.ion.system.IonSystemBuilder.setIonBinaryWriterBuilder(IonBinaryWriterBuilder) risque d'exposer sa représentation interne en stockant un objet externe modifiable dans com.amazon.ion.system.IonSystemBuilder.binaryWriterBuilder + May expose internal representation by incorporating reference to mutable object + com.amazon.ion.system.IonSystemBuilder.setIonBinaryWriterBuilder(IonBinaryWriterBuilder) may expose internal representation by storing an externally mutable object into IonSystemBuilder.binaryWriterBuilder - - At IonSystemBuilder.java:[lines 86-465] + + At IonSystemBuilder.java:[lines 73-452] In class com.amazon.ion.system.IonSystemBuilder - + In method com.amazon.ion.system.IonSystemBuilder.setIonBinaryWriterBuilder(IonBinaryWriterBuilder) @@ -2674,21 +2636,21 @@ Local variable stored in JVM register ? - - At IonSystemBuilder.java:[line 364] + + At IonSystemBuilder.java:[line 351] - Une méthode expose sa représentation interne en incorporant une référence à un objet modifiable - La méthode com.amazon.ion.system.IonSystemBuilder.setIonTextWriterBuilder(IonTextWriterBuilder) risque d'exposer sa représentation interne en stockant un objet externe modifiable dans com.amazon.ion.system.IonSystemBuilder.textWriterBuilder + May expose internal representation by incorporating reference to mutable object + com.amazon.ion.system.IonSystemBuilder.setIonTextWriterBuilder(IonTextWriterBuilder) may expose internal representation by storing an externally mutable object into IonSystemBuilder.textWriterBuilder - - At IonSystemBuilder.java:[lines 86-465] + + At IonSystemBuilder.java:[lines 73-452] In class com.amazon.ion.system.IonSystemBuilder - + In method com.amazon.ion.system.IonSystemBuilder.setIonTextWriterBuilder(IonTextWriterBuilder) @@ -2700,21 +2662,21 @@ Local variable stored in JVM register ? - - At IonSystemBuilder.java:[line 311] + + At IonSystemBuilder.java:[line 297] - Une méthode expose sa représentation interne en incorporant une référence à un objet modifiable - La méthode com.amazon.ion.system.IonSystemBuilder.setReaderBuilder(IonReaderBuilder) risque d'exposer sa représentation interne en stockant un objet externe modifiable dans com.amazon.ion.system.IonSystemBuilder.readerBuilder + May expose internal representation by incorporating reference to mutable object + com.amazon.ion.system.IonSystemBuilder.setReaderBuilder(IonReaderBuilder) may expose internal representation by storing an externally mutable object into IonSystemBuilder.readerBuilder - - At IonSystemBuilder.java:[lines 86-465] + + At IonSystemBuilder.java:[lines 73-452] In class com.amazon.ion.system.IonSystemBuilder - + In method com.amazon.ion.system.IonSystemBuilder.setReaderBuilder(IonReaderBuilder) @@ -2726,21 +2688,21 @@ Local variable stored in JVM register ? - - At IonSystemBuilder.java:[line 417] + + At IonSystemBuilder.java:[line 404] - Une méthode statique publique risque d'exposer une représentation interne en renvoyant un tableau - La méthode statique publique com.amazon.ion.system.IonSystemBuilder.standard() peut exposer une représentation interne en renvoyant com.amazon.ion.system.IonSystemBuilder.STANDARD + Public static method may expose internal representation by returning array + Public static com.amazon.ion.system.IonSystemBuilder.standard() may expose internal representation by returning IonSystemBuilder.STANDARD - - At IonSystemBuilder.java:[lines 86-465] + + At IonSystemBuilder.java:[lines 73-452] In class com.amazon.ion.system.IonSystemBuilder - + In method com.amazon.ion.system.IonSystemBuilder.standard() @@ -2749,8 +2711,8 @@ Field com.amazon.ion.system.IonSystemBuilder.STANDARD - - At IonSystemBuilder.java:[line 98] + + At IonSystemBuilder.java:[line 85] @@ -2771,8 +2733,8 @@ - Méthode equals() ne vérifiant pas la nullité - com.amazon.ion.util.Equivalence$Field.equals(Object) ne vérifie pas la nullité d'un paramètre + equals() method does not check for null argument + com.amazon.ion.util.Equivalence$Field.equals(Object) does not check for null argument At Equivalence.java:[lines 444-496] @@ -2791,8 +2753,8 @@ - Une méthode peut exposer sa représentation interne en renvoyant une référence à un objet modifiable - La méthode com.amazon.ion.util.JarInfo.getBuildTime() risque d'exposer sa représentation interne en renvoyant com.amazon.ion.util.JarInfo.ourBuildTime + May expose internal representation by returning reference to mutable object + com.amazon.ion.util.JarInfo.getBuildTime() may expose internal representation by returning JarInfo.ourBuildTime At JarInfo.java:[lines 43-120] @@ -2814,8 +2776,8 @@ - Une méthode expose sa représentation interne en incorporant une référence à un objet modifiable - La méthode new com.amazon.ion.util.Printer$Options(Printer) risque d'exposer sa représentation interne en stockant un objet externe modifiable dans com.amazon.ion.util.Printer$Options.this$0 + May expose internal representation by incorporating reference to mutable object + new com.amazon.ion.util.Printer$Options(Printer) may expose internal representation by storing an externally mutable object into Printer$Options.this$0 At Printer.java:[lines 88-115] @@ -2840,8 +2802,8 @@ - Devrait être une classe interne statique - La classe com.amazon.ion.util.Printer$Options devrait-elle être une classe interne static ? + Should be a static inner class + Should com.amazon.ion.util.Printer$Options be a _static_ inner class? At Printer.java:[lines 88-115] diff --git a/src/main/java/com/amazon/ion/impl/_Private_IonReaderBuilder.java b/src/main/java/com/amazon/ion/impl/_Private_IonReaderBuilder.java index c7e56f8abd..a7c50db90b 100644 --- a/src/main/java/com/amazon/ion/impl/_Private_IonReaderBuilder.java +++ b/src/main/java/com/amazon/ion/impl/_Private_IonReaderBuilder.java @@ -1,6 +1,5 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 - package com.amazon.ion.impl; import com.amazon.ion.IonCatalog; @@ -8,6 +7,7 @@ import com.amazon.ion.IonReader; import com.amazon.ion.IonTextReader; import com.amazon.ion.IonValue; +import com.amazon.ion.util.InputStreamInterceptor; import com.amazon.ion.system.IonReaderBuilder; import com.amazon.ion.util.IonStreamUtils; @@ -16,7 +16,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.Reader; -import java.util.zip.GZIPInputStream; +import java.util.Collections; +import java.util.List; import static com.amazon.ion.impl.LocalSymbolTable.DEFAULT_LST_FACTORY; import static com.amazon.ion.impl._Private_IonReaderFactory.makeReader; @@ -99,7 +100,7 @@ protected void mutationCheck() { * The TwoElementSequenceInputStream allows the second delegate InputStream to return valid data if it subsequently * receives more data, which is common when performing continuable reads. */ - private static final class TwoElementSequenceInputStream extends InputStream { + private static final class TwoElementInputStream extends InputStream { /** * The first InputStream in the sequence. @@ -121,7 +122,7 @@ private static final class TwoElementSequenceInputStream extends InputStream { * @param first first InputStream in the sequence. * @param last last InputStream in the sequence. */ - private TwoElementSequenceInputStream(final InputStream first, final InputStream last) { + private TwoElementInputStream(final InputStream first, final InputStream last) { this.first = first; this.last = last; this.in = first; @@ -192,6 +193,18 @@ interface IonReaderFromBytesFactoryBinary { IonReader makeReader(_Private_IonReaderBuilder builder, byte[] ionData, int offset, int length); } + private static void validateHeaderLength(int maxHeaderLength) { + if (maxHeaderLength > _Private_IonConstants.ARRAY_MAXIMUM_SIZE) { + // Note: we could choose an arbitrary limit lower than this. The purpose at this point is to avoid OOM + // in the case where Java cannot allocate an array of the requested size. + throw new IonException(String.format( + "The maximum header length %d exceeds the maximum array size %d.", + maxHeaderLength, + _Private_IonConstants.ARRAY_MAXIMUM_SIZE + )); + } + } + static IonReader buildReader( _Private_IonReaderBuilder builder, byte[] ionData, @@ -200,16 +213,29 @@ static IonReader buildReader( IonReaderFromBytesFactoryBinary binary, IonReaderFromBytesFactoryText text ) { - if (IonStreamUtils.isGzip(ionData, offset, length)) { - try { - return buildReader( - builder, - new GZIPInputStream(new ByteArrayInputStream(ionData, offset, length)), - _Private_IonReaderFactory::makeReaderBinary, - _Private_IonReaderFactory::makeReaderText - ); - } catch (IOException e) { - throw new IonException(e); + List streamInterceptors = builder.getInputStreamInterceptors(); + for (InputStreamInterceptor streamInterceptor : streamInterceptors) { + int headerLength = streamInterceptor.numberOfBytesNeededToDetermineMatch(); + validateHeaderLength(headerLength); + if (length < headerLength) { + continue; + } + if (streamInterceptor.isMatch(ionData, offset, length)) { + try { + return buildReader( + builder, + streamInterceptor.newInputStream(new ByteArrayInputStream(ionData, offset, length)), + _Private_IonReaderFactory::makeReaderBinary, + _Private_IonReaderFactory::makeReaderText, + // The builder provides only one level of detection, e.g. GZIP-compressed binary Ion *or* + // zstd-compressed binary Ion; *not* GZIP-compressed zstd-compressed binary Ion. Users that + // need to intercept multiple format layers can provide a custom InputStreamInterceptor to + // achieve this. + /*inputStreamInterceptors=*/ Collections.emptyList() + ); + } catch (IOException e) { + throw new IonException(e); + } } } if (IonStreamUtils.isIonBinary(ionData, offset, length)) { @@ -247,15 +273,6 @@ private static boolean startsWithIvm(byte[] buffer, int length) { return true; } - static final byte[] GZIP_HEADER = {0x1F, (byte) 0x8B}; - - private static boolean startsWithGzipHeader(byte[] buffer, int length) { - if (length >= GZIP_HEADER.length) { - return buffer[0] == GZIP_HEADER[0] && buffer[1] == GZIP_HEADER[1]; - } - return false; - } - @FunctionalInterface interface IonReaderFromInputStreamFactoryText { IonReader makeReader(IonCatalog catalog, InputStream source, _Private_LocalSymbolTableFactory lstFactory); @@ -266,27 +283,62 @@ interface IonReaderFromInputStreamFactoryBinary { IonReader makeReader(_Private_IonReaderBuilder builder, InputStream source, byte[] alreadyRead, int alreadyReadOff, int alreadyReadLen); } + /** + * Reads from the given source into the given byte array, stopping once either + *
    + *
  1. `length` bytes have been read, or
  2. + *
  3. the end of the source stream has been reached, or
  4. + *
  5. the source stream throws an exception.
  6. + *
+ * @param source the source of the bytes to read. + * @param destination the destination for the bytes read. + * @param length the number of bytes to attempt to read. + * @return the number of bytes read into `destination`. + */ + private static int fillToLengthOrStreamEnd(InputStream source, byte[] destination, int length) { + int bytesRead = 0; + while (bytesRead < length) { + int bytesToRead = length - bytesRead; + int bytesReadThisIteration; + try { + bytesReadThisIteration = source.read(destination, bytesRead, bytesToRead); + } catch (EOFException e) { + // Some InputStream implementations throw EOFException in certain cases to convey + // that the end of the stream has been reached. + break; + } catch (IOException e) { + throw new IonException(e); + } + if (bytesReadThisIteration < 0) { // This indicates the end of the stream. + break; + } + bytesRead += bytesReadThisIteration; + } + return bytesRead; + } + static IonReader buildReader( _Private_IonReaderBuilder builder, InputStream source, IonReaderFromInputStreamFactoryBinary binary, - IonReaderFromInputStreamFactoryText text + IonReaderFromInputStreamFactoryText text, + List inputStreamInterceptors ) { if (source == null) { throw new NullPointerException("Cannot build a reader from a null InputStream."); } + int maxHeaderLength = Math.max( + _Private_IonConstants.BINARY_VERSION_MARKER_SIZE, + inputStreamInterceptors.stream().mapToInt(InputStreamInterceptor::numberOfBytesNeededToDetermineMatch).max().orElse(0) + ); + validateHeaderLength(maxHeaderLength); // Note: this can create a lot of layers of InputStream wrappers. For example, if this method is called // from build(byte[]) and the bytes contain GZIP, the chain will be SequenceInputStream(ByteArrayInputStream, // GZIPInputStream -> PushbackInputStream -> ByteArrayInputStream). If this creates a drag on efficiency, // alternatives should be evaluated. - byte[] possibleIVM = new byte[_Private_IonConstants.BINARY_VERSION_MARKER_SIZE]; + byte[] possibleIVM = new byte[maxHeaderLength]; InputStream ionData = source; - int bytesRead; - try { - bytesRead = ionData.read(possibleIVM); - } catch (IOException e) { - throw new IonException(e); - } + int bytesRead = fillToLengthOrStreamEnd(ionData, possibleIVM, maxHeaderLength); // If the input stream is growing, it is possible that fewer than BINARY_VERSION_MARKER_SIZE bytes are // available yet. Simply check whether the stream *could* contain binary Ion based on the available bytes. // If it can't, fall back to text. @@ -296,19 +348,20 @@ static IonReader buildReader( // stream will always be empty (in which case it doesn't matter whether a text or binary reader is used) // or it's a binary stream (in which case the correct reader was created) or it's a growing text stream // (which has always been unsupported). - if (startsWithGzipHeader(possibleIVM, bytesRead)) { - try { - ionData = new GZIPInputStream( - new TwoElementSequenceInputStream(new ByteArrayInputStream(possibleIVM, 0, bytesRead), ionData) - ); + for (InputStreamInterceptor streamInterceptor : inputStreamInterceptors) { + if (bytesRead < streamInterceptor.numberOfBytesNeededToDetermineMatch()) { + continue; + } + if (streamInterceptor.isMatch(possibleIVM, 0, bytesRead)) { try { - bytesRead = ionData.read(possibleIVM); - } catch (EOFException e) { - // Only a GZIP header was available, so this may be a binary Ion stream. - bytesRead = 0; + ionData = streamInterceptor.newInputStream( + new TwoElementInputStream(new ByteArrayInputStream(possibleIVM, 0, bytesRead), ionData) + ); + } catch (IOException e) { + throw new IonException(e); } - } catch (IOException e) { - throw new IonException(e); + bytesRead = fillToLengthOrStreamEnd(ionData, possibleIVM, _Private_IonConstants.BINARY_VERSION_MARKER_SIZE); + break; } } if (startsWithIvm(possibleIVM, bytesRead)) { @@ -316,7 +369,7 @@ static IonReader buildReader( } InputStream wrapper; if (bytesRead > 0) { - wrapper = new TwoElementSequenceInputStream( + wrapper = new TwoElementInputStream( new ByteArrayInputStream(possibleIVM, 0, bytesRead), ionData ); @@ -333,7 +386,8 @@ public IonReader build(InputStream source) this, source, _Private_IonReaderFactory::makeReaderBinary, - _Private_IonReaderFactory::makeReaderText + _Private_IonReaderFactory::makeReaderText, + getInputStreamInterceptors() ); } diff --git a/src/main/java/com/amazon/ion/impl/_Private_IonReaderFactory.java b/src/main/java/com/amazon/ion/impl/_Private_IonReaderFactory.java index 1f0e83ce57..735ac4a408 100644 --- a/src/main/java/com/amazon/ion/impl/_Private_IonReaderFactory.java +++ b/src/main/java/com/amazon/ion/impl/_Private_IonReaderFactory.java @@ -1,18 +1,5 @@ -/* - * Copyright 2007-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 package com.amazon.ion.impl; import static com.amazon.ion.impl.UnifiedInputStreamX.makeStream; @@ -105,11 +92,13 @@ public static final IonReader makeReaderText(IonCatalog catalog, public static IonReader makeSystemReaderText(InputStream is) { + _Private_IonReaderBuilder builder = (_Private_IonReaderBuilder) _Private_IonReaderBuilder.standard(); return _Private_IonReaderBuilder.buildReader( - (_Private_IonReaderBuilder) _Private_IonReaderBuilder.standard(), + builder, is, _Private_IonReaderFactory::makeSystemReaderBinary, - _Private_IonReaderFactory::makeSystemReaderText + _Private_IonReaderFactory::makeSystemReaderText, + builder.getInputStreamInterceptors() ); } diff --git a/src/main/java/com/amazon/ion/system/IonReaderBuilder.java b/src/main/java/com/amazon/ion/system/IonReaderBuilder.java index 0c0771749b..76725c9a78 100644 --- a/src/main/java/com/amazon/ion/system/IonReaderBuilder.java +++ b/src/main/java/com/amazon/ion/system/IonReaderBuilder.java @@ -1,20 +1,8 @@ -/* - * Copyright 2007-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 package com.amazon.ion.system; +import com.amazon.ion.util.GzipStreamInterceptor; import com.amazon.ion.IonBufferConfiguration; import com.amazon.ion.IonCatalog; import com.amazon.ion.IonException; @@ -23,11 +11,17 @@ import com.amazon.ion.IonSystem; import com.amazon.ion.IonTextReader; import com.amazon.ion.IonValue; +import com.amazon.ion.util.InputStreamInterceptor; import com.amazon.ion.impl._Private_IonReaderBuilder; import java.io.IOException; import java.io.InputStream; import java.io.Reader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.ServiceLoader; /** * Build a new {@link IonReader} from the given {@link IonCatalog} and data @@ -42,9 +36,20 @@ public abstract class IonReaderBuilder { + // Default stream interceptors, which always begin with the GZIP interceptor. + private static final List DEFAULT_STREAM_INTERCEPTORS = Collections.singletonList(GzipStreamInterceptor.INSTANCE); + + // Stream interceptors detected by the ClassLoader. + // Note: each ClassLoader may have access to a different list of detected interceptors, but static initialization + // is performed once per load of the class, and is guaranteed thread-safe. Therefore, there is no need to manually + // maintain a thread-safe cache of detected interceptors. + private static final List DETECTED_STREAM_INTERCEPTORS = Collections.unmodifiableList(detectStreamInterceptorsOnClasspath()); + + private IonCatalog catalog = null; private boolean isIncrementalReadingEnabled = false; private IonBufferConfiguration bufferConfiguration = IonBufferConfiguration.DEFAULT; + private List streamInterceptors = null; protected IonReaderBuilder() { @@ -55,6 +60,7 @@ protected IonReaderBuilder(IonReaderBuilder that) this.catalog = that.catalog; this.isIncrementalReadingEnabled = that.isIncrementalReadingEnabled; this.bufferConfiguration = that.bufferConfiguration; + this.streamInterceptors = that.streamInterceptors == null ? null : new ArrayList<>(that.streamInterceptors); } /** @@ -263,6 +269,77 @@ public IonBufferConfiguration getBufferConfiguration() { return bufferConfiguration; } + /** + * Adds an {@link InputStreamInterceptor} to the end of the list that the builder will attempt + * to apply to a stream before creating {@link IonReader} instances over that stream. + * {@link GzipStreamInterceptor} is always consulted first, and need not be added. The first + * interceptor in the list that matches the stream will be used; if any chaining of interceptors + * is required, it is up to the caller to provide a custom interceptor implementation to + * achieve this. + *

+ * Users may also or instead register implementations as service providers on the classpath. + * See {@link ServiceLoader} for details about how to do this. + *

+ * The list of stream interceptors available to the reader always begins with + * {@link GzipStreamInterceptor} and is followed by: + *

    + *
  1. any stream interceptors detected on the classpath using + * {@link ServiceLoader#load(Class)}, then
  2. + *
  3. any stream interceptor(s) added by calling this method.
  4. + *
+ * + * @param streamInterceptor the stream interceptor to add. + * + * @return this builder instance, if mutable; + * otherwise a mutable copy of this builder. + */ + public IonReaderBuilder addInputStreamInterceptor(InputStreamInterceptor streamInterceptor) { + IonReaderBuilder b = mutable(); + if (b.streamInterceptors == null) { + // 4 is arbitrary, but more would be very rare. + b.streamInterceptors = new ArrayList<>(DETECTED_STREAM_INTERCEPTORS.size() + 4); + b.streamInterceptors.addAll(DETECTED_STREAM_INTERCEPTORS); + } + b.streamInterceptors.add(streamInterceptor); + return b; + } + + /** + * Detects implementations of {@link InputStreamInterceptor} available to the {@link ClassLoader} that loaded + * this class, appending any implementations found to the list of stream interceptors enabled by default. + * @return the stream interceptors. + */ + private static List detectStreamInterceptorsOnClasspath() { + ServiceLoader loader = ServiceLoader.load( + InputStreamInterceptor.class, + // The ClassLoader used to load this class. Each ClassLoader may have access to different resources. + IonReaderBuilder.class.getClassLoader() + ); + Iterator interceptorIterator = loader.iterator(); + if (!interceptorIterator.hasNext()) { + // Avoid allocating a new list in the common case: no custom interceptors detected. + return DEFAULT_STREAM_INTERCEPTORS; + } + List interceptorsOnClasspath = new ArrayList<>(4); // 4 is arbitrary, but more would be very rare. + interceptorsOnClasspath.addAll(DEFAULT_STREAM_INTERCEPTORS); + interceptorIterator.forEachRemaining(interceptorsOnClasspath::add); + return interceptorsOnClasspath; + } + + /** + * Gets the {@link InputStreamInterceptor} instances available to this builder. The returned list will always begin + * with the default stream interceptor, which detects GZIP. Any stream interceptor(s) detected on the classpath + * by {@link ServiceLoader#load(Class)} will immediately follow. Any stream interceptor(s) manually added using + * {@link #addInputStreamInterceptor(InputStreamInterceptor)} will occur at the end of the list. + * @return an unmodifiable view of the stream interceptors currently configured. + */ + public List getInputStreamInterceptors() { + if (streamInterceptors == null) { + return DETECTED_STREAM_INTERCEPTORS; + } + return Collections.unmodifiableList(streamInterceptors); + } + /** * Based on the builder's configuration properties, creates a new IonReader * instance over the given block of Ion data, detecting whether it's text or diff --git a/src/main/java/com/amazon/ion/util/GzipStreamInterceptor.java b/src/main/java/com/amazon/ion/util/GzipStreamInterceptor.java new file mode 100644 index 0000000000..0de575cede --- /dev/null +++ b/src/main/java/com/amazon/ion/util/GzipStreamInterceptor.java @@ -0,0 +1,46 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package com.amazon.ion.util; + +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.GZIPInputStream; + +/** + * The interceptor for GZIP streams. This is a singleton that may be accessed using {@link #INSTANCE}. + */ +public enum GzipStreamInterceptor implements InputStreamInterceptor { + + INSTANCE; + + private static final byte[] GZIP_HEADER = {0x1F, (byte) 0x8B}; + + @Override + public String formatName() { + return "gzip"; + } + + @Override + public int numberOfBytesNeededToDetermineMatch() { + return GZIP_HEADER.length; + } + + @Override + public boolean isMatch(byte[] candidate, int offset, int length) { + if (candidate == null || length < GZIP_HEADER.length) { + return false; + } + + for (int i = 0; i < GZIP_HEADER.length; i++) { + if (GZIP_HEADER[i] != candidate[offset + i]) { + return false; + } + } + return true; + } + + @Override + public InputStream newInputStream(InputStream interceptedStream) throws IOException { + return new GZIPInputStream(interceptedStream); + } +} diff --git a/src/main/java/com/amazon/ion/util/InputStreamInterceptor.java b/src/main/java/com/amazon/ion/util/InputStreamInterceptor.java new file mode 100644 index 0000000000..11ef43750c --- /dev/null +++ b/src/main/java/com/amazon/ion/util/InputStreamInterceptor.java @@ -0,0 +1,53 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package com.amazon.ion.util; + +import com.amazon.ion.IonReader; +import com.amazon.ion.system.IonReaderBuilder; + +import java.io.IOException; +import java.io.InputStream; + +/** + * An interceptor to be consulted by the {@link com.amazon.ion.system.IonReaderBuilder} when creating an + * {@link IonReader} over a user-provided stream. This allows users to transform a stream's raw bytes into + * valid text or binary Ion. + * + * @see com.amazon.ion.system.IonReaderBuilder#addInputStreamInterceptor(InputStreamInterceptor) + * @see com.amazon.ion.system.IonSystemBuilder#withReaderBuilder(IonReaderBuilder) + */ +public interface InputStreamInterceptor { + + /** + * The name of the format the interceptor recognizes. + * @return a constant String. + */ + String formatName(); + + /** + * The number of bytes required to be read from the beginning of the stream in order to determine whether + * it matches the format relevant to this interceptor. If a stream contains fewer than the number of bytes + * returned by this method, then this interceptor will not be considered a match and + * {@link #isMatch(byte[], int, int)} will not be called. + * @return the length in bytes. + */ + int numberOfBytesNeededToDetermineMatch(); + + /** + * Determines whether the given candidate byte sequence matches this format. + * @param candidate the candidate byte sequence. + * @param offset the offset into the candidate bytes to begin matching. + * @param length the number of bytes (beginning at 'offset') in `candidate`. Must be greater than or equal to + * {@link #numberOfBytesNeededToDetermineMatch()}. + * @return true if the candidate byte sequence matches; otherwise, false. + */ + boolean isMatch(byte[] candidate, int offset, int length); + + /** + * Creates a new InputStream that transforms the bytes in the given InputStream into valid text or binary Ion. + * @param interceptedStream the stream containing bytes in this format. + * @return a new InputStream. + * @throws IOException if thrown when constructing the new InputStream. + */ + InputStream newInputStream(InputStream interceptedStream) throws IOException; +} diff --git a/src/test/java/com/amazon/ion/system/IonReaderBuilderTest.java b/src/test/java/com/amazon/ion/system/IonReaderBuilderTest.java index b7d379922e..c1aaab02f4 100644 --- a/src/test/java/com/amazon/ion/system/IonReaderBuilderTest.java +++ b/src/test/java/com/amazon/ion/system/IonReaderBuilderTest.java @@ -1,18 +1,5 @@ -/* - * Copyright 2007-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 package com.amazon.ion.system; import static com.amazon.ion.TestUtils.gzippedBytes; @@ -26,18 +13,21 @@ import static org.junit.Assert.fail; import com.amazon.ion.BitUtils; +import com.amazon.ion.util.GzipStreamInterceptor; import com.amazon.ion.IonBufferConfiguration; import com.amazon.ion.IonCatalog; import com.amazon.ion.IonException; import com.amazon.ion.IonReader; import com.amazon.ion.IonType; import com.amazon.ion.IonWriter; +import com.amazon.ion.util.InputStreamInterceptor; import com.amazon.ion.impl.ResizingPipedInputStream; import com.amazon.ion.impl._Private_IonBinaryWriterBuilder; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.util.List; import java.util.zip.GZIPOutputStream; import com.amazon.ion.impl._Private_IonConstants; @@ -234,4 +224,14 @@ public void incompleteIvmFailsCleanly(boolean isIncremental) throws Exception { reader.close(); } + @Test + public void gzipInterceptorEnabledByDefault() { + IonReaderBuilder builder = IonReaderBuilder.standard(); + List interceptors = builder.getInputStreamInterceptors(); + assertEquals(1, interceptors.size()); + assertEquals(GzipStreamInterceptor.INSTANCE.formatName(), interceptors.get(0).formatName()); + // The list returned from IonReaderBuilder.getStreamInterceptors() is unmodifiable. + assertThrows(UnsupportedOperationException.class, () -> interceptors.add(GzipStreamInterceptor.INSTANCE)); + } + } diff --git a/src/test/java/com/amazon/ion/util/ZstdStreamInterceptorTest.java b/src/test/java/com/amazon/ion/util/ZstdStreamInterceptorTest.java new file mode 100644 index 0000000000..89caa971cf --- /dev/null +++ b/src/test/java/com/amazon/ion/util/ZstdStreamInterceptorTest.java @@ -0,0 +1,439 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package com.amazon.ion.util; + +import com.amazon.ion.IonException; +import com.amazon.ion.IonSystem; +import com.amazon.ion.system.IonBinaryWriterBuilder; +import com.amazon.ion.system.IonReaderBuilder; +import com.amazon.ion.system.IonSystemBuilder; +import com.amazon.ion.system.IonTextWriterBuilder; +import com.amazon.ion.IonReader; +import com.amazon.ion.IonType; +import com.amazon.ion.IonWriter; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.net.URLConnection; +import java.net.URLStreamHandler; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiConsumer; +import java.util.function.Function; + +import com.github.luben.zstd.ZstdInputStream; +import com.github.luben.zstd.ZstdOutputStream; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Demonstrates how a StreamInterceptor that recognizes Zstd streams can be plugged into the IonReaderBuilder and + * IonSystem. + */ +public class ZstdStreamInterceptorTest { + + private static final byte[] ZSTD_HEADER = {(byte) 0x28, (byte) 0xB5, (byte) 0x2F, (byte) 0xFD}; + + public static class ZstdStreamInterceptor implements InputStreamInterceptor { + + @Override + public String formatName() { + return "zstd"; + } + + @Override + public int numberOfBytesNeededToDetermineMatch() { + return ZSTD_HEADER.length; + } + + @Override + public boolean isMatch(byte[] candidate, int offset, int length) { + if (candidate == null || length < ZSTD_HEADER.length) { + return false; + } + + for (int i = 0; i < ZSTD_HEADER.length; i++) { + if (ZSTD_HEADER[i] != candidate[offset + i]) { + return false; + } + } + return true; + } + + @Override + public InputStream newInputStream(InputStream interceptedStream) throws IOException { + return new ZstdInputStream(interceptedStream).setContinuous(true); + } + } + + public enum ZstdStream { + BINARY_STREAM_READER(builder -> builder.build(stream(binaryBytes()))), + TEXT_STREAM_READER(builder -> builder.build(stream(textBytes()))), + BINARY_BYTES_READER(builder -> builder.build(binaryBytes())), + TEXT_BYTES_READER(builder -> builder.build(textBytes())), + BINARY_STREAM_SYSTEM(builder -> system(builder).newReader((stream(binaryBytes())))), + TEXT_STREAM_SYSTEM(builder -> system(builder).newReader((stream(textBytes())))), + BINARY_BYTES_SYSTEM(builder -> system(builder).newReader(binaryBytes())), + TEXT_BYTES_SYSTEM(builder -> system(builder).newReader(textBytes())); + + private final Function readerFactory; + + ZstdStream(Function readerFactory) { + this.readerFactory = readerFactory; + } + + IonReader newReader(IonReaderBuilder builder) { + return readerFactory.apply(builder); + } + + static IonSystem system(IonReaderBuilder builder) { + return IonSystemBuilder.standard().withReaderBuilder(builder).build(); + } + + static byte[] textBytes() { + return writeCompressedStream(IonTextWriterBuilder.standard()::build); + } + + static byte[] binaryBytes() { + return writeCompressedStream(IonBinaryWriterBuilder.standard()::build); + } + + static InputStream stream(byte[] bytes) { + return new ByteArrayInputStream(bytes); + } + + private static byte[] writeCompressedStream(Function writerBuilder) { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + try (IonWriter writer = writerBuilder.apply(new ZstdOutputStream(bytes))) { + writer.writeInt(123); + } catch (IOException e) { + throw new IllegalStateException(e); + } + return bytes.toByteArray(); + } + } + + @ParameterizedTest + @EnumSource(ZstdStream.class) + public void interceptorsFunctionProperly(ZstdStream stream) throws IOException { + IonReaderBuilder builder = IonReaderBuilder.standard().addInputStreamInterceptor(new ZstdStreamInterceptor()); + try (IonReader reader = stream.newReader(builder)) { + assertEquals(IonType.INT, reader.next()); + assertEquals(123, reader.intValue()); + } + } + + public static class CustomInterceptorClassLoader extends URLClassLoader { + + public CustomInterceptorClassLoader() { + // Allow this ClassLoader to load all classes relevant to the builder and custom InputStreamInterceptor + // implementations being tested. + super( + new URL[] { + IonReaderBuilder.class.getProtectionDomain().getCodeSource().getLocation(), + ZstdStreamInterceptor.class.getProtectionDomain().getCodeSource().getLocation(), + ZstdInputStream.class.getProtectionDomain().getCodeSource().getLocation() + }, + IonReaderBuilder.class.getClassLoader().getParent() + ); + } + + @Override + public Enumeration getResources(String name) throws IOException { + if (name.equals("META-INF/services/" + InputStreamInterceptor.class.getName())) { + URL dummyUrl = new URL("unused", "unused", 42, "unused", new URLStreamHandler() { + @Override + protected URLConnection openConnection(URL url) { + return new URLConnection(url) { + @Override + public void connect() { + // Nothing to do. + } + + @Override + public InputStream getInputStream() { + return new ByteArrayInputStream(ZstdStreamInterceptor.class.getName().getBytes(StandardCharsets.UTF_8)); + } + }; + } + }); + return Collections.enumeration(Collections.singletonList(dummyUrl)); + } + return super.getResources(name); + } + } + + /** + * Asserts that the given IonReaderBuilder has a GzipStreamInterceptor followed an InputStreamInterceptor of the + * given type. + * @param readerBuilder the builder. + * @param interceptorType the type of the InputStreamInterceptor to follow GzipStreamInterceptor. + */ + private static void assertGzipThen(IonReaderBuilder readerBuilder, Class interceptorType) { + List streamInterceptors = readerBuilder.getInputStreamInterceptors(); + assertEquals(2, streamInterceptors.size()); + assertSame(GzipStreamInterceptor.INSTANCE, streamInterceptors.get(0)); + assertTrue(streamInterceptors.get(1).getClass().isAssignableFrom(interceptorType)); + } + + /** + * Asserts that the given IonReaderBuilder has a GzipStreamInterceptor followed an InputStreamInterceptor of the + * given type, performing all accesses via reflection. This must be used when an instance has been manually + * loaded by a custom ClassLoader, as such instances are not compatible with vanilla Java written in this context + * (for example, a manually loaded `IonReaderBuilder` would throw a `ClassCastException` if assigned to + * `IonReaderBuilder` in this test). + * @param builderClass the Class that represents the custom-loaded IonReaderBuilder. + * @param builderInstance the builder instance. + * @param interceptorTypes the type of the InputStreamInterceptor(s) to follow GzipStreamInterceptor. + */ + private static List assertGzipThen(Class builderClass, Object builderInstance, Class... interceptorTypes) { + try { + Method getInputStreamInterceptorsMethod = builderClass.getMethod("getInputStreamInterceptors"); + List streamInterceptors = (List) getInputStreamInterceptorsMethod.invoke(builderInstance); + assertEquals(interceptorTypes.length + 1, streamInterceptors.size()); + assertEquals(GzipStreamInterceptor.class.getName(), streamInterceptors.get(0).getClass().getName()); + for (int i = 0; i < interceptorTypes.length; i++) { + Class interceptorType = interceptorTypes[i]; + assertEquals(interceptorType.getName(), streamInterceptors.get(i + 1).getClass().getName()); + } + return streamInterceptors; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Executes the given target using an IonReaderBuilder instance loaded from the given custom ClassLoader. + * @param classLoader the custom ClassLoader to use. + * @param target the code to execute. + */ + private void executeWithCustomClassLoader(ClassLoader classLoader, BiConsumer, Object> target) { + Class ionReaderBuilderClass; + Object ionReaderBuilderInstance; + try { + // Note: below, 'true' forces static initialization. + ionReaderBuilderClass = Class.forName(IonReaderBuilder.class.getName(), true, classLoader); + Method factoryMethod = ionReaderBuilderClass.getMethod("standard"); + ionReaderBuilderInstance = factoryMethod.invoke(null); + } catch (Exception e) { + throw new RuntimeException(e); + } + target.accept(ionReaderBuilderClass, ionReaderBuilderInstance); + } + + @Test + public void gzipAlwaysAvailableWhenZstdAddedManually() { + IonReaderBuilder builder = IonReaderBuilder.standard().addInputStreamInterceptor(new ZstdStreamInterceptor()); + assertGzipThen(builder, ZstdStreamInterceptor.class); + } + + @Test + public void gzipAlwaysAvailableWhenZstdDetectedOnClasspath() { + executeWithCustomClassLoader( + new CustomInterceptorClassLoader(), + (builderClass, builder) -> assertGzipThen(builderClass, builder, ZstdStreamInterceptor.class) + ); + } + + @Test + public void manuallyAddedInterceptorsComeAfterDetectedInterceptors() { + executeWithCustomClassLoader( + new CustomInterceptorClassLoader(), + (builderClass, builder) -> { + // The custom ClassLoader adds Zstd. This should occur after GZIP (the default) but before the one + // added manually. + try { + Class zstdInterceptorClass = builderClass.getClassLoader().loadClass(ZstdStreamInterceptor.class.getName()); + Class dummyInterceptorClass = builderClass.getClassLoader().loadClass(LengthTooLongInterceptor.class.getName()); + Object dummyInterceptorInstance = dummyInterceptorClass.getConstructor(int.class).newInstance(0); + // Manually load the interface with the same ClassLoader so that it's compatible with the instance. + Class inputStreamInterceptorInterface = builderClass.getClassLoader().loadClass(InputStreamInterceptor.class.getName()); + Method addInterceptor = builderClass.getMethod("addInputStreamInterceptor", inputStreamInterceptorInterface); + assertGzipThen( + builderClass, + addInterceptor.invoke(builder, dummyInterceptorInstance), + zstdInterceptorClass, // Zstd is the first to follow GZIP because it was detected on the classpath. + dummyInterceptorClass // Manually added interceptors come last. + ); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + + /** + * Configures the given thread to capture any uncaught exceptions thrown during execution of the thread. + * @param thread the thread to configure. + * @return a reference to the exception thrown (if any) during execution of the thread. + */ + private AtomicReference registerExceptionHandler(Thread thread) { + AtomicReference error = new AtomicReference<>(); + thread.setUncaughtExceptionHandler((t, e) -> error.set(e)); + return error; + } + + /** + * Fails the test if the given reference points to any exception. + * @param error an error reference. + */ + private void failOnAnyError(AtomicReference error) { + Throwable t = error.get(); + if (t != null) { + Assertions.fail(t); + } + } + + // This is a multithreaded test; execute multiple times to increase the chances of triggering a race condition if + // one exists. + @RepeatedTest(100) + public void differentClassLoadersInEachThread() throws Exception { + Thread withDefaultClassLoader = new Thread(() -> { + IonReaderBuilder readerBuilder = IonReaderBuilder.standard(); + List streamInterceptors = readerBuilder.getInputStreamInterceptors(); + assertEquals(1, streamInterceptors.size()); + assertSame(GzipStreamInterceptor.INSTANCE, streamInterceptors.get(0)); + }); + + AtomicReference withDefaultClassLoaderError = registerExceptionHandler(withDefaultClassLoader); + + Thread withCustomClassLoader = new Thread(() -> { + AtomicReference> streamInterceptors = new AtomicReference<>(); + executeWithCustomClassLoader( + Thread.currentThread().getContextClassLoader(), // This will be our custom ClassLoader (set below) + (builderClass, builder) -> { + streamInterceptors.set(assertGzipThen(builderClass, builder, ZstdStreamInterceptor.class)); + } + ); + // Verify that a new IonReaderBuilder instance does not re-detect the interceptors applicable to this + // ClassLoader. + executeWithCustomClassLoader( + Thread.currentThread().getContextClassLoader(), // This will be our custom ClassLoader (set below) + (builderClass, builder) -> { + assertSame( + streamInterceptors.get(), + assertGzipThen(builderClass, builder, ZstdStreamInterceptor.class) + ); + } + ); + }); + + AtomicReference withCustomClassLoaderError = registerExceptionHandler(withCustomClassLoader); + withCustomClassLoader.setContextClassLoader(new CustomInterceptorClassLoader()); + + withDefaultClassLoader.start(); + withCustomClassLoader.start(); + + // While the spawned threads are working, verify they do not affect the parent thread. + IonReaderBuilder builder = IonReaderBuilder.standard().addInputStreamInterceptor(new LengthTooLongInterceptor(0)); + assertGzipThen(builder, LengthTooLongInterceptor.class); + + withDefaultClassLoader.join(); + withCustomClassLoader.join(); + + failOnAnyError(withDefaultClassLoaderError); + failOnAnyError(withCustomClassLoaderError); + } + + public static class LengthTooLongInterceptor implements InputStreamInterceptor { + + private final int length; + + public LengthTooLongInterceptor(int length) { + this.length = length; + } + + @Override + public String formatName() { + return null; + } + + @Override + public int numberOfBytesNeededToDetermineMatch() { + return length; + } + + @Override + public boolean isMatch(byte[] candidate, int offset, int length) { + return Assertions.fail("This method should be unreachable."); + } + + @Override + public InputStream newInputStream(InputStream interceptedStream) { + return Assertions.fail("This method should be unreachable."); + } + } + + @ParameterizedTest + @EnumSource(ZstdStream.class) + public void notInterceptedWhenStreamLengthIsLessThanHeaderLength(ZstdStream stream) throws IOException { + IonReaderBuilder builder = IonReaderBuilder.standard() + // The LengthTooLongInterceptor should be skipped, then the ZstdStreamInterceptor matched. + .addInputStreamInterceptor(new LengthTooLongInterceptor(1000)) // None of the test data is this long. + .addInputStreamInterceptor(new ZstdStreamInterceptor()); + try (IonReader reader = stream.newReader(builder)) { + assertEquals(IonType.INT, reader.next()); + assertEquals(123, reader.intValue()); + } + } + + @ParameterizedTest + @EnumSource(ZstdStream.class) + public void expectFailureWhenHeaderLengthIsInvalid(ZstdStream stream) { + IonReaderBuilder builder = IonReaderBuilder.standard() + // This header length is invalid because an array of that size cannot be allocated. + .addInputStreamInterceptor(new LengthTooLongInterceptor(Integer.MAX_VALUE)) + .addInputStreamInterceptor(new ZstdStreamInterceptor()); + assertThrows(IonException.class, () -> stream.newReader(builder)); + } + + private static class OneBytePerReadInputStream extends InputStream { + + private final InputStream delegate; + + OneBytePerReadInputStream(InputStream delegate) { + this.delegate = delegate; + } + + @Override + public int read() throws IOException { + return delegate.read(); + } + + @Override + public int read(byte[] bytes, int off, int len) throws IOException { + int b = delegate.read(); + if (b < 0) { + return -1; + } + bytes[off] = (byte) b; + return 1; + } + } + + @Test + public void headerRequiresMultipleInputStreamReads() throws IOException { + IonReaderBuilder builder = IonReaderBuilder.standard() + .addInputStreamInterceptor(new ZstdStreamInterceptor()); + try (IonReader reader = builder.build(new OneBytePerReadInputStream(new ByteArrayInputStream(ZstdStream.binaryBytes())))) { + assertEquals(IonType.INT, reader.next()); + assertEquals(123, reader.intValue()); + } + } +}