/* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jdbi.v3.core.mapper; import static java.util.Objects.requireNonNull; import static org.jdbi.v3.core.generic.GenericTypes.getErasedType; import static org.jdbi.v3.core.generic.GenericTypes.resolveType; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.sql.ResultSet; import java.sql.SQLException; import java.util.AbstractMap; import java.util.Map; import java.util.Optional; import org.jdbi.v3.core.config.ConfigRegistry; import org.jdbi.v3.core.config.JdbiConfig; import org.jdbi.v3.core.statement.StatementContext; /** * Maps rows to {@link Map.Entry Map.Entry<K, V>}, provided there are mappers registered for types K and V. This mapper is registered out of the box. * * @param <K> entry key type * @param <V> entry value type */ public class MapEntryMapper<K, V> implements RowMapper<Map.Entry<K, V>> { private static final TypeVariable<Class<Map.Entry>> KEY_PARAM; private static final TypeVariable<Class<Map.Entry>> VALUE_PARAM; static { TypeVariable<Class<Map.Entry>>[] mapParams = Map.Entry.class.getTypeParameters(); KEY_PARAM = mapParams[0]; VALUE_PARAM = mapParams[1]; } private final RowMapper<K> keyMapper; private final RowMapper<V> valueMapper; @SuppressWarnings("unchecked") static RowMapperFactory factory() { return (type, config) -> { if (type instanceof ParameterizedType && getErasedType(type).equals(Map.Entry.class)) { Type keyType = resolveType(KEY_PARAM, type); Type valueType = resolveType(VALUE_PARAM, type); RowMapper<?> keyMapper = getKeyMapper(keyType, config); RowMapper<?> valueMapper = getValueMapper(valueType, config); return Optional.of(new MapEntryMapper(keyMapper, valueMapper)); } return Optional.empty(); }; } private static RowMapper<?> getKeyMapper(Type keyType, ConfigRegistry config) { String column = config.get(Config.class).getKeyColumn(); if (column == null) { return config.get(RowMappers.class) .findFor(keyType) .orElseThrow(() -> new NoSuchMapperException("No row mapper registered for map key " + keyType)); } else { return config.get(ColumnMappers.class) .findFor(keyType) .map(mapper -> new SingleColumnMapper<>(mapper, column)) .orElseThrow(() -> new NoSuchMapperException("No column mapper registered for map key " + keyType + " in column " + column)); } } private static RowMapper<?> getValueMapper(Type valueType, ConfigRegistry config) { String column = config.get(Config.class).getValueColumn(); if (column == null) { return config.get(RowMappers.class) .findFor(valueType) .orElseThrow(() -> new NoSuchMapperException("No row mapper registered for map value " + valueType)); } else { return config.get(ColumnMappers.class) .findFor(valueType) .map(mapper -> new SingleColumnMapper<>(mapper, column)) .orElseThrow(() -> new NoSuchMapperException("No column mapper registered for map value " + valueType + " in column " + column)); } } private MapEntryMapper(RowMapper<K> keyMapper, RowMapper<V> valueMapper) { this.keyMapper = keyMapper; this.valueMapper = valueMapper; } @Override public Map.Entry<K, V> map(ResultSet rs, StatementContext ctx) throws SQLException { return new AbstractMap.SimpleImmutableEntry<>(keyMapper.map(rs, ctx), valueMapper.map(rs, ctx)); } @Override public RowMapper<Map.Entry<K, V>> specialize(ResultSet rs, StatementContext ctx) throws SQLException { return new MapEntryMapper<>(keyMapper.specialize(rs, ctx), valueMapper.specialize(rs, ctx)); } /** * Configuration class for MapEntryMapper. */ public static class Config implements JdbiConfig<Config> { public Config() { } private Config(Config that) { this.keyColumn = that.keyColumn; this.valueColumn = that.valueColumn; } private String keyColumn; private String valueColumn; String getKeyColumn() { return keyColumn; } /** * Sets the column that map entry keys are loaded from. If set, keys will be loaded from the given column, using the {@link ColumnMapper} registered * for the key type. If unset, keys will be loaded using the {@link RowMapper} registered for the key type, from whichever columns that row mapper * uses. * * @param keyColumn the key column name. * @return this config object, for call chaining */ public Config setKeyColumn(String keyColumn) { this.keyColumn = requireNonNull(keyColumn); return this; } String getValueColumn() { return valueColumn; } /** * Sets the column that map entry values are loaded from. If set, values will be loaded from the given column, using the {@link ColumnMapper} * registered for the value type. If unset, values will be loaded using the {@link RowMapper} registered for the value type, from whichever columns * that row mapper uses. * * @param valueColumn the value column name. * @return this config object, for call chaining */ public Config setValueColumn(String valueColumn) { this.valueColumn = requireNonNull(valueColumn); return this; } @Override public Config createCopy() { return new Config(this); } } }