/* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch 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.elasticsearch.search.aggregations.support; import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.RandomAccessOrds; import org.apache.lucene.index.SortedNumericDocValues; import org.apache.lucene.util.BytesRef; import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.index.fielddata.AbstractRandomAccessOrds; import org.elasticsearch.index.fielddata.MultiGeoPointValues; import org.elasticsearch.index.fielddata.SortedBinaryDocValues; import org.elasticsearch.index.fielddata.SortedNumericDoubleValues; import java.io.IOException; /** * Utility class that allows to return views of {@link ValuesSource}s that * replace the missing value with a configured value. */ public enum MissingValues { ; // TODO: we could specialize the single value case public static ValuesSource.Bytes replaceMissing(final ValuesSource.Bytes valuesSource, final BytesRef missing) { return new ValuesSource.Bytes() { @Override public SortedBinaryDocValues bytesValues(LeafReaderContext context) throws IOException { SortedBinaryDocValues values = valuesSource.bytesValues(context); return replaceMissing(values, missing); } }; } static SortedBinaryDocValues replaceMissing(final SortedBinaryDocValues values, final BytesRef missing) { return new SortedBinaryDocValues() { private int count; @Override public BytesRef valueAt(int index) { if (count > 0) { return values.valueAt(index); } else if (index == 0) { return missing; } else { throw new IndexOutOfBoundsException(); } } @Override public void setDocument(int docId) { values.setDocument(docId); count = values.count(); } @Override public int count() { return count == 0 ? 1 : count; } }; } public static ValuesSource.Numeric replaceMissing(final ValuesSource.Numeric valuesSource, final Number missing) { final boolean missingIsFloat = missing.longValue() != (long) missing.doubleValue(); final boolean isFloatingPoint = valuesSource.isFloatingPoint() || missingIsFloat; return new ValuesSource.Numeric() { @Override public boolean isFloatingPoint() { return isFloatingPoint; } @Override public SortedBinaryDocValues bytesValues(LeafReaderContext context) throws IOException { return replaceMissing(valuesSource.bytesValues(context), new BytesRef(missing.toString())); } @Override public SortedNumericDocValues longValues(LeafReaderContext context) throws IOException { final SortedNumericDocValues values = valuesSource.longValues(context); return replaceMissing(values, missing.longValue()); } @Override public SortedNumericDoubleValues doubleValues(LeafReaderContext context) throws IOException { final SortedNumericDoubleValues values = valuesSource.doubleValues(context); return replaceMissing(values, missing.doubleValue()); } }; } static SortedNumericDocValues replaceMissing(final SortedNumericDocValues values, final long missing) { return new SortedNumericDocValues() { private int count; @Override public void setDocument(int doc) { values.setDocument(doc); count = values.count(); } @Override public long valueAt(int index) { if (count > 0) { return values.valueAt(index); } else if (index == 0) { return missing; } else { throw new IndexOutOfBoundsException(); } } @Override public int count() { return count == 0 ? 1 : count; } }; } static SortedNumericDoubleValues replaceMissing(final SortedNumericDoubleValues values, final double missing) { return new SortedNumericDoubleValues() { private int count; @Override public void setDocument(int doc) { values.setDocument(doc); count = values.count(); } @Override public double valueAt(int index) { if (count > 0) { return values.valueAt(index); } else if (index == 0) { return missing; } else { throw new IndexOutOfBoundsException(); } } @Override public int count() { return count == 0 ? 1 : count; } }; } public static ValuesSource.Bytes replaceMissing(final ValuesSource.Bytes.WithOrdinals valuesSource, final BytesRef missing) { return new ValuesSource.Bytes.WithOrdinals() { @Override public SortedBinaryDocValues bytesValues(LeafReaderContext context) throws IOException { SortedBinaryDocValues values = valuesSource.bytesValues(context); return replaceMissing(values, missing); } @Override public RandomAccessOrds ordinalsValues(LeafReaderContext context) { RandomAccessOrds values = valuesSource.ordinalsValues(context); return replaceMissing(values, missing); } @Override public RandomAccessOrds globalOrdinalsValues(LeafReaderContext context) { RandomAccessOrds values = valuesSource.globalOrdinalsValues(context); return replaceMissing(values, missing); } }; } static RandomAccessOrds replaceMissing(final RandomAccessOrds values, final BytesRef missing) { final long missingOrd = values.lookupTerm(missing); if (missingOrd >= 0) { // The value already exists return replaceMissingOrd(values, missingOrd); } else { final long insertedOrd = -1 - missingOrd; return insertOrd(values, insertedOrd, missing); } } static RandomAccessOrds replaceMissingOrd(final RandomAccessOrds values, final long missingOrd) { return new AbstractRandomAccessOrds() { private int cardinality = 0; @Override public void doSetDocument(int docID) { values.setDocument(docID); cardinality = values.cardinality(); } @Override public BytesRef lookupOrd(long ord) { return values.lookupOrd(ord); } @Override public long getValueCount() { return values.getValueCount(); } @Override public long ordAt(int index) { if (cardinality > 0) { return values.ordAt(index); } else if (index == 0) { return missingOrd; } else { throw new IndexOutOfBoundsException(); } } @Override public int cardinality() { return cardinality == 0 ? 1 : cardinality; } }; } static RandomAccessOrds insertOrd(final RandomAccessOrds values, final long insertedOrd, final BytesRef missingValue) { return new AbstractRandomAccessOrds() { private int cardinality = 0; @Override public void doSetDocument(int docID) { values.setDocument(docID); cardinality = values.cardinality(); } @Override public BytesRef lookupOrd(long ord) { if (ord < insertedOrd) { return values.lookupOrd(ord); } else if (ord > insertedOrd) { return values.lookupOrd(ord - 1); } else { return missingValue; } } @Override public long getValueCount() { return 1 + values.getValueCount(); } @Override public long ordAt(int index) { if (cardinality > 0) { final long ord = values.ordAt(index); if (ord < insertedOrd) { return ord; } else { return ord + 1; } } else if (index == 0) { return insertedOrd; } else { throw new IndexOutOfBoundsException(); } } @Override public int cardinality() { return cardinality == 0 ? 1 : cardinality; } }; } public static ValuesSource.GeoPoint replaceMissing(final ValuesSource.GeoPoint valuesSource, final GeoPoint missing) { return new ValuesSource.GeoPoint() { @Override public SortedBinaryDocValues bytesValues(LeafReaderContext context) throws IOException { return replaceMissing(valuesSource.bytesValues(context), new BytesRef(missing.toString())); } @Override public MultiGeoPointValues geoPointValues(LeafReaderContext context) { final MultiGeoPointValues values = valuesSource.geoPointValues(context); return replaceMissing(values, missing); } }; } static MultiGeoPointValues replaceMissing(final MultiGeoPointValues values, final GeoPoint missing) { return new MultiGeoPointValues() { private int count; @Override public GeoPoint valueAt(int index) { if (count > 0) { return values.valueAt(index); } else if (index == 0) { return missing; } else { throw new IndexOutOfBoundsException(); } } @Override public void setDocument(int docId) { values.setDocument(docId); count = values.count(); } @Override public int count() { return count == 0 ? 1 : count; } }; } }