com.github.erchu.beancp.DeclarativeMapImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.github.erchu.beancp.DeclarativeMapImpl.java

Source

/*
 * bean-cp
 * Copyright (c) 2014, Rafal Chojnacki, All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3.0 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.
 */
package com.github.erchu.beancp;

import java.util.function.Consumer;
import java.util.function.Supplier;
import static org.apache.commons.lang3.Validate.*;

/**
 * Default implementation of {@link DeclarativeMap} interface.
 *
 * @param <S> source class
 * @param <D> destination class
 */
final class DeclarativeMapImpl<S, D> implements DeclarativeMap<S, D>, MappingExecutor<S, D> {

    private static enum MapMode {

        CONFIGURATION, EXECUTION
    }

    private final String INVALID_STATEMENT_ORDER_MESSAGE = "Invalid statement order. Check "
            + DeclarativeMap.class.getSimpleName() + " interface API documentation for details.";

    private final Class<S> _sourceClass;

    private final Class<D> _destinationClass;

    private final DeclarativeMapSetup<S, D> _configuration;

    private Supplier<D> _destinationObjectBuilder;

    private MapMode mode = MapMode.CONFIGURATION;

    private boolean _constructDestinationObjectUsingExecuted;

    private boolean _beforeMapExecuted;

    private boolean _useConventionExecuted;

    private boolean _bindBindConstantOrMapExecuted;

    private boolean _afterMapExecuted;

    private Mapper _executionPhaseMapper;

    private MapConventionExecutor _executionPhaseMapConvention;

    private final ThreadLocal<S> _executionPhaseSourceReference = new ThreadLocal<>();

    private final ThreadLocal<D> _executionPhaseDestinationReference = new ThreadLocal<>();

    private MappingInfo _configurationPhaseMappingsInfo;

    public DeclarativeMapImpl(final Class<S> sourceClass, final Class<D> destinationClass,
            final DeclarativeMapSetup<S, D> configuration) {
        _configuration = configuration;
        _sourceClass = sourceClass;
        _destinationClass = destinationClass;
    }

    @Override
    public <T> DeclarativeMap<S, D> bind(final Supplier<T> fromFunction, final Consumer<T> toMember,
            final BindingOption<S, D, T>... options) {
        notNull(fromFunction, "fromFunction");
        notNull(toMember, "toMember");

        if (mode == MapMode.CONFIGURATION) {
            if (_afterMapExecuted) {
                throw new MapperConfigurationException(INVALID_STATEMENT_ORDER_MESSAGE);
            }

            _bindBindConstantOrMapExecuted = true;
        }

        if (mode == MapMode.EXECUTION) {
            boolean map = shouldBeMapped(options);

            if (map) {
                T getValue = fromFunction.get();

                if (getValue == null) {
                    for (BindingOption<S, D, T> i : options) {
                        if (i.getNullSubstitution() != null) {
                            getValue = i.getNullSubstitution();
                            break;
                        }
                    }
                }

                toMember.accept(getValue);
            }
        }

        return this;
    }

    @Override
    public <T> DeclarativeMap<S, D> bindConstant(final T constantValue, final Consumer<T> toMember,
            final BindingOption<S, D, T>... options) {
        notNull(toMember, "toMember");

        if (mode == MapMode.CONFIGURATION) {
            if (_afterMapExecuted) {
                throw new MapperConfigurationException(INVALID_STATEMENT_ORDER_MESSAGE);
            }

            _bindBindConstantOrMapExecuted = true;
        }

        if (mode == MapMode.CONFIGURATION) {
            for (BindingOption<S, D, T> i : options) {
                if (i.getNullSubstitution() != null) {
                    throw new MapperConfigurationException(
                            "Null substitution option not allowed for bindConstant.");
                }
            }
        }

        if (mode == MapMode.EXECUTION) {
            boolean map = shouldBeMapped(options);

            if (map) {
                toMember.accept(constantValue);
            }
        }

        return this;
    }

    @Override
    public <SI, DI> DeclarativeMap<S, D> mapInner(final Supplier<SI> supplierFunction, final Consumer<DI> toMember,
            final Class<DI> toMemberClass, final BindingOption<S, D, DI>... options) {
        return mapInner(supplierFunction, toMember, null, toMemberClass, options);
    }

    @Override
    public <SI, DI> DeclarativeMap<S, D> mapInner(final Supplier<SI> supplierFunction, final Consumer<DI> toMember,
            final Supplier<DI> toMemberGetter, final Class<DI> toMemberClass,
            final BindingOption<S, D, DI>... options) {
        notNull(supplierFunction, "supplierFunction");
        notNull(toMember, "toMember");

        if (mode == MapMode.CONFIGURATION) {
            if (_afterMapExecuted) {
                throw new MapperConfigurationException(INVALID_STATEMENT_ORDER_MESSAGE);
            }

            _bindBindConstantOrMapExecuted = true;
        }

        if (mode == MapMode.EXECUTION) {
            SI currentSourceValue = supplierFunction.get();

            if (currentSourceValue == null) {
                toMember.accept(null);
            } else {
                DI currentDestinationMemberValue;

                if (toMemberGetter == null) {
                    currentDestinationMemberValue = null;
                } else {
                    currentDestinationMemberValue = toMemberGetter.get();
                }

                if (currentDestinationMemberValue == null) {
                    DI mapResult = _executionPhaseMapper.map(currentSourceValue, toMemberClass);
                    toMember.accept(mapResult);
                } else {
                    _executionPhaseMapper.map(currentSourceValue, currentDestinationMemberValue);
                }
            }
        }

        return this;
    }

