/* * Copyright 2016 Sai Pullabhotla. * * 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 com.jmethods.catatumbo.mappers; import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.util.HashMap; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; import com.google.cloud.datastore.EntityValue; import com.google.cloud.datastore.FullEntity; import com.google.cloud.datastore.IncompleteKey; import com.google.cloud.datastore.NullValue; import com.google.cloud.datastore.Value; import com.google.cloud.datastore.ValueBuilder; import com.jmethods.catatumbo.Mapper; import com.jmethods.catatumbo.MapperFactory; import com.jmethods.catatumbo.MappingException; import com.jmethods.catatumbo.NoSuitableMapperException; import com.jmethods.catatumbo.impl.IntrospectionUtils; /** * An implementation of {@link Mapper} interface for mapping {@link Map} types to/from Cloud * Datastore. * * @author Sai Pullabhotla * */ public class MapMapper implements Mapper { /** * Map Type */ private Type mapType; /** * Class of Map */ private Class<?> mapClass; /** * Type of Keys in the map */ private Class<?> keyClass; /** * Type of Values in the Map */ private Class<?> valueClass; /** * A Mapper for mapping the Values of the map */ private Mapper valueMapper; /** * Creates a new instance of <code>MapMapper</code>. * * @param type * the type of Map */ public MapMapper(Type type) { this.mapType = type; Class<?>[] classArray = IntrospectionUtils.resolveMapType(mapType); mapClass = classArray[0]; keyClass = classArray[1] == null ? String.class : classArray[1]; if (!(keyClass.equals(String.class))) { throw new MappingException( String.format("Unsupported type %s for Map's key. Keys must be of type %s", keyClass.getName(), String.class.getName())); } valueClass = classArray[2]; initializeMapper(); } /** * Initializes the mapper for values in the Map. */ private void initializeMapper() { if (valueClass == null) { valueMapper = CatchAllMapper.getInstance(); } else { try { valueMapper = MapperFactory.getInstance().getMapper(valueClass); } catch (NoSuitableMapperException exp) { valueMapper = CatchAllMapper.getInstance(); } } } @SuppressWarnings("unchecked") @Override public ValueBuilder<?, ?, ?> toDatastore(Object input) { if (input == null) { return NullValue.newBuilder(); } Map<String, ?> map = (Map<String, ?>) input; FullEntity.Builder<IncompleteKey> entityBuilder = FullEntity.newBuilder(); for (Map.Entry<String, ?> entry : map.entrySet()) { String key = entry.getKey(); entityBuilder.set(key, valueMapper.toDatastore(entry.getValue()).build()); } return EntityValue.newBuilder(entityBuilder.build()); } @SuppressWarnings("unchecked") @Override public Object toModel(Value<?> input) { if (input instanceof NullValue) { return null; } EntityValue entityValue = (EntityValue) input; FullEntity<?> entity = entityValue.get(); Map<String, Object> map; if (Modifier.isAbstract(mapClass.getModifiers())) { if (SortedMap.class.equals(mapClass)) { map = new TreeMap<>(); } else { map = new HashMap<>(); } } else { map = (Map<String, Object>) IntrospectionUtils.instantiateObject(mapClass); } for (String property : entity.getNames()) { map.put(property, valueMapper.toModel(entity.getValue(property))); } return map; } }