/*
* 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.flink.api.java.typeutils.runtime;
import org.apache.flink.annotation.Internal;
import org.apache.flink.api.common.typeutils.CompositeTypeComparator;
import org.apache.flink.api.common.typeutils.TypeComparator;
import org.apache.flink.core.memory.DataInputView;
import org.apache.flink.core.memory.DataOutputView;
import org.apache.flink.core.memory.MemorySegment;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* Null-aware comparator that wraps a comparator which does not support null references.
* <p>
* NOTE: This class assumes to be used within a composite type comparator (such
* as {@link RowComparator}) that handles serialized comparison.
*/
@Internal
public class NullAwareComparator<T> extends TypeComparator<T> {
private static final long serialVersionUID = 1L;
private final TypeComparator<T> wrappedComparator;
private final boolean order;
// number of flat fields
private final int flatFields;
// stores the null for reference comparison
private boolean nullReference = false;
public NullAwareComparator(TypeComparator<T> wrappedComparator, boolean order) {
this.wrappedComparator = wrappedComparator;
this.order = order;
this.flatFields = wrappedComparator.getFlatComparators().length;
}
@Override
public int hash(T record) {
if (record != null) {
return wrappedComparator.hash(record);
} else {
return 0;
}
}
@Override
public void setReference(T toCompare) {
if (toCompare == null) {
nullReference = true;
} else {
nullReference = false;
wrappedComparator.setReference(toCompare);
}
}
@Override
public boolean equalToReference(T candidate) {
// both values are null
if (candidate == null && nullReference) {
return true;
}
// one value is null
else if (candidate == null || nullReference) {
return false;
}
// no null value
else {
return wrappedComparator.equalToReference(candidate);
}
}
@Override
public int compareToReference(TypeComparator<T> referencedComparator) {
NullAwareComparator otherComparator = (NullAwareComparator) referencedComparator;
boolean otherNullReference = otherComparator.nullReference;
// both values are null -> equality
if (nullReference && otherNullReference) {
return 0;
}
// first value is null -> inequality
// but order is considered
else if (nullReference) {
return order ? 1 : -1;
}
// second value is null -> inequality
// but order is considered
else if (otherNullReference) {
return order ? -1 : 1;
}
// no null values
else {
return wrappedComparator.compareToReference(otherComparator.wrappedComparator);
}
}
@Override
public int compare(T first, T second) {
// both values are null -> equality
if (first == null && second == null) {
return 0;
}
// first value is null -> inequality
// but order is considered
else if (first == null) {
return order ? -1 : 1;
}
// second value is null -> inequality
// but order is considered
else if (second == null) {
return order ? 1 : -1;
}
// no null values
else {
return wrappedComparator.compare(first, second);
}
}
@Override
public int compareSerialized(
DataInputView firstSource,
DataInputView secondSource) throws IOException {
throw new UnsupportedOperationException(
"Comparator does not support null-aware serialized comparision.");
}
@Override
public boolean supportsNormalizedKey() {
return wrappedComparator.supportsNormalizedKey();
}
@Override
public boolean supportsSerializationWithKeyNormalization() {
return false;
}
@Override
public int getNormalizeKeyLen() {
int len = wrappedComparator.getNormalizeKeyLen();
if (len == Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
} else {
// add one for a null byte
return len + 1;
}
}
@Override
public boolean isNormalizedKeyPrefixOnly(int keyBytes) {
return wrappedComparator.isNormalizedKeyPrefixOnly(keyBytes - 1);
}
@Override
public void putNormalizedKey(T record, MemorySegment target, int offset, int numBytes) {
if (numBytes > 0) {
// write a null byte with padding
if (record == null) {
target.putBoolean(offset, false);
// write padding
for (int j = 0; j < numBytes - 1; j++) {
target.put(offset + 1 + j, (byte) 0);
}
}
// write a non-null byte with key
else {
target.putBoolean(offset, true);
// write key
wrappedComparator.putNormalizedKey(record, target, offset + 1, numBytes - 1);
}
}
}
@Override
public void writeWithKeyNormalization(T record, DataOutputView target) throws IOException {
throw new UnsupportedOperationException(
"Record serialization with leading normalized keys not supported.");
}
@Override
public T readWithKeyDenormalization(T reuse, DataInputView source) throws IOException {
throw new UnsupportedOperationException(
"Record deserialization with leading normalized keys not supported.");
}
@Override
public boolean invertNormalizedKey() {
return wrappedComparator.invertNormalizedKey();
}
@Override
public TypeComparator<T> duplicate() {
return new NullAwareComparator<T>(wrappedComparator.duplicate(), order);
}
@Override
public int extractKeys(Object record, Object[] target, int index) {
if (record == null) {
for (int i = 0; i < flatFields; i++) {
target[index + i] = null;
}
return flatFields;
} else {
return wrappedComparator.extractKeys(record, target, index);
}
}
@Override
public TypeComparator[] getFlatComparators() {
// determine the flat comparators and wrap them again in null-aware comparators
List<TypeComparator<?>> flatComparators = new ArrayList<>();
if (wrappedComparator instanceof CompositeTypeComparator) {
((CompositeTypeComparator) wrappedComparator).getFlatComparator(flatComparators);
} else {
flatComparators.add(wrappedComparator);
}
TypeComparator<?>[] result = new TypeComparator[flatComparators.size()];
for (int i = 0; i < result.length; i++) {
result[i] = new NullAwareComparator<>(flatComparators.get(i), order);
}
return result;
}
}