package org.apache.lucene.index; /* * 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. */ import java.util.HashMap; import java.util.Map; import org.apache.lucene.index.DocValues.Type; import org.apache.lucene.util.BytesRef; // TODO: maybe we should not automagically promote // types... and instead require a given field always has the // same type? /** * Type promoter that promotes {@link DocValues} during merge based on their * {@link Type} and {@link #getValueSize()} * * @lucene.internal */ class TypePromoter { private final static Map<Integer,Type> FLAGS_MAP = new HashMap<Integer,Type>(); private static final TypePromoter IDENTITY_PROMOTER = new IdentityTypePromoter(); public static final int VAR_TYPE_VALUE_SIZE = -1; private static final int IS_INT = 1 << 0 | 1 << 2; private static final int IS_BYTE = 1 << 1; private static final int IS_FLOAT = 1 << 2 ; /* VAR & FIXED == VAR */ private static final int IS_VAR = 1 << 3; private static final int IS_FIXED = 1 << 3 | 1 << 4; /* if we have FIXED & FIXED with different size we promote to VAR */ private static final int PROMOTE_TO_VAR_SIZE_MASK = ~(1 << 3); /* STRAIGHT & DEREF == STRAIGHT (dense values win) */ private static final int IS_STRAIGHT = 1 << 5; private static final int IS_DEREF = 1 << 5 | 1 << 6; private static final int IS_SORTED = 1 << 7; /* more bits wins (int16 & int32 == int32) */ private static final int IS_8_BIT = 1 << 8 | 1 << 9 | 1 << 10 | 1 << 11 | 1 << 12 | 1 << 13; // 8 private static final int IS_16_BIT = 1 << 9 | 1 << 10 | 1 << 11 | 1 << 12 | 1 << 13; // 9 private static final int IS_32_BIT = 1 << 10 | 1 << 11 | 1 << 13; private static final int IS_64_BIT = 1 << 11; private static final int IS_32_BIT_FLOAT = 1 << 12 | 1 << 13; private static final int IS_64_BIT_FLOAT = 1 << 13; private Type type; private int flags; private int valueSize; /** * Returns a positive value size if this {@link TypePromoter} represents a * fixed variant, otherwise <code>-1</code> * * @return a positive value size if this {@link TypePromoter} represents a * fixed variant, otherwise <code>-1</code> */ public int getValueSize() { return valueSize; } static { for (Type type : Type.values()) { TypePromoter create = create(type, VAR_TYPE_VALUE_SIZE); FLAGS_MAP.put(create.flags, type); } } /** * Creates a new {@link TypePromoter} * */ protected TypePromoter() {} /** * Creates a new {@link TypePromoter} * * @param type * the {@link Type} this promoter represents * * @param flags * the promoters flags * @param valueSize * the value size if {@link #IS_FIXED} or <code>-1</code> otherwise. */ protected TypePromoter(Type type, int flags, int valueSize) { this.type = type; this.flags = flags; this.valueSize = valueSize; } /** * Resets the {@link TypePromoter} * * @param type * the {@link Type} this promoter represents * * @param flags * the promoters flags * @param valueSize * the value size if {@link #IS_FIXED} or <code>-1</code> otherwise. */ protected TypePromoter set(Type type, int flags, int valueSize) { this.type = type; this.flags = flags; this.valueSize = valueSize; return this; } /** * Creates a new promoted {@link TypePromoter} based on this and the given * {@link TypePromoter} or <code>null</code> iff the {@link TypePromoter} * aren't compatible. * * @param promoter * the incoming promoter * @return a new promoted {@link TypePromoter} based on this and the given * {@link TypePromoter} or <code>null</code> iff the * {@link TypePromoter} aren't compatible. */ public TypePromoter promote(TypePromoter promoter) { return promote(promoter, newPromoter()); } private TypePromoter promote(TypePromoter promoter, TypePromoter spare) { int promotedFlags = promoter.flags & this.flags; TypePromoter promoted = reset(FLAGS_MAP.get(promotedFlags), valueSize, spare); if (promoted == null) { return TypePromoter.create(DocValues.Type.BYTES_VAR_STRAIGHT, TypePromoter.VAR_TYPE_VALUE_SIZE); } if ((promoted.flags & IS_BYTE) != 0 && (promoted.flags & IS_FIXED) == IS_FIXED) { if (this.valueSize == promoter.valueSize) { return promoted; } return reset(FLAGS_MAP.get(promoted.flags & PROMOTE_TO_VAR_SIZE_MASK), VAR_TYPE_VALUE_SIZE, spare); } return promoted; } /** * Returns the {@link Type} of this {@link TypePromoter} * * @return the {@link Type} of this {@link TypePromoter} */ public Type type() { return type; } private boolean isTypeCompatible(TypePromoter promoter) { int promotedFlags = promoter.flags & this.flags; return (promotedFlags & 0x7) > 0; } private boolean isBytesCompatible(TypePromoter promoter) { int promotedFlags = promoter.flags & this.flags; return (promotedFlags & IS_BYTE) > 0 && (promotedFlags & (IS_FIXED | IS_VAR)) > 0; } private boolean isNumericSizeCompatible(TypePromoter promoter) { int promotedFlags = promoter.flags & this.flags; return (promotedFlags & IS_BYTE) == 0 && (((promotedFlags & IS_FIXED) > 0 && (promotedFlags & (IS_8_BIT)) > 0) || (promotedFlags & IS_VAR) > 0); } @Override public String toString() { return "TypePromoter [type=" + type + ", sizeInBytes=" + valueSize + "]"; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + flags; result = prime * result + ((type == null) ? 0 : type.hashCode()); result = prime * result + valueSize; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; TypePromoter other = (TypePromoter) obj; if (flags != other.flags) return false; if (type != other.type) return false; if (valueSize != other.valueSize) return false; return true; } /** * Creates a new {@link TypePromoter} for the given type and size per value. * * @param type * the {@link Type} to create the promoter for * @param valueSize * the size per value in bytes or <code>-1</code> iff the types have * variable length. * @return a new {@link TypePromoter} */ public static TypePromoter create(Type type, int valueSize) { return reset(type, valueSize, new TypePromoter()); } private static TypePromoter reset(Type type, int valueSize, TypePromoter promoter) { if (type == null) { return null; } switch (type) { case BYTES_FIXED_DEREF: return promoter.set(type, IS_BYTE | IS_FIXED | IS_DEREF, valueSize); case BYTES_FIXED_SORTED: return promoter.set(type, IS_BYTE | IS_FIXED | IS_SORTED, valueSize); case BYTES_FIXED_STRAIGHT: return promoter.set(type, IS_BYTE | IS_FIXED | IS_STRAIGHT, valueSize); case BYTES_VAR_DEREF: return promoter.set(type, IS_BYTE | IS_VAR | IS_DEREF, VAR_TYPE_VALUE_SIZE); case BYTES_VAR_SORTED: return promoter.set(type, IS_BYTE | IS_VAR | IS_SORTED, VAR_TYPE_VALUE_SIZE); case BYTES_VAR_STRAIGHT: return promoter.set(type, IS_BYTE | IS_VAR | IS_STRAIGHT, VAR_TYPE_VALUE_SIZE); case FIXED_INTS_16: return promoter.set(type, IS_INT | IS_FIXED | IS_STRAIGHT | IS_16_BIT, valueSize); case FIXED_INTS_32: return promoter.set(type, IS_INT | IS_FIXED | IS_STRAIGHT | IS_32_BIT, valueSize); case FIXED_INTS_64: return promoter.set(type, IS_INT | IS_FIXED | IS_STRAIGHT | IS_64_BIT, valueSize); case FIXED_INTS_8: return promoter.set(type, IS_INT | IS_FIXED | IS_STRAIGHT | IS_8_BIT, valueSize); case FLOAT_32: return promoter.set(type, IS_FLOAT | IS_FIXED | IS_STRAIGHT | IS_32_BIT_FLOAT, valueSize); case FLOAT_64: return promoter.set(type, IS_FLOAT | IS_FIXED | IS_STRAIGHT | IS_64_BIT_FLOAT, valueSize); case VAR_INTS: return promoter.set(type, IS_INT | IS_VAR | IS_STRAIGHT, VAR_TYPE_VALUE_SIZE); default: throw new IllegalStateException(); } } public static int getValueSize(DocValues.Type type, BytesRef ref) { switch (type) { case VAR_INTS: case BYTES_VAR_DEREF: case BYTES_VAR_SORTED: case BYTES_VAR_STRAIGHT: return -1; case BYTES_FIXED_DEREF: case BYTES_FIXED_SORTED: case BYTES_FIXED_STRAIGHT: assert ref != null; return ref.length; case FIXED_INTS_16: return 2; case FLOAT_32: case FIXED_INTS_32: return 4; case FLOAT_64: case FIXED_INTS_64: return 8; case FIXED_INTS_8: return 1; default: throw new IllegalArgumentException("unknonw docvalues type: " + type.name()); } } /** * Returns a {@link TypePromoter} that always promotes to the type provided to * {@link #promote(TypePromoter)} */ public static TypePromoter getIdentityPromoter() { return IDENTITY_PROMOTER; } private static TypePromoter newPromoter() { return new TypePromoter(null, 0, -1); } private static class IdentityTypePromoter extends TypePromoter { public IdentityTypePromoter() { super(null, 0, -1); } @Override protected TypePromoter set(Type type, int flags, int valueSize) { throw new UnsupportedOperationException("can not reset IdendityPromotoer"); } @Override public TypePromoter promote(TypePromoter promoter) { return promoter; } } static class TypeCompatibility { private final TypePromoter base; private final TypePromoter spare; TypeCompatibility(Type type, int valueSize) { this.base = create(type, valueSize); spare = newPromoter(); } boolean isCompatible(Type type, int valueSize) { TypePromoter reset = reset(type, valueSize, spare); if (base.isTypeCompatible(reset)) { if (base.isBytesCompatible(reset)) { return base.valueSize == -1 || base.valueSize == valueSize; } else if (base.flags == reset.flags) { return true; } else if (base.isNumericSizeCompatible(reset)) { return base.valueSize == -1 || (base.valueSize > valueSize && valueSize > 0); } } return false; } Type getBaseType() { return base.type(); } int getBaseSize() { return base.valueSize; } } }