/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License 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 org.apache.cassandra.db.marshal; import java.nio.ByteBuffer; import java.io.IOException; import java.util.List; import java.util.Iterator; import org.apache.cassandra.cql3.CQL3Type; import org.apache.cassandra.cql3.ColumnSpecification; import org.apache.cassandra.cql3.Lists; import org.apache.cassandra.cql3.Maps; import org.apache.cassandra.cql3.Sets; import org.apache.cassandra.db.rows.Cell; import org.apache.cassandra.db.rows.CellPath; import org.apache.cassandra.io.util.DataInputPlus; import org.apache.cassandra.io.util.DataOutputPlus; import org.apache.cassandra.serializers.CollectionSerializer; import org.apache.cassandra.serializers.MarshalException; import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.utils.ByteBufferUtil; /** * The abstract validator that is the base for maps, sets and lists (both frozen and non-frozen). * * Please note that this comparator shouldn't be used "manually" (as a custom * type for instance). */ public abstract class CollectionType<T> extends AbstractType<T> { public static CellPath.Serializer cellPathSerializer = new CollectionPathSerializer(); public enum Kind { MAP { public ColumnSpecification makeCollectionReceiver(ColumnSpecification collection, boolean isKey) { return isKey ? Maps.keySpecOf(collection) : Maps.valueSpecOf(collection); } }, SET { public ColumnSpecification makeCollectionReceiver(ColumnSpecification collection, boolean isKey) { return Sets.valueSpecOf(collection); } }, LIST { public ColumnSpecification makeCollectionReceiver(ColumnSpecification collection, boolean isKey) { return Lists.valueSpecOf(collection); } }; public abstract ColumnSpecification makeCollectionReceiver(ColumnSpecification collection, boolean isKey); } public final Kind kind; protected CollectionType(ComparisonType comparisonType, Kind kind) { super(comparisonType); this.kind = kind; } public abstract AbstractType<?> nameComparator(); public abstract AbstractType<?> valueComparator(); protected abstract List<ByteBuffer> serializedValues(Iterator<Cell> cells); @Override public abstract CollectionSerializer<T> getSerializer(); public ColumnSpecification makeCollectionReceiver(ColumnSpecification collection, boolean isKey) { return kind.makeCollectionReceiver(collection, isKey); } public String getString(ByteBuffer bytes) { return BytesType.instance.getString(bytes); } public ByteBuffer fromString(String source) { try { return ByteBufferUtil.hexToBytes(source); } catch (NumberFormatException e) { throw new MarshalException(String.format("cannot parse '%s' as hex bytes", source), e); } } public boolean isCollection() { return true; } @Override public void validateCellValue(ByteBuffer cellValue) throws MarshalException { if (isMultiCell()) valueComparator().validateCellValue(cellValue); else super.validateCellValue(cellValue); } /** * Checks if this collection is Map. * @return <code>true</code> if this collection is a Map, <code>false</code> otherwise. */ public boolean isMap() { return kind == Kind.MAP; } @Override public boolean isFreezable() { return true; } // Overrided by maps protected int collectionSize(List<ByteBuffer> values) { return values.size(); } public ByteBuffer serializeForNativeProtocol(Iterator<Cell> cells, ProtocolVersion version) { assert isMultiCell(); List<ByteBuffer> values = serializedValues(cells); int size = collectionSize(values); return CollectionSerializer.pack(values, size, version); } @Override public boolean isCompatibleWith(AbstractType<?> previous) { if (this == previous) return true; if (!getClass().equals(previous.getClass())) return false; CollectionType tprev = (CollectionType) previous; if (this.isMultiCell() != tprev.isMultiCell()) return false; // subclasses should handle compatibility checks for frozen collections if (!this.isMultiCell()) return isCompatibleWithFrozen(tprev); if (!this.nameComparator().isCompatibleWith(tprev.nameComparator())) return false; // the value comparator is only used for Cell values, so sorting doesn't matter return this.valueComparator().isValueCompatibleWith(tprev.valueComparator()); } @Override public boolean isValueCompatibleWithInternal(AbstractType<?> previous) { // for multi-cell collections, compatibility and value-compatibility are the same if (this.isMultiCell()) return isCompatibleWith(previous); if (this == previous) return true; if (!getClass().equals(previous.getClass())) return false; CollectionType tprev = (CollectionType) previous; if (this.isMultiCell() != tprev.isMultiCell()) return false; // subclasses should handle compatibility checks for frozen collections return isValueCompatibleWithFrozen(tprev); } /** A version of isCompatibleWith() to deal with non-multicell (frozen) collections */ protected abstract boolean isCompatibleWithFrozen(CollectionType<?> previous); /** A version of isValueCompatibleWith() to deal with non-multicell (frozen) collections */ protected abstract boolean isValueCompatibleWithFrozen(CollectionType<?> previous); public CQL3Type asCQL3Type() { return new CQL3Type.Collection(this); } @Override public boolean equals(Object o, boolean ignoreFreezing) { if (this == o) return true; if (!(o instanceof CollectionType)) return false; CollectionType other = (CollectionType)o; if (kind != other.kind) return false; if (!ignoreFreezing && isMultiCell() != other.isMultiCell()) return false; return nameComparator().equals(other.nameComparator(), ignoreFreezing) && valueComparator().equals(other.valueComparator(), ignoreFreezing); } @Override public String toString() { return this.toString(false); } private static class CollectionPathSerializer implements CellPath.Serializer { public void serialize(CellPath path, DataOutputPlus out) throws IOException { ByteBufferUtil.writeWithVIntLength(path.get(0), out); } public CellPath deserialize(DataInputPlus in) throws IOException { return CellPath.create(ByteBufferUtil.readWithVIntLength(in)); } public long serializedSize(CellPath path) { return ByteBufferUtil.serializedSizeWithVIntLength(path.get(0)); } public void skip(DataInputPlus in) throws IOException { ByteBufferUtil.skipWithVIntLength(in); } } }