001/* 002 * Copyright (C) 2016 The Guava Authors 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package com.google.common.collect; 018 019import static com.google.common.base.Preconditions.checkNotNull; 020 021import com.google.common.annotations.Beta; 022import com.google.common.annotations.GwtCompatible; 023import java.util.ArrayList; 024import java.util.List; 025import java.util.NoSuchElementException; 026import java.util.Optional; 027import java.util.stream.Collector; 028import org.checkerframework.checker.nullness.qual.Nullable; 029 030/** 031 * Collectors not present in {@code java.util.stream.Collectors} that are not otherwise associated 032 * with a {@code com.google.common} type. 033 * 034 * @author Louis Wasserman 035 * @since 21.0 036 */ 037@Beta 038@GwtCompatible 039public final class MoreCollectors { 040 041 /* 042 * TODO(lowasser): figure out if we can convert this to a concurrent AtomicReference-based 043 * collector without breaking j2cl? 044 */ 045 private static final Collector<Object, ?, Optional<Object>> TO_OPTIONAL = 046 Collector.of( 047 ToOptionalState::new, 048 ToOptionalState::add, 049 ToOptionalState::combine, 050 ToOptionalState::getOptional, 051 Collector.Characteristics.UNORDERED); 052 053 /** 054 * A collector that converts a stream of zero or one elements to an {@code Optional}. The returned 055 * collector throws an {@code IllegalArgumentException} if the stream consists of two or more 056 * elements, and a {@code NullPointerException} if the stream consists of exactly one element, 057 * which is null. 058 */ 059 @SuppressWarnings("unchecked") 060 public static <T> Collector<T, ?, Optional<T>> toOptional() { 061 return (Collector) TO_OPTIONAL; 062 } 063 064 private static final Object NULL_PLACEHOLDER = new Object(); 065 066 private static final Collector<Object, ?, Object> ONLY_ELEMENT = 067 Collector.of( 068 ToOptionalState::new, 069 (state, o) -> state.add((o == null) ? NULL_PLACEHOLDER : o), 070 ToOptionalState::combine, 071 state -> { 072 Object result = state.getElement(); 073 return (result == NULL_PLACEHOLDER) ? null : result; 074 }, 075 Collector.Characteristics.UNORDERED); 076 077 /** 078 * A collector that takes a stream containing exactly one element and returns that element. The 079 * returned collector throws an {@code IllegalArgumentException} if the stream consists of two or 080 * more elements, and a {@code NoSuchElementException} if the stream is empty. 081 */ 082 @SuppressWarnings("unchecked") 083 public static <T> Collector<T, ?, T> onlyElement() { 084 return (Collector) ONLY_ELEMENT; 085 } 086 087 /** 088 * This atrocity is here to let us report several of the elements in the stream if there were more 089 * than one, not just two. 090 */ 091 private static final class ToOptionalState { 092 static final int MAX_EXTRAS = 4; 093 094 @Nullable Object element; 095 @Nullable List<Object> extras; 096 097 ToOptionalState() { 098 element = null; 099 extras = null; 100 } 101 102 IllegalArgumentException multiples(boolean overflow) { 103 StringBuilder sb = 104 new StringBuilder().append("expected one element but was: <").append(element); 105 for (Object o : extras) { 106 sb.append(", ").append(o); 107 } 108 if (overflow) { 109 sb.append(", ..."); 110 } 111 sb.append('>'); 112 throw new IllegalArgumentException(sb.toString()); 113 } 114 115 void add(Object o) { 116 checkNotNull(o); 117 if (element == null) { 118 this.element = o; 119 } else if (extras == null) { 120 extras = new ArrayList<>(MAX_EXTRAS); 121 extras.add(o); 122 } else if (extras.size() < MAX_EXTRAS) { 123 extras.add(o); 124 } else { 125 throw multiples(true); 126 } 127 } 128 129 ToOptionalState combine(ToOptionalState other) { 130 if (element == null) { 131 return other; 132 } else if (other.element == null) { 133 return this; 134 } else { 135 if (extras == null) { 136 extras = new ArrayList<>(); 137 } 138 extras.add(other.element); 139 if (other.extras != null) { 140 this.extras.addAll(other.extras); 141 } 142 if (extras.size() > MAX_EXTRAS) { 143 extras.subList(MAX_EXTRAS, extras.size()).clear(); 144 throw multiples(true); 145 } 146 return this; 147 } 148 } 149 150 Optional<Object> getOptional() { 151 if (extras == null) { 152 return Optional.ofNullable(element); 153 } else { 154 throw multiples(false); 155 } 156 } 157 158 Object getElement() { 159 if (element == null) { 160 throw new NoSuchElementException(); 161 } else if (extras == null) { 162 return element; 163 } else { 164 throw multiples(false); 165 } 166 } 167 } 168 169 private MoreCollectors() {} 170}