    @Override
    public DeclarativeMapImpl<S, D> useConvention(final MapConvention mapConvention) {
        notNull(mapConvention, "mapConvention");

        MapConventionExecutor conventionExecutor = new MapConventionExecutor(mapConvention);

        if (mode == MapMode.CONFIGURATION) {
            if (_useConventionExecuted) {
                throw new MapperConfigurationException(
                        "useConventionExecuted() cannot be called " + "more than once.");
            }

            if (_bindBindConstantOrMapExecuted || _afterMapExecuted) {
                throw new MapperConfigurationException(INVALID_STATEMENT_ORDER_MESSAGE);
            }

            // Build and cache result
            conventionExecutor.build(_configurationPhaseMappingsInfo, _sourceClass, _destinationClass);
            _executionPhaseMapConvention = conventionExecutor;

            _useConventionExecuted = true;
        }

        if (mode == MapMode.EXECUTION) {
            // use cached convention
            _executionPhaseMapConvention.map(_executionPhaseMapper, _executionPhaseSourceReference.get(),
                    _executionPhaseDestinationReference.get());
        }

        return this;
    }

    @Override
    public DeclarativeMap<S, D> beforeMap(final Action action) {
        return beforeMap(notUsed -> action.invoke());
    }

    @Override
    public DeclarativeMap<S, D> beforeMap(final Consumer<Mapper> action) {
        if (mode == MapMode.CONFIGURATION) {
            if (_useConventionExecuted || _bindBindConstantOrMapExecuted || _afterMapExecuted) {
                throw new MapperConfigurationException(INVALID_STATEMENT_ORDER_MESSAGE);
            }

            _beforeMapExecuted = true;
        }

        if (mode == MapMode.EXECUTION) {
            action.accept(_executionPhaseMapper);
        }

        return this;
    }

    @Override
    public DeclarativeMap<S, D> afterMap(final Action action) {
        return afterMap(notUsed -> action.invoke());
    }

    @Override
    public DeclarativeMap<S, D> afterMap(final Consumer<Mapper> action) {
        if (mode == MapMode.CONFIGURATION) {
            _afterMapExecuted = true;
        }

        if (mode == MapMode.EXECUTION) {
            action.accept(_executionPhaseMapper);
        }

        return this;
    }

    @Override
    public DeclarativeMap<S, D> constructDestinationObjectUsing(final Supplier<D> destinationObjectBuilder) {
        notNull(destinationObjectBuilder, "destinationObjectBuilder");

        if (mode == MapMode.CONFIGURATION) {
            if (_beforeMapExecuted || _useConventionExecuted || _bindBindConstantOrMapExecuted
                    || _afterMapExecuted) {
                throw new MapperConfigurationException(INVALID_STATEMENT_ORDER_MESSAGE);
            }

            if (_constructDestinationObjectUsingExecuted) {
                throw new MapperConfigurationException(
                        "constructDestinationObjectUsing() cannot " + "be called more than once.");
            }

            _constructDestinationObjectUsingExecuted = true;
        }

        setDestinationObjectBuilder(destinationObjectBuilder);

        return this;
    }

    void configure(MappingInfo configurationPhaseMappingsInfo) {
        if (mode != MapMode.CONFIGURATION) {
            throw new IllegalStateException("Map was already configured.");
        }

        FakeObjectBuilder proxyBuilder = new FakeObjectBuilder();
        S sourceObject = proxyBuilder.createFakeObject(_sourceClass);
        D destinationObject = proxyBuilder.createFakeObject(_destinationClass);

        _beforeMapExecuted = _bindBindConstantOrMapExecuted = _afterMapExecuted = false;
        _configurationPhaseMappingsInfo = configurationPhaseMappingsInfo;

        // Source and destination object instances are not required by DeclarativeMapImpl 
        // in CONFIGURATION mode, but Java lambda handling mechanizm requires 
        // non-null value, so we need to create proxy instance. Unfortunatelly
        // this enforces constraint on source and destination classes as in javadoc.
        _configuration.apply(this, sourceObject, destinationObject);

        // release reference
        _configurationPhaseMappingsInfo = null;

        mode = MapMode.EXECUTION;
    }

    void execute(final Mapper caller, final S source, final D destination) {
        if (mode != MapMode.EXECUTION) {
            throw new IllegalStateException("Map is not configured. Use configure() first.");
        }

        _executionPhaseMapper = caller;

        try {
            _executionPhaseSourceReference.set(source);
            _executionPhaseDestinationReference.set(destination);

            _configuration.apply(this, source, destination);
        } finally {
            _executionPhaseSourceReference.set(null);
            _executionPhaseDestinationReference.set(null);
        }
    }

    @Override
    public Class<S> getSourceClass() {
        return _sourceClass;
    }

    @Override
    public Class<D> getDestinationClass() {
        return _destinationClass;
    }

    Supplier<D> getDestinationObjectBuilder() {
        return _destinationObjectBuilder;
    }

    void setDestinationObjectBuilder(final Supplier<D> destinationObjectBuilder) {
        notNull(destinationObjectBuilder, "destinationObjectBuilder");

        _destinationObjectBuilder = destinationObjectBuilder;
    }

    private <T> boolean shouldBeMapped(final BindingOption<S, D, T>[] options) {
        boolean map = true;

        for (BindingOption<S, D, T> i : options) {
            if (i.getMapWhenCondition() != null && i.getMapWhenCondition().get() == false) {
                map = false;
                break;
            }
        }

        return map;
    }
}