com.torodb.core.transaction.metainf.WrapperMutableMetaDocPart.java Source code

Java tutorial

Introduction

Here is the source code for com.torodb.core.transaction.metainf.WrapperMutableMetaDocPart.java

Source

/*
 * ToroDB
 * Copyright  2014 8Kdata Technology (www.8kdata.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

package com.torodb.core.transaction.metainf;

import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Maps;
import com.google.common.collect.Table;
import com.torodb.core.TableRef;
import com.torodb.core.annotations.DoNotChange;
import com.torodb.core.transaction.metainf.ImmutableMetaDocPart.Builder;
import org.jooq.lambda.Seq;
import org.jooq.lambda.tuple.Tuple2;

import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
 *
 */
public class WrapperMutableMetaDocPart implements MutableMetaDocPart {

    private final ImmutableMetaDocPart wrapped;
    /**
     * This table contains all fields contained by wrapper and all new fields
     */
    private final Table<String, FieldType, ImmutableMetaField> newFields;
    /**
     * This list just contains the fields that have been added on this wrapper but not on the wrapped
     * object.
     */
    private final List<ImmutableMetaField> addedFields;
    private final Map<String, ImmutableMetaField> addedFieldsByIndetifiers;
    private final Consumer<WrapperMutableMetaDocPart> changeConsumer;
    private final EnumMap<FieldType, ImmutableMetaScalar> newScalars;
    @SuppressWarnings("checkstyle:LineLength")
    private final HashMap<String, Tuple2<ImmutableMetaIdentifiedDocPartIndex, MetaElementState>> indexesByIdentifier;
    @SuppressWarnings("checkstyle:LineLength")
    private final Map<String, Tuple2<ImmutableMetaIdentifiedDocPartIndex, MetaElementState>> aliveIndexesMap;
    private final List<MutableMetaDocPartIndex> addedMutableIndexes;

    public WrapperMutableMetaDocPart(ImmutableMetaDocPart wrapped,
            Consumer<WrapperMutableMetaDocPart> changeConsumer) {
        this.wrapped = wrapped;

        newFields = HashBasedTable.create();

        wrapped.streamFields().forEach((field) -> newFields.put(field.getName(), field.getType(), field));
        addedFields = new ArrayList<>();
        addedFieldsByIndetifiers = new HashMap<>();
        this.changeConsumer = changeConsumer;
        this.newScalars = new EnumMap<>(FieldType.class);
        indexesByIdentifier = new HashMap<>();
        wrapped.streamIndexes().forEach((docPartIndexindex) -> {
            indexesByIdentifier.put(docPartIndexindex.getIdentifier(),
                    new Tuple2<>(docPartIndexindex, MetaElementState.NOT_CHANGED));
        });
        aliveIndexesMap = Maps.filterValues(indexesByIdentifier, tuple -> tuple.v2().isAlive());
        addedMutableIndexes = new ArrayList<>();
    }

    @Override
    public ImmutableMetaField addMetaField(String name, String identifier, FieldType type)
            throws IllegalArgumentException {
        if (getMetaFieldByNameAndType(name, type) != null) {
            throw new IllegalArgumentException(
                    "There is another field with the name " + name + " whose type is " + type);
        }

        assert getMetaFieldByIdentifier(identifier) == null : "There is another field with the identifier "
                + identifier;

        ImmutableMetaField newField = new ImmutableMetaField(name, identifier, type);
        newFields.put(name, type, newField);
        addedFields.add(newField);
        addedFieldsByIndetifiers.put(newField.getIdentifier(), newField);
        changeConsumer.accept(this);
        return newField;
    }

    @Override
    public ImmutableMetaScalar addMetaScalar(String identifier, FieldType type) throws IllegalArgumentException {
        if (getScalar(type) != null) {
            throw new IllegalArgumentException(
                    "There is another scalar with type " + type + ", " + "whose identifier is " + identifier);
        }
        ImmutableMetaScalar scalar = new ImmutableMetaScalar(identifier, type);
        newScalars.put(type, scalar);
        changeConsumer.accept(this);
        return scalar;
    }

    @Override
    public Stream<ImmutableMetaField> streamAddedMetaFields() {
        return addedFields.stream();
    }

