com.amazon.ion.impl.lite.IonContainerLite Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ion-java Show documentation
Show all versions of ion-java Show documentation
A Java implementation of the Amazon Ion data notation.
/*
* 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.
*/
package com.amazon.ion.impl.lite;
import com.amazon.ion.ContainedValueException;
import com.amazon.ion.IonContainer;
import com.amazon.ion.IonDatagram;
import com.amazon.ion.IonException;
import com.amazon.ion.IonValue;
import com.amazon.ion.NullValueException;
import com.amazon.ion.ReadOnlyValueException;
import com.amazon.ion.SymbolTable;
import com.amazon.ion.ValueVisitor;
import com.amazon.ion.impl._Private_IonConstants;
import com.amazon.ion.impl._Private_IonContainer;
import com.amazon.ion.impl._Private_Utils;
import java.util.HashMap;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.NoSuchElementException;
abstract class IonContainerLite
extends IonValueLite
implements _Private_IonContainer, IonContext
{
protected int _child_count;
protected IonValueLite[] _children;
protected int structuralModificationCount;
// IonValueLite delegates calls to getSystem() to the parent context. If the value is deeply nested, the
// the parent will delegate the call to getSystem() to its own parent context. This delegation repeats until
// the top level is reached. This linear walk to the root of the DOM can be very expensive.
// As an optimization, each container holds a reference to its parent's IonSystem and overrides IonValueLite's
// implementation of getSystem(). Scalar IonValueLite implementations will continue to delegate to the parent
// context, but the parent context will always be able to provide the IonSystem without further delegation.
protected IonSystemLite ionSystem;
protected IonContainerLite(ContainerlessContext context, boolean isNull)
{
// we'll let IonValueLite handle this work as we always need to know
// our context and if we should start out as a null value or not
super(context, isNull);
this.ionSystem = context.getSystem();
}
IonContainerLite(IonContainerLite existing, IonContext context, boolean isStruct) {
super(existing, context);
this.ionSystem = existing.getSystem();
boolean retainingSIDs = false;
int childCount = existing._child_count;
this._child_count = childCount;
// when cloning the children we establish 'this' the cloned outer container as the context
if (existing._children != null) {
boolean isDatagram = this instanceof IonDatagramLite;
this._children = new IonValueLite[childCount];
for (int i = 0; i < childCount; i++) {
IonValueLite child = existing._children[i];
IonContext childContext = isDatagram
? TopLevelContext.wrap(child.getAssignedSymbolTable(), (IonDatagramLite)this)
: this;
IonValueLite copy = child.clone(childContext);
if (isStruct) {
if(child.getFieldName() == null) {
// when name is null it could be a sid 0 so we need to perform the full symbol token lookup.
// this is expensive so only do it when necessary
// TODO profile `getKnownFieldNameSymbol` to see if we can improve its performance so branching
// is not necessary. https://github.com/amzn/ion-java/issues/140
copy.setFieldNameSymbol(child.getKnownFieldNameSymbol());
}
else {
// if we have a non null name copying it is sufficient
copy.setFieldName(child.getFieldName());
}
}
this._children[i] = copy;
retainingSIDs |= copy._isSymbolIdPresent();
}
// unfortunately due to the existing behavior in IonValueLite copy-constructor where annotation SID's are
// preserved across the copy-constructor IF they have no resolved text it means that encodings could have
// been preserved on the child - therefore the cloned children each have to be re-interrogated and the
// setting updated IF such a change has occurred.
_isSymbolIdPresent(retainingSIDs);
}
}
// See the comment on the `ionSystem` member field for more information.
@Override
public IonSystemLite getSystem() {
return ionSystem;
}
@Override
public abstract void accept(ValueVisitor visitor) throws Exception;
@Override
public abstract IonContainer clone();
public void clear()
{
checkForLock();
if (_isNullValue())
{
assert _children == null;
assert _child_count == 0;
_isNullValue(false);
}
else if (!isEmpty())
{
detachAllChildren();
_child_count = 0;
structuralModificationCount++;
}
}
private void detachAllChildren()
{
for (int ii=0; ii<_child_count; ii++) {
IonValueLite child = _children[ii];
child.detachFromContainer();
_children[ii] = null;
}
}
public boolean isEmpty() throws NullValueException
{
validateThisNotNull();
return (size() == 0);
}
public IonValue get(int index)
throws NullValueException
{
validateThisNotNull();
IonValueLite value = get_child(index);
assert(value._isAutoCreated() == false);
return value;
}
public final Iterator iterator()
{
return listIterator(0);
}
public final ListIterator listIterator()
{
return listIterator(0);
}
public ListIterator listIterator(int index)
{
if (isNullValue())
{
if (index != 0) throw new IndexOutOfBoundsException();
return _Private_Utils. emptyIterator();
}
return new SequenceContentIterator(index, isReadOnly());
}
/** Encapsulates an iterator and implements a custom remove method */
/* this is tied to the _child array of the IonSequenceImpl
* through the _children and _child_count members which this
* iterator directly uses.
*
* TODO with the updated next and previous logic, particularly
* the force_position_sync logic and lastMoveWasPrevious flag
* we could implement add and set correctly.
*
* NOTE this closely resembles the user and system iterators
* defined in datagram, so changes here are likely to be needed
* in datagram as well.
*/
protected class SequenceContentIterator
implements ListIterator
{
protected final boolean __readOnly;
protected boolean __lastMoveWasPrevious;
protected int __pos;
protected IonValueLite __current;
public SequenceContentIterator(int index, boolean readOnly)
{
if (_isLocked() && !readOnly) {
throw new IllegalStateException("you can't open an updatable iterator on a read only value");
}
if (index < 0 || index > _child_count) {
throw new IndexOutOfBoundsException(Integer.toString(index));
}
__pos = index;
__readOnly = readOnly;
}
// split to encourage the in-lining of the common
// case where we don't actually do anything
protected final void force_position_sync()
{
if (__pos <= 0 || __pos > _child_count) {
return;
}
if (__current == null || __current == _children[__pos - 1]) {
return;
}
force_position_sync_helper();
}
private final void force_position_sync_helper()
{
if (__readOnly) {
throw new IonException("read only sequence was changed");
}
// look forward, which happens on insert
// notably insert of a local symbol table
// or a IVM if this is in a datagram
for (int ii=__pos; ii<_child_count; ii++) {
if (_children[ii] == __current) {
__pos = ii;
if (!__lastMoveWasPrevious) {
__pos++;
}
return;
}
}
// look backward, which happens on delete
// of a member preceding us, but should not
// happen if the delete is through this
// operator
for (int ii=__pos-1; ii>=0; ii--) {
if (_children[ii] == __current) {
__pos = ii;
if (!__lastMoveWasPrevious) {
__pos++;
}
return;
}
}
throw new IonException("current member of iterator has been removed from the containing sequence");
}
public void add(IonValue element)
{
throw new UnsupportedOperationException();
}
public final boolean hasNext()
{
// called in nextIndex(): force_position_sync();
return (nextIndex() < _child_count);
}
public final boolean hasPrevious()
{
// called in previousIndex(): force_position_sync();
return (previousIndex() >= 0);
}
public IonValue next()
{
int next_idx = nextIndex();
if (next_idx >= _child_count) {
throw new NoSuchElementException();
}
__current = _children[next_idx];
__pos = next_idx + 1; // after a next the pos will be past the current
__lastMoveWasPrevious = false;
return __current;
}
public final int nextIndex()
{
force_position_sync();
if (__pos >= _child_count) {
return _child_count;
}
int next_idx = __pos;
// whether we previous-ed to get here or
// next-ed to get here the next index is
// whatever the current position is
return next_idx;
}
public IonValue previous()
{
force_position_sync();
int prev_idx = previousIndex();
if (prev_idx < 0) {
throw new NoSuchElementException();
}
__current = _children[prev_idx];
__pos = prev_idx;
__lastMoveWasPrevious = true;
return __current;
}
public final int previousIndex()
{
force_position_sync();
int prev_idx = __pos - 1;
if (prev_idx < 0) {
return -1;
}
return prev_idx;
}
/**
* Sets the container to dirty after calling {@link Iterator#remove()}
* on the encapsulated iterator
*/
public void remove()
{
if (__readOnly) {
throw new UnsupportedOperationException();
}
force_position_sync();
int idx = __pos;
if (!__lastMoveWasPrevious) {
// position is 1 ahead of the array index
idx--;
}
if (idx < 0) {
throw new ArrayIndexOutOfBoundsException();
}
IonValueLite concrete = __current;
int concrete_idx = concrete._elementid();
assert(concrete_idx == idx);
// here we remove the member from the container's list of elements
remove_child(idx);
patch_elements_helper(concrete_idx);
if (!__lastMoveWasPrevious) {
// if we next-ed onto this member we have to back up
// because the next member is now current (otherwise
// the position is fine where it is)
__pos--;
}
__current = null;
}
public void set(IonValue element)
{
throw new UnsupportedOperationException();
}
}
public void makeNull()
{
clear(); // this checks for the lock
_isNullValue(true); // but clear() leaves the value non-null
}
public boolean remove(IonValue element)
{
checkForLock();
if (element.getContainer() != this) {
return false;
}
// Get all the data into the DOM, since the element will be losing
// its backing store.
IonValueLite concrete = (IonValueLite) element;
int pos = concrete._elementid();
IonValueLite child = get_child(pos);
if (child == concrete) // Yes, instance identity.
{
// no, this is done in remove_child and will
// if called first it will corrupt the elementid
// no: concrete.detachFromContainer();
remove_child(pos);
patch_elements_helper(pos);
return true;
}
throw new AssertionError("element's index is not correct");
}
public int size()
{
if (isNullValue()) {
return 0;
}
return get_child_count();
}
@Override
void makeReadOnlyInternal()
{
if (_isLocked()) return;
if (_children != null) {
for (int ii=0; ii<_child_count; ii++) {
IonValueLite child = _children[ii];
child.makeReadOnlyInternal();
}
}
// we don't need to call our copy of clear symbol ID's
// which recurses since the calls to child.makeReadOnly
// will have clear out the child symbol ID's already
// as the children were marked read only. But we do need
// to call the base clear which will clear out the symbol
// table reference if one exists.
super.clearSymbolIDValues();
_isLocked(true);
}
/**
* methods from IonValue
*
* public void deepMaterialize()
* public IonContainer getContainer()
* public int getFieldId()
* public String getFieldName()
* public String[] getTypeAnnotations()
* public boolean hasTypeAnnotation(String annotation)
* public boolean isNullValue()
* public boolean isReadOnly()
* public void removeTypeAnnotation(String annotation)
*/
/*
* IonContext methods
*
* note that the various get* methods delegate
* to our context.
*
* However getParentThroughContext() returns
* our this pointer since we are the container.
*
* We always have a context. Either a concrete
* context if we are a loose value or a container
* we are contained in.
*
*/
public final IonContainerLite getContextContainer()
{
return this;
}
/**
* @return {@code null}, since symbol tables are only directly assigned
* to top-level values.
*/
public final SymbolTable getContextSymbolTable()
{
return null;
}
@Override
boolean attemptClearSymbolIDValues()
{
boolean symbolIDsAllCleared = super.attemptClearSymbolIDValues();
for (int ii = 0; ii < get_child_count(); ii++)
{
IonValueLite child = get_child(ii);
// NOTE: recursion is done to #clearSymbolIDValues rather than #attemptClearSymbolIDValues in order to
// set the SYMBOL ID PRESENT status flag correctly.
symbolIDsAllCleared &= child.clearSymbolIDValues();
}
return symbolIDsAllCleared;
}
/**
* @param child
*/
public boolean add(IonValue child)
throws NullPointerException, IllegalArgumentException,
ContainedValueException
{
int size = get_child_count();
add(size, (IonValueLite) child);
// updateElementIds - not needed since we're adding at the end
// this.patch_elements_helper(size);
return true;
}
/**
* Ensures that a potential new child is non-null, has no container,
* is not read-only, and is not a datagram.
*
* @throws NullPointerException
* if {@code child} is {@code null}.
* @throws ContainedValueException
* if {@code child} is already part of a container.
* @throws ReadOnlyValueException
* if {@code child} is read only.
* @throws IllegalArgumentException
* if {@code child} is an {@link IonDatagram}.
*/
void validateNewChild(IonValue child)
throws ContainedValueException, NullPointerException,
IllegalArgumentException
{
if (child.getContainer() != null) // Also checks for null.
{
throw new ContainedValueException();
}
if (child.isReadOnly()) throw new ReadOnlyValueException();
if (child instanceof IonDatagram)
{
String message =
"IonDatagram can not be inserted into another IonContainer.";
throw new IllegalArgumentException(message);
}
assert child instanceof IonValueLite
: "Child was not created by the same ValueFactory";
assert getSystem() == child.getSystem()
|| getSystem().getClass().equals(child.getSystem().getClass());
}
/**
* Validates the child and checks locks.
*
* @param child
* must not be null.
* @throws NullPointerException
* if the element is null
.
* @throws IndexOutOfBoundsException
* if the index is out of range (index < 0 || index > size()).
*/
void add(int index, IonValueLite child)
throws ContainedValueException, NullPointerException
{
if ((index < 0) || (index > get_child_count()))
{
throw new IndexOutOfBoundsException();
}
checkForLock();
validateNewChild(child);
add_child(index, child);
patch_elements_helper(index + 1);
assert((index >= 0)
&& (index < get_child_count())
&& (child == get_child(index))
&& (child._elementid() == index));
}
//////////////////////////////////////////////////////
//////////////////////////////////////////////////////
// helper routines for managing the member children
//////////////////////////////////////////////////////
//////////////////////////////////////////////////////
/**
* sizes for the various types of containers
* expected to be tuned.
*/
static final int[] INITIAL_SIZE = make_initial_size_array();
static int[] make_initial_size_array() {
int[] sizes = new int[_Private_IonConstants.tidDATAGRAM + 1];
sizes[_Private_IonConstants.tidList] = 1;
sizes[_Private_IonConstants.tidSexp] = 4;
sizes[_Private_IonConstants.tidStruct] = 5;
sizes[_Private_IonConstants.tidDATAGRAM] = 3;
return sizes;
}
static final int[] NEXT_SIZE = make_next_size_array();
static int[] make_next_size_array() {
int[] sizes = new int[_Private_IonConstants.tidDATAGRAM + 1];
sizes[_Private_IonConstants.tidList] = 4;
sizes[_Private_IonConstants.tidSexp] = 8;
sizes[_Private_IonConstants.tidStruct] = 8;
sizes[_Private_IonConstants.tidDATAGRAM] = 10;
return sizes;
}
final protected int initialSize()
{
switch (this.getType()) {
case LIST: return 1;
case SEXP: return 4;
case STRUCT: return 5;
case DATAGRAM: return 3;
default: return 4;
}
}
final protected int nextSize(int current_size, boolean call_transition)
{
if (current_size == 0) {
int new_size = initialSize();
return new_size;
}
int next_size;
switch (this.getType()) {
case LIST: next_size = 4; break;
case SEXP: next_size = 8; break;
case STRUCT: next_size = 8; break;
case DATAGRAM: next_size = 10; break;
default: return current_size * 2;
}
if (next_size > current_size) {
// note that unrecognized sizes, either due to unrecognized type id
// or some sort of custom size in the initial allocation, meh.
if (call_transition) {
transitionToLargeSize(next_size);
}
}
else {
next_size = current_size * 2;
}
return next_size;
}
/**
* This is overriden in {@link IonStructLite} to add the {@link HashMap} of
* field names when the struct becomes moderately large.
*
* @param size
*/
void transitionToLargeSize(int size)
{
return;
}
public final int get_child_count() {
return _child_count;
}
public final IonValueLite get_child(int idx) {
if (idx < 0 || idx >= _child_count) {
throw new IndexOutOfBoundsException(Integer.toString(idx));
}
return _children[idx];
}
final IonValueLite set_child(int idx, IonValueLite child)
{
if (idx < 0 || idx >= _child_count) {
throw new IndexOutOfBoundsException(Integer.toString(idx));
}
if (child == null) {
throw new NullPointerException();
}
IonValueLite prev = _children[idx];
_children[idx] = child;
// FIXME this doesn't update the child's context or index
// which is done by add_child() above
return prev;
}
/**
* Does not validate the child or check locks.
*/
protected int add_child(int idx, IonValueLite child)
{
_isNullValue(false); // if we add children we're not null anymore
child.setContext(this.getContextForIndex(child, idx));
if (_children == null || _child_count >= _children.length) {
int old_len = (_children == null) ? 0 : _children.length;
int new_len = this.nextSize(old_len, true);
assert(new_len > idx);
IonValueLite[] temp = new IonValueLite[new_len];
if (old_len > 0) {
System.arraycopy(_children, 0, temp, 0, old_len);
}
_children = temp;
}
if (idx < _child_count) {
System.arraycopy(_children, idx, _children, idx+1, _child_count-idx);
}
_child_count++;
_children[idx] = child;
structuralModificationCount++;
child._elementid(idx);
if (!_isSymbolIdPresent() && child._isSymbolIdPresent())
{
cascadeSIDPresentToContextRoot();
}
return idx;
}
IonContext getContextForIndex(IonValue element, int index){
return this;
}
/**
* Does not check locks.
*/
void remove_child(int idx)
{
assert(idx >=0);
assert(idx < get_child_count()); // this also asserts child count > 0
assert get_child(idx) != null : "No child at index " + idx;
_children[idx].detachFromContainer();
int children_to_move = _child_count - idx - 1;
if (children_to_move > 0) {
System.arraycopy(_children, idx+1, _children, idx, children_to_move);
}
_child_count--;
_children[_child_count] = null;
structuralModificationCount++;
}
public final void patch_elements_helper(int lowest_bad_idx)
{
// patch the element Id's for all the children from
// the child who was earliest in the array (lowest index)
for (int ii=lowest_bad_idx; ii