Skip to content

Commit

Permalink
enable mapping of package private classes
Browse files Browse the repository at this point in the history
  • Loading branch information
kirviq committed Feb 3, 2017
1 parent 5491939 commit f25c128
Show file tree
Hide file tree
Showing 12 changed files with 183 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ public <T, A, B> ConstructorMapping<T> resolve(ClassMap<A, B> classMap, Type<T>
}

boolean foundDeclaredConstructor = false;
Constructor<T>[] constructors = (Constructor<T>[]) targetClass.getRawType().getConstructors();
Constructor<T>[] constructors = (Constructor<T>[]) targetClass.getRawType().getDeclaredConstructors();
TreeMap<Integer, ConstructorMapping<T>> constructorsByMatchedParams = new TreeMap<Integer, ConstructorMapping<T>>();
for (Constructor<T> constructor: constructors) {
ConstructorMapping<T> constructorMapping = new ConstructorMapping<T>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
*/
package ma.glasnost.orika.impl;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

import ma.glasnost.orika.MappingContext;
import ma.glasnost.orika.ObjectFactory;

Expand All @@ -29,24 +32,38 @@
*/
public class DefaultConstructorObjectFactory<T> implements ObjectFactory<T> {

private final Class<T> type;
private final String description;
private Constructor<T> constructor;

public DefaultConstructorObjectFactory(Class<T> type) {
this.type = type;
this.constructor = getDefaultConstructor(type);
constructor.setAccessible(true);
this.description = getClass().getSimpleName() + "<" + type.getSimpleName() + ">";
}

@SuppressWarnings("unchecked")
private static <T> Constructor<T> getDefaultConstructor(Class<T> type) {
for (Constructor<?> c : type.getDeclaredConstructors()) {
if (c.getParameterTypes().length == 0) {
return (Constructor<T>) c;
}
}
return null;
}

/* (non-Javadoc)
* @see ma.glasnost.orika.ObjectFactory#create(java.lang.Object, ma.glasnost.orika.MappingContext)
*/
@Override
public T create(Object source, MappingContext mappingContext) {
try {
return type.newInstance();
return constructor.newInstance();
} catch (InstantiationException e) {
throw new IllegalStateException(e);
} catch (IllegalAccessException e) {
throw new IllegalStateException(e);
} catch (InvocationTargetException e) {
throw new IllegalStateException(e);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1053,7 +1053,7 @@ public <T, S> ObjectFactory<T> lookupObjectFactory(final Type<T> destinationType
sourceType));
}

Constructor<?>[] constructors = targetType.getRawType().getConstructors();
Constructor<?>[] constructors = targetType.getRawType().getDeclaredConstructors();
if (useAutoMapping || !isBuilt) {
if (constructors.length == 1 && constructors[0].getParameterTypes().length == 0) {
/*
Expand Down
34 changes: 34 additions & 0 deletions core/src/main/java/ma/glasnost/orika/impl/generator/Analysis.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package ma.glasnost.orika.impl.generator;

import java.lang.reflect.Modifier;

class Analysis {

static Visibility getMostRestrictiveVisibility(Class<?> classToCheck) {
Visibility visibility = Visibility.PUBLIC;
Class<?> currentClass = classToCheck;
while (currentClass != null) {
int modifiers = currentClass.getModifiers();
if (Modifier.isPrivate(modifiers)) {
visibility = Visibility.PRIVATE;
} else if (Modifier.isProtected(modifiers) && visibility != Visibility.PRIVATE) {
visibility = Visibility.PROTECTED;
} else if (Modifier.isPublic(modifiers)) {
// visibility = Visibility.PUBLIC not needed because if visibiliy were anything
// else than PUBLIC we wouldn't set it anyways
} else if (visibility != Visibility.PRIVATE && visibility != Visibility.PROTECTED) {
visibility = Visibility.PACKAGE;
}
currentClass = currentClass.getEnclosingClass();
}
return visibility;
}

enum Visibility {
PRIVATE, PACKAGE, PROTECTED, PUBLIC
}

private Analysis() {
throw new UnsupportedOperationException("not instantiable");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import java.lang.reflect.Modifier;
import ma.glasnost.orika.impl.generator.Analysis.Visibility;

/**
* Uses Eclipse JDT to format and compile the source for the specified
Expand Down Expand Up @@ -124,20 +124,9 @@ protected void writeClassFile(String packageName, String simpleClassName, byte[]

public void assureTypeIsAccessible(Class<?> type) throws SourceCodeGenerationException {
try {
if (!Modifier.isPublic(type.getModifiers())) {
Visibility visibility = Analysis.getMostRestrictiveVisibility(type);
if (visibility != Analysis.Visibility.PUBLIC && visibility != Analysis.Visibility.PACKAGE) {
throw new SourceCodeGenerationException(type + " is not accessible");
} else if (type.isMemberClass()) {
/*
* The type needs to be publicly accessible (including it's
* enclosing classes if any)
*/
Class<?> currentType = type;
while (currentType != null) {
if (!Modifier.isPublic(type.getModifiers())) {
throw new SourceCodeGenerationException(type + " is not accessible");
}
currentType = currentType.getEnclosingClass();
}
}

assertTypeAccessible.invoke(compiler, type);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.Random;
import java.util.WeakHashMap;
Expand All @@ -35,6 +34,7 @@
import javassist.CtNewMethod;
import javassist.LoaderClassPath;
import javassist.NotFoundException;
import ma.glasnost.orika.impl.generator.Analysis.Visibility;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -149,23 +149,10 @@ private boolean registerClassLoader(ClassLoader cl) {
* assertClassLoaderAccessible(java.lang.Class)
*/
public void assureTypeIsAccessible(Class<?> type) throws SourceCodeGenerationException {

if (!type.isPrimitive()) {

if (!Modifier.isPublic(type.getModifiers())) {
Visibility visibility = Analysis.getMostRestrictiveVisibility(type);
if (visibility != Analysis.Visibility.PUBLIC && visibility != Analysis.Visibility.PACKAGE) {
throw new SourceCodeGenerationException(type + " is not accessible");
} else if (type.isMemberClass()) {
/*
* The type needs to be publicly accessible (including it's
* enclosing classes if any)
*/
Class<?> currentType = type;
while (currentType != null) {
if (!Modifier.isPublic(type.getModifiers())) {
throw new SourceCodeGenerationException(type + " is not accessible");
}
currentType = currentType.getEnclosingClass();
}
}

String className = type.getName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ public SourceCodeContext(final String baseClassName, Class<?> superClass, Mappin

int namePos = safeBaseClassName.lastIndexOf(".");
if (namePos > 0) {
this.packageName = safeBaseClassName.substring(0, namePos - 1);
this.packageName = safeBaseClassName.substring(0, namePos);
this.classSimpleName = safeBaseClassName.substring(namePos + 1);
} else {
this.packageName = "ma.glasnost.orika.generated";
Expand Down
33 changes: 32 additions & 1 deletion core/src/main/java/ma/glasnost/orika/metadata/ClassMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package ma.glasnost.orika.metadata;

import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
Expand Down Expand Up @@ -161,7 +162,37 @@ public Mapper<A, B> getCustomizedMapper() {
* @return
*/
public String getMapperClassName() {
return "Orika_" + getBTypeName() + "_" + getATypeName() + "_Mapper";
String className = "Orika_" + getBTypeName() + "_" + getATypeName() + "_Mapper";

boolean aIsPublic = Modifier.isPublic(getAType().getRawType().getModifiers());
boolean bIsPublic = Modifier.isPublic(getBType().getRawType().getModifiers());

if (aIsPublic) {
if (bIsPublic) {
// both public, no package needed
return className;
} else {
// A public, B not --> use package of B
return getPackageName(getBType()) + "." + className;
}
} else {
if (bIsPublic) {
// A not public, B is --> use package of A
return getPackageName(getAType()) + "." + className;
} else {
// both package private --> make sure they're in the same package
String aPackage = getPackageName(getAType());
if (aPackage.equals(getPackageName(getBType()))) {
return aPackage + "." + className;
} else {
throw new RuntimeException(getAType() + " and " + getBType() + " are both package private but are in different packages");
}
}
}
}

private static String getPackageName(Type<?> type) {
return type.getCanonicalName().replaceFirst("\\.[^\\.]+$", "");
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package ma.glasnost.orika.test.packageprivate;

import ma.glasnost.orika.MapperFacade;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.test.MappingUtil;
import ma.glasnost.orika.test.packageprivate.otherpackage.SomePublicDto;
import org.junit.Test;

import static org.junit.Assert.assertEquals;

public class PackagePrivateTestCase {

@Test
public void testMappingPackagePrivateToPublic() {
SomePrivateEntity source = new SomePrivateEntity();
source.setField("test value");

final SomePublicDto actual = getMapperFacade().map(source, SomePublicDto.class);

assertEquals(source.getField(), actual.getField());
}

@Test
public void testMappingPublicToPackagePrivate() {
SomePublicDto source = new SomePublicDto();
source.setField("test value");

final SomePrivateEntity actual = getMapperFacade().map(source, SomePrivateEntity.class);

assertEquals(source.getField(), actual.getField());
}

@Test
public void testMappingPackagePrivateToPackagePrivate() {
SomePrivateEntity source = new SomePrivateEntity();
source.setField("test value");

final SimilarEntity actual = getMapperFacade().map(source, SimilarEntity.class);

assertEquals(source.getField(), actual.getField());
}

private MapperFacade getMapperFacade() {
final MapperFactory mapperFactory = MappingUtil.getMapperFactory(true);
mapperFactory.classMap(SomePrivateEntity.class, SomePublicDto.class);
mapperFactory.classMap(SomePrivateEntity.class, SimilarEntity.class);
return mapperFactory.getMapperFacade();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package ma.glasnost.orika.test.packageprivate;

class SimilarEntity {
private String field;

public String getField() {
return field;
}

public void setField(String field) {
this.field = field;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package ma.glasnost.orika.test.packageprivate;

class SomePrivateEntity {
private String field;

public String getField() {
return field;
}

public void setField(String field) {
this.field = field;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package ma.glasnost.orika.test.packageprivate.otherpackage;

public class SomePublicDto {
private String field;

public String getField() {
return field;
}

public void setField(String field) {
this.field = field;
}
}

0 comments on commit f25c128

Please sign in to comment.