/** * 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.NoSuchCastException; import com.foundationdb.server.types.TCast; import com.foundationdb.server.types.TClass; import com.foundationdb.server.types.TInstance; import com.google.common.base.Objects; import com.google.common.collect.Sets; import java.util.Collection; import java.util.Map; import java.util.Set; public final class TCastResolver { private final TCastsRegistry castsRegistry; public TCastResolver(TCastsRegistry castsRegistry) { this.castsRegistry = castsRegistry; } public TCast cast(TInstance source, TInstance target) { return castsRegistry.cast(source.typeClass(), target.typeClass()); } Collection<Map<TClass, TCast>> castsBySource() { return castsRegistry.castsBySource(); } /** * Returns the common of the two types. For either argument, a <tt>null</tt> value is interpreted as any type. At * least one of the input TClasses must be non-<tt>null</tt>. If one of the inputs is null, the result is always * the other input. * @param tClass1 the first type class * @param tClass2 the other type class * @return the common class, or <tt>null</tt> if none were found * @throws IllegalArgumentException if both inputs are <tt>null</tt> */ public TClass commonTClass(TClass tClass1, TClass tClass2) { // NOTE: // This method shares some concepts with #reduceToMinimalCastGroups, but the two methods seem different enough // that they're best implemented without much common code. But this could be an opportunity for refactoring. // handle easy cases where one or the other is null if (tClass1 == null) { if (tClass2 == null) throw new IllegalArgumentException("both inputs can't be null"); return tClass2.widestComparable(); } if (tClass2 == null) return tClass1.widestComparable(); // If they're the same class, this is a really easy question to answer. if (tClass1.equals(tClass2)) return tClass1; // Alright, neither is null and they're both different. Try the hard way. Set<? extends TClass> t1Targets = stronglyCastableFrom(tClass1); Set<? extends TClass> t2Targets = stronglyCastableFrom(tClass2); // TODO: The following is not very efficient -- opportunity for optimization? // Sets.intersection works best when the first arg is smaller, so do that. Set<? extends TClass> set1, set2; if (t1Targets.size() < t2Targets.size()) { set1 = t1Targets; set2 = t2Targets; } else { set1 = t2Targets; set2 = t1Targets; } Set<? extends TClass> castGroup = Sets.intersection(set1, set2); // N^2 operation number 1 // The cast group is the set of type classes such that for each element C of castGroup, both tClass1 and tClass2 // can be strongly cast to C. castGroup is thus the set of common types for { tClass1, tClass2 }. We now need // to find the MOST SPECIFIC cast M such that any element of castGroup which is not M can be strongly castable // from M. if (castGroup.isEmpty()) throw new NoSuchCastException(tClass1, tClass2); // N^2 operation number 2... TClass mostSpecific = null; for (TClass candidate : castGroup) { if (isMostSpecific(candidate, castGroup)) { if (mostSpecific == null) mostSpecific = candidate; else return null; } } return mostSpecific; } /** * Find the registered cast going from source to taret. * @param source Type to cast from * @param target Type to cast to * @return Return matching cast or <tt>null</tt> if none */ public TCast cast(TClass source, TClass target) { return castsRegistry.cast(source, target); } public Set<TClass> stronglyCastableFrom(TClass tClass) { return castsRegistry.stronglyCastableFrom(tClass); } public boolean strongCastExists(TClass source, TClass target) { return isStrong(castsRegistry.cast(source, target)); } public boolean isMostSpecific(TClass candidate, Set<? extends TClass> castGroup) { for (TClass inner : castGroup) { if (candidate.equals(inner)) continue; if (!stronglyCastable(candidate, inner)) { return false; } } return true; } public boolean isIndexFriendly(TClass source, TClass target) { return Objects.equal( source.name().categoryName(), target.name().categoryName() ); } public boolean stronglyCastable(TClass source, TClass target) { return isStrong(castsRegistry.cast(source, target)); } boolean isStrong(TCast cast) { return (cast != null) && castsRegistry.isStrong(cast); } }