| /* |
| * 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.calcite.util; |
| |
| import org.apache.calcite.rel.metadata.NullSentinel; |
| |
| import com.google.common.collect.Collections2; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Iterators; |
| |
| import java.util.AbstractSet; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.Set; |
| |
| /** |
| * An immutable set that may contain null values. |
| * |
| * <p>If the set cannot contain null values, use {@link ImmutableSet}. |
| * |
| * <p>We do not yet support sorted sets. |
| * |
| * @param <E> Element type |
| */ |
| public class ImmutableNullableSet<E> extends AbstractSet<E> { |
| @SuppressWarnings("rawtypes") |
| private static final Set SINGLETON_NULL = |
| new ImmutableNullableSet(ImmutableSet.of(NullSentinel.INSTANCE)); |
| |
| private static final Set<Integer> SINGLETON = Collections.singleton(0); |
| |
| private final ImmutableSet<Object> elements; |
| |
| private ImmutableNullableSet(ImmutableSet<Object> elements) { |
| this.elements = Objects.requireNonNull(elements); |
| } |
| |
| @Override public Iterator<E> iterator() { |
| return Iterators.transform(elements.iterator(), e -> |
| e == NullSentinel.INSTANCE ? null : (E) e); |
| } |
| |
| @Override public int size() { |
| return elements.size(); |
| } |
| |
| @Override public boolean contains(Object o) { |
| return elements.contains(o == null ? NullSentinel.INSTANCE : o); |
| } |
| |
| @Override public boolean remove(Object o) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override public boolean removeAll(Collection<?> c) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** |
| * Returns an immutable set containing the given elements. |
| * |
| * <p>Behavior is as {@link ImmutableSet#copyOf(Iterable)} |
| * except that this set allows nulls. |
| */ |
| @SuppressWarnings({"unchecked", "StaticPseudoFunctionalStyleMethod"}) |
| public static <E> Set<E> copyOf(Iterable<? extends E> elements) { |
| if (elements instanceof ImmutableNullableSet |
| || elements instanceof ImmutableSet |
| || elements == Collections.emptySet() |
| || elements == Collections.emptySortedSet() |
| || elements == SINGLETON_NULL |
| || elements.getClass() == SINGLETON.getClass()) { |
| return (Set<E>) elements; |
| } |
| final ImmutableSet<Object> set; |
| if (elements instanceof Collection) { |
| final Collection<E> collection = (Collection<E>) elements; |
| switch (collection.size()) { |
| case 0: |
| return ImmutableSet.of(); |
| case 1: |
| E element = Iterables.getOnlyElement(collection); |
| return element == null ? SINGLETON_NULL : ImmutableSet.of(element); |
| default: |
| set = ImmutableSet.copyOf( |
| Collections2.transform(collection, e -> |
| e == null ? NullSentinel.INSTANCE : e)); |
| } |
| } else { |
| set = ImmutableSet.copyOf( |
| Iterables.transform(elements, e -> |
| e == null ? NullSentinel.INSTANCE : e)); |
| } |
| if (set.contains(NullSentinel.INSTANCE)) { |
| return new ImmutableNullableSet<>(set); |
| } else { |
| return (Set<E>) set; |
| } |
| } |
| |
| /** |
| * Returns an immutable set containing the given elements. |
| * |
| * <p>Behavior as |
| * {@link ImmutableSet#copyOf(Object[])} |
| * except that this set allows nulls. |
| */ |
| public static <E> Set<E> copyOf(E[] elements) { |
| return copyOf(elements, true); |
| } |
| |
| private static <E> Set<E> copyOf(E[] elements, boolean needCopy) { |
| // If there are no nulls, ImmutableSet is better. |
| if (!containsNull(elements)) { |
| return ImmutableSet.copyOf(elements); |
| } |
| |
| final Object[] objects = |
| needCopy ? Arrays.copyOf(elements, elements.length, Object[].class) |
| : elements; |
| for (int i = 0; i < objects.length; i++) { |
| if (objects[i] == null) { |
| objects[i] = NullSentinel.INSTANCE; |
| } |
| } |
| return new ImmutableNullableSet<E>(ImmutableSet.copyOf(objects)); |
| } |
| |
| private static <E> boolean containsNull(E[] elements) { |
| for (E element : elements) { |
| if (element == null) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** Creates an immutable set of 1 element. */ |
| public static <E> Set<E> of(E e1) { |
| //noinspection unchecked |
| return e1 == null ? (Set<E>) SINGLETON_NULL : ImmutableSet.of(e1); |
| } |
| |
| /** Creates an immutable set of 2 elements. */ |
| @SuppressWarnings("unchecked") |
| public static <E> Set<E> of(E e1, E e2) { |
| return copyOf((E []) new Object[] {e1, e2}, false); |
| } |
| |
| /** Creates an immutable set of 3 elements. */ |
| @SuppressWarnings("unchecked") |
| public static <E> Set<E> of(E e1, E e2, E e3) { |
| return copyOf((E []) new Object[] {e1, e2, e3}, false); |
| } |
| |
| /** Creates an immutable set of 4 elements. */ |
| @SuppressWarnings("unchecked") |
| public static <E> Set<E> of(E e1, E e2, E e3, E e4) { |
| return copyOf((E []) new Object[] {e1, e2, e3, e4}, false); |
| } |
| |
| /** Creates an immutable set of 5 or more elements. */ |
| @SuppressWarnings("unchecked") |
| public static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, E... others) { |
| Object[] elements = new Object[5 + others.length]; |
| elements[0] = e1; |
| elements[1] = e2; |
| elements[2] = e3; |
| elements[3] = e4; |
| elements[4] = e5; |
| System.arraycopy(others, 0, elements, 5, others.length); |
| return copyOf((E []) elements, false); |
| } |
| |
| /** |
| * Returns a new builder. The generated builder is equivalent to the builder |
| * created by the {@link Builder} constructor. |
| */ |
| public static <E> Builder<E> builder() { |
| return new Builder<>(); |
| } |
| |
| /** |
| * A builder for creating immutable nullable set instances. |
| * |
| * @param <E> element type |
| */ |
| public static final class Builder<E> { |
| private final List<E> contents = new ArrayList<>(); |
| |
| /** |
| * Creates a new builder. The returned builder is equivalent to the builder |
| * generated by |
| * {@link ImmutableNullableSet#builder}. |
| */ |
| public Builder() {} |
| |
| /** |
| * Adds {@code element} to the {@code ImmutableNullableSet}. |
| * |
| * @param element the element to add |
| * @return this {@code Builder} object |
| */ |
| public Builder<E> add(E element) { |
| contents.add(element); |
| return this; |
| } |
| |
| /** |
| * Adds each element of {@code elements} to the |
| * {@code ImmutableNullableSet}. |
| * |
| * @param elements the {@code Iterable} to add to the |
| * {@code ImmutableNullableSet} |
| * @return this {@code Builder} object |
| * @throws NullPointerException if {@code elements} is null |
| */ |
| public Builder<E> addAll(Iterable<? extends E> elements) { |
| Iterables.addAll(contents, elements); |
| return this; |
| } |
| |
| /** |
| * Adds each element of {@code elements} to the |
| * {@code ImmutableNullableSet}. |
| * |
| * @param elements the elements to add to the {@code ImmutableNullableSet} |
| * @return this {@code Builder} object |
| * @throws NullPointerException if {@code elements} is null |
| */ |
| public Builder<E> add(E... elements) { |
| for (E element : elements) { |
| add(element); |
| } |
| return this; |
| } |
| |
| /** |
| * Adds each element of {@code elements} to the |
| * {@code ImmutableNullableSet}. |
| * |
| * @param elements the elements to add to the {@code ImmutableNullableSet} |
| * @return this {@code Builder} object |
| * @throws NullPointerException if {@code elements} is null |
| */ |
| public Builder<E> addAll(Iterator<? extends E> elements) { |
| Iterators.addAll(contents, elements); |
| return this; |
| } |
| |
| /** |
| * Returns a newly-created {@code ImmutableNullableSet} based on the |
| * contents of the {@code Builder}. |
| */ |
| public Set<E> build() { |
| return copyOf(contents); |
| } |
| } |
| } |