Université Française d'Egypte

Cours TIC409

Octobre 2012

Durée : 2h.

Seuls documents autorisés : supports du cours sous la forme de fichiers PDF ou de supports imprimés. Les énoncés et corrections des TP sont interdits. Éteignez les téléphones portables.

Important : la présentation et la lisibilité du code compteront dans la note finale. Vous êtes autorisé à écrire le code (et seulement le code) avec un crayon à papier si c'est parfaitement lisible (pas de crayon trop clair). Ajoutez des commentaires quand vous pensez que ça peut être utile au correcteur. Ne mettez pas de commentaires évidents qui n'ajoutent rien à votre code.

Respectez le découpage en questions et l'ordre des questions. Les numéros des questions devront apparaître clairement sur votre feuille. Si vous sautez une question, laissez un blanc sur votre copie pour cette question, en indiquant bien quelle question vous sautez.

Exercice 1 (10 points)

IMPORTANT : dans cet exercice, il n'est pas question de persistance. On vous demande juste d'écrire de simples classes Java. Dans l'exercice suivant vous ajouterez la persistance avec JPA.

Une application manipule des questionnaires qui sont posés lors d'examens pour des étudiants.

Un questionnaire contient des questions à choix multiples : l'étudiant peut choisir aucune, une ou plusieurs réponses parmi les réponses qui lui sont proposées (il donne les numéros, séparés par une virgule ; voir exemple d'exécution ci-dessous).

Voici l'exemple d'un tel questionnaire posé à un étudiant, avec les réponses de l'étudiant en caractères gras et plus grands :

QCM d'informatique

-------------
Question 1. Qu'est-ce que Java ?
1. Un langage objet
2. Un langage fonctionnel
3. Un langage compilé
Tapez les numéros des bonnes réponses : 1,3xxbb Tapez les numéros des bonnes réponses : 1,3
-------------
Question 2. Un langage compilé est-il
1. plus sûr qu'un langage interprété
2. plus souple qu'un langage interprété
3. plus gentil
Tapez les numéros des bonnes réponses : 1

Votre note : 20.0 sur 20

Le questionnaire lui-même est représenté par

Une question est représentée par

Une réponse est représentée par

Des précisions importantes (surtout pour l'exercice suivant sur JPA) :

Pour fixer les idées, voici une méthode main pour tester ces classes :

    // Construit le questionnaire
    Questionnaire questionnaire = new Questionnaire("QCM d'informatique");
questionnaire.setTheme("Informatique");
Question question = new Question("Qu'est-ce que Java ?");
question.ajouterReponse("Un langage objet", true);
question.ajouterReponse("Un langage fonctionnel", false);
question.ajouterReponse("Un langage compilé", true);
questionnaire.ajouterQuestion(question);
question = new Question("Un langage compilé est-il");
question.ajouterReponse("plus sûr qu'un langage interprété", true);
question.ajouterReponse("plus souple qu'un langage interprété", false);
question.ajouterReponse("plus gentil", false);
questionnaire.ajouterQuestion(question);

// Pose le questionnaire
double note = questionnaire.lancer();
// Affiche la note obtenue
System.out.println();
System.out.println("Votre note : " + note);

Question 1

On suppose qu'il existe une méthode private double lireReponsesAuClavier() dans la classe Question. On ne vous demande pas d'écrire cette méthode. Cette méthode affiche "Tapez les numéros des bonnes réponses :", lit ce que l'utilisateur a tapé au clavier en réponse à la question et retourne une note sur 1. Par exemple, si la réponse est parfaite, la note est 1, si l'utilisateur n'a coché qu'une seule bonne réponse sur 2, la note est 0.5, s'il n'a coché que 2 bonnes réponses sur 3, la réponse est 0.666666, etc... Ne vous préoccupez pas de la façon de calculer la note, utilisez seulement la valeur retournée par la méthode.

Cette méthode lireReponsesAuClavier lance une exception contrôlée questionnaire.FormatException (vous supposerez que cette classe existe déjà et vous ne devez donc pas donner son code Java) si l'utilisateur n'a pas donné un bon format pour la réponse ; par exemple s'il a tapé "1, 3xxbb" comme dans l'exemple d'exécution ci-dessus. dans votre code vous devrez redemander la réponse à la question tant que l'utilisateur n'a pas répondu avec un bon format.

Donnez le code d'une méthode lireReponse (c'est à vous de donner les paramètres et le type retour) qui demande à l'étudiant la réponse à la question, jusqu'à ce qu'il donne un bon format (on accepte évidemment toute réponse avec le bon format, même si ça n'est pas la bonne réponse à la question).

Question 2

