/**
* Copyright (C) 2010 Michael Mosmann <michael@mosmann.de>
*
* 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 de.flapdoodle.mongoom.mapping.context;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import org.bson.types.Code;
import org.bson.types.CodeWScope;
import org.bson.types.ObjectId;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import de.flapdoodle.mongoom.exceptions.MappingException;
import de.flapdoodle.mongoom.logging.LogConfig;
import de.flapdoodle.mongoom.mapping.ITransformation;
import de.flapdoodle.mongoom.mapping.ITypeInfo;
import de.flapdoodle.mongoom.mapping.ITypeVisitor;
import de.flapdoodle.mongoom.mapping.naming.FieldAnnotationNaming;
import de.flapdoodle.mongoom.mapping.naming.FieldTypeNaming;
import de.flapdoodle.mongoom.mapping.naming.IPropertyNaming;
import de.flapdoodle.mongoom.mapping.naming.PrefixFieldNaming;
import de.flapdoodle.mongoom.mapping.naming.PropertyName;
import de.flapdoodle.mongoom.mapping.naming.PropertyNamingList;
import de.flapdoodle.mongoom.mapping.properties.PropertyReference;
import de.flapdoodle.mongoom.mapping.reflection.ClassMateTypeResolver;
import de.flapdoodle.mongoom.mapping.reflection.ITypeResolver;
import de.flapdoodle.mongoom.mapping.types.EnumVisitor;
import de.flapdoodle.mongoom.mapping.types.ListVisitor;
import de.flapdoodle.mongoom.mapping.types.NativeTypeVisitor;
import de.flapdoodle.mongoom.mapping.types.ObjectIdVisitor;
import de.flapdoodle.mongoom.mapping.types.PojoVisitor;
import de.flapdoodle.mongoom.mapping.types.ReferenceVisitor;
import de.flapdoodle.mongoom.mapping.types.SetVisitor;
import de.flapdoodle.mongoom.mapping.types.truncations.ByteVisitor;
import de.flapdoodle.mongoom.mapping.types.truncations.CharVisitor;
import de.flapdoodle.mongoom.mapping.types.truncations.FloatVisitor;
import de.flapdoodle.mongoom.mapping.types.truncations.ShortVisitor;
import de.flapdoodle.mongoom.mapping.versions.IVersionFactory;
import de.flapdoodle.mongoom.mapping.versions.StringVersionFactory;
import de.flapdoodle.mongoom.types.Reference;
public class MappingContext implements IMappingContext {
private static final Logger _logger = LogConfig.getLogger(MappingContext.class);
Map<Class<?>, ITypeVisitor> hasSubtypeVisitors = Maps.newLinkedHashMap();
{
hasSubtypeVisitors.put(Enum.class, new EnumVisitor());
}
Map<Class<?>, ITypeVisitor> typeVisitors = Maps.newLinkedHashMap();
{
typeVisitors.put(ObjectId.class, new ObjectIdVisitor());
typeVisitors.put(Reference.class, new ReferenceVisitor());
typeVisitors.put(Set.class, new SetVisitor());
typeVisitors.put(List.class, new ListVisitor());
typeVisitors.put(String.class, new NativeTypeVisitor<String>(String.class));
typeVisitors.put(Boolean.class, new NativeTypeVisitor<Boolean>(Boolean.class));
typeVisitors.put(boolean.class, new NativeTypeVisitor<Boolean>(boolean.class));
typeVisitors.put(Float.class, new FloatVisitor());
typeVisitors.put(float.class, new FloatVisitor());
typeVisitors.put(Double.class, new NativeTypeVisitor<Double>(Double.class));
typeVisitors.put(double.class, new NativeTypeVisitor<Double>(double.class));
typeVisitors.put(Byte.class, new ByteVisitor());
typeVisitors.put(byte.class, new ByteVisitor());
typeVisitors.put(Short.class, new ShortVisitor());
typeVisitors.put(short.class, new ShortVisitor());
typeVisitors.put(Integer.class, new NativeTypeVisitor<Integer>(Integer.class));
typeVisitors.put(int.class, new NativeTypeVisitor<Integer>(int.class));
typeVisitors.put(Long.class, new NativeTypeVisitor<Long>(Long.class));
typeVisitors.put(long.class, new NativeTypeVisitor<Long>(long.class));
typeVisitors.put(Character.class, new CharVisitor());
typeVisitors.put(char.class, new CharVisitor());
typeVisitors.put(byte[].class, new NativeTypeVisitor<byte[]>(byte[].class));
typeVisitors.put(Date.class, new NativeTypeVisitor<Date>(Date.class));
typeVisitors.put(Pattern.class, new NativeTypeVisitor<Pattern>(Pattern.class));
typeVisitors.put(Code.class, new NativeTypeVisitor<Code>(Code.class));
typeVisitors.put(CodeWScope.class, new NativeTypeVisitor<CodeWScope>(CodeWScope.class));
}
Map<Class<?>, IVersionFactory<?>> versionFactories = Maps.newLinkedHashMap();
{
versionFactories.put(String.class, new StringVersionFactory());
}
ITypeVisitor _defaultVisitor = new PojoVisitor();
IPropertyNaming _defaultNaming = new PropertyNamingList(Lists.newArrayList(new FieldTypeNaming(),new FieldAnnotationNaming(),new PrefixFieldNaming()));
ITypeResolver _defaultTypeResolver = new ClassMateTypeResolver();
@Override
public <Type> ITypeVisitor<Type, ?> getVisitor(ITypeInfo containerType, ITypeInfo type) {
// _logger.severe("getVisitor: " + containerType + " -> " + type);
Class<?> typeForVisitor = type.getType();
if (typeForVisitor==null) throw new MappingException(type.getDeclaringClass(),"Type is null ("+type+")");
ITypeVisitor result = typeVisitors.get(typeForVisitor);
if (result == null) {
for (Class<?> superType : hasSubtypeVisitors.keySet()) {
if (superType.isAssignableFrom(typeForVisitor)) {
return hasSubtypeVisitors.get(superType);
}
}
result = _defaultVisitor;
}
return result;
}
Map<TransformationKey, ITransformation<?, ?>> _transformations = Maps.newHashMap();
Map<TransformationKey, ProxyTransformation<?, ?>> _lazy = Maps.newHashMap();
@Override
public ITransformation<?, ?> transformation(ITypeInfo field) {
TransformationKey key = TransformationKey.with(field);
ITransformation<?, ?> result = _transformations.get(key);
if (result == null) {
result = _lazy.get(key);
if (result == null) {
ProxyTransformation proxy = new ProxyTransformation();
_lazy.put(key, proxy);
}
}
return result;
}
@Override
public void setTransformation(ITypeInfo field, ITransformation<?, ?> transformation) {
TransformationKey key = TransformationKey.with(field);
_transformations.put(key, transformation);
ProxyTransformation proxy = _lazy.remove(key);
if (proxy != null) {
proxy.setParent(transformation);
}
}
@Override
public IPropertyNaming naming() {
return _defaultNaming;
}
@Override
public IVersionFactory<?> versionFactory(ITypeInfo field) {
return versionFactories.get(field.getType());
}
@Override
public ITypeResolver typeResolver() {
return _defaultTypeResolver;
}
static class TransformationKey {
private final Class<?> _type;
private final java.lang.reflect.Type _genericType;
public TransformationKey(Class<?> type, java.lang.reflect.Type genericType) {
_type = type;
_genericType = genericType;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((_genericType == null)
? 0
: _genericType.hashCode());
result = prime * result + ((_type == null)
? 0
: _type.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
TransformationKey other = (TransformationKey) obj;
if (_genericType == null) {
if (other._genericType != null)
return false;
} else if (!_genericType.equals(other._genericType))
return false;
if (_type == null) {
if (other._type != null)
return false;
} else if (!_type.equals(other._type))
return false;
return true;
}
public static TransformationKey with(ITypeInfo typeInfo) {
return new TransformationKey(typeInfo.getType(), typeInfo.getGenericType());
}
}
static class ProxyTransformation<Bean, Mapped> implements ITransformation<Bean, Mapped> {
ITransformation<Bean, Mapped> _parent;
ThreadLocal<Set<Integer>> _loopBeanMap = new ThreadLocal<Set<Integer>>();
protected void setParent(ITransformation<Bean, Mapped> parent) {
_parent = parent;
}
@Override
public Mapped asObject(Bean value) {
boolean remove=false;
try {
remove=checkLoop(value);
return _parent.asObject(value);
} finally {
clearLoop(remove,value);
}
}
private void clearLoop(boolean remove, Bean value) {
if (remove) {
int hashCode = System.identityHashCode(value);
Set<Integer> beanSet = _loopBeanMap.get();
if (!beanSet.remove(hashCode))
{
_loopBeanMap.set(null);
throw new MappingException(value.getClass(),"Something went wrong with loop detection");
}
if (beanSet.isEmpty()) {
_loopBeanMap.set(null);
}
}
}
private boolean checkLoop(Bean value) {
int hashCode = System.identityHashCode(value);
Set<Integer> beanSet = _loopBeanMap.get();
if (beanSet==null) {
beanSet=Sets.newHashSet();
_loopBeanMap.set(beanSet);
}
if (!beanSet.add(hashCode)) throw new MappingException(value.getClass(),"Loop detected");
return true;
}
@Override
public Bean asEntity(Mapped object) {
return _parent.asEntity(object);
}
@Override
public <Source> ITransformation<Source, ?> propertyTransformation(PropertyName<Source> property) {
return _parent.propertyTransformation(property);
}
@Override
public <Source> PropertyName<Source> propertyName(PropertyReference<Source> property) {
return _parent.propertyName(property);
}
// @Override
// public <Source> ITransformation<Source, ?> propertyTransformation(TypedPropertyName<Source> property) {
// return _parent.propertyTransformation(property);
// }
@Override
public PropertyName<?> propertyName(String property) {
return _parent.propertyName(property);
}
// @Override
// public ITransformation<?, ?> propertyTransformation(String property) {
// return _parent.propertyTransformation(property);
// }
@Override
public Set<PropertyName<?>> properties() {
return _parent.properties();
}
// @Override
// public Set<TypedPropertyName<?>> properties() {
// return _parent.properties();
// }
}
public static IMappingContextFactory<MappingContext> factory() {
return new IMappingContextFactory<MappingContext>() {
@Override
public MappingContext newContext() {
return new MappingContext();
}
};
}
}