23
23
import java .nio .charset .StandardCharsets ;
24
24
import java .util .ArrayList ;
25
25
import java .util .Arrays ;
26
+ import java .util .Collection ;
26
27
import java .util .HashMap ;
27
28
import java .util .Iterator ;
28
29
import java .util .List ;
36
37
import static com .amazon .ion .SystemSymbols .NAME_SID ;
37
38
import static com .amazon .ion .SystemSymbols .SYMBOLS_SID ;
38
39
import static com .amazon .ion .SystemSymbols .VERSION_SID ;
40
+ import static com .amazon .ion .impl ._Private_Utils .safeEquals ;
39
41
40
42
/**
41
43
* An IonCursor capable of application-level parsing of binary Ion streams.
@@ -82,6 +84,12 @@ class IonReaderContinuableApplicationBinary extends IonReaderContinuableCoreBina
82
84
// symbol table is encountered in the stream.
83
85
private SymbolTable cachedReadOnlySymbolTable = null ;
84
86
87
+ // The cached SymbolTable that was determined to be a superset of the reader's current symbol table during a call
88
+ // to 'isSymbolTableSubsetOf'. This is set to null whenever the reader encounters a new symbol table. Therefore,
89
+ // when non-null, determining whether the reader's symbol table is a subset of a given table is as simple as
90
+ // checking whether that table is the same as 'lastSupersetSymbolTable'.
91
+ private SymbolTable lastSupersetSymbolTable = null ;
92
+
85
93
// The reusable annotation iterator.
86
94
private final AnnotationSequenceIterator annotationIterator = new AnnotationSequenceIterator ();
87
95
@@ -206,6 +214,110 @@ private SymbolTable getSystemSymbolTable() {
206
214
return SharedSymbolTable .getSystemSymbolTable (getIonMajorVersion ());
207
215
}
208
216
217
+ boolean compareSymbolTableImportsArrayToList (SymbolTable [] arr , int arrayLength , List <SymbolTable > list ) {
218
+ // Note: the array variant must begin with a system symbol table, while the list variant must not.
219
+ if (arrayLength - 1 != list .size ()) {
220
+ return false ;
221
+ }
222
+ for (int i = 1 ; i < arrayLength ; i ++) {
223
+ // TODO amazon-ion/ion-java/issues/18 Currently, we check imports by their references, which
224
+ // is overly strict; imports that have different references but the same symbols should pass the check.
225
+ // However, this is a cheaper check and is compatible with how common Catalog implementations handle
226
+ // shared tables.
227
+ if (list .get (i - 1 ) != arr [i ]) {
228
+ return false ;
229
+ }
230
+ }
231
+ return true ;
232
+ }
233
+
234
+ boolean compareSymbolsArrayToCollection (String [] arr , int arrayLength , Collection <String > collection ) {
235
+ // Precondition: the collection contains at least as many elements as the array.
236
+ Iterator <String > collectionIterator = collection .iterator ();
237
+ for (int i = 0 ; i < arrayLength ; i ++) {
238
+ if (!safeEquals (arr [i ], collectionIterator .next ())) {
239
+ return false ;
240
+ }
241
+ }
242
+ return true ;
243
+ }
244
+
245
+ /**
246
+ * Determines whether the symbol table active at the reader's current position is a subset of another symbol table,
247
+ * meaning that every symbol in the reader's symbol table is present and has the same symbol ID in the other
248
+ * table.
249
+ * @param other another symbol table.
250
+ * @return true if the reader's symbol table is a subset of the other table; otherwise, false.
251
+ */
252
+ boolean isSymbolTableSubsetOf (SymbolTable other )
253
+ {
254
+ if (lastSupersetSymbolTable != null ) {
255
+ // lastSupersetSymbolTable is reset when the reader's symbol table changes, so we know the reader's symbol
256
+ // table is the same as it was when last compared. This is an optimization that avoids the more expensive
257
+ // comparisons when this method is called repetitively within the same symbol table contexts. This
258
+ // commonly happens during repetitive calls to IonWriter.writeValue(IonReader), which is used directly
259
+ // by users and by the looping wrapper IonWriter.writeValues(IonReader).
260
+ return other == lastSupersetSymbolTable && other .getMaxId () == lastSupersetSymbolTable .getMaxId ();
261
+ }
262
+
263
+ int numberOfLocalSymbols = localSymbolMaxOffset + 1 ;
264
+ int maxId = imports .getMaxId () + numberOfLocalSymbols ;
265
+
266
+ // Note: the first imported table is always the system symbol table.
267
+ boolean isSystemSymbolTable = numberOfLocalSymbols == 0 && imports .getImportedTablesNoCopy ().length == 1 ;
268
+ boolean otherHasPrivateAttributes = other instanceof _Private_LocalSymbolTable ;
269
+ _Private_LocalSymbolTable otherLocal = otherHasPrivateAttributes ? (_Private_LocalSymbolTable ) other : null ;
270
+ if (isSystemSymbolTable ) {
271
+ if (other .isSystemTable () && maxId == other .getMaxId ()) {
272
+ // Both represent the same system table.
273
+ lastSupersetSymbolTable = other ;
274
+ return true ;
275
+ }
276
+ // The other symbol table might not literally be the system symbol table, but if it's a local symbol table
277
+ // with zero local symbols and zero imports, that counts.
278
+ if (otherHasPrivateAttributes && otherLocal .getNumberOfLocalSymbols () == 0 && otherLocal .getImportedTablesAsList ().isEmpty ()) {
279
+ lastSupersetSymbolTable = other ;
280
+ return true ;
281
+ }
282
+ return false ;
283
+ }
284
+ if (!otherHasPrivateAttributes ) {
285
+ // The reader's symbol table is not a system symbol table, but the other is. Other cannot be a superset.
286
+ return false ;
287
+ }
288
+ if (maxId > otherLocal .getMaxId ()) return false ;
289
+
290
+ // NOTE: the following uses of _Private_LocalSymbolTable utilize knowledge of the implementation used by
291
+ // the binary writer, which has the only known use case for this method. Specifically, we call the interface
292
+ // method variants that return lists instead of arrays because we know this matches the binary writer's symbol
293
+ // table's internal representation and therefore does not require copying. If this method ends up being used
294
+ // for other symbol table implementations, which is unlikely, we should add logic to choose the most efficient
295
+ // variant to call for the particular implementation (such as by adding something like a `boolean usesArrays()`
296
+ // method to the interface).
297
+
298
+ SymbolTable [] readerImports = imports .getImportedTablesNoCopy ();
299
+ if (!compareSymbolTableImportsArrayToList (readerImports , readerImports .length , otherLocal .getImportedTablesAsList ())) {
300
+ return false ;
301
+ }
302
+
303
+ // Superset extends subset if subset doesn't have any declared symbols.
304
+ if (numberOfLocalSymbols == 0 ) {
305
+ lastSupersetSymbolTable = other ;
306
+ return true ;
307
+ }
308
+
309
+ // Superset must have same/more declared (local) symbols than subset.
310
+ if (numberOfLocalSymbols > otherLocal .getNumberOfLocalSymbols ()) return false ;
311
+
312
+ Collection <String > otherSymbols = otherLocal .getLocalSymbolsNoCopy ();
313
+ if (!compareSymbolsArrayToCollection (symbols , numberOfLocalSymbols , otherSymbols )) {
314
+ return false ;
315
+ }
316
+
317
+ lastSupersetSymbolTable = other ;
318
+ return true ;
319
+ }
320
+
209
321
/**
210
322
* Read-only snapshot of the local symbol table at the reader's current position.
211
323
*/
@@ -431,6 +543,21 @@ public _Private_LocalSymbolTable makeCopy() {
431
543
public SymbolTable [] getImportedTablesNoCopy () {
432
544
return importedTables .getImportedTablesNoCopy ();
433
545
}
546
+
547
+ @ Override
548
+ public List <SymbolTable > getImportedTablesAsList () {
549
+ throw new UnsupportedOperationException ("Call getImportedTablesNoCopy() instead." );
550
+ }
551
+
552
+ @ Override
553
+ public List <String > getLocalSymbolsNoCopy () {
554
+ throw new UnsupportedOperationException ("If this is needed, add a no-copy variant that returns an array." );
555
+ }
556
+
557
+ @ Override
558
+ public int getNumberOfLocalSymbols () {
559
+ return idToText .length ;
560
+ }
434
561
}
435
562
436
563
/**
@@ -442,6 +569,7 @@ private void resetSymbolTable() {
442
569
Arrays .fill (symbols , 0 , localSymbolMaxOffset + 1 , null );
443
570
localSymbolMaxOffset = -1 ;
444
571
cachedReadOnlySymbolTable = null ;
572
+ lastSupersetSymbolTable = null ;
445
573
}
446
574
447
575
/**
@@ -474,6 +602,7 @@ protected void restoreSymbolTable(SymbolTable symbolTable) {
474
602
}
475
603
localSymbolMaxOffset = snapshot .maxId - firstLocalSymbolId ;
476
604
System .arraycopy (snapshot .idToText , 0 , symbols , 0 , snapshot .idToText .length );
605
+ lastSupersetSymbolTable = null ;
477
606
} else {
478
607
// Note: this will only happen when `symbolTable` is the system symbol table.
479
608
resetSymbolTable ();
@@ -626,6 +755,9 @@ private void finishReadingSymbolTableStruct() {
626
755
}
627
756
localSymbolMaxOffset += newSymbols .size ();
628
757
}
758
+ // Note: last superset table is reset even if new symbols were simply appended because there's no
759
+ // guarantee those symbols are reflected in the superset table.
760
+ lastSupersetSymbolTable = null ;
629
761
state = State .READING_VALUE ;
630
762
}
631
763
0 commit comments