/**
* 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.types.*;
import com.foundationdb.server.types.TypesTestClass;
import com.foundationdb.server.types.mcompat.mtypes.MString;
import com.foundationdb.server.types.value.ValueSource;
import com.foundationdb.server.types.value.ValueTarget;
import com.foundationdb.server.types.texpressions.Constantness;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
public final class TypesRegistryServiceImplTest {
private final TClass CLASS_A = new TypesTestClass("A");
private final TClass CLASS_B = new TypesTestClass("B");
private final TClass CLASS_C = new TypesTestClass("C");
private final TClass CLASS_D = new TypesTestClass("D");
private final TClass CLASS_E = new TypesTestClass("E");
@Test
public void typesRegistry() {
InstanceFinder finder = createFinder();
TypesRegistry typesRegistry = new TypesRegistry(finder.find(TClass.class));
assertEquals("A", CLASS_A, typesRegistry.getTypeClass("testbundle", "a"));
assertEquals("B", CLASS_B, typesRegistry.getTypeClass("testbundle", "B"));
assertEquals("C", CLASS_C, typesRegistry.getTypeClass("testbundle", "c"));
assertEquals("D", CLASS_D, typesRegistry.getTypeClass("testbundle", "D"));
assertEquals("E", CLASS_E, typesRegistry.getTypeClass("testbundle", "e"));
assertNull("Z", typesRegistry.getTypeClass("testbundle", "Z"));
}
@Test
public void createSelfCasts() {
InstanceFinder finder = createFinder();
Map<TClass, Map<TClass, TCast>> casts = TCastsRegistry.createCasts(finder.find(TClass.class), finder);
checkAll(casts,
new CastCheck(CLASS_A, CLASS_A),
new CastCheck(CLASS_B, CLASS_B),
new CastCheck(CLASS_C, CLASS_C),
new CastCheck(CLASS_D, CLASS_D),
new CastCheck(CLASS_E, CLASS_E));
}
private InstanceFinderBuilder createFinder() {
InstanceFinderBuilder finder = new InstanceFinderBuilder();
finder.put(TClass.class, MString.VARCHAR);
finder.put(TClass.class, CLASS_A);
finder.put(TClass.class, CLASS_B);
finder.put(TClass.class, CLASS_C);
finder.put(TClass.class, CLASS_D);
finder.put(TClass.class, CLASS_E);
return finder;
}
@Test
public void simpleCastPath() {
InstanceFinderBuilder finder = createFinder();
TCastPath path = TCastPath.create(CLASS_A, CLASS_B, CLASS_C);
finder.put(TCastPath.class, path);
Map<TClass, Map<TClass, TCast>> casts = TCastsRegistry.createCasts(finder.find(TClass.class), finder);
singleStepCasts(casts);
TCastsRegistry.createDerivedCasts(casts, finder);
checkAll(casts,
// self casts
new CastCheck(CLASS_A, CLASS_A),
new CastCheck(CLASS_B, CLASS_B),
new CastCheck(CLASS_C, CLASS_C),
new CastCheck(CLASS_D, CLASS_D),
new CastCheck(CLASS_E, CLASS_E),
// single-step casts
new CastCheck(CLASS_A, CLASS_B),
new CastCheck(CLASS_B, CLASS_C),
new CastCheck(CLASS_C, CLASS_D),
new CastCheck(CLASS_D, CLASS_E),
// cast path
new CastCheck(CLASS_A, CLASS_C)
);
}
@Test
public void jumpingCastPath() {
InstanceFinderBuilder finder = createFinder();
finder.put(TCastPath.class, TCastPath.create(CLASS_A, CLASS_B, CLASS_C, CLASS_D, CLASS_E));
Map<TClass, Map<TClass, TCast>> casts = TCastsRegistry.createCasts(finder.find(TClass.class), finder);
singleStepCasts(casts);
putCast(new BogusCast(CLASS_A, CLASS_D), casts);
TCastsRegistry.createDerivedCasts(casts, finder);
checkAll(casts,
// self casts
new CastCheck(CLASS_A, CLASS_A),
new CastCheck(CLASS_B, CLASS_B),
new CastCheck(CLASS_C, CLASS_C),
new CastCheck(CLASS_D, CLASS_D),
new CastCheck(CLASS_E, CLASS_E),
// cast path from A
new CastCheck(CLASS_A, CLASS_B),
new CastCheck(CLASS_A, CLASS_C),
new CastCheck(CLASS_A, CLASS_D),
new CastCheck(CLASS_A, CLASS_E),
// cast path from B
new CastCheck(CLASS_B, CLASS_C),
new CastCheck(CLASS_B, CLASS_D),
new CastCheck(CLASS_B, CLASS_E),
// cast path from C
new CastCheck(CLASS_C, CLASS_D),
new CastCheck(CLASS_C, CLASS_E),
// cast path from D
new CastCheck(CLASS_D, CLASS_E)
);
}
private void singleStepCasts(Map<TClass, Map<TClass, TCast>> casts) {
putCast(new BogusCast(CLASS_A, CLASS_B), casts);
putCast(new BogusCast(CLASS_B, CLASS_C), casts);
putCast(new BogusCast(CLASS_C, CLASS_D), casts);
putCast(new BogusCast(CLASS_D, CLASS_E), casts);
}
private void putCast(TCast cast, Map<TClass, Map<TClass, TCast>> outMap) {
Object o = outMap.get(cast.sourceClass()).put(cast.targetClass(), cast);
assert o == null : "putting " + cast + " into " + outMap; // shouldn't happen
}
private void checkAll(Map<TClass, Map<TClass, TCast>> actual, CastCheck... expected) {
// translate the SelfCastCheck[] to a Map
// create the full expected list, which includes VARCHAR -> VARCHAR and VARCHAR <-> T for each type
// in the actuals keys
List<CastCheck> allExpected = new ArrayList<>();
Collections.addAll(allExpected, expected);
for (TClass src : actual.keySet()) {
if (src != MString.VARCHAR) {
allExpected.add(new CastCheck(src, MString.VARCHAR));
allExpected.add(new CastCheck(MString.VARCHAR, src));
}
}
allExpected.add(new CastCheck(MString.VARCHAR, MString.VARCHAR));
Map<TClass,Map<TClass,CastCheck>> expectedMap = tClassMap();
for (CastCheck check : allExpected) {
Map<TClass, CastCheck> map = expectedMap.get(check.source);
if (map == null) {
map = tClassMap();
expectedMap.put(check.source, map);
}
CastCheck old = map.put(check.target, check);
assertNull("duplicate for " + check, old);
}
// Translate the casts map to use SelfCastCheck
Map<TClass,Map<TClass,CastCheck>> actualsMap = tClassMap();
for (Map.Entry<TClass,Map<TClass,TCast>> bySourceEntry : actual.entrySet()) {
TClass source = bySourceEntry.getKey();
Map<TClass, CastCheck> translated = tClassMap();
Object old = actualsMap.put(source, translated);
assert old == null : actual; // shouldn't happen!
for (Map.Entry<TClass,TCast> byTargetEntry : bySourceEntry.getValue().entrySet()) {
TClass target = byTargetEntry.getKey();
TCast cast = byTargetEntry.getValue();
CastCheck check = new CastCheck(cast.sourceClass(), cast.targetClass());
old = translated.put(target, check);
assertNull("duplicate with " + bySourceEntry, old);
}
}
// now just check the two maps
assertEquals("casts map", toString(expectedMap), toString(actualsMap)); // easy-to-view diff in IDEs
assertEquals("casts map", expectedMap, actualsMap);
}
private String toString(Map<TClass, Map<TClass, CastCheck>> map) {
StringBuilder sb = new StringBuilder();
for (Map.Entry<TClass, Map<TClass, CastCheck>> entry : map.entrySet()) {
sb.append(entry.getKey()).append('\n');
for (Map.Entry<TClass,CastCheck> subentry: entry.getValue().entrySet()) {
sb.append(" ").append(subentry).append('\n');
}
}
return sb.toString();
}
private <V> TreeMap<TClass, V> tClassMap() {
return new TreeMap<>(tClassComparator);
}
private static class CastCheck {
@Override
public String toString() {
return "cast(" + source + " to " + target + ")";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CastCheck that = (CastCheck) o;
return source.equals(that.source) && target.equals(that.target);
}
@Override
public int hashCode() {
int result = source.hashCode();
result = 31 * result + target.hashCode();
return result;
}
private CastCheck(TClass source, TClass target) {
this.source = source;
this.target = target;
}
private final TClass source;
private final TClass target;
}
private static class BogusCast extends TCastBase {
private BogusCast(TClass sourceClass, TClass targetClass) {
super(sourceClass, targetClass, Constantness.UNKNOWN);
}
@Override
public void doEvaluate(TExecutionContext context, ValueSource source, ValueTarget target) {
throw new UnsupportedOperationException();
}
}
private Comparator<TClass> tClassComparator = new Comparator<TClass>() {
@Override
public int compare(TClass o1, TClass o2) {
return o1.toString().compareTo(o2.toString());
}
};
}