Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[JAVA] Enumérations représentant totalement l'entité #373

Open
kbuntrock opened this issue Aug 8, 2024 · 2 comments · May be fixed by #384
Open

[JAVA] Enumérations représentant totalement l'entité #373

kbuntrock opened this issue Aug 8, 2024 · 2 comments · May be fixed by #384
Assignees
Labels
domain: generation Related to the generated code type: feature New feature or request

Comments

@kbuntrock
Copy link

kbuntrock commented Aug 8, 2024

En présence d'une liste de références immutables, la génération actuelle java génère une entité dont l'attribut primary key devient une enumération. Entrainant une petite gymnastique ensuite suivant si on manipule l'entity ou la primary key seulement.

Je trouve pertinent de générer directement une énumération pour représenter plus fidèlement l'objet référence et simplifier sa manipulation.

Exemple :

Représentation du modèle :

class:
  name: TypeUtilisateur
  comment: Le type d'un utilisateur
  reference: true
  properties:
    - name: Code
      comment: Code du type
      domain: CODE
      primaryKey: true
    - name: Libelle
      comment: Libellé du type d'utilisateur
      domain: LIBELLE
  values:
    ADMINISTRATEUR: { Code: ADM, Libelle: Administrateur }
    GESTIONNAIRE: { Code: GES, Libelle: Gestionnaire }
    CLIENT: { Code: CLI, Libelle: Client }

Génération actuelle (2 fichiers) :

TypeUtilisateurCode :

/**
 * Enumération des valeurs possibles de la propriété Code de la classe TypeUtilisateur.
 */
public enum TypeUtilisateurCode {
	/**
	 * Administrateur.
	 */
	ADM,
	/**
	 * Client.
	 */
	CLI,
	/**
	 * Gestionnaire.
	 */
	GES;
}

TypeUtilisateur :

/**
 * Le type d'un utilisateur.
 */
@Generated("TopModel : https://github.com/klee-contrib/topmodel")
@Entity
@Table(name = "TYPE_UTILISATEUR")
@Immutable
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
public class TypeUtilisateur {

	/**
	 * Code du type.
	 */
	@Id
	@Column(name = "CODE", nullable = false, length = 3, columnDefinition = "varchar")
	@Enumerated(EnumType.STRING)
	private TypeUtilisateurCode code;

	/**
	 * Libellé du type d'utilisateur.
	 */
	@Column(name = "LIBELLE", nullable = true, length = 15, columnDefinition = "varchar")
	private String libelle;

	/**
	 * No arg constructor.
	 */
	public TypeUtilisateur() {
		// No arg constructor
	}

	public static final TypeUtilisateur ADM = new TypeUtilisateur(TypeUtilisateurCode.ADM);
	public static final TypeUtilisateur CLI = new TypeUtilisateur(TypeUtilisateurCode.CLI);
	public static final TypeUtilisateur GES = new TypeUtilisateur(TypeUtilisateurCode.GES);

	/**
	 * Enum constructor.
	 * @param code Code dont on veut obtenir l'instance.
	 */
	public TypeUtilisateur(TypeUtilisateurCode code) {
		this.code = code;
		switch(code) {
		case ADM :
			this.libelle = "utilisateur.typeUtilisateur.values.ADMINISTRATEUR";
			break;
		case CLI :
			this.libelle = "utilisateur.typeUtilisateur.values.CLIENT";
			break;
		case GES :
			this.libelle = "utilisateur.typeUtilisateur.values.GESTIONNAIRE";
			break;
		}
	}

	/**
	 * Getter for code.
	 *
	 * @return value of {@link tuto.entities.utilisateur.TypeUtilisateur#code code}.
	 */
	public TypeUtilisateurCode getCode() {
		return this.code;
	}

	/**
	 * Getter for libelle.
	 *
	 * @return value of {@link tuto.entities.utilisateur.TypeUtilisateur#libelle libelle}.
	 */
	public String getLibelle() {
		return this.libelle;
	}

	/**
	 * Set the value of {@link tuto.entities.utilisateur.TypeUtilisateur#code code}.
	 * @param code value to set
	 */
	public void setCode(TypeUtilisateurCode code) {
		this.code = code;
	}

	/**
	 * Set the value of {@link tuto.entities.utilisateur.TypeUtilisateur#libelle libelle}.
	 * @param libelle value to set
	 */
	public void setLibelle(String libelle) {
		this.libelle = libelle;
	}
}