Donnez le code de la classe Questionnaire pour que la méthode main donnée ci-dessus fonctionne. Vous supposerez déjà écrites les autres classes et la méthode lireReponse de la question précédente (si vous utilisez une méthode d'une autre classe, indiquez dans un commentaire ce que fait cette méthode).

On ne vous demande pas les import de la classe Questionnaire.

La méthode lancer lance tout le questionnaire : elle affiche toutes les questions, et calcule la note de l'étudiant compte tenu de ses réponses aux questions. Evidemment vous utiliserez la méthode lireReponse de la question 1. A la fin du questionnaire la note sur 20 est affichée. On ne vous demande pas une belle mise en forme pour cette note ; votre programme pourra afficher "Votre note : 12.6754 sur 20" (étudiez le code de la méthode main pour comprendre).

Attention, la méthode ajouterQuestion doit gérer "les 2 bouts" de l'association.

N'oubliez pas la méthode toString.

Correction

Question 1

public double lireReponse() {
  while (true) {
    try {
      return lireReponsesAuClavier();
    } catch (FormatException ex) {
      // Pas demandé mais on pourrait l'ajouter
// System.out.println(ex.getMessage()); } } }

Question 2

package entites;


import java.util.ArrayList;
import java.util.List;

/**
 * Un QCM.
 */
public class Questionnaire {
  /**
   * Les questions du QCM.
   */
  private List<Question> questions = new ArrayList<>();
  /**
   * Le titre du Questionnaire.
   */
  private String titre;
  /**
   * Le thème du questionnaire. Permet de classer les questionnaires.
   */
  private String theme;
  
  /**
   * Crée un questionnaire avec un titre.
   * @param titre titre du questionnaire.
   */
  public Questionnaire(String titre) {
    this.titre = titre;
  }

  public void setTheme(String theme) {
    this.theme = theme;
  }
  
  public double lancer() {
    double total = 0;
    int numQuestion = 0;
    for (Question question : questions) {
      // Affiche la question avec l'en-tête passée en paramètre
      question.afficher("Question " + ++numQuestion + ". ");
      double noteQuestion = question.lireReponse();
      total += noteQuestion;
    }
    return total / questions.size() * 20;
  }


  /**
   * Ajoute une question au questionnaire.
   * Gère les 2 bouts de l'association.
   * @param question question qui est ajoutée.
   */
  public void ajouterQuestion(Question question) {
    this.questions.add(question);
    question.mettreDansQuestionnaire(this);
  }
  
  @Override
  public String toString() {
    return "Questionnaire{" + "questions=" + questions 
      + ", titre=" + titre + ", theme=" + theme + '}';
  }
}

Exercice 2 (10 points)

Question 1

Ecrivez un squelette pour chacune des classes Questionnaire, Question et Reponse de telle sorte que les questionnaires puissent être sauvegardés dans une base de données relationnelle en utilisant JPA.

On ne vous demande pas d'écrire les méthodes ou les constructeurs (attention...), sauf s'ils présentent un intérêt du point de vue de la persistance. Ecrivez donc seulement l'en-tête de la classe, la déclaration des attributs (tous les attributs que vous imaginez être dans ces classes, même ceux qui ne sont pas persistants), les constructeurs et méthodes indispensables pour JPA et évidemment les annotations liées à JPA. Vous utiliserez l'accés par champ (pas l'accès par "getter"). Toutes les classes auront des identificateurs non significatifs générés automatiquement.

Relisez bien le début de l'exercice 1 pour avoir des informations sur les classes et les associations entre les classes.

Question 2

Dans la classe qui contient la méthode main, écrivez une méthode static sauvegarderQuestionnaire(Questionnaire) qui sera appelée à la fin de la méthode main et qui permettra de sauvegarder dans la base de données le questionnaire passé en paramètre.

Pour simplifier, vous supposerez l'existence d'une méthode List<Question> getQuestions() dans la classe Questionnaire, qui retourne toutes les questions du questionnaire (même si vous ne l'avez pas écrite dans votre classe Questionnaire ; on ne vous demande pas de l'écrire).

N'oubliez pas de fermer proprement les ressources utilisées.

Question 3

Donnez l'ordre JPQL pour obtenir les titres de tous les questionnaires de la base de données qui ont un certain thème (le thème est un paramètre de la requête).

Correction

Question 1

Classe Questionnaire

/**
 * Un QCM.
 */
@Entity
public class Questionnaire {
  @Id @GeneratedValue
  private int id;
  /**
   * Les questions du QCM.
   */
  @ManyToMany
  private ArrayList<Question> questions = new ArrayList<Question>();

  /**
   * Le titre du Questionnaire.
   */
  private String titre;
  /**
   * Le thème du questionnaire. Permet de classer les questionnaires.
   */
  private String theme;

  
  // Obligatoire avec JPA
  public Questionnaire() {
  }

Classe Question :

/**
 * Une question du QCM
 */
@Entity
public class Question {
  @Id @GeneratedValue
  private int id;
  /**
   * Les questionnaires qui contiennent cette question.
   */
  @ManyToMany(mappedBy="questions")
  private List<Questionnaire> questionnaires = new ArrayList<Questionnaire>();
  /**
   * Les réponses possibles aux questions.
   * Elles sont chargées en mémoire lorsque la question l'est.
* Elles sont rendues persistantes lorsque la question l'est.
*/ @OneToMany(cascade={CascadeType.PERSIST,CascadeType.MERGE}, fetch=FetchType.EAGER) private List<Reponse> reponses = new ArrayList<Reponse>(); /** * Enoncé de la question */ private String enonce; public Question() { }

Classe Reponse :

/**
 * Une réponse possible pour une question
 */
@Entity
public class Reponse {
  @Id @GeneratedValue
  private int id;
  
  /**
   * Intitulé de cette réponse.
   */
  private String intitule;
  /**
   * Vrai si et seulement si la réponse doit être "cochée" pour donner
   * une bonne réponse à la question.
   */
  private boolean ok;
  
  public Reponse() {
  }

Question 2

  private static void sauvegarderQuestionnaire(Questionnaire q) {
    EntityManagerFactory emf = null;
    EntityManager em = null;
    EntityTransaction tx = null;
    try {
      emf = Persistence.createEntityManagerFactory("QCM");
      em = emf.createEntityManager();
      tx = em.getTransaction();
      tx.begin();
      List questions = q.getQuestion();
      for (Question question : questions) {
        em.persist(question);
      }
      em.persist(q);
      tx.commit();
    }
    catch(Exception e) {
      if (tx != null) {
        tx.rollback();
      }
    }
    finally {
      if (em != null) {
        em.close();
      }
      if (emf != null) {
        emf.close();
      }
    }
  }

  

Question 3

select q.titre from Questionnaire q where q.theme = :theme