Java Bean to Java Bean mapper that recursively copies data from one object to another
—
Runtime access to mapping metadata and configuration for debugging, introspection, and dynamic behavior based on mapping definitions.
Primary interface for accessing mapping metadata and configuration information.
/**
* Provides read-only access to mapping metadata and configuration
*/
public interface MappingMetadata {
/**
* Gets all class mappings defined in the mapper
* @return list of all class mapping metadata
*/
List<ClassMappingMetadata> getClassMappings();
/**
* Gets class mappings by source class name
* @param sourceClassName fully qualified source class name
* @return list of class mappings for the specified source class
*/
List<ClassMappingMetadata> getClassMappingsBySourceName(String sourceClassName);
/**
* Gets class mappings by destination class name
* @param destinationClassName fully qualified destination class name
* @return list of class mappings for the specified destination class
*/
List<ClassMappingMetadata> getClassMappingsByDestinationName(String destinationClassName);
/**
* Gets specific class mapping by source and destination class names
* @param sourceClassName fully qualified source class name
* @param destinationClassName fully qualified destination class name
* @return class mapping metadata or null if not found
*/
ClassMappingMetadata getClassMappingByName(String sourceClassName, String destinationClassName);
/**
* Gets class mappings by source class
* @param sourceClass source class type
* @return list of class mappings for the specified source class
*/
List<ClassMappingMetadata> getClassMappingsBySource(Class<?> sourceClass);
/**
* Gets class mappings by destination class
* @param destinationClass destination class type
* @return list of class mappings for the specified destination class
*/
List<ClassMappingMetadata> getClassMappingsByDestination(Class<?> destinationClass);
/**
* Gets specific class mapping by source and destination classes
* @param sourceClass source class type
* @param destinationClass destination class type
* @return class mapping metadata or null if not found
*/
ClassMappingMetadata getClassMapping(Class<?> sourceClass, Class<?> destinationClass);
}Provides metadata about individual class-to-class mappings.
/**
* Provides metadata about class-level mappings
*/
public interface ClassMappingMetadata {
/**
* Gets the source class name
* @return fully qualified source class name
*/
String getSourceClassName();
/**
* Gets the destination class name
* @return fully qualified destination class name
*/
String getDestinationClassName();
/**
* Gets the source class type
* @return source class
*/
Class<?> getSourceClass();
/**
* Gets the destination class type
* @return destination class
*/
Class<?> getDestinationClass();
/**
* Gets all field mappings for this class mapping
* @return list of field mapping metadata
*/
List<FieldMappingMetadata> getFieldMappings();
/**
* Gets field mapping by source field name
* @param sourceFieldName source field name
* @return field mapping metadata or null if not found
*/
FieldMappingMetadata getFieldMappingBySource(String sourceFieldName);
/**
* Gets field mapping by destination field name
* @param destinationFieldName destination field name
* @return field mapping metadata or null if not found
*/
FieldMappingMetadata getFieldMappingByDestination(String destinationFieldName);
/**
* Gets the mapping ID if specified
* @return mapping ID or null if not specified
*/
String getMappingId();
/**
* Indicates if this is a bidirectional mapping
* @return true if bidirectional, false otherwise
*/
boolean isBidirectional();
/**
* Gets custom converter class if specified for this mapping
* @return custom converter class or null if not specified
*/
Class<? extends CustomConverter> getCustomConverter();
}Provides metadata about individual field-to-field mappings.
/**
* Provides metadata about field-level mappings
*/
public interface FieldMappingMetadata {
/**
* Gets the source field name
* @return source field name
*/
String getSourceFieldName();
/**
* Gets the destination field name
* @return destination field name
*/
String getDestinationFieldName();
/**
* Gets the source field type
* @return source field class type
*/
Class<?> getSourceFieldType();
/**
* Gets the destination field type
* @return destination field class type
*/
Class<?> getDestinationFieldType();
/**
* Gets custom converter class if specified for this field
* @return custom converter class or null if not specified
*/
Class<? extends CustomConverter> getCustomConverter();
/**
* Gets custom converter ID if specified for this field
* @return custom converter ID or null if not specified
*/
String getCustomConverterId();
/**
* Indicates if this field is mapped by reference
* @return true if copy by reference, false if deep copy
*/
boolean isCopyByReference();
/**
* Gets date format if specified for date field conversions
* @return date format string or null if not specified
*/
String getDateFormat();
/**
* Indicates if this field mapping is conditional
* @return true if conditional, false otherwise
*/
boolean isConditional();
/**
* Gets the condition expression if this is a conditional mapping
* @return condition expression or null if not conditional
*/
String getCondition();
/**
* Indicates if this field is excluded from mapping
* @return true if excluded, false otherwise
*/
boolean isExcluded();
}import com.github.dozermapper.core.Mapper;
import com.github.dozermapper.core.metadata.MappingMetadata;
import com.github.dozermapper.core.metadata.ClassMappingMetadata;
Mapper mapper = DozerBeanMapperBuilder.buildDefault();
// Get metadata - this initializes mappings if not already done
MappingMetadata metadata = mapper.getMappingMetadata();
// List all class mappings
List<ClassMappingMetadata> allMappings = metadata.getClassMappings();
for (ClassMappingMetadata classMapping : allMappings) {
System.out.println("Mapping: " + classMapping.getSourceClassName() +
" -> " + classMapping.getDestinationClassName());
}// Find mappings for a specific source class
List<ClassMappingMetadata> userMappings =
metadata.getClassMappingsBySource(User.class);
// Find mappings for a specific destination class
List<ClassMappingMetadata> dtoMappings =
metadata.getClassMappingsByDestination(UserDto.class);
// Find specific mapping between two classes
ClassMappingMetadata specificMapping =
metadata.getClassMapping(User.class, UserDto.class);ClassMappingMetadata classMapping = metadata.getClassMapping(User.class, UserDto.class);
if (classMapping != null) {
List<FieldMappingMetadata> fieldMappings = classMapping.getFieldMappings();
for (FieldMappingMetadata fieldMapping : fieldMappings) {
System.out.println("Field: " + fieldMapping.getSourceFieldName() +
" -> " + fieldMapping.getDestinationFieldName());
if (fieldMapping.getCustomConverter() != null) {
System.out.println(" Custom converter: " +
fieldMapping.getCustomConverter().getSimpleName());
}
if (fieldMapping.isCopyByReference()) {
System.out.println(" Copy by reference");
}
if (fieldMapping.isConditional()) {
System.out.println(" Condition: " + fieldMapping.getCondition());
}
}
}public class MappingValidator {
public void validateMappings(Mapper mapper) {
MappingMetadata metadata = mapper.getMappingMetadata();
List<ClassMappingMetadata> mappings = metadata.getClassMappings();
for (ClassMappingMetadata mapping : mappings) {
validateClassMapping(mapping);
}
}
private void validateClassMapping(ClassMappingMetadata mapping) {
// Validate source and destination classes exist
try {
Class<?> sourceClass = mapping.getSourceClass();
Class<?> destClass = mapping.getDestinationClass();
// Check if classes are compatible
if (!areClassesCompatible(sourceClass, destClass)) {
logger.warn("Potentially incompatible mapping: {} -> {}",
sourceClass.getSimpleName(), destClass.getSimpleName());
}
// Validate field mappings
validateFieldMappings(mapping.getFieldMappings());
} catch (Exception e) {
logger.error("Invalid mapping configuration: {} -> {}",
mapping.getSourceClassName(), mapping.getDestinationClassName(), e);
}
}
private void validateFieldMappings(List<FieldMappingMetadata> fieldMappings) {
for (FieldMappingMetadata fieldMapping : fieldMappings) {
// Check if field types are compatible
if (!areTypesCompatible(fieldMapping.getSourceFieldType(),
fieldMapping.getDestinationFieldType())) {
// Ensure custom converter exists for incompatible types
if (fieldMapping.getCustomConverter() == null &&
fieldMapping.getCustomConverterId() == null) {
logger.warn("No converter for incompatible types: {} -> {}",
fieldMapping.getSourceFieldType().getSimpleName(),
fieldMapping.getDestinationFieldType().getSimpleName());
}
}
}
}
private boolean areClassesCompatible(Class<?> source, Class<?> dest) {
// Implementation for class compatibility check
return true; // Simplified
}
private boolean areTypesCompatible(Class<?> sourceType, Class<?> destType) {
// Implementation for type compatibility check
return sourceType.equals(destType) ||
destType.isAssignableFrom(sourceType) ||
isAutoConvertible(sourceType, destType);
}
private boolean isAutoConvertible(Class<?> source, Class<?> dest) {
// Check for built-in conversions (String <-> primitives, etc.)
return false; // Simplified
}
}public class MappingDocumentationGenerator {
public void generateDocumentation(Mapper mapper, PrintWriter writer) {
MappingMetadata metadata = mapper.getMappingMetadata();
List<ClassMappingMetadata> mappings = metadata.getClassMappings();
writer.println("# Dozer Mapping Documentation");
writer.println();
for (ClassMappingMetadata mapping : mappings) {
generateClassMappingDoc(mapping, writer);
}
}
private void generateClassMappingDoc(ClassMappingMetadata mapping, PrintWriter writer) {
writer.println("## " + mapping.getSourceClass().getSimpleName() +
" → " + mapping.getDestinationClass().getSimpleName());
writer.println();
if (mapping.getMappingId() != null) {
writer.println("**Mapping ID:** " + mapping.getMappingId());
writer.println();
}
writer.println("**Source:** " + mapping.getSourceClassName());
writer.println("**Destination:** " + mapping.getDestinationClassName());
writer.println();
List<FieldMappingMetadata> fieldMappings = mapping.getFieldMappings();
if (!fieldMappings.isEmpty()) {
writer.println("### Field Mappings");
writer.println();
writer.println("| Source Field | Destination Field | Type | Notes |");
writer.println("|--------------|-------------------|------|-------|");
for (FieldMappingMetadata fieldMapping : fieldMappings) {
generateFieldMappingDoc(fieldMapping, writer);
}
writer.println();
}
}
private void generateFieldMappingDoc(FieldMappingMetadata fieldMapping, PrintWriter writer) {
String sourceField = fieldMapping.getSourceFieldName();
String destField = fieldMapping.getDestinationFieldName();
String type = getFieldMappingType(fieldMapping);
String notes = getFieldMappingNotes(fieldMapping);
writer.printf("| %s | %s | %s | %s |%n",
sourceField, destField, type, notes);
}
private String getFieldMappingType(FieldMappingMetadata fieldMapping) {
if (fieldMapping.getCustomConverter() != null) {
return "Custom";
} else if (fieldMapping.isCopyByReference()) {
return "Reference";
} else if (fieldMapping.isConditional()) {
return "Conditional";
} else {
return "Auto";
}
}
private String getFieldMappingNotes(FieldMappingMetadata fieldMapping) {
StringBuilder notes = new StringBuilder();
if (fieldMapping.getCustomConverter() != null) {
notes.append("Converter: ").append(fieldMapping.getCustomConverter().getSimpleName());
}
if (fieldMapping.getCustomConverterId() != null) {
if (notes.length() > 0) notes.append(", ");
notes.append("Converter ID: ").append(fieldMapping.getCustomConverterId());
}
if (fieldMapping.getDateFormat() != null) {
if (notes.length() > 0) notes.append(", ");
notes.append("Date format: ").append(fieldMapping.getDateFormat());
}
if (fieldMapping.isConditional()) {
if (notes.length() > 0) notes.append(", ");
notes.append("Condition: ").append(fieldMapping.getCondition());
}
return notes.toString();
}
}public class MappingDiscoveryService {
public boolean canMap(Mapper mapper, Class<?> sourceClass, Class<?> destClass) {
MappingMetadata metadata = mapper.getMappingMetadata();
ClassMappingMetadata mapping = metadata.getClassMapping(sourceClass, destClass);
return mapping != null;
}
public List<Class<?>> getAvailableDestinations(Mapper mapper, Class<?> sourceClass) {
MappingMetadata metadata = mapper.getMappingMetadata();
List<ClassMappingMetadata> mappings = metadata.getClassMappingsBySource(sourceClass);
return mappings.stream()
.map(ClassMappingMetadata::getDestinationClass)
.collect(Collectors.toList());
}
public List<String> getCustomFields(Mapper mapper, Class<?> sourceClass, Class<?> destClass) {
MappingMetadata metadata = mapper.getMappingMetadata();
ClassMappingMetadata mapping = metadata.getClassMapping(sourceClass, destClass);
if (mapping == null) {
return Collections.emptyList();
}
return mapping.getFieldMappings().stream()
.filter(field -> field.getCustomConverter() != null ||
field.getCustomConverterId() != null)
.map(FieldMappingMetadata::getDestinationFieldName)
.collect(Collectors.toList());
}
}public class MappingAnalyzer {
public void analyzeMappings(Mapper mapper) {
MappingMetadata metadata = mapper.getMappingMetadata();
List<ClassMappingMetadata> mappings = metadata.getClassMappings();
int totalMappings = mappings.size();
int customConverterMappings = 0;
int conditionalMappings = 0;
int referenceMappings = 0;
Map<Class<? extends CustomConverter>, Integer> converterUsage = new HashMap<>();
for (ClassMappingMetadata mapping : mappings) {
for (FieldMappingMetadata field : mapping.getFieldMappings()) {
if (field.getCustomConverter() != null) {
customConverterMappings++;
converterUsage.merge(field.getCustomConverter(), 1, Integer::sum);
}
if (field.isConditional()) {
conditionalMappings++;
}
if (field.isCopyByReference()) {
referenceMappings++;
}
}
}
System.out.println("Mapping Analysis:");
System.out.println("Total class mappings: " + totalMappings);
System.out.println("Fields with custom converters: " + customConverterMappings);
System.out.println("Conditional field mappings: " + conditionalMappings);
System.out.println("Reference copy mappings: " + referenceMappings);
System.out.println("\nConverter Usage:");
converterUsage.entrySet().stream()
.sorted(Map.Entry.<Class<? extends CustomConverter>, Integer>comparingByValue().reversed())
.forEach(entry -> System.out.println(entry.getKey().getSimpleName() + ": " + entry.getValue()));
}
}/**
* Exception thrown when metadata lookup fails
*/
public class MetadataLookupException extends MappingException {
public MetadataLookupException(String message);
public MetadataLookupException(String message, Throwable cause);
public MetadataLookupException(Throwable cause);
}public class SafeMetadataAccess {
public Optional<ClassMappingMetadata> findMapping(Mapper mapper,
Class<?> source, Class<?> dest) {
try {
MappingMetadata metadata = mapper.getMappingMetadata();
ClassMappingMetadata mapping = metadata.getClassMapping(source, dest);
return Optional.ofNullable(mapping);
} catch (MetadataLookupException e) {
logger.warn("Failed to lookup mapping metadata", e);
return Optional.empty();
}
}
}Install with Tessl CLI
npx tessl i tessl/maven-com-github-dozermapper--dozer-core