    @Override
    public ImmutableMetaField getAddedFieldByIdentifier(String identifier) {
        return addedFieldsByIndetifiers.get(identifier);
    }

    @Override
    public Stream<? extends ImmutableMetaScalar> streamAddedMetaScalars() {
        return newScalars.values().stream();
    }

    @Override
    public MutableMetaDocPartIndex addMetaDocPartIndex(boolean unique) {
        MutableMetaDocPartIndex newIndex = new WrapperMutableMetaDocPartIndex(unique, this::onDocPartIndexChange);
        addedMutableIndexes.add(newIndex);
        return newIndex;
    }

    @Override
    public boolean removeMetaDocPartIndexByIdentifier(String indexId) {
        ImmutableMetaIdentifiedDocPartIndex metaDocPartIndex = getMetaDocPartIndexByIdentifier(indexId);
        if (metaDocPartIndex == null) {
            return false;
        }

        indexesByIdentifier.put(metaDocPartIndex.getIdentifier(),
                new Tuple2<>(metaDocPartIndex, MetaElementState.REMOVED));
        changeConsumer.accept(this);
        return true;
    }

    @Override
    @SuppressWarnings("checkstyle:LineLength")
    public Stream<ChangedElement<ImmutableMetaIdentifiedDocPartIndex>> streamModifiedMetaDocPartIndexes() {
        return indexesByIdentifier.values().stream().filter(tuple -> tuple.v2().hasChanged())
                .map(PojoChangedElement::new);
    }

    @Override
    @DoNotChange
    public Iterable<MutableMetaDocPartIndex> getAddedMutableMetaDocPartIndexes() {
        return addedMutableIndexes;
    }

    @Override
    public ImmutableMetaDocPart immutableCopy() {
        if (addedFields.isEmpty() && newScalars.isEmpty()
                && indexesByIdentifier.values().stream().noneMatch(tuple -> tuple.v2().hasChanged())) {
            return wrapped;
        } else {
            ImmutableMetaDocPart.Builder builder = new Builder(wrapped);
            for (ImmutableMetaField addedField : addedFields) {
                builder.put(addedField);
            }
            for (ImmutableMetaScalar value : newScalars.values()) {
                builder.put(value);
            }

            indexesByIdentifier.values().forEach(tuple -> {
                switch (tuple.v2()) {
                case ADDED:
                case MODIFIED:
                case NOT_CHANGED:
                    builder.put(tuple.v1());
                    break;
                case REMOVED:
                    builder.remove(tuple.v1());
                    break;
                case NOT_EXISTENT:
                default:
                    throw new AssertionError("Unexpected case " + tuple.v2());
                }
            });
            return builder.build();
        }
    }

    @Override
    public TableRef getTableRef() {
        return wrapped.getTableRef();
    }

    @Override
    public String getIdentifier() {
        return wrapped.getIdentifier();
    }

    @Override
    public Stream<? extends ImmutableMetaField> streamFields() {
        return newFields.values().stream();
    }

    @Override
    public Stream<? extends ImmutableMetaField> streamMetaFieldByName(String columnName) {
        return newFields.row(columnName).values().stream();
    }

    @Override
    public ImmutableMetaField getMetaFieldByNameAndType(String fieldName, FieldType type) {
        return newFields.get(fieldName, type);
    }

    @Override
    public ImmutableMetaField getMetaFieldByIdentifier(String fieldId) {
        return newFields.values().stream().filter((field) -> field.getIdentifier().equals(fieldId)).findAny()
                .orElse(null);
    }

    @Override
    public Stream<? extends MetaScalar> streamScalars() {
        return Stream.concat(newScalars.values().stream(), wrapped.streamScalars());
    }

    @Override
    public MetaScalar getScalar(FieldType type) {
        ImmutableMetaScalar scalar = newScalars.get(type);
        if (scalar != null) {
            return scalar;
        }
        return wrapped.getScalar(type);
    }

    @Override
    public Stream<? extends ImmutableMetaIdentifiedDocPartIndex> streamIndexes() {
        return aliveIndexesMap.values().stream().map(tuple -> tuple.v1());
    }

