/**
* Copyright (C) 2009-2013 FoundationDB, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.foundationdb.server.types.service;
import com.foundationdb.server.error.AkibanInternalException;
import com.foundationdb.server.error.NoSuchCastException;
import com.foundationdb.server.types.InputSetFlags;
import com.foundationdb.server.types.TClass;
import com.foundationdb.server.types.TOverload;
import com.foundationdb.server.types.aksql.AkBundle;
import com.foundationdb.server.types.common.types.NoAttrTClass;
import com.foundationdb.server.types.texpressions.TValidatedOverload;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Collections2;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
final class ResolvablesRegistry<V extends TValidatedOverload> implements Iterable<V> {
public Iterable<? extends ScalarsGroup<V>> get(String name) {
List<ScalarsGroup<V>> result = overloadsByName.get(name.toLowerCase());
return result.isEmpty() ? null : result;
}
public Collection<? extends Map.Entry<String, ScalarsGroup<V>>> entriesByName() {
return Collections.unmodifiableCollection(overloadsByName.entries());
}
public Collection<ScalarsGroup<V>> allScalarsGroups() {
return Collections2.transform(
entriesByName(),
new Function<Map.Entry<String, ScalarsGroup<V>>, ScalarsGroup<V>>() {
@Override
public ScalarsGroup<V> apply(Map.Entry<String, ScalarsGroup<V>> input) {
return input.getValue();
}
});
}
public boolean containsKey(String name) {
return overloadsByName.containsKey(name.toLowerCase());
}
@Override
public Iterator<V> iterator() {
return new InternalIterator();
}
public static <R extends TOverload, V extends TValidatedOverload>
ResolvablesRegistry<V> create(InstanceFinder finder,
TCastResolver castResolver,
Class<R> plainClass,
Function<R, V> validator)
{
ListMultimap<String, ScalarsGroup<V>> overloadsByName = createScalars(finder, castResolver, plainClass,
validator);
return new ResolvablesRegistry<>(overloadsByName);
}
ResolvablesRegistry(ListMultimap<String, ScalarsGroup<V>> overloadsByName) {
this.overloadsByName = overloadsByName;
}
private static <R extends TOverload, V extends TValidatedOverload>
ListMultimap<String, ScalarsGroup<V>> createScalars(InstanceFinder finder,
TCastResolver castResolver,
Class<R> plainClass,
Function<R, V> validator)
{
Multimap<String, V> overloadsByName = ArrayListMultimap.create();
int errors = 0;
for (R scalar : finder.find(plainClass)) {
try {
V validated = validator.apply(scalar);
String[] names = validated.registeredNames();
for (int i = 0; i < names.length; ++i)
names[i] = names[i].toLowerCase();
for (String name : names)
overloadsByName.put(name, validated);
} catch (RuntimeException e) {
rejectTOverload(scalar, e);
++errors;
} catch (AssertionError e) {
rejectTOverload(scalar, e);
++errors;
}
}
if (errors > 0) {
StringBuilder sb = new StringBuilder("Found ").append(errors).append(" error");
if (errors != 1)
sb.append('s');
sb.append(" while collecting scalar functions. Check logs for details.");
throw new AkibanInternalException(sb.toString());
}
ArrayListMultimap<String, ScalarsGroup<V>> results = ArrayListMultimap.create();
for (Map.Entry<String, Collection<V>> entry : overloadsByName.asMap().entrySet()) {
String overloadName = entry.getKey();
Collection<V> allOverloads = entry.getValue();
for (Collection<V> priorityGroup : scalarsByPriority(allOverloads)) {
ScalarsGroup<V> scalarsGroup = new ScalarsGroupImpl<>(priorityGroup, castResolver);
results.put(overloadName, scalarsGroup);
}
}
results.trimToSize();
return Multimaps.unmodifiableListMultimap(results);
}
private static <V extends TOverload> List<Collection<V>> scalarsByPriority(
Collection<V> overloads)
{
// First, we'll put this into a SortedMap<Integer, Collection<TVO>> so that we have each subset of the
// overloads grouped by priority. Then we'll go over those collections; for each one, we'll wrap it in
// an unmodifiable Collection (so that users of the iterator() can't modify the Collections).
// Finally, we'll wrap the result in an unmodifiable Collection (so that users can't remove Collections
// from it via the Iterator).
SortedMap<Integer, ArrayList<V>> byPriority = new TreeMap<>();
for (V overload : overloads) {
for (int priority : overload.getPriorities()) {
ArrayList<V> thisPriorityOverloads = byPriority.get(priority);
if (thisPriorityOverloads == null) {
thisPriorityOverloads = new ArrayList<>();
byPriority.put(priority, thisPriorityOverloads);
}
thisPriorityOverloads.add(overload);
}
}
List<Collection<V>> results = new ArrayList<>(byPriority.size());
for (ArrayList<V> priorityGroup : byPriority.values()) {
priorityGroup.trimToSize();
results.add(Collections.unmodifiableCollection(priorityGroup));
}
return results;
}
private static <R> void rejectTOverload(R overload, Throwable e) {
StringBuilder sb = new StringBuilder("rejecting overload ");
Class<?> overloadClass = overload == null ? null : overload.getClass();
try {
sb.append(overload).append(' ');
} catch (Exception e1) {
logger.error("couldn't toString overload: " + overload);
}
sb.append("from ").append(overloadClass);
logger.error(sb.toString(), e);
}
private final ListMultimap<String, ScalarsGroup<V>> overloadsByName;
// inner classes
private class InternalIterator implements Iterator<V> {
@Override
public boolean hasNext() {
final boolean haveNext;
if (withinGroupIterator != null && withinGroupIterator.hasNext()) {
haveNext = true;
}
else {
if (groupsIter.hasNext()) {
withinGroupIterator = groupsIter.next().getOverloads().iterator();
haveNext = withinGroupIterator.hasNext();
if (!haveNext)
withinGroupIterator = null;
}
else {
haveNext = false;
}
}
return haveNext;
}
@Override
public V next() {
if (!hasNext()) // also advances to the next withinGroupIterator, if needed
throw new NoSuchElementException();
return withinGroupIterator.next();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
private InternalIterator() {
this.groupsIter = allScalarsGroups().iterator();
this.withinGroupIterator = null;
}
private Iterator<? extends ScalarsGroup<V>> groupsIter;
private Iterator<? extends V> withinGroupIterator;
}
// class state
private static final Logger logger = LoggerFactory.getLogger(ResolvablesRegistry.class);
// static classes
static final TClass differentTargetTypes
= new NoAttrTClass(AkBundle.INSTANCE.id(), "differentTargets", null, null, 0, 0, 0, null, null, -1, null);
static final OverloadsFolder sameInputSets = new OverloadsFolder() {
@Override
protected TClass foldOne(TClass accumulated, TClass input) {
return (accumulated == input) ? accumulated : differentTargetTypes;
}
};
protected static class ScalarsGroupImpl<V extends TValidatedOverload> implements ScalarsGroup<V> {
@Override
public Collection<? extends V> getOverloads() {
return overloads;
}
public ScalarsGroupImpl(Collection<V> overloads, final TCastResolver castResolver) {
this.overloads = Collections.unmodifiableCollection(overloads);
// Compute the same-type-ats
// Tranform the map to a BitSet for efficiency
OverloadsFolder.Result<TClass> sameTypeResults = sameInputSets.fold(overloads);
sameTypeAt = sameTypeResults.toInputSetFlags(new Predicate<TClass>() {
@Override
public boolean apply(TClass input) {
return input != differentTargetTypes;
}
});
// compute common types
commonTypes = new OverloadsFolder() {
@Override
protected TClass foldOne(TClass accumulated, TClass input) {
if (accumulated == differentTargetTypes || input == differentTargetTypes)
return differentTargetTypes;
try {
return castResolver.commonTClass(accumulated, input);
}
catch (NoSuchCastException e) {
return differentTargetTypes;
}
}
}
.fold(overloads)
.transform(new Function<TClass, TClass>() {
@Override
public TClass apply(TClass input) {
return input == differentTargetTypes ? null : input;
}
});
}
@Override
public TClass commonTypeAt(int pos) {
return commonTypes.at(pos, null);
}
@Override
public boolean hasSameTypeAt(int pos)
{
return sameTypeAt.get(pos);
}
private final InputSetFlags sameTypeAt;
private final OverloadsFolder.Result<TClass> commonTypes;
private final Collection<? extends V> overloads;
}
}