Génération souhaitée (2 fichier, mais un seul à manipuler) :

./**
* La classe qui sera manipulée par les devs
*/
public enum TypeUtilisateur {
	ADMINISTRATEUR("ADM", "Administrateur"),
	GESTIONNAIRE("GES", "Gestionnaire"),
	CLIENT("CLI", "Client");

	private static final Map<String, TypeUtilisateur> MAP_BY_ID = new HashMap<>();

	static {
		for(final TypeUtilisateur typeUtilisateur : values()) {
			MAP_BY_ID.put(typeUtilisateur.code, typeUtilisateur);
		}
	}

	private final String code;
	private final String libelle;

	TypeUtilisateur(String code, String libelle) {
		this.code = code;
		this.libelle = libelle;
	}

	public static TypeUtilisateur fromId(final String code) {
		TypeUtilisateur typeUtilisateur = MAP_BY_ID.get(code);
		if(typeUtilisateur == null) {
			throw new NoSuchElementException("Enum TypeUtilisateur non trouvée pour code : " + code);
		}
		return typeUtilisateur;
	}

	public String getCode() {
		return code;
	}

	public String getLibelle() {
		return libelle;
	}
}

Et le converter associé

./**
* C'est ce qui va permettre de faire la conversion code <-> TypeUtilisateur
*/
@Converter
public class TypeUtilisateurConverter implements AttributeConverter<TypeUtilisateur, String> {

	@Override
	public String convertToDatabaseColumn(TypeUtilisateur typeUtilisateur) {
		return typeUtilisateur == null ? null : typeUtilisateur.getCode();
	}

	@Override
	public TypeUtilisateur convertToEntityAttribute(String code) {
		return TypeUtilisateur.fromCode(code);
	}
}

Dans les entités associés, la convertion est ensuire indiquée comme telle grace à l'annotation @convert :

/**
 * Type de l'utilisateur.
 */
@Convert(converter = TypeUtilisateurConverter.class)
@Column(name = "type", nullable = false)
private TypeUtilisateur type;

What do you think?

@JabX JabX added type: feature New feature or request domain: generation Related to the generated code labels Aug 23, 2024
@gideruette
Copy link
Collaborator

Ca peut poser problème de supprimer le fait que ce soit une association. Il faut donc le mettre en option. Mais en dehors de ça, c'est faisable. Par contre je ne vois pas de nécessité de retransformer typeUtilisateurCode en String, même si effectivement il serait beaucoup moins utilisé.

@dchallas
Copy link

dchallas commented Sep 13, 2024

Quelques remarques :

  • le cache pour rechercher l'énum peut être externalisé, un peu moins pratique mais ne "pollue" l'énum
  • L'annotation @Enumerated(EnumType.STRING) serai à utiliser à la place du converteur

1- Le converteur ne me parrait pas utile, on peut utiliser directement @Enumerated(EnumType.STRING)

2- J'utilise ma classe EnumLookup qui permet de prendre en charge la recherche d'énumération par Enum::name (return null si pas trouvé :)) Donc pas besoin de surcharger l'énum pour cela.

A lire : https://dzone.com/articles/java-enum-lookup-by-name-or-field-without-throwing

import Collections.associateBy;

import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;

@slf4j
@UtilityClass
public final class EnumLookup {
// Attention on ne peut réinitialiser ce cache, mais c'est uniquement sur des enums
private static final Map<Class, Map> cache = new HashMap<>();

public static <T, E extends Enum<E>> Map<T, E> lookupNullable(final Class<E> clazz) {
    return (Map<T, E>) lookupNullable(clazz, Enum::name);
}

private static <E extends Enum<E>> Map<String, E> lookupNullable(final Class<E> clazz,
        final Function<E, String> mapper) {
    final var indexCache = (Map<String, E>) cache.get(clazz);
    if (indexCache != null) {
        return indexCache;
    }
    final var index = associateBy(EnumSet.allOf(clazz), mapper);
    cache.putIfAbsent(clazz, index);
    return index;
}

public static <E extends Enum<E>> E lookupNullable(final Class<E> clazz, final String displayName) {
    final E e = lookupNullable(clazz, Enum::name).get(displayName);
    if (e == null) {
        LOGGER.debug("looking up {} not found ", displayName);
    }
    return e;
}

}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
domain: generation Related to the generated code type: feature New feature or request
Projects
Status: No status
Development

Successfully merging a pull request may close this issue.

4 participants