All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.amazon.ion.impl.lite.IonStructLite Maven / Gradle / Ivy

There is a newer version: 1.11.9
Show newest version
/*
 * 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.IonStruct;
import com.amazon.ion.IonType;
import com.amazon.ion.IonValue;
import com.amazon.ion.SymbolToken;
import com.amazon.ion.ValueFactory;
import com.amazon.ion.ValueVisitor;
import com.amazon.ion.impl._Private_CurriedValueFactory;
import com.amazon.ion.UnknownSymbolException;

import java.io.PrintWriter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;


final class IonStructLite
    extends IonContainerLite
    implements IonStruct
{
    private static final int HASH_SIGNATURE =
        IonType.STRUCT.toString().hashCode();
    // TODO amazon-ion/ion-java/issues/41: add support for _isOrdered

    IonStructLite(ContainerlessContext context, boolean isNull)
    {
        super(context, isNull);
    }

    private IonStructLite(IonStructLite existing, IonContext context)
    {
        super(existing, context);
        // The field map provides optimized lookups when the struct is larger than a few fields. Rather than being
        // eagerly copied during clone, it is created on-demand. This has been shown to improve performance when cloning
        // structs, even in cases where the field map is eventually needed.
        this._field_map = null;
        this._field_map_duplicate_count = existing._field_map_duplicate_count;
        this.hasNullFieldName = existing.hasNullFieldName;
    }

    private Map _field_map;
    private boolean hasNullFieldName = false;

    public int                      _field_map_duplicate_count;

    @Override
    public IonStructLite clone()
    {
        return (IonStructLite) deepClone(false);
    }

    @Override
    IonValueLite shallowClone(IonContext context) {
        return new IonStructLite(this, context);
    }

    @Override
    protected void transitionToLargeSize(int size)
    {
        if (_field_map != null) return;

        build_field_map();
    }
    private void build_field_map()
    {
        int size = (_children == null) ? 0 : _children.length;

        _field_map = new HashMap<>((int) Math.ceil(size / 0.75f), 0.75f); // avoids unconditional growth
        _field_map_duplicate_count = 0;

        int count = get_child_count();
        for (int ii=0; ii newFieldIdx) {
                newFieldIdx = idx;
            }
        }
        _field_map.put(fieldName, newFieldIdx);
    }
    private void remove_field(String fieldName, int lowest_idx, int copies)
    {
        if (_field_map == null) {
            return;
        }

        _field_map.remove(fieldName);
        _field_map_duplicate_count -= (copies - 1);
    }

    private void remove_field_from_field_map(String fieldName, int idx)
    {
        Integer field_idx = _field_map.get(fieldName);
        assert(field_idx != null);

        if (field_idx != idx) {
            // if the map has a different index, this must
            // be a duplicate, and this copy isn't in the map
            assert(_field_map_duplicate_count > 0);
            _field_map_duplicate_count--;
        }
        else if (_field_map_duplicate_count > 0) {
            // if we have any duplicates we have to check
            // every time since we don't track which field
            // is duplicated - so any dup can be expensive
            int ii = find_last_duplicate(fieldName, idx);

            if (ii == -1) {
                // this is the last copy of this key
                _field_map.remove(fieldName);
            }
            else {
                // replaces this fields (the one being
                // removed) array idx in the map with
                // the preceding duplicates index
                _field_map.put(fieldName, ii);
                _field_map_duplicate_count--;
            }
        }
        else {
            // since there are not dup's we can just update
            // the map by removing this fieldname
            _field_map.remove(fieldName);
        }
    }

    private void patch_map_elements_helper(int removed_idx)
    {
        if (_field_map == null) {
            return;
        }

        if (removed_idx >= get_child_count()) {
            // if this was the at the end of the list
            // there's nothing to change
            return;
        }

        for (int ii=removed_idx; ii> it = _field_map.entrySet().iterator();
        out.print("   map: [");
        boolean first = true;
        while (it.hasNext()) {
            Entry e = it.next();
            if (!first) {
                out.print(",");
            }
            out.print(e.getKey()+":"+e.getValue());
            first = false;
        }
        out.println("]");
    }

    @Override
    public String validate()
    {
        if (_field_map == null) {
            return null;
        }
        String error = "";
        Iterator> it = _field_map.entrySet().iterator();
        while (it.hasNext()) {
            Entry e = it.next();
            int idx = e.getValue();
            IonValueLite v = (idx >= 0 && idx < get_child_count()) ? get_child(idx) : null;
            if (v == null || idx != v._elementid() || (e.getKey().equals(v.getFieldName()) == false)) {
                error += "map entry ["+e+"] doesn't match list value ["+v+"]\n";
            }
        }

        return (error == "") ? null : error;
    }

    private int find_last_duplicate(String fieldName, int existing_idx)
    {
        for (int ii=existing_idx; ii>0; ) {
            ii--;
            IonValueLite field = get_child(ii);
            if (fieldName.equals(field.getFieldName())) {
                return ii;
            }
        }
        assert(there_is_only_one(fieldName, existing_idx));
        return -1;
    }
    private boolean there_is_only_one(String fieldName, int existing_idx)
    {
        int count = 0;
        for (int ii=0; ii fields =
                new HashSet(Arrays.asList(fieldNames));
            if (keep && fields.contains(null))
            {
                throw new NullPointerException("Can't retain an unknown field name");
            }

            clone = getSystem().newEmptyStruct();
            clone._children = new IonValueLite[_children.length];
            clone._child_count = 0;
            for (int i = 0; i < _child_count; i++) {
                IonValueLite value = _children[i];
                if (fields.contains(value._fieldName) == keep)
                {
                    IonValueLite clonedChild = (IonValueLite) value.clone();
                    // This ensures that we don't copy an unknown field name.
                    clonedChild._fieldName = value.getFieldName();
                    clonedChild._elementid(clone._child_count);
                    clonedChild._context = clone;
                    if (!clone._isSymbolIdPresent() && clonedChild._isSymbolIdPresent())
                    {
                        clone.cascadeSIDPresentToContextRoot();
                    }
                    clone._children[clone._child_count++] = clonedChild;
                }
            }
        }


        if (_annotations != null && _annotations.length > 0) {
            clone._annotations = _annotations.clone();
            clone.checkAnnotationsForSids();
        }

        return clone;
    }


    @Override
    public IonType getType()
    {
        return IonType.STRUCT;
    }


    public boolean containsKey(Object fieldName)
    {
        String name = (String) fieldName;
        return (null != get(name));
    }

    public boolean containsValue(Object value)
    {
        IonValue v = (IonValue) value;
        return (v.getContainer() == this);
    }

    public IonValue get(String fieldName)
    {
        int field_idx = find_field_helper(fieldName);
        IonValue field;

        if (field_idx < 0) {
            if(hasNullFieldName) throw new UnknownSymbolException("Unable to determine whether the field exists because the struct contains field names with unknown text.");
            field = null;
        } else {
            field = get_child(field_idx);
        }

        return field;
    }

    private boolean fieldMapIsActive(int proposedSize) {
        if (_field_map != null) return true;
        if (proposedSize <= STRUCT_INITIAL_SIZE) return false;
        if (_isLocked()) return false;
        build_field_map();
        return true;
    }

    private int find_field_helper(String fieldName)
    {
        validateFieldName(fieldName);

        if (isNullValue()) {
            // nothing to see here, move along
        }
        else if (fieldMapIsActive(_child_count)) {
            Integer idx = _field_map.get(fieldName);
            if (idx != null) {
                return idx;
            }
        }
        else {
            int ii, size = get_child_count();
            for (ii=0; ii m)
    {
        // TODO this is very inefficient
        for (Entry entry : m.entrySet())
        {
            put(entry.getKey(), entry.getValue());
        }
    }

    /**
     * put is "make this value the one and only value
     * associated with this fieldName".  The side effect
     * is that if there were multiple fields with this
     * name when put is complete there will only be the
     * one value in the collection.
     */
    public void put(String fieldName, IonValue value)
    {
        checkForLock();

        validateFieldName(fieldName);
        if (value != null) validateNewChild(value);

        int lowestRemovedIndex = _child_count;
        boolean any_removed = false;

        // first we remove the any existing fields
        // associated with fieldName (which may be none)
        if (_field_map_duplicate_count == 0 && fieldMapIsActive(_child_count))
        {
            // we have a map and no duplicates so the index
            // (aka map) is all we need to find the only
            // value associated with fieldName, if there is one
            Integer idx = _field_map.get(fieldName);
            if (idx != null) {
                lowestRemovedIndex = idx;
                remove_field_from_field_map(fieldName, lowestRemovedIndex);
                remove_child(lowestRemovedIndex);
                any_removed = true;
            }
        }
        else {
            // either we don't have a map (index) or there
            // are duplicates in both cases we have to
            // scan the child list directly.
            // Walk backwards to minimize array movement
            // as we remove fields as we encounter them.
            int copies_removed = 0;
            for (int ii = get_child_count(); ii > 0; )
            {
                ii--;
                IonValueLite child = get_child(ii);
                if (fieldName.equals(child._fieldName))
                {
                    // done by remove_child: child.detachFromContainer();
                    remove_child(ii);
                    lowestRemovedIndex = ii;
                    copies_removed++;
                    any_removed = true;
                }
            }
            if (any_removed) {
                remove_field(fieldName, lowestRemovedIndex, copies_removed);
            }
        }
        if (any_removed) {
            patch_map_elements_helper(lowestRemovedIndex);
            patch_elements_helper(lowestRemovedIndex);
        }

        // once we've removed any existing copy we now add,
        // this (delete + add == put) turns out be be the
        // right choice since:
        //   1 - because of possible duplicates we can't
        //       guarantee the idx is stable
        //   2 - we have to maintain the hash and that
        //       really means we end up with the delete
        //       anyway
        // strictly speaking this approach, while simpler,
        // is more expensive when we don't have a has and
        // the value already exists, and it's not at the
        // end of the field list anyway.
        if (value != null) {
            add(fieldName, value);
        }
    }

    @Override
    void beforeIteratorRemove(IonValueLite value, int idx) {
        if (_field_map != null) {
            remove_field_from_field_map(value.getFieldName(), idx);
        }
    }

    @Override
    void afterIteratorRemove(IonValueLite value, int idx) {
        if (_field_map != null) {
            patch_map_elements_helper(idx);
        }
    }

    public IonValue remove(String fieldName)
    {
        checkForLock();

        IonValue field = get(fieldName);
        if (field == null) {
            return null;
        }

        int idx = ((IonValueLite)field)._elementid();

        // update the hash map first we don't want
        // the child list changed until we've done
        // this since the map update expects the
        // index value of the remove field to be
        // correct and unchanged.
        if (_field_map != null) {
            remove_field_from_field_map(fieldName, idx);
        }

        super.remove(field);

        if (_field_map != null) {
            patch_map_elements_helper(idx);
        }

        return field;
    }

    @Override
    public boolean remove(IonValue element)
    {
        if (element == null) {
            throw new NullPointerException();
        }

        checkForLock();

        if (element.getContainer() != this) {
            return false;
        }

        IonValueLite concrete = (IonValueLite) element;
        int idx = concrete._elementid();

        // update the hash map first we don't want
        // the child list changed until we've done
        // this since the map update expects the
        // index value of the remove field to be
        // correct and unchanged.
        if (_field_map != null) {
            remove_field_from_field_map(concrete.getFieldName(), idx);
        }

        super.remove(concrete);

        if (_field_map != null) {
            patch_map_elements_helper(idx);
        }

        return true;
    }

    public boolean removeAll(String... fieldNames)
    {
        boolean removedAny = false;

        checkForLock();

        int size = get_child_count();
        for (int ii=size; ii>0; ) {
            ii--;
            IonValue field = get_child(ii);
            if (isListedField(field, fieldNames)) {
                field.removeFromContainer();
                removedAny = true;
            }
        }

        return removedAny;
    }

    public boolean retainAll(String... fieldNames)
    {
        checkForLock();

        boolean removedAny = false;
        int size = get_child_count();
        for (int ii=size; ii>0; ) {
            ii--;
            IonValue field = get_child(ii);
            if (! isListedField(field, fieldNames))
            {
                field.removeFromContainer();
                removedAny = true;
            }
        }
        return removedAny;
    }

    /**
     *
     * @param field must not be null.  It is not required to have a field name.
     * @param fields must not be null, and must not contain and nulls.
     * @return true if {@code field.getFieldName()} is in {@code fields}.
     */
    private static boolean isListedField(IonValue field, String[] fields)
    {
        String fieldName = field.getFieldName();
        for (String key : fields)
        {
            if (key.equals(fieldName)) return true;
        }
        return false;
    }


    /**
     * Ensures that a given field name is valid. Used as a helper for
     * methods that have that precondition.
     *
     * @throws NullPointerException if the fieldName
     * is null.
     */
    private static void validateFieldName(String fieldName)
    {
        if (fieldName == null)
        {
            throw new NullPointerException("fieldName is null");
        }
    }

    @Override
    public void accept(ValueVisitor visitor) throws Exception
    {
        visitor.visit(this);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy