package
com.hector;
import
java.io.File;
import
java.io.IOException;
import
java.lang.reflect.Constructor;
import
java.lang.reflect.Field;
import
java.lang.reflect.InvocationTargetException;
import
java.lang.reflect.Method;
import
java.math.BigDecimal;
import
java.net.URL;
import
java.util.ArrayList;
import
java.util.Arrays;
import
java.util.Collection;
import
java.util.Enumeration;
import
java.util.HashMap;
import
java.util.HashSet;
import
java.util.List;
import
java.util.Map;
import
java.util.Set;
import
javax.persistence.Embeddable;
import
javax.persistence.Entity;
import
org.junit.Assert;
import
org.junit.Test;
import
org.powermock.reflect.Whitebox;
import
org.slf4j.Logger;
import
org.slf4j.LoggerFactory;
/**
* Test automatique des getter/setter. A voir si on s'en sert.
*/
public
class
BeansAutomatedTest {
/** logger */
private
static
final
Logger LOGGER = LoggerFactory
.getLogger(BeansAutomatedTest.
class
);
/** prefixes possibles pour les noms des getters */
public
static
final
String[] POSSIBLE_GETTER_PREFIXES = {
"get"
,
"is"
,
"has"
};
/** prefixes possibles pour les noms des setters */
private
static
final
String[] POSSIBLE_SETTER_PREFIXES = {
"set"
};
/**
* map des implémentations à utiliser pour instancier des interfaces. Ex:
* List --> ArrayList
*/
private
static
final
Map <Class <?>, Class <?>> IMPLEMENTATIONS_TO_USE =
new
HashMap <Class <?>, Class <?>>();
/**
* map des wrapper de types primitifs à utiliser pour tester les
* getter/setter sur des Integer, Boolean...
*/
private
static
final
Set <Class <?>> BOXED_PRIMITIVES =
new
HashSet <Class <?>>();
/**
* map des primitifs à utiliser pour tester les getter/setter sur des types
* primitifs int, boolean...
*/
private
static
final
Map <Class <?>, Object> PRIMITIVES_TO_USE =
new
HashMap <Class <?>, Object>();
/** map des tableaux de champs à ignorer par classe */
private
static
final
Map <String, List <String>> IGNORED_FIELDS =
new
HashMap <String, List <String>>();
/** list des classes ignorées */
private
static
final
List <String> IGNORED_BEANS =
new
ArrayList <String>();
private
static
enum
TypeTest {
Entities, Representations, Dto, Vo
}
static
{
IMPLEMENTATIONS_TO_USE.put(List.
class
, ArrayList.
class
);
IMPLEMENTATIONS_TO_USE.put(Set.
class
, HashSet.
class
);
IMPLEMENTATIONS_TO_USE.put(Collection.
class
, ArrayList.
class
);
IMPLEMENTATIONS_TO_USE.put(Map.
class
, HashMap.
class
);
BOXED_PRIMITIVES.add(Integer.
class
);
BOXED_PRIMITIVES.add(Long.
class
);
BOXED_PRIMITIVES.add(Character.
class
);
BOXED_PRIMITIVES.add(Double.
class
);
BOXED_PRIMITIVES.add(Float.
class
);
BOXED_PRIMITIVES.add(Boolean.
class
);
BOXED_PRIMITIVES.add(BigDecimal.
class
);
PRIMITIVES_TO_USE.put(
int
.
class
,
0
);
PRIMITIVES_TO_USE.put(Integer.
class
,
0
);
PRIMITIVES_TO_USE.put(
long
.
class
, 0L);
PRIMITIVES_TO_USE.put(Long.
class
, 0L);
PRIMITIVES_TO_USE.put(
char
.
class
,
'\0'
);
PRIMITIVES_TO_USE.put(Character.
class
,
'\0'
);
PRIMITIVES_TO_USE.put(
boolean
.
class
,
true
);
PRIMITIVES_TO_USE.put(Boolean.
class
, Boolean.TRUE);
PRIMITIVES_TO_USE.put(
float
.
class
, 0f);
PRIMITIVES_TO_USE.put(Float.
class
, 0f);
PRIMITIVES_TO_USE.put(
double
.
class
, 0d);
PRIMITIVES_TO_USE.put(Double.
class
, 0d);
PRIMITIVES_TO_USE.put(BigDecimal.
class
, BigDecimal.ZERO);
IGNORED_FIELDS
.put(
"com.hector.initialization.portefeuilledecommandes.PortefeuilleDeCommandesDto"
,
Arrays.asList(
new
String[] {
"paysGamme"
,
"paysCommerce"
,
"paysProgramme"
}));
IGNORED_FIELDS
.put(
"com.hector.batch.referentiels.produit.bcv.BcvRequestHeaderDto"
,
Arrays.asList(
new
String[] {
"header"
}));
IGNORED_FIELDS.put(
"com.hector.domain.centredemontage.JourDeProduction"
,
Arrays.asList(
new
String[] {
"dateJourProd"
}));
IGNORED_FIELDS
.put(
"com.hector.rest.commande.RechercheCommandePlanifieRepresentation"
,
Arrays.asList(
new
String[] {
"familles"
}));
IGNORED_FIELDS
.put(
"com.hector.cycleprogramme.AppariementRepresentation"
,
Arrays.asList(
new
String[] {
"alerte"
}));
IGNORED_FIELDS
.put(
"com.hector.rest.cycleprogramme.CycleProgrammeRepresentation"
,
Arrays.asList(
new
String[] {
"numSeqAnnee"
}));
IGNORED_FIELDS
.put(
"com.hector.rest.seuilsAlerteventes.SeuilsAlerteVentesRepresentation"
,
Arrays.asList(
new
String[] {
"codePcom"
}));
IGNORED_FIELDS
.put(
"com.hector.rest.traitement.TraitementDeDefilementRepresentation"
,
Arrays.asList(
new
String[] {
"annee"
,
"Numordre"
}));
IGNORED_FIELDS
.put(
"com.hector.rest.centredemontage.DifferentielcalendrierRepresentation"
,
Arrays.asList(
new
String[] {
"dateJourProdstring"
}));
IGNORED_BEANS
.add(
"com.hector.batch.diffusion.evn.EVNRequestFooterDto"
);
IGNORED_BEANS
.add(
"com.hector.batch.diffusion.evn.EVNRequestHeaderDto"
);
IGNORED_BEANS
.add(
"com.hector.batch.diffusion.ftr.FDPFtrRequestBodyDto"
);
IGNORED_BEANS
.add(
"com.hector.batch.diffusion.sisif.SisifDiffOfEnvoyeUsineRequestBodyDto"
);
IGNORED_BEANS
.add(
"com.hector.batch.luoreactualisee.PostEusiKeyDto"
);
IGNORED_BEANS
.add(
"com.hector.batch.replanification.officialisation.IodaDto"
);
IGNORED_BEANS.add(
"com.hector.rest.PEDBaseRepresentation"
);
IGNORED_BEANS
.add(
"com.hector.rest.chart.XDateYIntSizeValuesRepresentation"
);
IGNORED_BEANS
.add(
"com.hector.rest.chart.XDateYIntValuesRepresentation"
);
IGNORED_BEANS
.add(
"com.hector.rest.chart.XDateYIntZIntValuesRepresentation"
);
IGNORED_BEANS
.add(
"com.hector.rest.pfab.PossibiliteFabricationRepresentation"
);
IGNORED_BEANS
.add(
"com.hector.rest.replanification.AttributionCessionModeRepresentation"
);
IGNORED_BEANS
.add(
"com.hector.rest.replanification.AttributionCessionSemaineRepresentation"
);
IGNORED_BEANS
.add(
"com.hector.rest.replanification.AvanceRetardVolumeCafRepresentation"
);
IGNORED_BEANS
.add(
"com.hector.rest.replanification.CommandesEspaceAttenteRepresentation"
);
IGNORED_BEANS
.add(
"com.hector.rest.replanification.PopulationSFPRepresentation"
);
IGNORED_BEANS
.add(
"com.hector.rest.replanification.ResultatRessourceRepresentation"
);
IGNORED_BEANS
.add(
"com.hector.rest.replanification.SuiviQualitatifReplanifRepresentation"
);
IGNORED_BEANS
.add(
"com.hector.rest.ressource.AleaRessourceRepresentation"
);
IGNORED_BEANS.add(
"com.hector.rest.slot.SlotVoRepresentation"
);
IGNORED_BEANS.add(
"com.hector.rest.slot.TimeLineRepresentation"
);
}
@Test
public
void
testPEDAppDtos() {
new
PEDBeansAutomatedTest().test(
"com.hector.app"
, TypeTest.Dto);
}
@Test
public
void
testPEDBatchDtos() {
new
PEDBeansAutomatedTest().test(
"com.hector.batch"
, TypeTest.Dto);
}
@Test
public
void
testPEDDomainEntities() {
new
PEDBeansAutomatedTest().test(
"com.hector.domain"
,
TypeTest.Entities);
}
@Test
public
void
testPEDDomainVos() {
new
PEDBeansAutomatedTest().test(
"com.hector.domain"
, TypeTest.Vo);
}
@Test
public
void
testPEDWebRepresentations() {
new
PEDBeansAutomatedTest().test(
"com.hector.rest"
,
TypeTest.Representations);
}
/**
* Méthode principale de test.
* 1. Collecte les entités.
* 2. Teste les entités.
* 3. Affiche les résultats.
*
* @param domainPkg
* package contenant les entités à tester. Seul un niveau
* d'arborescence est supporté pour l'instant (domain/container,
* domain/magasin....)
*/
public
void
test(String domainPkg, TypeTest typeTest) {
List <Class <?>> entitiesToTest = collectAllEntities(domainPkg, typeTest);
TestResult allTestsResults =
new
TestResult();
for
(Class <?> entity : entitiesToTest) {
if
(IGNORED_BEANS.contains(entity.getName())) {
LOGGER.debug(
"Bean {} ignored"
, entity.getName());
allTestsResults.addIgnoredBean();
}
else
{
allTestsResults.merge(testEntity(entity));
}
}
LOGGER.info(
"{} beans of type {} have been tested in {}"
,
entitiesToTest.size(), typeTest, domainPkg);
LOGGER.info(
" --> {} beans ignored"
,
allTestsResults.getNbBeansIgnored());
LOGGER.info(
" --> {} fields tested OK"
,
allTestsResults.getNbTestedSucceeded());
LOGGER.info(
" --> {} fields tested KO"
,
allTestsResults.getNbTestedFailed());
LOGGER.info(
" --> {} fields could not be tested due to primitive/implementation issues"
,
allTestsResults.getNbNotTestedDueToPrimitiveOrImp());
LOGGER.info(
" --> {} fields could not be tested due to other issues"
,
allTestsResults.getNbNotTestedOther());
LOGGER.info(
" --> {} fields ignored"
, allTestsResults.getNbIgnored());
Assert.assertEquals(
0
, allTestsResults.getNbTestedFailed());
Assert.assertEquals(
0
,
allTestsResults.getNbNotTestedDueToPrimitiveOrImp());
Assert.assertEquals(
0
, allTestsResults.getNbNotTestedOther());
}
/**
* try to test the getter / setter of the entity. Return true if it was
* tested
*
* @param entityClass
* entity to test
* @return number of tested getter/setter pairs
*/
private
TestResult testEntity(Class <?> entityClass) {
LOGGER.debug(
"Trying to test {}"
, entityClass.getName());
List <String> ignoredFields = (IGNORED_FIELDS.containsKey(entityClass
.getName())) ? IGNORED_FIELDS.get(entityClass.getName())
:
new
ArrayList <String>();
TestResult testResultClass =
new
TestResult();
try
{
Object entityInstance = tryToInstantiateClass(entityClass);
if
(entityInstance ==
null
) {
LOGGER.warn(
"Bean {} could not be instantiated."
,
entityClass.getName());
testResultClass.merge(TestResultType.NOT_TESTED_DUE_TO_OTHER
.getTestResult());
}
else
{
for
(Field field : entityClass.getDeclaredFields()) {
if
(ignoredFields.contains(field.getName())) {
LOGGER.debug(
"Field {} of {} ignored."
,
field.getName(), entityClass.getName());
testResultClass.merge(TestResultType.IGNORED
.getTestResult());
}
else
{
Method getter =
this
.findGetter(entityClass, field);
Method setter =
this
.findSetter(entityClass, field);
if
(getter !=
null
&& setter !=
null
) {
LOGGER.debug(
"Getter and setter found for field {}."
,
field.getName());
TestResultType oneTest = setThenGet(entityInstance,
field, setter, getter);
testResultClass.merge(oneTest.getTestResult());
}
}
}
}
}
catch
(SecurityException e) {
LOGGER.warn(e.getMessage(), e);
}
catch
(IllegalArgumentException e) {
LOGGER.warn(e.getMessage(), e);
}
return
testResultClass;
}
/**
* Set puis get un objet. Vérifie que l'objet setté est bien l'objet getté.
*
* @param entityInstance
* instance de l'entité testée
* @param field
* attribut de l'instance
* @param setter
* setter de l'attribut
* @param getter
* getter de l'attribut
* @return résultat du test
*/
private
TestResultType setThenGet(Object entityInstance, Field field,
Method setter, Method getter) {
TestResultType result = TestResultType.NOT_TESTED_DUE_TO_OTHER;
try
{
Class <?> type = field.getType();
Object objectToSet = createInstanceOfType(type);
if
(objectToSet ==
null
) {
LOGGER.warn(
"Class {} : could not create {}"
, entityInstance
.getClass().getName(), field.getName());
result = TestResultType.NOT_TESTED_DUE_TO_PRIMITIVE_OR_IMPLEMENTATION;
}
else
{
setter.invoke(entityInstance, objectToSet);
Object resultOfGet = getter.invoke(entityInstance);
if
((!type.isPrimitive() && resultOfGet != objectToSet)
|| (type.isPrimitive() && !resultOfGet
.equals(objectToSet))) {
LOGGER.warn(
"Class "
+ entityInstance.getClass().getName()
+
": one of "
+ getter.getName() +
"()/"
+ setter.getName() +
"() is wrong.\n"
+
"The getter do not return what the setter set."
);
result = TestResultType.TESTED_AND_FAILED;
}
else
{
LOGGER.debug(
"!!! successfully tested {}"
, field.getName());
result = TestResultType.TESTED_AND_SUCCEEDED;
}
}
}
catch
(IllegalAccessException e) {
LOGGER.warn(e.getMessage(), e);
}
catch
(IllegalArgumentException e) {
LOGGER.warn(e.getMessage(), e);
}
catch
(InvocationTargetException e) {
LOGGER.warn(e.getMessage(), e);
}
catch
(SecurityException e) {
LOGGER.warn(e.getMessage(), e);
}
return
result;
}
/**
* Crée une instance de l'objet de type type ou fournis un objet par défaut
* si c'est une primitive, une enum, etc...
*
* @param type
* le type
* @return une instance/une primitive/une enum
*/
protected
Object createInstanceOfType(Class <?> type) {
Object objectToSet =
null
;
try
{
if
(type.isEnum()) {
List <?> enumConstants = Arrays.asList(type.getEnumConstants());
objectToSet = enumConstants.get(
0
);
}
else
if
(type.isPrimitive() || typeIsBoxedPrimitive(type)) {
objectToSet = PRIMITIVES_TO_USE.get(type);
if
(objectToSet ==
null
) {
LOGGER.warn(
"No primitive to use for {}"
, type);
}
}
else
if
(type.isInterface()) {
Class <?> typeImp = IMPLEMENTATIONS_TO_USE.get(type);
if
(typeImp ==
null
) {
LOGGER.warn(
"No implementation defined for {}"
, type);
}
objectToSet = typeImp.newInstance();
}
else
{
objectToSet = tryToInstantiateClass(type);
}
if
(objectToSet ==
null
) {
objectToSet = Whitebox.newInstance(type);
}
}
catch
(IllegalArgumentException e) {
LOGGER.warn(e.getMessage(), e);
}
catch
(InstantiationException e) {
LOGGER.warn(e.getMessage(), e);
}
catch
(IllegalAccessException e) {
LOGGER.warn(e.getMessage(), e);
}
return
objectToSet;
}
private
Object tryToInstantiateClass(Class <?> type) {
Object instance =
null
;
Constructor <?>[] constructors = type.getDeclaredConstructors();
for
(Constructor <?> constructor : constructors) {
constructor.setAccessible(
true
);
Class <?>[] paramTypes = constructor.getParameterTypes();
try
{
List <Object> params =
new
ArrayList <Object>();
for
(Class <?> paramType : paramTypes) {
params.add(paramType.getDeclaredConstructor().newInstance());
}
instance = constructor.newInstance(params.toArray());
}
catch
(NoSuchMethodException | InstantiationException
| IllegalAccessException | IllegalArgumentException
| InvocationTargetException | SecurityException e) {
LOGGER.debug(
"Could not instantiate {} with {} "
,
type.getName(), constructor.getName());
}
}
return
instance;
}
/**
* Type est un type primitif
*
* @param type
* le type
* @return Type est un type primitif
*/
private
boolean
typeIsBoxedPrimitive(Class <?> type) {
return
BOXED_PRIMITIVES.contains(type);
}
/**
* Trouve le setter pour un attribut d'une entitée
*
* @param entity
* la classe de l'entité
* @param field
* l'attribut
* @return le setter ou null si non trouvé
*/
private
Method findSetter(Class <?> entity, Field field) {
for
(String setterPrefix : POSSIBLE_SETTER_PREFIXES) {
try
{
String setterToTry = setterPrefix
+ capitalizeFirstLetter(field.getName());
LOGGER.debug(
"Trying setter {}"
, setterToTry);
Method setter = entity.getMethod(setterToTry, field.getType());
return
setter;
}
catch
(SecurityException e) {
LOGGER.warn(
"Security exception for class {}"
, entity.getName());
}
catch
(NoSuchMethodException e) {
LOGGER.debug(
"No setter found"
);
}
}
return
null
;
}
/**
* Renvoie la string avec la première lettre en majuscule
*
* @param str
* la string
* @return string avec la première lettre en majuscule
*/
private
String capitalizeFirstLetter(String str) {
if
(str.length() ==
0
) {
return
str;
}
else
if
(str.length() ==
1
) {
return
String.valueOf(Character.toUpperCase(str.charAt(
0
)));
}
return
Character.toUpperCase(str.charAt(
0
)) + str.substring(
1
);
}
/**
* Trouve le getter pour un attribut d'une entitée
*
* @param entity
* la classe de l'entité
* @param field
* l'attribut
* @return le getter ou null si non trouvé
*/
private
Method findGetter(Class <?> entity, Field field) {
for
(String getterPrefix : POSSIBLE_GETTER_PREFIXES) {
try
{
String getterToTry = getterPrefix
+ capitalizeFirstLetter(field.getName());
LOGGER.debug(
"Trying getter {}"
, getterToTry);
Method getter = entity.getMethod(getterToTry);
return
getter;
}
catch
(SecurityException e) {
LOGGER.warn(
"Security exception for class {}"
, entity.getName());
}
catch
(NoSuchMethodException e) {
LOGGER.debug(
"No setter found"
);
}
}
return
null
;
}
/**
* Collecte toutes les entités du package
*
* @param pkg
* le pkg de domain
* @return les entités collectées
*/
private
List <Class <?>> collectAllEntities(String pkg, TypeTest typeTest) {
List <Class <?>> entitiesToTest =
new
ArrayList <Class <?>>();
try
{
List <Class <?>> classes = getClasses(pkg);
for
(Class <?> potentialEntity : classes) {
LOGGER.debug(
"Potential entity : "
+ potentialEntity.getName());
if
(TypeTest.Entities.equals(typeTest)
&& potentialEntity.getAnnotation(Entity.
class
) !=
null
) {
LOGGER.debug(
" ->>>> this is an Entity"
);
entitiesToTest.add(potentialEntity);
}
else
if
(TypeTest.Representations.equals(typeTest)
&& potentialEntity.getSimpleName().endsWith(
"Representation"
)) {
LOGGER.debug(
" ->>>> this is a Representation"
);
entitiesToTest.add(potentialEntity);
}
else
if
(TypeTest.Dto.equals(typeTest)
&& potentialEntity.getSimpleName().endsWith(
"Dto"
)) {
LOGGER.debug(
" ->>>> this is a Dto"
);
entitiesToTest.add(potentialEntity);
}
else
if
(TypeTest.Vo.equals(typeTest)
&& potentialEntity.getAnnotation(Embeddable.
class
) !=
null
) {
LOGGER.debug(
" ->>>> this is a Vo"
);
entitiesToTest.add(potentialEntity);
}
}
}
catch
(ClassNotFoundException | IOException e) {
Assert.fail(
"Unable to scan package "
+ pkg);
LOGGER.error(
"Unable to scan package."
, e);
}
return
entitiesToTest;
}
private
static
List <Class <?>> getClasses(String packageName)
throws
ClassNotFoundException, IOException {
ClassLoader classLoader = Thread.currentThread()
.getContextClassLoader();
Assert.assertNotNull(classLoader);
String path = packageName.replace(
'.'
,
'/'
);
Enumeration <URL> resources = classLoader.getResources(path);
List <File> dirs =
new
ArrayList <File>();
while
(resources.hasMoreElements()) {
URL resource = resources.nextElement();
dirs.add(
new
File(resource.getFile()));
}
ArrayList <Class <?>> classes =
new
ArrayList <Class <?>>();
for
(File directory : dirs) {
classes.addAll(findClasses(directory, packageName));
}
return
classes;
}
/**
* Recursive method used to find all classes in a given directory and
* subdirs.
*
* @param directory
* The base directory
* @param packageName
* The package name for classes found inside the base directory
* @return The classes
* @throws ClassNotFoundException
*/
private
static
List <Class <?>> findClasses(File directory, String packageName)
throws
ClassNotFoundException {
List <Class <?>> classes =
new
ArrayList <Class <?>>();
if
(!directory.exists()) {
return
classes;
}
File[] files = directory.listFiles();
for
(File file : files) {
if
(file.isDirectory()) {
Assert.assertFalse(file.getName().contains(
"."
));
classes.addAll(findClasses(file,
packageName +
"."
+ file.getName()));
}
else
if
(file.getName().endsWith(
".class"
)) {
try
{
classes.add(Class.forName(packageName
+
'.'
+ file.getName().substring(
0
,
file.getName().length() -
6
)));
}
catch
(Throwable e) {
LOGGER.warn(
"Could not initialize {}"
, file.getName());
}
}
}
return
classes;
}
}