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

net.projectmonkey.PropertyMap 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;

import net.projectmonkey.builder.ConverterExpression;
import net.projectmonkey.builder.MapExpression;
import net.projectmonkey.builder.ProviderExpression;
import net.projectmonkey.config.Configuration.AccessLevel;
import net.projectmonkey.internal.MappingBuilderImpl;
import net.projectmonkey.internal.util.Assert;
import net.projectmonkey.internal.util.TypeResolver;


/**
 * A PropertyMap defines mappings between properties for a particular source and destination type.
 * 

* To create a PropertyMap simply extend {@code PropertyMap}, supplying type arguments to represent * the source type {@code } and destination type {@code }, then override the * {@link #configure()} method. * *

 *   public class OrderMap extends PropertyMap<Order, OrderDTO>() {
 *     protected void configure() {
 *       map().setCustomer(source.getCustomerName());
 *     }
 *   };
 * 
* *

Mapping EDSL

*

* PropertyMap uses an Embedded Domain Specific Language (EDSL) to define how source and destination * methods and values map to each other. The Mapping EDSL allows you to define mappings using actual * code that references the source and destination properties you wish to map. Usage of the EDSL is * demonstrated in the examples below. * *

Mapping

*

* This example maps the destination type's {@code setName} method to the source type's * {@code getFirstName} method. * *

    map().setName(source.getFirstName());
* * This example maps the destination type's {@code setEmployer} method to the constant * {@code "Initech"}. * *
    map().setEmployer("Initech");
* * Map statements can also be written to accept a source property, allowing mapping to a destination * whose type does not match the source property's type: * *
    map(source.getAge()).setAgeString(null);
* * Similar for constant values: * *
    map(21).setAgeString(null);
* * Note: Since the {@code setAgeString} method requires a value we simply pass in * {@code null} which is unused. * *

Deep mapping

*

* This example Maps the destination type's {@code setAge} method to the source type's * {@code getCustomer().getAge()} method hierarchy, allowing deep mapping to occur between the * source and destination methods. * *

    map().setAge(source.getCustomer().getAge());
* * This example maps the destination type's {@code getCustomer().setName()} method hierarchy to the * source type's {@code getPerson().getFirstName()} method hierarchy. * *
    map().getCustomer().setName(source.getPerson().getFirstName());
* * Note: In order populate the destination object, deep mapping requires the * {@code getCustomer} method to have a corresponding mutator, such as a {@code setCustomer} method * or an {@link net.projectmonkey.config.Configuration#setFieldAccessLevel(AccessLevel) accessible} * {@code customer} field. * *

Skipping properties

*

* This example specifies that the destination type's {@code setName} method should be skipped * during the mapping process. * *

    skip().setName(null);
* * Note: Since the {@code setName} method is skipped the {@code null} value is unused. * *

Converters

*

* This example specifies that the {@code toUppercase} {@link Converter} be used when mapping the * source type's {@code getName} method to the destination type's {@code setName} method: * *

    using(toUppercase).map().setName(source.getName());
* * This example specifies that the {@code personToNameConverter} {@link Converter} be used when * mapping the source object to the destination type's {@code setName} method: * *
    using(personToNameConverter).map(source).setName(null);
* * Note: Since a {@code source} object is given the {@code null} value passed to * {@code setName()} is unused. * *

Conditional mapping

*

* This example specifies that the {@code isLocalAddress} {@link Condition} must apply in order for * mapping to occur between the the the source type's {@code getAddress} method and the destination * type's {@code setAddress} method. If the condition does not apply, mapping to the * {@code setAddress} method will be skipped. * *

    when(isLocalAddress).map().setAddress(source.getAddress());
* * This example specifies that the {@code Conditions.isNull} {@link Condition} must apply in order * for mapping to the destination type's {@code setAge} method to be skipped. If the * condition does not apply, mapping will occur from the the source type's {@code getAge} method. * *
    when(Conditions.isNull).skip().setAge(source.getAge());
* *

Providers

*

* This example specifies that the {@code nameProvider} {@link Provider} be used to provide * destination name instances when mapping the source type's {@code getName} method to the * destination type's {@code setName}. * *

    withProvider(nameProvider).map().setName(source.getName());
* * @param source type * @param destination type * * @author Jonathan Halterman */ public abstract class PropertyMap { /** * The source instance to be used in a mapping declaration. See the EDSL * examples. *

* Throws: NullPointerException if dereferenced from outside the context of * {@link #configure()} . */ public S source; Class destinationType; Class sourceType; private MappingBuilderImpl builder; /** * Creates a new PropertyMap for the source and destination types {@code S} and {@code D}. * * @throws IllegalArgumentException if {@code S} and {@code D} are not declared */ @SuppressWarnings("unchecked") protected PropertyMap() { Class[] typeArguments = TypeResolver.resolveArguments(getClass(), PropertyMap.class); Assert.notNull(typeArguments, "Must declare source type argument and destination type argument for PropertyMap"); sourceType = (Class) typeArguments[0]; destinationType = (Class) typeArguments[1]; } /** * Creates a new PropertyMap for the {@code sourceType} and {@code destinationType}. */ protected PropertyMap(Class sourceType, Class destinationType) { this.sourceType = sourceType; this.destinationType = destinationType; } /** * Called by ModelMapper to configure mappings as defined in the PropertyMap. */ protected abstract void configure(); /** * Defines a mapping to a destination. See the EDSL examples. * * @throws IllegalStateException if called from outside the context of * {@link PropertyMap#configure()}. */ protected final D map() { checkBuilder(); return builder.map(); } /** * Defines a mapping from the {@code source} to a destination. See the See the EDSL * examples. * * @param source to map from * @throws IllegalStateException if called from outside the context of * {@link PropertyMap#configure()}. */ protected final D map(Object source) { checkBuilder(); return builder.map(source); } /** * Specifies that mapping for the destination property be skipped during the mapping process. See * the EDSL examples. * * @throws IllegalStateException if called from outside the context of * {@link PropertyMap#configure()}. */ protected final D skip() { checkBuilder(); return builder.skip(); } /** * Specifies the {@code converter} to use for converting to the destination property hierarchy. * When used with deep mapping the {@code converter} should convert to an instance of the * last destination property. See the EDSL examples. * * @param converter to use when mapping the property * @throws IllegalStateException if called from outside the context of * {@link PropertyMap#configure()}. */ protected final MapExpression using(Converter converter) { checkBuilder(); return builder.using(converter); } /** * Specifies the {@code condition} that must apply in order for mapping to take place for a * particular destination property hierarchy. See the EDSL examples. * * @param condition that must apply when mapping the property * @throws IllegalStateException if called from outside the context of * {@link PropertyMap#configure()}. */ protected final ProviderExpression when(Condition condition) { checkBuilder(); return builder.when(condition); } /** * Specifies a provider to be used for providing instances of the mapped property. When used with * deep mapping the {@code provider} should provide an instance of the last destination * property. See the EDSL examples. * * @param provider to use for providing the destination property * @throws IllegalStateException if called from outside the context of * {@link PropertyMap#configure()}. */ protected final ConverterExpression withProvider(Provider provider) { checkBuilder(); return builder.withProvider(provider); } private void checkBuilder() { Assert.state(builder != null, "PropertyMap should not be used outside the context of PropertyMap.configure()."); } @SuppressWarnings("unused") private synchronized void configure(MappingBuilderImpl builder) { this.builder = builder; this.source = builder.getSource(); try { configure(); } finally { this.builder = null; this.source = null; } } }