/**
* 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.ServiceAlreadyStartedException;
import com.foundationdb.server.service.Service;
import com.foundationdb.server.service.jmx.JmxManageable;
import com.foundationdb.server.types.TAggregator;
import com.foundationdb.server.types.TCast;
import com.foundationdb.server.types.TClass;
import com.foundationdb.server.types.TKeyComparable;
import com.foundationdb.server.types.TScalar;
import com.foundationdb.server.types.TOverload;
import com.foundationdb.server.types.texpressions.TValidatedAggregator;
import com.foundationdb.server.types.texpressions.TValidatedOverload;
import com.foundationdb.server.types.texpressions.TValidatedScalar;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.DumperOptions.FlowStyle;
import org.yaml.snakeyaml.Yaml;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
public final class TypesRegistryServiceImpl
implements TypesRegistryService, Service, JmxManageable {
private static TypesRegistryService INSTANCE = null;
public static TypesRegistryService createRegistryService() {
if (INSTANCE == null) {
TypesRegistryServiceImpl registryService = new TypesRegistryServiceImpl();
registryService.start();
INSTANCE = registryService;
}
return INSTANCE;
}
public TypesRegistryServiceImpl() {
}
// TypesRegistryService interface
@Override
public TypesRegistry getTypesRegistry() {
return typesRegistry;
}
@Override
public OverloadResolver<TValidatedScalar> getScalarsResolver() {
return scalarsResolver;
}
@Override
public OverloadResolver<TValidatedAggregator> getAggregatesResolver() {
return aggregatesResolver;
}
@Override
public TCastResolver getCastsResolver() {
return castsResolver;
}
@Override
public TKeyComparable getKeyComparable(TClass left, TClass right) {
if (left == null || right == null)
return null;
return keyComparableRegistry.getClass(left, right);
}
@Override
public FunctionKind getFunctionKind(String name) {
if (scalarsResolver.isDefined(name))
return FunctionKind.SCALAR;
else if (aggregatesResolver.isDefined(name))
return FunctionKind.AGGREGATE;
else
return null;
}
// Service interface
@Override
public void start() {
InstanceFinder registry;
try {
registry = new ReflectiveInstanceFinder();
} catch (Exception e) {
logger.error("while creating registry", e);
throw new ServiceAlreadyStartedException("TypesRegistry");
}
start(registry);
}
@Override
public void stop() {
castsResolver = null;
scalarsRegistry = null;
aggreatorsRegistry = null;
tClasses = null;
keyComparableRegistry = null;
}
@Override
public void crash() {
stop();
}
// JmxManageable interface
@Override
public JmxObjectInfo getJmxObjectInfo() {
return new JmxObjectInfo("TypesRegistry", new Bean(), TypesRegistryMXBean.class);
}
// private methods
void start(InstanceFinder finder) {
tClasses = new HashSet<>(finder.find(TClass.class));
typesRegistry = new TypesRegistry(tClasses);
TCastsRegistry castsRegistry = new TCastsRegistry(tClasses, finder);
castsResolver = new TCastResolver(castsRegistry);
scalarsRegistry = ResolvablesRegistry.create(
finder,
castsResolver,
TScalar.class,
new Function<TScalar, TValidatedScalar>() {
@Override
public TValidatedScalar apply(TScalar input) {
return new TValidatedScalar(input);
}
}
);
scalarsResolver = new OverloadResolver<>(scalarsRegistry, castsResolver);
aggreatorsRegistry = ResolvablesRegistry.create(
finder,
castsResolver,
TAggregator.class,
new Function<TAggregator, TValidatedAggregator>() {
@Override
public TValidatedAggregator apply(TAggregator input) {
return new TValidatedAggregator(input);
}
}
);
aggregatesResolver = new OverloadResolver<>(aggreatorsRegistry, castsResolver);
keyComparableRegistry = new KeyComparableRegistry(finder);
}
// class state
private static final Logger logger = LoggerFactory.getLogger(TypesRegistryServiceImpl.class);
// object state
private volatile TypesRegistry typesRegistry;
private volatile TCastResolver castsResolver;
private volatile ResolvablesRegistry<TValidatedAggregator> aggreatorsRegistry;
private volatile OverloadResolver<TValidatedAggregator> aggregatesResolver;
private volatile ResolvablesRegistry<TValidatedScalar> scalarsRegistry;
private volatile OverloadResolver<TValidatedScalar> scalarsResolver;
private volatile KeyComparableRegistry keyComparableRegistry;
private volatile Collection<? extends TClass> tClasses;
// inner classes
private class Bean implements TypesRegistryMXBean {
@Override
public String describeTypes() {
return toYaml(typesDescriptors());
}
@Override
public String describeCasts() {
return toYaml(castsDescriptors());
}
@Override
public String describeScalars() {
return toYaml(describeOverloads(scalarsRegistry));
}
@Override
public String describeAggregates() {
return toYaml(describeOverloads(aggreatorsRegistry));
}
@Override
public String describeAll() {
Map<String,Object> all = new LinkedHashMap<>(5);
all.put("types", typesDescriptors());
all.put("casts", castsDescriptors());
all.put("scalar_functions", describeOverloads(scalarsRegistry));
all.put("aggregate_functions", describeOverloads(aggreatorsRegistry));
return toYaml(all);
}
private Object typesDescriptors() {
List<Map<String,Comparable<?>>> result = new ArrayList<>(tClasses.size());
for (TClass tClass : tClasses) {
Map<String,Comparable<?>> map = new LinkedHashMap<>();
buildTName("bundle", "name", tClass, map);
map.put("category", tClass.name().categoryName());
map.put("internalVersion", tClass.internalRepresentationVersion());
map.put("serializationVersion", tClass.serializationVersion());
map.put("fixedSize", tClass.hasFixedSerializationSize() ? tClass.fixedSerializationSize() : null);
result.add(map);
}
Collections.sort(result, new Comparator<Map<String, Comparable<?>>>() {
@Override
public int compare(Map<String, Comparable<?>> o1, Map<String, Comparable<?>> o2) {
return ComparisonChain.start()
.compare(o1.get("bundle"), o2.get("bundle"))
.compare(o1.get("category"), o2.get("category"))
.compare(o1.get("name"), o2.get("name"))
.result();
}
});
return result;
}
private Object castsDescriptors() {
// the starting size is just a guess
Collection<Map<TClass, TCast>> castsBySource = castsResolver.castsBySource();
List<Map<String,Comparable<?>>> result = new ArrayList<>(castsBySource.size() * 5);
for (Map<TClass,TCast> castsByTarget : castsBySource) {
for (TCast tCast : castsByTarget.values()) {
Map<String,Comparable<?>> map = new LinkedHashMap<>();
buildTName("source_bundle", "source_type", tCast.sourceClass(), map);
buildTName("target_bundle", "target_type", tCast.targetClass(), map);
map.put("strong", castsResolver.isStrong(tCast));
map.put("isDerived", tCast instanceof TCastsRegistry.ChainedCast);
result.add(map);
}
}
Collections.sort(result, new Comparator<Map<String, Comparable<?>>>() {
@Override
public int compare(Map<String, Comparable<?>> o1, Map<String, Comparable<?>> o2) {
return ComparisonChain.start()
.compare(o1.get("source_bundle"), o2.get("source_bundle"))
.compare(o1.get("source_type"), o2.get("source_type"))
.compare(o1.get("target_bundle"), o2.get("target_bundle"))
.compare(o1.get("target_type"), o2.get("target_type"))
.result();
}
});
return result;
}
private <V extends TValidatedOverload> Object describeOverloads(ResolvablesRegistry<V> registry) {
Multimap<String, TOverload> flattenedOverloads = HashMultimap.create();
for (Map.Entry<String, ScalarsGroup<V>> entry : registry.entriesByName()) {
String overloadName = entry.getKey();
ScalarsGroup<V> scalarsGroup = entry.getValue();
flattenedOverloads.putAll(overloadName, scalarsGroup.getOverloads());
}
return describeOverloads(flattenedOverloads.asMap(), Functions.toStringFunction());
}
private <T extends TOverload,S> Object describeOverloads(
Map<String, Collection<T>> elems, Function<? super T, S> format)
{
Map<String,Map<String,String>> result = new TreeMap<>();
for (Map.Entry<String, ? extends Collection<T>> entry : elems.entrySet()) {
Collection<T> overloads = entry.getValue();
Map<String,String> overloadDescriptions = new TreeMap<>();
int idSuffix = 1;
boolean allSamePriorities = allSamePriorities(overloads);
if (!allSamePriorities) {
List<T> asList = new ArrayList<>(overloads);
Collections.sort(asList, compareByPriorities);
overloads = asList;
}
for (T overload : overloads) {
final String overloadId = overload.id();
final String origDescription = String.valueOf(format.apply(overload));
String overloadDescription = origDescription;
// We don't care about efficiency in this loop, so let's keep the code simple
while (overloadDescriptions.containsKey(overloadDescription)) {
overloadDescription = origDescription + " [" + Integer.toString(idSuffix++) + ']';
}
if (!allSamePriorities)
overloadDescription = "priority " + Arrays.toString(overload.getPriorities()) + ' '
+ origDescription;
overloadDescriptions.put(overloadDescription, overloadId);
}
result.put(entry.getKey(), overloadDescriptions);
}
return result;
}
private <T extends TOverload> boolean allSamePriorities(Collection<T> overloads) {
Iterator<T> iter = overloads.iterator();
int[] firstPriorities = iter.next().getPriorities();
while (iter.hasNext()) {
int[] priorities = iter.next().getPriorities();
if (!Arrays.equals(firstPriorities, priorities))
return false;
}
return true;
}
private void buildTName(String bundleTag, String nameTag, TClass tClass, Map<String, Comparable<?>> out) {
out.put(bundleTag, tClass.name().bundleId().name());
out.put(nameTag, tClass.name().unqualifiedName());
}
private String toYaml(Object obj) {
DumperOptions options = new DumperOptions();
options.setAllowReadOnlyProperties(true);
options.setDefaultFlowStyle(FlowStyle.BLOCK);
options.setIndent(4);
return new Yaml(options).dump(obj);
}
Comparator<TOverload> compareByPriorities = new Comparator<TOverload>() {
@Override
public int compare(TOverload o1, TOverload o2) {
int[] o1Priorities = o1.getPriorities();
int[] o2Priorities = o2.getPriorities();
return lowest(o1Priorities) - lowest(o2Priorities);
}
};
}
private static int lowest(int[] ints) {
int result = ints[0];
for (int i = 1; i < ints.length; ++i)
result = Math.min(result, ints[i]);
return result;
}
}