    @Override
    public ImmutableMetaIdentifiedDocPartIndex getMetaDocPartIndexByIdentifier(String indexId) {
        Tuple2<ImmutableMetaIdentifiedDocPartIndex, MetaElementState> tuple = aliveIndexesMap.get(indexId);
        if (tuple == null) {
            return null;
        }
        return tuple.v1();
    }

    @Override
    public MutableMetaDocPartIndex getOrCreatePartialMutableDocPartIndexForMissingIndexAndNewField(
            MetaIndex missingIndex, List<String> identifiers, MetaField newField) {
        int position = identifiers.indexOf(newField.getIdentifier());
        Optional<MutableMetaDocPartIndex> matchingMutableDocPartIndex = Seq.seq(getAddedMutableMetaDocPartIndexes())
                .filter(docPartIndex -> docPartIndex.getMetaDocPartIndexColumnByPosition(position) == null
                        && identifiers.size() >= docPartIndex.size()
                        && missingIndex.isSubMatch(this, identifiers, docPartIndex)
                        // We ensure we do not pick a doc part index that fit a isSubMatch for our index but
                        // was the only chance for another combination. For example:
                        // 1. a_i, b_i, c_i are old fields
                        // 2. a_s, b_s, c_s are new fields
                        // 3. we have index a asc, b asc, c asc
                        // 4. we added doc part index a_s asc, null, null and a_s asc, b_i asc, null
                        // 5. we search for a sub match for a_s, b_i, c_s and found a_s asc, null, null
                        && noneNonCurrentAndNullIndexColumnIsNew(position, docPartIndex, identifiers))
                .findAny();
        MutableMetaDocPartIndex docPartIndex;
        if (matchingMutableDocPartIndex.isPresent()) {
            docPartIndex = matchingMutableDocPartIndex.get();
        } else {
            docPartIndex = addMetaDocPartIndex(missingIndex.isUnique());
            int index = 0;
            for (String identifier : identifiers) {
                if (getAddedFieldByIdentifier(identifier) == null) {
                    MetaIndexField indexField = missingIndex.getMetaIndexFieldByTableRefAndPosition(getTableRef(),
                            index);
                    docPartIndex.putMetaDocPartIndexColumn(index, identifier, indexField.getOrdering());
                }
                index++;
            }
        }
        MetaIndexField indexField = missingIndex.getMetaIndexFieldByTableRefAndPosition(getTableRef(), position);
        docPartIndex.putMetaDocPartIndexColumn(position, newField.getIdentifier(), indexField.getOrdering());
        return docPartIndex;
    }

    private boolean noneNonCurrentAndNullIndexColumnIsNew(int position, MutableMetaDocPartIndex docPartIndex,
            List<String> identifiers) {
        return IntStream.range(0, identifiers.size())
                .noneMatch(index -> index != position
                        && docPartIndex.getMetaDocPartIndexColumnByPosition(index) == null
                        && getAddedFieldByIdentifier(identifiers.get(index)) == null);
    }

    private boolean isTransitionAllowed(MetaIdentifiedDocPartIndex metaDocPartIndexIndex,
            MetaElementState newState) {
        MetaElementState oldState;
        Tuple2<ImmutableMetaIdentifiedDocPartIndex, MetaElementState> tuple = indexesByIdentifier
                .get(metaDocPartIndexIndex.getIdentifier());

        if (tuple == null) {
            oldState = MetaElementState.NOT_EXISTENT;
        } else {
            oldState = tuple.v2();
        }

        oldState.assertLegalTransition(newState);
        return true;
    }

    protected void onDocPartIndexChange(WrapperMutableMetaDocPartIndex changedIndex,
            ImmutableMetaIdentifiedDocPartIndex immutableIndex) {
        assert isTransitionAllowed(immutableIndex, MetaElementState.ADDED);

        if (getMetaDocPartIndexByIdentifier(immutableIndex.getIdentifier()) != null) {
            throw new IllegalArgumentException(
                    "There is another index with the identifier " + immutableIndex.getIdentifier());
        }

        addedMutableIndexes.remove(changedIndex);
        indexesByIdentifier.put(immutableIndex.getIdentifier(),
                new Tuple2<>(immutableIndex, MetaElementState.ADDED));
        changeConsumer.accept(this);
    }

    @Override
    public String toString() {
        return defautToString();
    }

}