All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.projectmonkey.internal.MappingEngineImpl Maven / Gradle / Ivy

/*
 * Copyright 2011 the original author or authors.
 *
 * 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 net.projectmonkey.internal;

import java.lang.reflect.Constructor;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import net.projectmonkey.Condition;
import net.projectmonkey.ConfigurationException;
import net.projectmonkey.Converter;
import net.projectmonkey.Provider;
import net.projectmonkey.TypeMap;
import net.projectmonkey.config.Configuration;
import net.projectmonkey.internal.converter.ConverterStore;
import net.projectmonkey.internal.util.Iterables;
import net.projectmonkey.internal.util.Primitives;
import net.projectmonkey.internal.util.Types;
import net.projectmonkey.spi.ConstantMapping;
import net.projectmonkey.spi.Mapping;
import net.projectmonkey.spi.MappingContext;
import net.projectmonkey.spi.MappingEngine;
import net.projectmonkey.spi.PropertyMapping;
import net.projectmonkey.spi.SourceMapping;


/**
 * MappingEngine implementation that caches ConditionalConverters by source and destination type
 * pairs.
 * 
 * @author Jonathan Halterman
 */
public class MappingEngineImpl implements MappingEngine {
  /** Cache of conditional converters */
  private final Map, Converter> converterCache = new ConcurrentHashMap, Converter>();
  private final Configuration configuration;
  private final TypeMapStore typeMapStore;
  private final ConverterStore converterStore;

  public MappingEngineImpl(final InheritingConfiguration configuration) {
    this.configuration = configuration;
    this.typeMapStore = configuration.typeMapStore;
    this.converterStore = configuration.converterStore;
  }

  /**
   * Initial entry point.
   */
  public  D map(final S source, final Class sourceType, final D destination, final Class destinationType) {
    MappingContextImpl context = new MappingContextImpl(source, sourceType,
        destination, destinationType, this);
    D result = null;

    try {
      result = map(context);
    } catch (ConfigurationException e) {
      throw e;
    } catch (ErrorsException e) {
      throw context.errors.toMappingException();
    } catch (Throwable t) {
      context.errors.errorMapping(sourceType, destinationType, t);
    }

    context.errors.throwMappingExceptionIfErrorsExist();
    return result;
  }

  /**
   * Performs mapping using a TypeMap if one exists, else a converter if one applies, else a newly
   * created TypeMap. Recursive entry point.
   */
  @Override
public  D map(final MappingContext context) {
    MappingContextImpl contextImpl = (MappingContextImpl) context;
    Class destinationType = context.getDestinationType();
    if (!Iterables.isIterable(destinationType) && contextImpl.currentlyMapping(destinationType))
      throw contextImpl.errors.errorCircularReference(destinationType).toException();

    D destination = null;
    TypeMap typeMap = typeMapStore.get(context.getSourceType(), context.getDestinationType());
    if (typeMap != null) {
      destination = typeMap(context, typeMap);
    } else {
      Converter converter = converterFor(context);
      if (converter != null) {
        destination = convert(context, converter);
      } else {
        // Call getOrCreate in case TypeMap was created concurrently
        typeMap = typeMapStore.getOrCreate(context.getSourceType(), context.getDestinationType(),
            this);
        destination = typeMap(context, typeMap);
      }
    }

    contextImpl.finishedMapping(destinationType);
    return destination;
  }

  /**
   * Performs a type mapping for the {@code typeMap} and {@code context}.
   */
   D typeMap(final MappingContext context, final TypeMap typeMap) {
    MappingContextImpl contextImpl = (MappingContextImpl) context;

    contextImpl.setTypeMap(typeMap);
    if (context.getDestination() == null) {
      D destination = createDestination(context);
      if (destination == null)
        return null;
    }

    @SuppressWarnings("unchecked")
    Condition condition = (Condition) typeMap.getCondition();
    Converter converter = typeMap.getConverter();
    if (condition == null || condition.applies(context)) {
      if (converter != null)
        return convert(context, converter);

      for (Mapping mapping : typeMap.getMappings()) {
        propertyMap(mapping, contextImpl);
      }
    }

    return context.getDestination();
  }

  @SuppressWarnings("unchecked")
  private  void propertyMap(final Mapping mapping, final MappingContextImpl context) {
    MappingImpl mappingImpl = (MappingImpl) mapping;
    if (context.isShaded(mappingImpl.getPath()))
      return;

    Condition condition = (Condition) mapping.getCondition();
    if (condition == null && mapping.isSkipped())
      return;

    Object source = resolveSourceValue(context, mapping);
    MappingContextImpl propertyContext = propertyContextFor(context, source,
        mapping);

    if (condition != null) {
      if (!condition.applies(propertyContext)) {
        context.shadePath(mappingImpl.getPath());
        return;
      } else if (mapping.isSkipped())
        return;
    }

    Converter converter = (Converter) mapping.getConverter();
    if (converter != null)
      context.shadePath(mappingImpl.getPath());
    else if (mapping instanceof SourceMapping)
      return;

    // Create last destination via provider prior to mapping/conversion
    createDestinationViaProvider(propertyContext);

    Object destinationValue = null;
    if (source != null)
      destinationValue = converter == null ? map(propertyContext) : convert(propertyContext,
          converter);
    setDestinationValue(context.getDestination(), destinationValue, propertyContext, mappingImpl);
  }

