![JAR search and dependency download from the Maven repository](/logo.png)
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;
}
}
}