  @SuppressWarnings("unchecked")
  private Object resolveSourceValue(final MappingContextImpl context, final Mapping mapping) {
    Object source = context.getSource();
    if (mapping instanceof PropertyMapping)
      for (Accessor accessor : (List) ((PropertyMapping) mapping).getSourceProperties()) {
    	context.setParentSource(source);
        source = accessor.getValue(source);
        if (source == null)
        {
          return null;
        }
      }
    else if (mapping instanceof ConstantMapping)
      source = ((ConstantMapping) mapping).getConstant();

    return source;
  }

  @SuppressWarnings({ "unchecked", "rawtypes" })
  private void setDestinationValue(Object destination, final Object destinationValue,
      final MappingContextImpl context, final MappingImpl mapping) {
    List mutatorChain = (List) mapping.getDestinationProperties();

    for (int i = 0; i < mutatorChain.size(); i++) {
      Mutator mutator = mutatorChain.get(i);

      // Handle last mutator in chain
      if (i == mutatorChain.size() - 1) {
        context.destinationCache.put(mutator, destinationValue);
        mutator.setValue(destination,
            destinationValue == null ? Primitives.defaultValue(mutator.getType())
                : destinationValue);
        if (destinationValue == null)
          context.shadePath(mapping.getPath());
      } else {
        Object intermediateDest = context.destinationCache.get(mutator);
        if (intermediateDest == null) {
          // Create intermediate destination via global provider
          if (configuration.getProvider() != null)
            intermediateDest = configuration.getProvider().get(
                new ProvisionRequestImpl(mutator.getType(), context.getParentSource()));
          else
            intermediateDest = instantiate(mutator.getType(), context.errors);

          if (intermediateDest == null)
            return;

          context.destinationCache.put(mutator, intermediateDest);
        }

        mutator.setValue(destination, intermediateDest);
        destination = intermediateDest;
      }
    }
  }

  /**
   * Returns a property context.
   */
  @SuppressWarnings({ "rawtypes", "unchecked" })
  private MappingContextImpl propertyContextFor(final MappingContextImpl context,
      final Object source, final Mapping mapping) {
    Class sourceType;
    if (mapping instanceof PropertyMapping) {
      sourceType = ((PropertyMapping) mapping).getLastSourceProperty().getType();
    } else if (mapping instanceof ConstantMapping) {
      Object constant = ((ConstantMapping) mapping).getConstant();
      sourceType = constant == null ? Object.class : Types.deProxy(constant.getClass());
    } else {
      sourceType = ((SourceMapping) mapping).getSourceType();
    }

    Class destinationType = (Class) mapping.getLastDestinationProperty().getType();
    return new MappingContextImpl(context, source, sourceType, null, destinationType, mapping);
  }

  /**
   * Performs a mapping using a Converter.
   */
  private  D convert(final MappingContext context, final Converter converter) {
    try {
      return converter.convert(context);
    } catch (ErrorsException e) {
      throw e;
    } catch (Exception e) {
      ((MappingContextImpl) context).errors.errorConverting(converter,
          context.getSourceType(), context.getDestinationType(), e);
      return null;
    }
  }

  /**
   * Retrieves a converter from the store or from the cache.
   */
  @SuppressWarnings("unchecked")
  private  Converter converterFor(final MappingContext context) {
    TypePair typePair = TypePair.of(context.getSourceType(), context.getDestinationType());
    Converter converter = (Converter) converterCache.get(typePair);
    if (converter == null) {
      converter = converterStore.getFirstSupported(context.getSourceType(),
          context.getDestinationType());
      if (converter != null)
        converterCache.put(typePair, converter);
    }

    return converter;
  }

  private  T instantiate(final Class type, final Errors errors) {
    try {
      Constructor constructor = type.getDeclaredConstructor();
      if (!constructor.isAccessible())
        constructor.setAccessible(true);
      return constructor.newInstance();
    } catch (Exception e) {
      errors.errorInstantiatingDestination(type, e);
      return null;
    }
  }

  @SuppressWarnings("unchecked")
  private  D createDestinationViaProvider(final MappingContextImpl context) {
    Provider provider = null;
    if (context.getMapping() != null)
      provider = (Provider) context.getMapping().getProvider();
    if (provider == null && context.typeMap() != null)
      provider = context.typeMap().getProvider();
    if (provider == null && configuration.getProvider() != null)
      provider = (Provider) configuration.getProvider();
    if (provider == null)
      return null;

    D destination = provider.get(context);
    context.setDestination(destination);
    return destination;
  }

  @Override
public  D createDestination(final MappingContext context) {
    MappingContextImpl contextImpl = (MappingContextImpl) context;
    D destination = createDestinationViaProvider(contextImpl);
    if (destination != null)
      return destination;

    destination = instantiate(context.getDestinationType(), contextImpl.errors);
    contextImpl.setDestination(destination);
    return destination;
  }
}