Editer Java


Des notes concernant java. Merci a Cyrille Herby pour son cours sur le site du zéro ainsi qu'à mes formateurs.


Bases


Un projet Java SE (simple edition, la version utilisée en général par le grand public) doit contenir au minimum une classe du même nom que le fichier source et une méthode nommée main, de type public et static, admettant en paramètre un tableau string et renvoyant void.

Environnement de développement


Pour exécuter du code java, vous aurez besoin d'une machine virtuelle.

Pour développer, vous aurez beosin du JDK (java developpment kit). Il en existe 2 versions : celle d'Oracle et une version libre nommée OpenJDK, quasi pas de différences entre les 2.


Sous Linux sudo apt-get update puis sudo apt-get install default-jre. Il peut être intéressant d'indiquer le répertoire de votre installation : export PATH=/home/demo/java/jdk1.8.0_77/bin:$PATH. Le JDK se trouve dans les dépôts linux, vous pouvez sans doute le trouver dans le gestionnaire de paquet de votre installation.

Bases du langage


Types primitifs : Entiers avec signe (byte, short, int, long -la valeur doit être préfixé d'un L), réels (float, double), caractère (un seul caractère de 16 bits, unicode), booléen. Ils ne peuvent pas être "null", ils ont une valeur par défaut (false pour un boolean, 0 pour les nombre, \u0000 pour char...).

Les "String" sont des objets, des ensembles de char.

Attention si on divise deux int et que essaie de mettre le résultat dans un troisième int, une erreur est possible si le résultat a une virgule.

Structures conditionnelles : if, if/else, if/else if/else, switch.
Les accolades sont facultatives s'il n'y a qu'une seule ligne dans la condition.

Exemple :

if(b==1){ a=3; System.out.print("1"); }



switch (a) { case 0: System.out.println("A vaut zéro"); break;boolean case 20: System.out.println("A vaut vingt"); break; default: System.out.println("A ne vaut ni zéro ni vingt."); }


Structures itératives : while, do/while (à la différence de while, on entre au moins une fois dans la structure), for(debut;tant que;action). Break permet de sortir d'une itération. Continue permet d'arrêter la boucle et de passer éventuellement au tour de boucle suivant.



int a=1; while(a<10){ System.out.print(a); a++; } //Affiche 123456789 int b=1; do{ System.out.print(b); b++; }while(b<10); //Affiche 123456789 int i=0; for(i=1;i<=9;i++){ System.out.print(i); } //Affiche 123456789


Il existe aussi depuis java 5 une méthode particulière permettant de parcourir les tableaux for(type_du_tableau valeur_de_la_case_du_tableau : nom_dutableau) :


String tab[] = {"a", "b", "c", "d", "e"}; for(String valeur : tab){ System.out.print(valeur); } //Affiche abcde


Avec un tableau d'entier :

int tab2[] = {1, 2, 3, 4, 5}; for(int valeur : tab2){nouvelleEspece System.out.print(valeur); } //Affiche 12345


Affectation : a=b=1 signifie qu'on affecte la valeur 1 à A et B.
a=a+1 est équivalent à a+=1.
Par convention les noms de variables doivent commencer par une minuscule. Si une variable est composée de plusieurs mots, le premier est en minuscule et les suivants tout attachés avec leurs majuscules. Exemple : maVariableDeTest.
Il est possible de séparer les nombre par un underscore (qui sera ignoré lors del'exécution du programme) pour les rendre d'avantage lisibles :


int a=800_000;


On peut aussi utiliser la notation binaire (depuis java 7) ou hexadécimale en préfixant avec 0b ou 0x :

int a=0b110; //A=6. int b=0xF; //B=15


On peut faire suivre le nombre d'une lettre indiquant son type :

float a=9.4f; int b=0xF; //B=15


Pour cette raison, une variable ne peut pas commencer par un chiffre (sinon, impossible de distinguer le float 9f de la variable 9f).

Transtypage (ou cast) : pour transformer un type primitif en un autre. Si utilisé dans la même ligne qu'un calcul, le cast se fait après le calcul (sur le résultat).
Exemple pour utiliser la valeur de l'entier a dans le réel b :

int a = 1; float b = (float)a;



double a=9.6; int b=(int)a; //donnera 9


Attention :

char a='9'; int b=(int)a; //donnera 57, la valeur du caractère 9 dans la table ASCII.



Comparaison : < (inférieur ), <= (inférieur ou égal à), == (égal à), >= (supérieur ou égal à), > (supérieur à),!= (différent de).
Attention, pour comparer des strings utiliser chaineDeCaractere1.equals(chaineDeCaractere2)).

Opérateur ternaire : pour faire rapidement des tests if/else afin d'affecter des variables.

numJoueur = (numJoueur==1)?2:1; //assigner à numJoueur la valeur 2 si numJoueur=1, sinon 1.


Attention, pour optimiser java réutilise un objet String s'il voit qu'il existe déjà :

String a="abc"; String b="abc"; System.out.println(a==b); //renvoie true


Par contre, si on demande spécifiquement la création d'un objet string :

String a=new String("abc"); String b="abc"; System.out.println(a==b); //renvoie false




Incrémentation :
Attention au sens del'incrémentation.

int a=1; int b=a++; //b vaut a (1) puis a est incrémenté à 2.



int a=1; int b=++a; //a est incrémenté à 2 puis b vaut a (2).



Opérateur logiques : ! (non), & (et), | (ou), ^ (ou exclusif). && stoppe l'évaluation de la condition dès qu'elle est fausse (ça évite de tester 50 & inutiles si la première condition est fausse). De la même façon, || stoppe l'évaluation dès qu'elle est vraie.

Tableaux : un regroupement de variable d'un même type. On peut avoir des tableaux multidimensionnels. Attention, un tableau a un nombre de cases fixe, qui ne peut pas changer après qu'on l'ai déclaré (si on a absolument besoin de redimensionner un tableau, utiliser Arraylist).


//classique int[] tableau3b = new int[10]; //pré-rempli int tableau[]={1,2,3,4,5,6,7,8,9,10}; //2 dimensions, attention à la virgule entre les accolades int nombres[][] = { {0,2},{1,3} }; int nombresAutreExemple[][] = { {0,2,4}, {1,3,9}, {1,3,5} }; System.out.println(nombres[1][1]); //2 dimensions sans préciser le contenu int[][] multi = new int[5][10]; //une Arraylist qui accepte n'importe quoi ArrayList tableau2 = new ArrayList(); /une Arraylist qui n'accepte que des objets étudiants ArrayList<Etudiant> listeEtudiants = new ArrayList<Etudiant> ();


Attention² : Quand un tableau est passé à une méthode, c'est une référence vers ce tableau qui est passé. La méthode va donc modifier le tableau qu'on lui donne. Ci-dessous la méthode statique Mergesort va avoir une influence sur le tableau.

Integer[] monTableau={7,5,9,3,6,0,2,4}; Modifieur.ModifierMonTableau(monTableau); //monTableau a bien été modifié, sans besoin de monTableau=Modifieur.ModifierMonTableau(monTableau);


Méthodes : équivalent aux fonctions dans les autres langages.
Exemple avec une méthode accessible à tous (publique) qui ne retourne rien (donc void) pour afficher le contenu d'un tableau, appelée 3 fois :

package exosSDZ; public class classePrincipaleExosSDZ { public static void main(String[] args) { int tab1[]={1,2,3,4}; afficherContenu(tab1); } public static void afficherContenu(int tab[]){ for(int contenuCase:tab){ System.out.print(contenuCase); } } }


Il est possible d'avoir 2 méthodes du même nom pour traiter différents types, par exemple ici on peut traiter des tableau d'entier et de String (on appelle ça "surcharger une méthode") :


package exosSDZ; public class classePrincipaleExosSDZ { public static void main(String[] args) { int tab1[]={1,2,3,4}; String tab2[]={"aa","bb"}; afficherContenu(tab1); afficherContenu(tab2); } public static void afficherContenu(int tab[]){ for(int contenuCase:tab){ System.out.print(contenuCase); } } public static void afficherContenu(String tab[]){ for(String contenuCase:tab){ System.out.print(contenuCase); } } }



On parle de surcharge quand il y a 2 méthodes de noms identiques dans la même classe avec des paramètres différents. On peut surcharger les constructeurs ou les méthodes.

Attention à ne pas confondre surcharge et redéfinition.



Objets et classes


Pourquoi utiliser des objets et non pas des fonctions ?
On pourrait faire en procédural, un tableau voiture puis une fonction afficher_marque(voiture) au lieu d'une classevoiture puis voiture.afficherMarque(), mais le premier cas pose plusieurs problèmes :

-afficher_marque() est une fonction distincte de l'entité voiture. On a d'abord, par exemple, un tableau voiture avec plusieurs informations puis plus tard cette fonction. En orienté objet, on peut tout regrouper au sein d'une même classe, ce qui permet de voir en un coup d'oeil quelles fonctions sont associé à la voiture. Le code est plus facilement réutilisable, le travail plus facilement divisable (plusieurs développeurs écrivent différents objets avec leurs fonctions).
ocsjapon2/index.php?p=docs/vrac/perso/informatique/c%2B%2B

-dans l'exemple procédural, on pourrait mal déclarer le tableau et se retrouver avec une erreur en appelant afficher_marque. En orienté objet, on peut obliger à déclarer l'objet correctement. L'objet contrôle les éléments qui lui sont propres.

-on peut facilement autoriser ou interdire l'accès à certains objets à des parties du programme (avec public/private). Par exemple, en mettant un attribut d'un objet en private, on peut empêcher de le modifier directement (par exemple, si un objet a un attribut "nom", pour empêcher de faire objet.nom="AAA" à l'extérieur de l'objet). S'il y a besoin de modifier le nom de l'objet, on va plutôt donner des méthodes ("modifierNom" par exemple, qui pourra s'assurer que le nom respecte certaines formes, ou bien vérifier les autorisations de l'utilisateur...). Cette idée de protection des attributs des objets s'appelle l'encapsulation.

Une classe décrit une famille d'objets. C'est en quelque sorte le plan d'un objet. D'une classe va naitre des objets qu'on dit "instances" de cette classe.


Il existe des variables d'instances (définissant des propriétés des objets nomobjet.propriete), des variables de classe (propriétés communes à toutes les instances de la classe Classe.propriete), variables locales (qui ne sortiront pas de l'objet, utilisées dans les méthodes par exemple).

En java une classe doit commencer par une majuscule.

Ci-dessous une classe oiseau :

package exosSDZ; public class Oiseau { //ci dessous les "attributs" de l'objet //(en gros, des variables qui seront propres à chaque objet oiseau créés à partir de cette classe) //private pour pas qu'on puisse modifier directement oiseau.chant à l'extérieur de la classe private String chant; private String espece; private int taille; //on pourrait initialiser une taille par défaut ici mais ce n'est pas propre //un attribut statique, qui compte le nombre d'oiseaux //il sera partagé entre tous les oiseaux public static int nbrOiseaux=0; public Oiseau(){ //Le CONSTRUCTEUR. En initialisant l'objet, que va-t-il se passer ? Il ne renvoie rien, même pas Void //il a des attributs par défaut System.out.println("Creation d'un oiseau..."); chant="Cui cui !"; espece="oiseau générique"; taille=15; nbrOiseaux=nbrOiseaux+1; } //sauf si on a saisi des attributs public Oiseau(String saisieEspece, String saisieChant, int saisieTaille){ System.out.println("Creation d'un oiseau..."); chant=saisieChant; espece=saisieEspece; taille=saisieTaille; nbrOiseaux=nbrOiseaux+1; } public void chante(){ System.out.println(chant); } //Ci dessous un ACCESSEUR, une méthode pour ACCEDER à des variables d'instance (getter) //Retourne l'espèce public String donneEspece() { return espece; } //Ci dessous un MUTATEUR, une méthode pour MODIFIER des variables d'instance (setter) //Retourne l'espèce public void changeEspece(String nouvelleEspece) { espece=nouvelleEspece; } }


Qu'on peut appeler de la façon suivante :

package exosSDZ; public class classePrincipaleExosSDZ { public static void main(String[] args) { //ici on instancie un objet Oiseau (avec new), qu'on met dans un identifiant d'objet de type Oiseau. //oiseau1 est une sorte de lien qui permet d'accéder à cet objet Oiseau oiseau1=new Oiseau(); oiseau1.chante(); //oiseau2 est une sorte de lien qui permet d'accéder à cet objet Oiseau oiseau2=new Oiseau("hirondelle","Piouuu ! Piouuu !", 20); oiseau2.chante(); System.out.println(oiseau2.donneEspece()); oiseau2.changeEspece("colombe"); System.out.print(Oiseau.nbrOiseaux); } }


Ce qui affiche :

Creation d'un oiseau... Cui cui ! Creation d'un oiseau... Piouuu ! Piouuu ! hirondelle 2


Avec Eclipse, on peut générer automatiquement les accésseur (getters) et mutateurs (setters) en faisant lic droit dans la classe->source->generate getters and setters.

On peut faire :

package exosSDZ; public class classePrincipaleExosSDZ { public static void main(String[] args) { Oiseau oiseau1=new Oiseau(); oiseau1.chante(); //oiseau2 est une sorte de lien qui permet d'accéder à cet objet Oiseau oiseau2=new Oiseau("hirondelle","Piouuu ! Piouuu !", 20); oiseau2.chante(); oiseau2=oiseau1; //oiseau2 pointe maintenant vers le même objet que oiseau1 (oiseau par défaut). //l'hirondelle est innaccessible } }


This


this est une référence à l'objet en cours d'utilisation. Il est implicite, mais parfois on doit l'utiliser :
-quand un argument a le même nom qu'un attribut et qu'il faut les différencier :


private int taille; private String nomDonne; public MonObjet(int taille, String nomDonne){ this.taille=taille; //this obligatoire pour différencier nom=nomDonne; //this pas obligatoire car l'argument nomDonne et l'attribut nom n'ont pas le même nom. }


-quand on va renvoyer l'objet, il faut faire un return this;

Héritage


Une classe peut hériter des propriétés et des méthodes d'une autre. Par exemple, on pourrait avoir une classe animaux avec une propriété taille, puis une classe oiseau déclarée avec public class Oiseau extends Animaux{ . Attention, comme tous les autres éléments extérieurs à Animaux, Oiseau ne peut pas avoir accès au variables/méthodes "private". Pour qu'on autorise des classes héritées à utiliser ces variables, il faut les mettre en "protected".
Protected est considéré comme public dans le paquet et dans l'héritage, sinon c'est privé. C'est à dire que 2 classes dans le même package voient les attributs/méthodes "protected" l'une de l'autre.
On peut mettre private/protected/public sur des attributs, méthodes et classes.


package exosSDZ; public class Animal { protected int taille; protected String mange(){ return "Miam miam."; } }


On ne peut pas hériter de 2 classes en même temps (extends Mamifère, Animaux) mais les classes peuvent s’enchaîner.


On peut initialiser le constructeur de la classe mère en utilisant super().
De façon générale super permet d'appeler la classe mère, pour référencer une méthode de la classe supérieure.

On peut donc trouver ça dans la classe Oiseau :

public String mange(){ return super.mange()+", l'oiseau avait faim."; }


"mange()" est une méthode polymorphe, car elle est employée dans Animal et des Oiseau. Ce n'est pas la même chose qu'une méthode surchargée (deux méthodes du même noms différenciées par leurs arguments), ici, on différencie les méthodes selon l'objet. Dans l'exemple plus haut, c'est la méthode dans Oiseau qui est invoquée (appelant elle-même la méthode dans Animal).

Tous les objets héritent de la classe Object de Java. Ainsi, la méthode toString() ou la méthode equals() existent pour tous les objets. On peut bien sûr les redéfinir (polymorphisme) et faire en sorte que Oiseau.equals compare la race, Animal.equals compare l'espèce... equals(), toString() et hashCode() sont des méthodes souvent redéfinies pour bien correspondre à ce qu'on attend des objets. Il faut souvent redéfinir hashCode pour que notre comparaison personnalisée avec equals fonctionne.

Il est possible de stocker des Oiseau dans des Animal car les Oiseaux sont des animaux : c'est la covariance des variables.
On peut faire des tableaux d'animaux et y mettre des Oiseaux (ou autre animaux).


package exosSDZ; public class classePrincipaleExosSDZ { public static void main(String[] args) { Animal[] tabOiseaux= new Animal[2]; Oiseau oiseau1=new Oiseau(); tabOiseaux[0]=oiseau1; } }


Par contre, si on veut le faire chanter, il faut utiliser le transtypage. On peut mettre un objet Oiseau dans un tableau d'Animaux, mais il faut transtyper l'animal en oiseau pour le faire chanter :

package exosSDZ; public class classePrincipaleExosSDZ { public static void main(String[] args) { Animal[] tabOiseaux= new Animal[2]; Oiseau oiseau1=new Oiseau(); tabOiseaux[0]=oiseau1; ((Oiseau)tabOiseaux[0]).chante(); } }


Dans chaque classe, java créé une classe publique "Class" que l'on peut consulter pour trouver les méthodes (String.class.getMethods()), la superclasse String.class.getSuperclass()...

A noter également hashCode qui renvoit un identifiant unique pour un objet selon les éléments de comparaisons mis en place avec equals. System.out.print(oiseau1.hashCode()); renvoie 1808253012.

Un autre mot clef, final qui empêche une méthode d'être redéfinie ou qui empêche un objet d'avoir des enfants.
public final string methode(){

Enfin, on peut faire en sorte qu'une classe ne puisse pas être instanciée (c'est à dire interdire de créer un objet directement à partir de cette classe) avec le mot clef abstract.
Ainsi, pour éviter de donner le droit de faire Animal animal1=new Animal(); il faut définir la classe comme ceci :abstract class Animal {. On pourra toujours avoir des classe qui héritent de Animal, et des objets créés à partir de ces classes-ci.
On peut mettre des méthodes abstraites dans les classes abstraites : abstract void methode();, qu'il faudra nécessairement redéfinir dans les classes enfants.

Redéfinition


Quand une classe, qui hérite d'une autre, a la même méthode que la classe mère.
Si on met le mot clef final à une méthode, cela empêche sa redéfinition. public final void afficher()
Si on le met à un attribut, cela signifie que c'est une constante.

Interface


Une interface ressemble à une classe abstraite, mais elle n'a pas de variable et (jusqu'à java 8) n'avait pas d'implémentation de méthodes (que des méthodes abstraites).

On dit parfois qu'une interface est un contrat, car utiliser cette interface oblige à implémenter les méthodes listées par l'interface. Par contre, elle ne force pas à utiliser des attributs. Si on met un attribut dans une interface, celui-ci devient une constante pour toutes les classes l'utilisant.
On peut y mettre des méthodes statiques.

C'est très utile pour le travail en équipe, où une personne va écrire une interface avec des méthodes et où une seconde personne devra réaliser une classe en incluant obligatoirement ces méthodes.


Une classe peut avoir plusieurs interfaces : public class ClasseA extends ClasseB implements interface1,interface2 {

Exemple avec une classe perroquet liée avec l'interface ComportementPerroquet :

package exosSDZ; public interface ComportementPerroquet { void parle(); }



package exosSDZ; public class Perroquet extends Oiseau implements ComportementPerroquet { //obligatoire d'avoir cette méthode car elle est dans l'interface public void parle(){ System.out.print("Coco veut un gateau"); } public Perroquet(){ super(); } public Perroquet(String saisieEspece, String saisieChant, int saisieTaille){ super(saisieEspece,saisieChant,saisieTaille); } }



package exosSDZ; public class classePrincipaleExosSDZ { public static void main(String[] args) { Perroquet ara1=new Perroquet("Ara", "Kaww kaww",15); ara1.chante(); ara1.parle(); } }


Si on met un attribut dans une interface, il devient une constante partagée par toutes les instances des objets utilisant cette interface.


Depuis java 8, on peut mettre des méthodes dans les interfaces.

Une interface peut aussi être utilisé dans les fonctions.

On peut ainsi avoir une classe mamifère avec une interface animal public class Mamifere extends Animal.

Puis : predateur.manger(Animal)

C'est très pratique, cela donne des sortes de "types" à nos objets.

On pourra également mettre le mamifere dans un objet animal : Animal a=new Mamifere();

Depuis Java 8, les interfaces peuvent fournir des méthodes par défaut, qui seront utilisables directement par la classe qui implémente l'interface :

public interface Grogneur { default public void grogner(){ System.out.println("grr"); } }


Si une classe hérite de deux interfaces, comportant chacune une méthode du même nom, la classe ne saura pas quelle implémentation par défaut utiliser : il faudra donc redéfinir la méthode dans la classe.

super.maMethode() ne fonctionnera pas car il ne saura pas quelle méthode utiliser (les deux interfaces implémentés ont toutes les deux une méthode de ce nom). Dans la classe fille, il faudra faire ~Grogneur.super.grogner(); pour indiquer qu'on veut utiliser l'implémentation de la classe Grogneur.


Exceptions



Ce sont des objets de la classe Exception, spécialisés dans le traitement des erreurs. Soit cesera la JVM qui va générer automatiquement ces objets, soit c'est le développeur qui va générer un objet de type exception.

La clase Exception hérite de Throwable. Il y a différentes sous-classes d'Exception comme InterruptedException, ArithmeticException, NullPointerException... (diagramme)

Il faudra l'attraper avec un "catch" pour faire des traitements personnalisés, sinon, le programme s'arrêtera. On peut catch une Exception, une ArithmeticException...
Si on catch "Exception", on attrapera toutes les exceptions possibles. Si on essaie d'attraper une sous exception spécifique A, on peut oublier d'attraper l'exception B, ce qui fera planter le programme si B est levée. Il est donc plus sécurisant d'attraper "Exception", mais on ne sait pas trop ce qu'on attrape.


try { //instruction qu'on essaye d'exécuter } catch (ArithmeticException e) { //erreur sur laquelle on veut réagir (e contient le code de l'erreur) System.out.println(e); //on affiche le code de l'erreur } finally{ //action effectuée dans tous les cas } //le programme continue


On peut également propager l'exception d'un bout de code à l'autre avec "Throws". Ci-dessous la classe main appelle une méthode saisiId qui propage une exception (throws Exception) :


package testExceptions; import java.util.Scanner; public class ClassePrincipale { static int b=0; public static void main(String[] args) { int id; try { id=saisiId(); } catch (Exception e) { // TODO Auto-generated catch block System.out.println("Erreur : merci d'écrire un nombre."); } } public static int saisiId() throws Exception{ //on va propager l'exception générée par la JVM Scanner s=new Scanner(System.in); int id=s.nextInt(); return id; }


Une bonne pratique consiste à propager ses exceptions jusqu'au plus haut niveau possible et de toutes les traiter au même endroit. On peut mettre plusieurs catch à la suite pour attraper différentes exceptions.
Exemple :


/* Appelle le menu et récupère les erreurs. * Se relance lui-même en cas d'exception. */ public void lancementMenu(){ try{ this.menu(); } catch (StackOverflowError e){ System.out.println(Message.ErreurDepassementPile); this.lancementMenu(); } catch (NullPointerException e){ System.out.println(Message.ErreurObjetInexistant); this.lancementMenu(); } catch (PersistenceException e){ System.out.println(Message.ErreurDeConnectionBase); this.lancementMenu(); } catch (NumberFormatException IME){ //un parseInt qui s'est mal passé System.out.println(Message.ErreurMerciEntrerChiffre); this.lancementMenu(); } catch(Exception e){ System.out.println("*ERREUR NON PRISE EN CHARGE*"); e.printStackTrace(); this.lancementMenu(); //System.out.println("J'ai attrapé une exception !"); } }



On peut générer nous même des exceptions. Par exemple, si on demandeà l'utilisateur de saisir un nombre entre 1 et 10 mais qu'il saisi 11, la JVM ne pourra pas détecter qu'il y a une Exception, c'est à nous de la lever en écrivant throw new Exception(); :

public static void saisiIntervale() throws Exception{ //on va propager l'exception Scanner s=new Scanner(System.in); int id=s.nextInt(); if(id<0||id>10){ throw new Exception("lol"); //créer une nouvelle exception } }



Bien penser à : utiliser des exceptions pour la gestion des erreurs (sauf si ce sont des erreurs métiers, par exemple retirer de l'argent à un compte qui est à 0 ne pose pas de problème au point de vu informatique mais pose problème au niveau d'une banque donc erreur métier), attraper un maximum d'exception pour éviter les crashs, centraliser la gestions des exceptions avec des throws qui les remontent, réfléchir à la cause dans le catch (attraper Exception ou une sous-exception ?).

On peut également créer des classes d'exceptions personnalisées qu'on va réutiliser d'un programme à l'autre.


package testExceptions; public class ExceptionPerso extends Exception{ @Override public String getMessage(){ return super.getMessage()+" mon message perso"; } }


Enumération



Une énumération est une sorte de classe où on construit directement les objets. Elle doivent être dans leur propre fichier, comme une classe ou une interface. A utiliser lorsqu'on a besoin d'énumérer une petite liste de constante (à la différence d'un tableau, on ne risque pas de les modifier).

Un exemple simple (printemps, ete automne et hiver sont donc des objets, instances de :

package exosSDZ; public enum Saison { printemps, ete, automne, hiver; }


Puis, pour l'utiliser :

package exosSDZ; public class classePrincipaleExosSDZ { public static void main(String[] args) { System.out.print(Saison.automne); } }


On peut aussi mettre des attributs, des méthodes et des constructeurs dans les enums. Ci-dessous un exemple un peu plus complexe pour gérer le texte des erreurs dans une seule classe :

package traitement; public enum Message { ErreurDeConnectionBase ("*ERREUR :* Problème de connexion à la base de donnée."), ErreurEntrezChoix ("*ERREUR :* Merci d'entrer un chiffre correspondant à un des choix proposés."), ErreurAucuneBanque ("*ERREUR :* Il n'existe aucune banque dans la base de donnée."), ErreurMerciEntrerChiffre ("*ERREUR :* Merci d'entrer un chiffre."), ErreurBanqueIntrouvable ("*ERREUR :* Cette banque est introuvable."); private String texte = ""; //Constructeur Message(String texte){ this.texte = texte; } public String toString(){ return texte; } }


On l'appelle :

package traitement; public class classePrincipaleExosSDZ { public static void main(String[] args) { try{ Interface.saisie(); } catch (NumberFormatException IME){ //un parseInt qui s'est mal passé System.out.println(Message.ErreurMerciEntrerChiffre); this.lancementMenu(); } } }


Ce qui affiche "*ERREUR :* Merci d'entrer un chiffre correspondant à un des choix proposés." en résultat.

Chaque élément de l'enum est une variable statique vers un objet instancié.

Par exemple, la ligne deviendra ErreurDeConnectionBase ("*ERREUR :* Problème de connexion à la base de donnée.") équivaut à public static final Message ErreurDeConnectionBase = new Message("*ERREUR :* Problème de connexion à la base de donnée.");.



Bonne refs sur les enums :
http://www.kdgregory.com/index.php?page=java.enum
http://blog.xebia.fr/2008/07/23/enumerations-utilisation-avancee/
http://www.jmdoudoux.fr/java/dej/chap-jdk1.5.htm#jdk1.5-8

Package



En java les class, interfaces et enum sont regroupées dans des packages, qui sont des sortes de dossiers. Un package facilite la réutilisation de plusieurs classes sur d'autres programmes.
A noter que si on utilise pas le mot clef public (ni private, ni protected, ce qui revient à utiliser default), les variables et les méthodes ne sont visibles qu'à l'intérieur du package.

Collection d'objets


On peut mettre les objets dans des tableaux (à condition de respecter le type d'objet) ou dans des structures spécialement adaptées pour ça qui sont plus lentes mais plus souple. Par exemple, un ArrayList peut être redimensionné après sa création (ce qui prend beaucoup de mémoire) et permet de stocker n'importe quel type d'objet.

On peut utiliser une boucle ordinaire avec i puis monArrayList.get(i)

Pour remplacer un élément par un autre dans un ArrayList: MonArrayList.set(MonArrayList.indexOf(employe1),employe2);

On peut aussi utiliser un itérateur pour parcourir les collections :

package exosSDZ; import java.util.ArrayList; import java.util.ListIterator; public class classePrincipaleExosSDZ { public static void main(String[] args) { ArrayList demoArrayList = new ArrayList(); demoArrayList.add(356); demoArrayList.add("Chaîne de caractère."); //ITERATEUR ListIterator interateur = demoArrayList.listIterator(); while(interateur.hasNext()){ System.out.println(interateur.next()); } } }


Depuis java 5, on peut utiliser une boucle de type "for each" qui revient au même que d'utiliser un itérateur :

package exosSDZ; import java.util.ArrayList; import java.util.ListIterator; public class classePrincipaleExosSDZ { public static void main(String[] args) { ArrayList demoArrayList = new ArrayList(); demoArrayList.add(356); demoArrayList.add("Chaîne de caractère."); for(Object valeur : demoArrayList){ System.out.println(valeur); } }


Il est beaucoup plus rapide d'utiliser un "for each" comme ci-dessus que de chercher ce que contient tableau[1], puis tableau[2]... avec une boucle avec un "i".

Outre l'ArrayList, il existe aussi les LinkedList organisées de façon différentes : chaque élément de la LinkedList pointe vers l'élément suivant.Ainsi, il est plus lent d'ajouter un élément vers le milieu d'une LinkedList que pour une ArrayList, mais par contre il sera plus rapide d'ajouter des éléments au début ou à la fin d'une LinkedList.

Si on stocke toujours le même type de donnée et qu'on a pas besoin de redimensionner le tableau après sa déclaration (on peut faire une déclaration avec une variable : tableau[var]), privilégier les tableaux pour des raisons de performance.

Il existe également des Set, ne stockant qu'une fois un objet dans toute la collection (HashSet demoArrayList = new HashSet();)

Enfin, pour les tableaux associatif (clef renvoie valeur), il existe les map (Hashtable et HashMap).


package exosSDZ; import java.util.Hashtable; public class classePrincipaleExosSDZ { public static void main(String[] args) { Hashtable demoHashTable = new Hashtable(); demoHashTable.put("taille", 45); demoHashTable.put("EffetsVisuels",true); demoHashTable.put("PageDemarrage", "http://www.site.com"); System.out.println(demoHashTable.get("taille")); System.out.println(demoHashTable.get("EffetsVisuels")); System.out.println(demoHashTable.get("PageDemarrage")); } }


Généricité



Permet de faire une classe qui accepte plusieurs types. Au lieu d'utiliser des types de variables précis dans sa classe (public class Oiseau extends Animal {private String chant;......), on va indiquer des types variables.

Exemple :

package exosSDZ; public class TestGenericite<a,b>{ private a valeur; private b valeur2; public TestGenericite(a varA, b varB){ valeur=varA; } public a retourneValeurA(){ return valeur; } }


Puis :

package exosSDZ; import java.util.Hashtable; public class classePrincipaleExosSDZ { public static void main(String[] args) { //la classe marche avec un string et un entier, ATTENTION il faut préciser "integer" et non pas écrire "int" TestGenericite<String,Integer> ObjetTestGenericite=new TestGenericite<String,Integer>("chaine",2); System.out.println(ObjetTestGenericite.retourneValeurA()); //également avec un entier et un float TestGenericite<Integer,Double> ObjetTestGenericite2=new TestGenericite<Integer,Double>(2,2.3); System.out.print(ObjetTestGenericite2.retourneValeurA()); } }


Les classes s'occupant de collection d'objets (ArrayList...) utilisent ce principe. Ainsi, on peut forcer une collection à ne contenir que des integers :

ArrayList<Integer> demoArrayList = new ArrayList<Integer>(); demoArrayList.add(356); demoArrayList.add(2);


Fonctionne (on précise qu'on veut des animaux mais on stocke des oiseaux) :

package exosSDZ; import java.util.ArrayList; public class classePrincipaleExosSDZ { public static void main(String[] args) { ArrayList<Animal> demoArrayList = new ArrayList<Animal>(); Oiseau oiseau1=new Oiseau(); demoArrayList.add(oiseau1); } }


Fonctionne :


package exosSDZ; import java.io.File; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Hashtable; public class classePrincipaleExosSDZ { public static void main(String[] args) { ArrayList<Animal> demoArrayList = new ArrayList<Animal>(); ArrayList<Animal> demoArrayList2 = new ArrayList<Animal>(); //les 2 tableaux deviennt les mêmes (???!!) demoArrayList=demoArrayList2; Oiseau oiseau1=new Oiseau(); demoArrayList.add(oiseau1); for(Object valeur : demoArrayList2){ System.out.println(valeur); } } }


Mais on ne pourrait pas faire demoArrayList=demoArrayList2; si demoArrayList et demoArrayList2 n'étaient pas du même type, même des types enfants :

package exosSDZ; import java.io.File; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Hashtable; public class classePrincipaleExosSDZ { public static void main(String[] args) { ArrayList<Animal> demoArrayList = new ArrayList<Animal>(); ArrayList<Oiseau> demoArrayList2 = new ArrayList<Oiseau>(); demoArrayList=demoArrayList2; //ne FONCTIONNE PAS } }


A moins de faire ArrayList demoArrayList = new ArrayList();, ou bien en utilisant "?" ArrayList<?> demoArrayList = new ArrayList<Animal>(); pour accepter tous les types ou bien en acceptant les types "héritant de..." avec ArrayList<? extends Animal> demoArrayList = new ArrayList<Animal>();. On peut aussi utiliser ArrayList<? extends Animal> demoArrayList = new ArrayList<Animal>();
On peut aussi faire l'inverse : au lieu d'autoriser les classes hérités, on peut autoriser les classes parents (superclasses) avec le mot clef "super" : ArrayList<? super Oiseau> demoArrayList



Bon tutoriel ici.

Autre exemple au cas où le précédent n'est pas clair, on ne pourra pas faire :

private static void testMethod(List<Object> params) { //on n'accepte que des objets pour cette méthode. for (Object o : params) { System.out.println(o); } public static void main(String[] args) { List<String> ls = new ArrayList<String>(); //une liste de string testMethod(ls); //cette methode ne fonctionnera pas, vu qu'on a dit qu'elle n'accepte que des objets }


Il faudrait private static void testMethod(List<?> params) { (on appelle "?" un wildcard (jocker).

Attention, en utilisant "?" ou "extends", la collection passe en lecture seule.

Réflexivité


La réflexion, c'est la capacité à lire et modifier un objet à l'exécution. En java, on ne peut que lire (pas possible de modifier l'implémentation des méthodes ou le nom des attributs pendant l'exécution du code).
monObjet.getClass() donne un objet de type class contenant les métadonnées (constructeurs disponibles pour cet objet, attributs, méthodes, type....).

Le code pour récupérer les méthodes de la classe Lon pourrait ressembler à ça :

Lion l=new Lion(); Class donnees=l.getClass(); Methode[] methods=donnees.getMethods();


On peut lancer une méthode ainsi récupérée avec evoke().

Généricité


Imaginons une classe "Box", avec comme attribut un objet :

public class Box { private Object object; public void set(Object object) { this.object = object; } public Object get() { return object; } }


On pourrait stocker n'importe quel objet :

Box b=new Box(); b.set("test"); //on stocke une string System.out.println(b.get()); b.set(19); //on stock un Integer System.out.println(b.get());


La généricité nous permettrait de limiter à un seul type d'objet par instance :

Box<String> b=new Box<>(); //on ne stockera que des Strings b.set("test"); System.out.println(b.get()); b.set(19); //interdit, on a dit qu'on ne stockait que des strings dans cette instance ! System.out.println(b.get());



Le code de Box deviendra :

public class Box<T> { private T object; public void set(T object) { this.object = object; } public Object get() { return object; } }


Threads


Dans une application, on peut avoir plusieurs processus qui sont exécutés sans se bloquer entre eux. On appelle ça des threads. Par exemple, on pourra avoir un thread qui charge la base de donnée et un qui réagit aux actions de l'utilisateur.
La méthode "main" est un thread. Tous les Threads implémentent l'interface "Runnable".

2 façons de faire :
-soit "MonThread extends Thread" (par contre il ne peut pas hériter d'autre chose).Pour le lancer il suffira de faire MonTread monThread=new MonTread(); puis MonThread.start();. On implémente la classe runnable indirectement (car on hérite de Thread qui l'implémente lui même).
-soit "MonTread implements runnable", ce qui nous laisse la possibilité de le faire hériter d'autre chose mais un peu plus compliqué car pour le lancer il faudra faire :
Thread t=new Thread(new monThread());.

On trouvera tout le temps une méthode run(), contenant le traitement quand le code est actif. Pour lancer le run, il faudra faire maClasseImplementantRunnable.start().
On utilisait autrefois MonThread.sleep(), MonThread.resume() etc, mais ces méthodes sont dépréciées. Préférer Thread.interrupt, wait, notify.

Un problème très courant avec les threads : quand plusieurs processus vont accéder au même objet, ils risquent de se gêner les uns les autres. Il faut un mécanisme de gestion de la concurrence avec l'utilisation du mot réservé "synchronised" couplé à un système de gestion de la file d'attente (pour éviter que les threads essaient d'accéder en permanence à la ressource).
En swing il n'y a pas depolitique de sécurité vis à vis des threads. Par contre en Android, un thread ne peut pas manipuler la vue directement, sauf le thread déclaré comme contrôleur de l'activité.On aura donc vue->controleur activity<-handler<-thread.

Voir exercice "le diner de philosophe" pour s'entraîner à l'utilisation des threads.

Connexion BDD


On va être dans l'utilisation de librairie fournies en standard.
JDBC : Java database connectivité.

Le schéma suivant peut être utilisé :
Appli---JDBC(interface pour se connecter à la base)---Driver JDBC (fourni par éditeur SGBD)---SGBD


Problèmes :
-on utilise directement le langage du SGBD avec lequel on travaille, ce qui implique qu'on devra changer le code si on change de SGBD.
-on reçoit des collections non-typées, ce qui implique reconstruction des objets dans l'appli, ce qui demande pas mal de traitements.

Pour résoudre ces problèmes on peut ajouter une librairie intermédiaire de type JPA (Java Persistence API), qui fait parti d'une catégorie de produits proposant une technique de programmation "ORM" (Object Relational Mapping - on travaille sur sa base de données en utilisant des objets qui seront convertis par l'ORM).

Tous les grands frameworks (swing, Zend et Symphony en php) ont des ORM, du code intermédiaire qui nous permet d'éviter d'écrire directement des requêtes en SQL : l'idée est d'écrire du code de manipulation d'objets qui sera retranscris en requête par l'ORM.

Il existe différentes implémentations de JPA (qui est un ensemble de spécifications), notamment "Eclipse Link" pour Eclipse. Il existe aussi Hibernate,Toplink... ce sont des frameworks qui permettent de travailler en ORM.

Le schéma peut ressembler à :
Appli---JPA (EclipseLink ou Hibernate pour générer des entités model)---JDBC---Driver---SGBD

JPA va renvoyer des objets à notre application, ce qui permet de les traiter plus facilement.

Une application JPA fonctionne avec un fichier persistence.xml qui contient tout le paramétrage pour accéder à la base.

Une entité, une représentation sous forme de classes deséléments BDD, est du code JAVA précédé par des annotations (par ex, si on a une table client, on aura @entity pour indiquer que c'est une table, @primary pour la clef primaire...). On peut les écrire ou les générer. On aura un objet de type Entity Manager. L'entity manager nous donne des méthodes java pour utiliser la base. Par exemple, une methode.persist(object) pourra être équivalent à un "insert". C'est souvent la solution la plus pertinente, mais d'autres existent.

On peut aussi :
-mélanger le code java et le SQL (mauvaise idée car le code n'est pas portable d'une SGBD à l'autre).
-utiliser du code java et du JPQL(une variante orienté objet de SQL pour Java, simplifiant beaucoup les requêtes, exemple listant les noms des clients chez qui a eu lieu une intervention sur un produit supérieur à 300e : select i.codeclt.nomclt FROM Intervention i where i.produit.prix>300e, au lieu de faire plusieurs join on utilise l'objet pour récupérer l'attribut prix de l'objet produit de l'objet intervention).
-utiliser API criteria, du code Java se rapprochant très fortement de SQL (avec des méthodes pour faire select, where... les lignes de codes sont parfois très longues et difficile à lire avec cette techniques).


Un exemple de connexion très basique utilisant uniquement JDBC (clic droit sur projet, build path, add library, Connectivity Driver et prendre le driver de votre BDD) :

package testJDBCsimple; import java.sql.* public class ClassePrincipale { public static void main(String[] args) { Connection con; try { con = DriverManager.getConnection("jdbc:mysql://localhost:3306/mabase", "root", "monmotdepasse"); Statement stmt = con.createStatement(); ResultSet rset = stmt.executeQuery("SELECT * FROM MATABLE"); while ( rset.next() ) { System.out.println(rset.getString("MONCHAMP")); } rset.close(); stmt.close(); con.close(); } catch (Exception ex) { ex.printStackTrace(); } } }


Configuration d'Eclipse pour JPA


Vous aurez besoin d'Eclipse JEE, du driver JPA pour votre SGBD, d'une connexion internet pour récupérer Eclipse Link.
Il existe dans Eclipse JEE (absent dans la version standard) un système de "facets" pour configurer son projet et notamment mettre en place JPA.

-Créer un projet Java (et non pas un projet JPA).
-Clic droit sur le projet à gauche, propriétés, selectionner "Project Facets".
-Cocher JPA dans la liste, puis, cliquer sur le lien en dessous "Further Configuration Available".
-(Si cette étape n'a pas été faite dans Eclipse/ si Eclipse Link n'est pas coché :) choisissez plateforme Eclipse Link puis cliquez sur le bouton télécharger (une disquette avec une flèche vers le bas).
-Puis add connection, choisissez votre SGBD et entrez un nom qui identifiera votre connexion dans Eclipse.
-(Si cette étape n'a pas été faite dans Eclipse) Tout en haut de la fenêtre indiquant "Specify a profile", on vous demandera un driver. Cliquer sur le + pour l'ajouter et allez chercher le .jar avec votre driver (mysql-connector-java-5.1.39-bin.jar par exemple), supprimer éventuellement les chemins non-valides, confirmez.
-Mettez le nom de votre base et son chemin (remplacer "database").
-Cliquez sur "test connection" qui devra être au vert, puis fermer cette fenêtre en appuyant sur finish.
-Dans la fenêtre "Modify JPA Faceted Project" où vous avez téléchargé Eclipse, ne pas oublier de cocher "Add driver library to build path", puis "ok" 2 fois.

Vous devez maintenant avoir plusieurs éléments dans le dossier de votre projet (à gauche) :EcliseLink, le driver JDBC et, dans le dossier src/META-INF, un fichier persistence.xml qui contient les informations de configuration.
Double cliquez sur persistence.xml.

-Dans l'onglet "connection", la transaction type est à changer pour "Resource Local" si vous n'utilisez pas de serveur d'application.
-Cliquez sur "Populate from connection" et choisissez la connexion appropriée.

Vous pouvez gérer les connections créées dans Eclipse dans la fenêtre "Data Source Explorer".

Connexion à une BDD via Netbeans+Glassfish


3 éléments: dans le menu "service" (windows->services) on doit avoir une connexion de netbeans à la base, le serveur d'application (glassfish) doit être connecté à la base, le fichier persistence.xml doit être correct pour que notre appli puisse s'y connecter. Attention, Glassfish n'acceptera pas un utilisateur sans mot de passe.
Dans Netbeans, menu service, clic droit sur database, new connection, choisir le connecteur pour mysql, entrez le nom de la base, l'utilisateur, le mot de passe. Notez l'url de connexion (exemple : jdbc:mysql://localhost:3306/maBaseDeDonnees), elle sera utile par la suite.
Créez un nouveau projet Java web en sélectionnant le serveur d'application que vous souhaitez utiliser (glassfish, payara...) et vos packages (controleur, data access, modele par exemple...). Clic droit sur le package modele, new other, persistence, entity class from database. Data source, new data source, sélectionnez la connection créée précédemment, donnez le nom JNDI de votre choix et ajoutez les tables. Dans la fenêtre suivante vous pouvez décocher "Generate JAXB Annotations". Dans la fenêtre suivant celle-ci, Collection Type peut être mis en java.util.List (il y a 2 autres choix proposés : java.util.collection, la classe mère, qui n'ordonne pas les collections et ne propose donc pas d'accéder directement à l'élément n°X directement et java.util.set qui interdit les doublons).

Un fichier glassfish-resources doit s'être créé, contenant les informations qu'utilisera glassfish pour se connecter à la base. N'oubliez pas de récupérer le nom de l'unité de persistence pour votre EntityManagerFactory dans le fichier persistence.xml qui s'est également créé.

A partir d'ici, le projet est fonctionnel mais n'est pas enregistré dans glassfih. A chaque déploiement de l'appli, le fichier glassfish-resources seralu par le serveur pour créer un pool de connexion (servent à diminuer la charge sur le serveur en répartissant les requêtes entre les clients). Il va falloir configurer Glassfish pour qu'il se connecte à la base, puis nous configurerons notre projet pour qu'il passe par Glassfish.

Première chose, Glassfish aura besoin du driver pour mysql.Vous devrez mettre mysql-connector-java-5.1.39-bin.jar dans /home/aka/glassfish-4.1.1/glassfish/domains/domain1/lib.

Dans l'onglet services, servers, redémarrez Glassfish et connectez vous sur l'interface d'admin (localhost:4848) pour accéder à l'interface d'administration. Ressources, JDBC connection pool, new, donnez le nom que vous voulez, ressource type java.sql.Driver, database MySql.
Puis next, ping enabled pour tester la connexion, et tout en bas renseignez les champs user,password et url avec l'url récupérée plus haut (sinon, clic droit propriété sur votre connexion dans Netbeans pour la récupérer). On doit avoir un ping succeded à la fin. Si le ping échoue, vérifiez que le serveur mysql a été mis au bon endroit (essayez un autre domaine/lib). Vérifier que le driver classname est bien com.mysql.jdbc.Driver, que votre serveur mysql soit démarré, que les autorisations soient bonnes.
On ne peut pas encore accéder au pool, il faut lui donner un nom de ressource. JDBC ressources, new. Le nom doit être de forme jdbc/nomdemabase et sélectionnez le pool que vous venez de créer.

Dans le persistence.xml de votre projet, cochez "use Java transaction API", et le nom de la ressource que vous venez de créer. Il faudra avoir dans le xml "transaction-type="JTA" et "jdbc/votreBase".

Si vous avez un plantage dans la console d'administration de glassfish (un bug connu empêche la création de bases sur glassfish 4.1.1),vous pouvez installer dans netbeans un serveur alternatif à glassfish comme Paraya (clicdroit sur servers, add new, glassfish et sélectionner le jar de paraya).


Exemple application DAO


DAO : Direct Access Object.

Un patron de conception avec 2 aspects importants : une classe abstraite supportant la généricité et souvent une interface pour verrouiller les méthodes. On aura forcément une classe mère abstraite (DAO), un héritage et la classe mère supportera la généricité.

DAO1Banque---->(Abstract)DAO (peut implémenter une interface)<-----DAOClient


On va construire une application dans 3packages ; modèle (qui contiendra les entités), accès (qui contiendra les méthodes pour communiquer avec la BDD) et traitement (qui contiendra les méthodes d'interactions -le menu où l'utilisateur va saisir les opérations etc).

Dans le modèle, on va avoir des clsses qui vont représenter la structure de la base. Par exemple, si on a une table client et une banque banque, on aura une classe Banque et une Client.
On peut générer le code des entités : clic droit sur le projet->JPA Tools->Generate entities from tables (cliquer sur l'icône connexion pour charger les entités si besoin)->next->next->Key Generator->identity.

Des fichiers .java seront générés avec des annotations : @Entity pour indiquer qu'il s'agit d'une table, @Id pour indiquer la clef primaire, @ManyToOne indique la relation, @JoinColumn(name="id_compte_debite") pour indiquer la colonne de la clef étrangère... Il faudra pour un modèle au moins un constructeur sans argument et des getters et setters sur l'ensemble des attributs. les noms des attributs correspondent aux champs de la table (ils peuvent être changés si besoin avec une annotation). Ces entités seront gérées par un objet EntityManager.

Si on souhaite donner à une class une variable qui ne sera pas persistée en base, on peut utiliser l'annotation "@Transient".

Dans la couche access, créer la classe DAO, abstract, la rendre générique en mettant entre chevrons une lettre qui va correspondre à une type d'objet (ne pas mettre un type déjà existant). On va créer un entity manager qui va gérer les entités, pour le créer, on aura besoin d'un objet EntityManagerFactory.

Puis, on va créer des méthodes permettant de manipuler les données de l'entité qu'on va envoyer à l'entity manager.


package acces; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; public abstract class DAO <T>{ protected EntityManager em; //pour gérer les objets public DAO(){ //on ne peut pas directement créer un entity manager, il faut une factory EntityManagerFactory emf=Persistence.createEntityManagerFactory("exoCDI_banqueJPA"); //on trouve le nom dans le xml, <persistence-unit name="exoCDI_banqueJPA".... em=emf.createEntityManager(); //création de l'entity manager } public void ecrire(T objetEntite) //utilisation de la généricité (pour récupérer des entités client, banque, compte... etc. { em.getTransaction().begin(); //debute la transaction, càd verrouille l'enregistrement em.persist(objetEntite); //enverra une commande "insert into" em.getTransaction().commit();//valide la transaction (enlève le verrou sur l'enregistrement en base) } public void maj(T objetEntite) { em.getTransaction().begin(); em.merge(objetEntite); em.getTransaction().commit(); } public void supprimer(T objetEntite) { em.getTransaction().begin(); try { T objettrouve=em.merge(objetEntite); //il met à jour l'objet et le retourne em.remove(objettrouve); //supprime l'objet de la base em.getTransaction().commit(); } catch (Exception e) { em.getTransaction().rollback(); //erreur, annulation de la transaction } } }


em.getTransaction().begin(); sert à verrouiller l'enregistrement dans la BDD pour qu'on soit le seul à pouvoir le modifier. En Java EE, on n'a pas à gérer les transactions, c'est souvent fait par le serveur d'application.

On va maintenant faire une classe DAOclient et une DAObanque, vu que DAO est une classe abstraite, c'est le seul moyen d'avoir accès à un Entity Manager et aux méthodes créées précédemment :


package acces; import java.util.List; import modele.Banque; //ne pas oublier pour pouvoir lire ailleurs que dans notre package "acces" public class DaoBanque extends Dao<Banque> { //une méthode pour renvoyer toutes les banques dans une liste public List<Banque> liretous() { return em.createNativeQuery("select * from banque",Client.class).getResultList(); } public Banque rechercherparId(int id) //cherche avec la clef { return em.find(Banque.class, id); } }


Puis, dans la classe demo :

package traitement; import acces.*; import modele.*; public class Demo { public static void main(String args[]){ Banque b=new Banque(); b.setNom("Banque de Paris"); DaoBanque daoBanque=new DaoBanque(); daoBanque.ecrire(b); } }


Exemple de DAO générique (on lui donne la classe à renvoyer donc plus besoin de créer des classes qui en héritent) :

package modele.access; import java.lang.reflect.ParameterizedType; import java.util.List; import java.util.Observable; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; import javax.persistence.Query; public class Dao<T> extends Observable{ //utilisation de la généricité private EntityManager em; //pour gérer les objets private Class classeARenvoyer; //pour savoir quelle classe doit être renvoyée public Dao(Class classeARenvoyer){ //on ne peut pas directement créer un entity manager, il faut une factory EntityManagerFactory emf=Persistence.createEntityManagerFactory("examJavaPU"); //on trouve le nom dans le fichier persistence.xml em=emf.createEntityManager(); //création de l'entity manager this.classeARenvoyer=classeARenvoyer; //assignation de la classe que l'on va renvoyer } public void ecrire(T objetEntite) { em.getTransaction().begin(); //debute la transaction, càd verrouille l'enregistrement em.persist(objetEntite); //enverra une commande SQL "insert into..." em.getTransaction().commit();//valide la transaction (enlève le verrou sur l'enregistrement en base) } public void maj(T objetEntite) { em.getTransaction().begin(); //debute la transaction, càd verrouille l'enregistrement em.merge(objetEntite); em.getTransaction().commit();//valide la transaction (enlève le verrou sur l'enregistrement en base) this.setChanged(); this.notifyObservers(objetEntite); } public void rafraichir(T objetEntite) //par exemple, on a ajouté un enseignant à un objet Classe //il faut rafraichir l'enseignant pour que la classe apparaisse dans sa liste de classe { em.getTransaction().begin(); //debute la transaction, càd verrouille l'enregistrement em.refresh(objetEntite); em.getTransaction().commit();//valide la transaction (enlève le verrou sur l'enregistrement en base) } public T rechercherparId(int id) //cherche avec la clef { return (T)em.find(classeARenvoyer, id); } public List<T> liretous() { return em.createNativeQuery("select * from "+classeARenvoyer.getSimpleName().toLowerCase(),classeARenvoyer).getResultList(); } public List<T> renvoyerListeRechercheParChamp(String champ, String valeur){ return em.createNativeQuery("SELECT * FROM "+classeARenvoyer.getSimpleName().toLowerCase()+" where "+champ+" LIKE \"%"+valeur+"%\"", classeARenvoyer).getResultList(); } public void supprimer(T objetEntite) { em.getTransaction().begin(); try { T objettrouve=em.merge(objetEntite); //il met à jour l'objet et le retourne em.remove(objettrouve); //supprime l'objet de la base em.getTransaction().commit(); } catch (Exception e) { em.getTransaction().rollback(); //erreur, annulation de la transaction } } }

Modele-Vue-Controleur



MVC (modèle vue controleur) est un patron de conception qui peut être appliqué à java, php...

3 pakages :

-Modele : On y trouve des modèle (les "entités", par exemple des objets "étudiants"), qui représentent les données internes et effectue des calculs internes sur leurs attributs (une entité "commande" pourrait avoir une méthode pour calculer le prix total). Par exemple "nom/prenom/id" pour une classe étudiant, "mesure1/mesure2/taux" pour une classe convertisseur...
C'est le modèle qui va donner l'ordre automatiquement à la vue de se rafraichir en cas de modification (méthode notifyObservers). Hérite de "Observable".
On y inclue un sous-package accès avec les DAO.
On doit y ajouter un observer : mon_modele.addObserver(ce_qui_observe);
En général c'est en lien avec une base de donnée, mais il peut aussi être lié à un webservice par exemple.

-Vue : L'affichage, on a généralement une classe fenêtre qui initialise une JFrame, l'affiche, positionne les boutons.
Elle observe le modèle en implémentant "Observer", avec une méthode update qui récupère les infos du modèle et met à jour l'affichage.
Elle envoie des infos au contrôleur avec une méthode addActionListener (positionnée par exemple sur un bouton bouton.addActionListener(controleur)).

-Controleur : s'occupe des traitements, implémentant ActionListener pour qu'il puisse réagir à des actions dans la vue.
Modifie le modèle (avec des xxxx.set) et la vue (en changeant par exemple les panels).

Il faut faire des associations entre ces 3 packages :
-la vue doit avoir en attribut l'objet principal du controleur et celui du modèle.
-le controleur doit avoir en attribut l'objet de la vue (la fenêtre).

Il y a deux philosophie : la fenetre envoie les infos au controlleur (push) ou bien le controlleur récupère des infos de la fenêtre (pull).


En MVC la vue connaît le modèle, en MVP (Modèle vue présentation, comme dans Primefaces) non.

Exemples divers




package exoCDI_login; import java.util.Scanner; public class classeExoCDI_login { public static void main(String[] args) { Scanner scan_saisie = new Scanner (System.in); System.out.println("Entrez votre mail :"); String mail = scan_saisie.next(); //s'il y a bien un @ au moins en 2e lettre && que le dernier point est après le dernier arobas if(mail.indexOf("@")>2 && mail.lastIndexOf(".")>6 && mail.length()>9){ System.out.println("Votre adresse mail est juste !"); }else{ System.out.println("Votre adresse mail est fausse !"); } } }


En regex :

package exoCDI_loginEreg; import java.util.Scanner; public class ExoCDI_loginEregClasse { public static void main(String[] args) { Scanner scan_saisie = new Scanner (System.in); System.out.println("Entrez votre mail :"); String mail = scan_saisie.next(); //s'il y a bien un @ au moins en 2e lettre && que le dernier point est après le dernier arobas if(mail.matches("\\w{2,}\\w+\\@\\w{2,}[.].*")){ System.out.println("Votre adresse mail est juste !"); }else{ System.out.println("Votre adresse mail est fausse !"); } } }


Convertisseur Celcius Fahrenheit :

package exoCDI_convertisseurCelciusFahrenheit; import java.util.Scanner; public class ClassePrincipaleConvertisseurCF { public static void main(String[] args) { int a=1; int b=++a; System.out.println(a+","+b); boolean quitter=false; while (quitter==false) { System.out.println("===================================="); System.out.println( "Entrez la température suivie de son unité (ex : 25.5C) pour la convertir (ou q pour quitter)."); Scanner scan_saisie = new Scanner(System.in); String temp = scan_saisie.next(); if(temp.contains(",")){ temp=temp.replace(",", "."); }for(i=1;i<=9;i++){ if (temp.endsWith("C") || temp.endsWith("c")) { float celcius = Float.parseFloat(temp.substring(0, temp.length() - 1)); System.out.println(" " + celcius + " Celcius fait "+(celcius * 9 / 5 + 32)+" Fahrenheit."); } else if (temp.endsWith("F") || temp.endsWith("f")) { float Fahrenheit = Float.parseFloat(temp.substring(0, temp.length() - 1)); System.out.println(" " + Fahrenheit + " Celcius fait "+((Fahrenheit - 32) * 5 / 9)+" Celcius."); } else if (temp.endsWith("Q") || temp.endsWith("q")) { quitter=true; } else { System.out.println( "ERREUR : Tapez la température suivie de C pour celcius et F pour Fahrenheit (exemples : 23C ou 33F)"); } } } }


Morpion 2 joueurs :

package exoCDI_morpion; import java.util.Scanner; public class ClassePrincipale_exoCDImorpion { public static void main(String[] args) { int[] casesPlateau = new int[10]; boolean finDuJeu=false; int i=0; int numJoueur=1; int positionDemandee=0; for(i=1;i<=9;i++){ casesPlateau[i]=0; } while (finDuJeu==false) { System.out.println("|_|A|B|C|"); for(i=1;i<=9;i++){ if(i==1){ System.out.print("|1"); } System.out.print("|"); if(casesPlateau[i]==1){ System.out.print("X"); }else if(casesPlateau[i]==2){ System.out.print("O"); }else{ System.out.print(" "); } if(i==3){ System.out.println("|"); System.out.print("|2"); } if(i==6){ System.out.println("|"); System.out.print("|3"); } } System.out.println("|"); positionDemandee=1; System.out.println("JOUEUR "+numJoueur+" : entrez les coordonnées désirée (ex : A1)."); Scanner scan_saisie = new Scanner(System.in); String saisie = scan_saisie.next(); //A=65, B=66... positionDemandee+=((int)saisie.charAt(0)-65); if(saisie.substring(1,2).equals("1")){ //c'est bon }else if(saisie.substring(1,2).equals("2")){ positionDemandee+=3; }else if(saisie.substring(1,2).equals("3")){ positionDemandee+=6; }else{ System.out.println("Erreur : Merci de saisir un chiffre entre 1 et 3."); } if(casesPlateau[positionDemandee]==0){ casesPlateau[positionDemandee]=numJoueur; numJoueur = (numJoueur==1)?2:1; }else{ System.out.println("Erreur : Cette case est déjà occupée !"); } for(i=1;i<=2;i++){ //on teste pour les deux joueurs if(casesPlateau[1]==i && casesPlateau[2]==i && casesPlateau[3]==i || casesPlateau[4]==i && casesPlateau[5]==i && casesPlateau[6]==i || casesPlateau[7]==i && casesPlateau[8]==i && casesPlateau[9]==i){ System.out.println("**************************\r\nVictoire du joueur "+i+" !"); finDuJeu=true; } if(casesPlateau[1]==i && casesPlateau[4]==i && casesPlateau[7]==i || casesPlateau[2]==i && casesPlateau[5]==i && casesPlateau[8]==i || casesPlateau[3]==i && casesPlateau[6]==i && casesPlateau[9]==i){ System.out.println("**************************\r\nVictoire du joueur "+i+" !"); finDuJeu=true; } if(casesPlateau[1]==i && casesPlateau[5]==i && casesPlateau[9]==i || casesPlateau[3]==i && casesPlateau[5]==i && casesPlateau[7]==i){ System.out.println("**************************\r\nVictoire du joueur "+i+" !"); finDuJeu=true; } } } } }



//factorielle d'un nombre package exoParistech1; public class Main { public static void main(String[] args) { int factorielle=1; int i; //pour chaque argument... for(String argument:args){ factorielle=1; //on essaie... try{ //..de multiplier en boucle l'argument en descendant d'un chiffre for(i=Integer.parseInt(argument);i>1;i=i-1){ factorielle*=i; } } catch(NumberFormatException e){ System.out.println("Merci d'entrer un nombre."); System.exit(0); } System.out.println("Factorielle de "+argument+"="+factorielle); } } }


Un bout de code pour faire continuer l'utilisateur s'il appuie sur entrée :

System.out.println("Appuyez sur entrée pour continuer...."); try{System.in.read();} catch(Exception e){}


Création d'une fenêtre Swing :


package testBoutonsSDZ; import javax.swing.JFrame; import javax.swing.JPanel; public class ClassePrincipale { public static void main(String[] args) { JFrame fenetrePrincipale=new JFrame(); fenetrePrincipale.setTitle(""); fenetrePrincipale.setSize(400, 300); fenetrePrincipale.setLocationRelativeTo(null); //Termine le programme si on ferme fenetrePrincipale.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //Il faut créer un JPanelpour manipuler des éléments à l'intérieur JPanel panneau=new JPanel(); //on associe le JPanel à notre fenêtre fenetrePrincipale.setContentPane(panneau); //on affiche la fenêtre (à la fin pour que son contenu soit rafraichit fenetrePrincipale.setVisible(true); } }


Avec des éléments (voir http://circe.univ-fcomte.fr/docs/java/tutorial.20060804/ui/features/components.html pour les éléments):

package testSwing; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.JSlider; import javax.swing.KeyStroke; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.util.ArrayList; import java.util.Arrays; import javax.swing.ButtonGroup; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JTable; import javax.swing.JOptionPane; import javax.swing.table.DefaultTableModel; public class ClassePrincipale { public static void main(String[] args) { //1 - Créé la fenêtre JFrame fenetrePrincipale=new JFrame(); fenetrePrincipale.setTitle(""); fenetrePrincipale.setSize(400, 300); fenetrePrincipale.setLocationRelativeTo(null); fenetrePrincipale.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//termine le programme si on ferme //2 - Il faut créer un JPanelpour manipuler des éléments à l'intérieur JPanel panneau=new JPanel(); //3 - on associe le JPanel à notre fenêtre fenetrePrincipale.setContentPane(panneau); //4 - ajout d'un layout au Jpanel FlowLayout disposition = new FlowLayout(); panneau.setLayout(disposition); //5 - créé et positionne les éléments dans le Jpanel JLabel texte = new JLabel("Très impressionant"); panneau.add(texte); int dialogResult = JOptionPane.showConfirmDialog (fenetre, "Voulez-vous vraiment SUPPRIMER ce client ?","information",JOptionPane.YES_NO_OPTION); if(dialogResult == JOptionPane.YES_OPTION){ daoClient.supprimer(fenetre.getPanneauModifClient().getClient()); fenetre.afficheSelectionClient(); } JButton bouton = new JButton("Cliquez-moi"); panneau.add(bouton); JCheckBox caseACocher=new JCheckBox("Cochez-moi"); panneau.add(caseACocher); ArrayList<String> liste=new ArrayList<String>(Arrays.asList("Choisissez-moi !","Non, moi !", "Moi moi moi !")); JComboBox menuDeroulant=new JComboBox(liste.toArray()); panneau.add(menuDeroulant); JMenuBar menuBar = new JMenuBar(); JMenu menu = new JMenu("Un Menu"); menu.setMnemonic(KeyEvent.VK_A); menu.getAccessibleContext().setAccessibleDescription( "Description de ce menu"); menuBar.add(menu); JMenuItem menuItem = new JMenuItem("Un sous menu texte",KeyEvent.VK_T); menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_1, ActionEvent.ALT_MASK)); menuItem.getAccessibleContext().setAccessibleDescription("Description de ce sous menu"); menu.add(menuItem); fenetrePrincipale.setJMenuBar(menuBar); //message d'alerte JOptionPane.showMessageDialog(null, "Erreur de saisie"); JList elementListe=new JList(liste.toArray()); panneau.add(elementListe); JRadioButton boutonRadio=new JRadioButton("Tickez-moi"); JRadioButton boutonRadio2=new JRadioButton("Et moi en même temps !"); ButtonGroup group = new ButtonGroup(); group.add(boutonRadio); group.add(boutonRadio2); panneau.add(boutonRadio); panneau.add(boutonRadio2); JSlider curseurDefilement=new JSlider(1,0,10,5); panneau.add(curseurDefilement); DefaultTableModel modeleTable; modeleTable = new DefaultTableModel(); JTableNotes = new JTable(modeleTable); JTableNotes.setModel(modeleTable);//puis quandon aura les données modeleTable.addRow(new Object[]{... String produitRecherche = JOptionPane.showInputDialog(fenetre, "Entrez l'id du produit"); //popup avec champ de texte //6 - affiche la fenêtre fenetrePrincipale.setVisible(true); } }


A priori, une JTable stocke ce qu'elle affiche (des Strings, des Integers...), on ne peut pas stocker des objets tels que des étudiants, des produits...

Dans la première colonne de la table, on aura par exemple un identifiant de l'objet :

int numLigneSelectionnee = fenetre.getPanneauListeCategorie().getjTableProduits().getSelectedRow(); int numColonneselectionnee = fenetre.getPanneauListeCategorie().getjTableProduits().getSelectedColumn(); String numDeLObjet=String.valueOf(fenetre.getPanneauListeCategorie().getjTableProduits().getModel().getValueAt(0, numLigneSelectionnee));



Une classe héritante de JPanel isolée (bonne pratique), qui utilise la librairie design grid layout, facilitant la création d'interface :

package vue; import java.awt.GridLayout; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextField; import modele.UnitesMesures; import net.java.dev.designgridlayout.DesignGridLayout; public class PanelResultat extends JPanel{ private Fenetre fenetre; private JLabel labelResultat; private JButton boutonRetour; public PanelResultat(Fenetre fenetre){ this.fenetre=fenetre; //4 - ajout d'un layout au Jpanel DesignGridLayout layout = new DesignGridLayout(this); //5 - créé et positionne les éléments dans le Jpanel labelResultat=new JLabel("ee"); boutonRetour=new JButton("Retour"); layout.row().grid().add(labelResultat); layout.row().grid().empty().add(boutonRetour); } }


Avec un JScrollPane :

package vue; import java.awt.Dimension; import java.util.ArrayList; import java.util.Arrays; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import net.java.dev.designgridlayout.DesignGridLayout; public class PanneauSelectionBanque extends JPanel { Fenetre fenetre; public PanneauSelectionBanque(Fenetre f){ this.fenetre=f; DesignGridLayout layout = new DesignGridLayout(this); //on liste les banques disponibles JList listeDeroulanteBanques=new JList(fenetre.getListBanques().toArray()); JScrollPane panelDeroulant=new JScrollPane(listeDeroulanteBanques); panelDeroulant.setPreferredSize(new Dimension(40, 0)); layout.row().grid().add(panelDeroulant); JButton boutonSelectionnerBanque = new JButton("Selectionner"); JButton boutonNouvelleBanque = new JButton("Créer une nouvelle banque"); layout.row().grid().add(boutonSelectionnerBanque); layout.row().grid().empty(); layout.row().grid().add(boutonNouvelleBanque); }


Si on a besoin de mettre à jour la JList, on peut utiliser un DefaultListModel (lorsqu'on mettra le modèle à jour la JList se mettra à jour aussi) :

listeBanque=new DefaultListModel(); for (int i=0; i<fenetre.getListBanques().toArray().length; i++) { listeBanque.addElement((Banque)fenetre.getListBanques().get(i)); } listeDeroulanteBanques=new JList<Banque>(listeBanque); listeDeroulanteBanques.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); JScrollPane panelDeroulant=new JScrollPane(listeDeroulanteBanques); panelDeroulant.setPreferredSize(new Dimension(40, 0));


C'est une bonne pratique d'avoir une fenêtre principale en tant que JFrame (public class FenetrePrincipale extends JFrame) et plusieurs panels différents qu'on peut changer de la façon suivante:

public Fenetre(Controleur controleur) { //code du constructeurs, différentes méthods etc... public void affichePanneauResultat(){ this.add(panneauResultat); this.revalidate(); this.repaint(); } public void afficheFormulaireDebut(){ this.add(panneauSaisie); this.revalidate(); this.repaint(); }


Ou même créer une fonction qui supprime le dernier panel utilisé et revalidate/repaint :

public void utilisePaneau(JPanel panneau){ if(dernierPanelUtilise!=null){ this.remove(dernierPanelUtilise); this.add(panneau); this.revalidate(); this.repaint(); dernierPanelUtilise=panneau; }else{ this.add(panneau); this.revalidate(); this.repaint(); dernierPanelUtilise=panneau; }



Pour l'aléatoire :

import java.util.Random; Random random = new Random(); random.nextInt(max - min + 1) + min


Timer:

package minuteur; import java.util.Timer; import java.util.TimerTask; //vu sur http://www.journaldunet.com/developpeur/tutoriel/jav/050623-java-repetition-timer-timertask.shtml public class RepetAction { Timer t; public RepetAction() { t = new Timer(); t.schedule(new MonAction(), 0, 1*1000); } class MonAction extends TimerTask { int nbrRepetitions = 3; public void run() { if (nbrRepetitions > 0) { System.out.println("Ca bosse dur!"); nbrRepetitions--; } else { System.out.println("Terminé!"); t.cancel(); } } } }


Puis évidemment dans le main : RepetAction verifHeureMin=new RepetAction();

Récupérer l'heure :

Calendar tempsActuel = Calendar.getInstance(); int heureActuelle = tempsActuel.get(Calendar.HOUR_OF_DAY);


Récupérer et formater la date :

import java.util.Date; //tout en haut du code, avec les imports import java.text.DateFormat; import java.text.SimpleDateFormat; //plus bas, dans la classe Date dateDuJour = new Date(); //renvoie la date du jour DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); //permet de donner un format à la date reçue dateFormat.format(dateDuJour); //renvoie un String de lafaçon donton aformaté plus haut (2016/10/08 14:34:25 par ex)


Parcourir une hashmap :

import java.util.Map.Entry; //import à faire en haut du code ! for(Entry<Reference, StockArticle> entry : listeArticles.entrySet()) { //on peut utiliser entry.getValue() et //entry.getKey() }


Pour utiliser Collections.sort(maCollection) afin de trier un objet personnalisé, il faut implémenter Comparable et surcharger la méthode compareTo :

public class Piece implements Comparable<Piece> //... //... public int compareTo(Piece p) { //tri par ordre décroissant if(this.getValeur()>p.getValeur()){ return -1; } if(this.getValeur()<p.getValeur()){ return 1; } return 0; }


Ne pas oublier qu'on peut comparer une date directement avec dateCommentaire.compareTo(o.dateCommentaire);

On peut aussi faire une "classe anonyme" (classe directement insérée dans une méthode) :

Collections.sort(listeClassement, new Comparator<Classement>() { //on classe la liste "listeClassement" //méthode anonyme @Override public int compare(Classement cl1, Classement cl2) { return cl2.getMoyenne().compareTo(cl1.getMoyenne()); //compareTo retourne 1, 0 ou -1 } });



Pour les tableaux, il suffit de faire Arrays.sort(monTableau);. Pour faire une copie : System.arraycopy(tableau1,0,copieTableau1,0,3);.

Exemple api criteria :

public List<Eleve> rechercherparNomAPICriteria(String nom) //cherche avec la clef { CriteriaBuilder constructeurRequete = em.getCriteriaBuilder(); CriteriaQuery<Eleve> requeteCriteria = constructeurRequete.createQuery(Eleve.class); Root<Eleve> c = requeteCriteria.from(Eleve.class); ParameterExpression<String> p = constructeurRequete.parameter(String.class); requeteCriteria.select(c).where(constructeurRequete.equal(c.get("nom"), nom)); Query requete = em.createQuery(requeteCriteria); return requete.getResultList(); }

JPQL


Java Persistent Query Language. Point commun avec SQL : la structure de la requête est similaire, elsmots réservés aussi (select, from where, group by, having...) à tel point qu'il est facile de les confondre. Contrairement à SQL, JPQL ne fait pas de requête sur la structure de la base mais sur le modèle objet.

Avantages de JPQL : il est universel (compatible toutes bases) et on garde une logique objet.

Un exemple select p from Produit p, où l'on voit que la majuscule est obligatoire car on interroge la classe.


package acces; import java.util.List; import model.Appartement; public class DaoAppartement extends Dao { public List<Appartement> listerAppartementsGeresParCarole() { return em.createQuery("select a.ref from Appartement a where a.representant.nomrep='Palege'",Appartement.class).getResultList(); } public List<Appartement> listerAppartementsGeresParCaroleAVillefranche() { return em.createQuery("select a.ref from Appartement a where a.representant.nomrep='Palege' AND a.client.villeclt='Villefranche'",Appartement.class).getResultList(); } public List<Object[]> renvoyerMoyennePrixDesAppartementsParSecteur(){ return em.createQuery("select a.secteur,AVG(a.pxvente) from Appartement a GROUP BY a.secteur",Object[].class).getResultList(); } public Long renvoyerNombreAppartementsALaSuperficieSupA80(){ return em.createQuery("select count(a) from Appartement a where a.superficie>80",java.lang.Long.class).getSingleResult(); } }


Pour récupérer les résultats d'un "group by", étant donné qu'on a pas de classe, on va avoir un tableau d'objets :

List<Object[]> lo=daoAppartement.renvoyerMoyennePrixDesAppartementsParSecteur(); for (Object[] tabObjet : lo) { System.out.println("Objet -->"+tabObjet[0]); System.out.println("Objet -->"+tabObjet[1]); }


Autre exemple de requêtes :
select p.designation,AVG(p.prix) from Produit p GROUP BY p.designation HAVING AVG(p.prix)>300

Convertir un BigDecimal en Double : java.math.BigDecimal bd1 = new java.math.BigDecimal(totalNotesGenerales/ListeResultatsEtudiant.size()); ou c.setMoyenneGenerale(BigDecimal.valueOf(totalNotesGenerales/ListeResultatsEtudiant.size()));.

MD5 :

//on vérifie le mot de passe String plaintext = motDePasse; MessageDigest m = null; try { m = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException ex) { Logger.getLogger(ControlConnexion.class.getName()).log(Level.SEVERE, null, ex); } m.reset(); m.update(plaintext.getBytes()); byte[] digest = m.digest(); BigInteger bigInt = new BigInteger(1,digest); String hashtext = bigInt.toString(16); // Now we need to zero pad it if you actually want the full 32 chars. while(hashtext.length() < 32 ){ hashtext = "0"+hashtext; }

Exemple MVC



import vue.Fenetre; public class Demarrage { public static void main(String[] args){ Fenetre fenetre=new Fenetre(); } }



package vue; import java.awt.BorderLayout; import java.awt.FlowLayout; import java.awt.GridLayout; import java.util.Observable; import java.util.Observer; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JTextField; import controleur.ControleFenetre; import modele.Convertisseur; public class Fenetre implements Observer { private JFrame fenetrePrincipale; private JLabel labelKm, labelMiles; private JTextField champTexteKm; private JTextField champTexteMiles; private JButton boutonreset, boutonConvertir; private ControleFenetre controleFenetre; private Convertisseur convertisseur; public Fenetre(){ convertisseur=new Convertisseur(); controleFenetre=new ControleFenetre(this,convertisseur); //association entre vue et controleur //1 - Créé la fenêtre JFrame fenetrePrincipale=new JFrame(); fenetrePrincipale.setTitle(""); fenetrePrincipale.setSize(400, 300); fenetrePrincipale.setLocationRelativeTo(null); fenetrePrincipale.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //2 - Il faut créer un JPanelpour manipuler des éléments à l'intérieur JPanel panneau=new JPanel(); //3 - on associe le JPanel à notre fenêtre fenetrePrincipale.setContentPane(panneau); //4 - ajout d'un layout au Jpanel BorderLayout dispositionNSEO = new BorderLayout(); panneau.setLayout(dispositionNSEO); //5 - créé et positionne les éléments dans le Jpanel JPanel JPanelCentre=new JPanel(); panneau.add(JPanelCentre,dispositionNSEO.CENTER); FlowLayout dispositionGrille = new FlowLayout(); JPanelCentre.setLayout(dispositionGrille); labelKm=new JLabel("Km "); JPanelCentre.add(labelKm); champTexteKm=new JTextField(5); JPanelCentre.add(champTexteKm); labelMiles=new JLabel("Miles "); JPanelCentre.add(labelMiles); champTexteMiles=new JTextField(5); JPanelCentre.add(champTexteMiles); JPanel JPanelSud=new JPanel(); panneau.add(JPanelSud,dispositionNSEO.SOUTH); JButton boutonReset=new JButton("RESET"); JPanelSud.add(boutonReset); JButton boutonConvertir=new JButton("CONVERTIR"); JPanelSud.add(boutonConvertir); convertisseur.addObserver(this); fenetrePrincipale.setVisible(true); boutonConvertir.addActionListener(controleFenetre); boutonReset.addActionListener(controleFenetre); } @Override public void update(Observable o, Object arg) { //réagit à un changement annoncé dans Observable o //arg argument éventuellement transmis this.lireModeleEtAppliqueModifDansAffichage(); } public void remiseAZeroChamps(){ champTexteKm.setText(""); champTexteMiles.setText(""); } public JTextField getChampTexteKm() { return champTexteKm; } public void lireModeleEtAppliqueModifDansAffichage(){ //on récupère la valeur de km et met à jour l'affichage //au cas où il y ait eu un reset champTexteKm.setText(String.valueOf((double)Math.round(convertisseur.getKm()*100)/100)); //on met à jour la valeur du champ miles champTexteMiles.setText(String.valueOf((double)Math.round(convertisseur.getMiles()*100)/100)); } public void afficheErreurSaisie(){ JOptionPane.showMessageDialog(null, "Erreur de saisie"); } }



package controleur; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JFrame; import controleur.ControleFenetre; import modele.Convertisseur; import vue.Fenetre; public class ControleFenetre implements ActionListener{ private Convertisseur convertisseur; private Fenetre fenetre; public ControleFenetre(Fenetre f, Convertisseur c){ this.fenetre=f; //association entre vue et controleur this.convertisseur=c; //association entre modele et controleur } @Override public void actionPerformed(ActionEvent e) { //throws Exception marche pas ici if(e.getActionCommand().equals("RESET")){ //le controleur reset les valeurs l'objet convertisseur (puis cet objet notifiera la vue) convertisseur.setKm(0); convertisseur.setMiles(0); } if(e.getActionCommand().equals("CONVERTIR")){ try { //change l'attribut miles de l'objet convertisseur par la valeur du champ km*0.62 convertisseur.convertir(Double.parseDouble(fenetre.getChampTexteKm().getText())); } catch (NumberFormatException e1) { // TODO Auto-generated catch block fenetre.afficheErreurSaisie(); } } } }



package modele; import java.util.Observable; public class Convertisseur extends Observable{ private double km; private double miles; public void setMiles(double miles) { this.miles = miles; this.setChanged(); //un boolean passe de true à false dans observable this.notifyObservers(); //on dit à la fenêtre qu'on a changé } public void convertir(double km){ this.setMiles(km*0.62); this.setKm(km); } public double getKm() { return km; } public void setKm(double km) { this.km=km; this.setChanged(); //un boolean passe de true à false dans observable this.notifyObservers(); //on dit à la fenêtre qu'on a changé } public double getMiles() { return miles; } }


Un message d'erreur qui se ferme tout seul après X secondes :

public void genererMsgBoxErreurFermetureAuto(String message){ JOptionPane pane = new JOptionPane(message, JOptionPane.ERROR_MESSAGE); MsgBoxErreurFermetureAuto = pane.createDialog(this, "Erreur"); ComponentAdapter adapteurPourFermeture=new ComponentAdapter() { @Override public void componentShown(ComponentEvent e) { super.componentShown(e); t = new Timer(10000,controleur); t.setActionCommand("finTimerMsgBoxErreur"); t.start(); } }; MsgBoxErreurFermetureAuto.addComponentListener(adapteurPourFermeture); MsgBoxErreurFermetureAuto.setVisible(true); }


Pareil mais avec des choix :

JButton boutonChangerMdp=new JButton("Changer le mot de passe"); boutonChangerMdp.setActionCommand("demandeChgrMdp"); boutonChangerMdp.addActionListener(controleur); JButton boutonFermer=new JButton("Fermer le programme"); boutonFermer.setActionCommand("demandeFermeture"); boutonFermer.addActionListener(controleur); JButton[] boutonsChoix = {boutonChangerMdp,boutonFermer}; JLabel messagePopUpProposeChangementMdp=new JLabel("Le serveur refuse la connexion avec les informations fournies dans config.properties. Essayez de changer le mot de passe. Sinon, supprimez le fichier config.properties et relancez l'application."); JLabel messageFermeturePopUpProposeChangmntMdp=new JLabel("(Fermeture automatique dans quelques secondes...)"); JLabel[] labels={messagePopUpProposeChangementMdp,messageFermeturePopUpProposeChangmntMdp}; JOptionPane msg = new JOptionPane(labels, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_OPTION,null,boutonsChoix,boutonsChoix[1]); dialogPopUpProposeChangementMdp = msg.createDialog("Accès refusé par le serveur SQL"); dialogPopUpProposeChangementMdp.setDefaultCloseOperation(JDialog.HIDE_ON_CLOSE); ComponentAdapter adapteurPourFermeture=new ComponentAdapter() { @Override public void componentShown(ComponentEvent e) { super.componentShown(e); t = new Timer(10000,controleur); t.setActionCommand("fenetreDemandantMdpArriveFinTimer"); t.start(); } }; dialogPopUpProposeChangementMdp.addComponentListener(adapteurPourFermeture); dialogPopUpProposeChangementMdp.setVisible(true);



public String afficherFenetreChangementRetournerMdp(){ timerAvantFermeture.stop(); JPasswordField pf = new JPasswordField(); int okCxl = JOptionPane.showConfirmDialog(null, pf, controleur.getGp().getUtilisateur()+"@"+controleur.getGp().getCheminbase(), JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE); String mdp=""; if (okCxl == JOptionPane.OK_OPTION) { mdp = new String(pf.getPassword()); }else{ System.out.println("clic sur croix"); } return mdp; }


Passer un string d'un encodage à l'autre :

String maChaine=new String(monAutreChaine.getBytes(StandardCharsets.ISO-8859-1),StandardCharsets.UTF_8);



Liens


http://perso.telecom-paristech.fr/~hudry/coursJava/exercices/

Java EE


Java Enterprise Edition, un ensemble de spécifications de composants pour développer des applications destinées aux entreprises (applications sur serveur, distribuées, à grande échelle). Permet notamment de développer des pages web.

On aura un client qui communiquera avec un serveur web (apache par exemple) qui communiquera avec un serveur d'application (glassfish ou JBoss) fonctionnant avec du Java.

Dans le serveur d'application se trouver le servlet, un objet qui redirige les requêtes vers la vue.
La vue est un fichier xhtml constitué d'un mélange de HTML, de JSTL (Java server page Standard Tag Library) et d'EL (Expression language). On va utiliser Primefaces, qui permet de faire plus de choses comme utiliser un gestionnaire de placement et donne des composants comme un calendrier, une galerie photos, un menu déroulant....

Plusieurs frameworks disponibles pour la vue : JSP (le premier, obsolète, où on avait des bouts de java dans la vue), JSF (recommandé) et des frameworks tiers comme Struts (abandonné depuis l'apparition de JSF), seam, spring.

Exemple :<b><h:outputText id="affichageVar1" value="#{controleur.var1}"/></b>
Les balises html sont <b>, "h:outputText" est du JSTL, "#{controlJeu.nom}" du EL.
La vue est gérée par un conteneur web.

Notion de Bean : le controleur, l'EJB et les entités doivent respecter la spécification "Bean" en respectant des règles.
-Tous les attributs doivent être privés.
-Il doit exister au moins un constructeur sans argument (car les beans sont instanciés automatiquement).
-Il doit y avoir des getters et setters sur l'ensemble des attributs.

Le controleur est un bean de type CDI (Contexts and Dependency Injection). On écrit la classe de cette objet, mais il sera créé créé par le serveur d'application (si notre classe s'appelle ControleurPage, un objet "controleurPage" sera créé au lancement de l'appli).

Par dessus ça se trouve la couche accès, fonctionnant avec des EJB (Entreprise Java Bean, ressemble beaucoup aux CDI mais en plus évolués, fait transaction, remoting, asynchrone, Timer -appel d'une fonction à intervale régulier, Security), DAO et encore au dessus les modèles avec les entités JPA (géré par glassfish). Les EJB sont géréspar un conteneur d'EJB.

Outre les applications web, une implémentation de Java EE doit également être capable de gérer 4 types de composants (Applet web -le navigateur du client sera le conteneur d'applet, Applications -applications java exécutée chez le client, et des Applications d'Entreprise exécutée dans le conteneur d'EJB).

On utilisera JAVA EE+EJB3+JPA2.2+JSF2.2+Primefaces.

Netbeans est livré en standard avec glassfish et l'intégration est bien faite pour le manipuler. Glassfish serveur est un serveur d'application très puissant utilisé par des gros sites.

Pour démarrer : new project -> java web -> web application. Nommer le projet, puis le server est glassfish server, java version 7. Le framework sera donc Java Server Faces et dans l'onglet components ont ajoutera primefaces. Dans le panneau de gauche, dans le dossier Web Pages/WEB-INF, rajouter un fichier vierges "beans.xml" (obligatoire pour respecter la spécification JEE7).

Deux fichiers xhtml sont créés par défaut.

Tout en haut, les infos html pour le navigateur. En dessous, les librairies utilisées (si on veut utiliser les éléments Primefaces dans une page il faudra mettre p="http://primefaces.org/ui). Les éléments sont préfixés selon la librairie. Par exemple <p:layout fullPage="true"> est un élément primefaces car il commence par "p".

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui"> <f:view contentType="text/html"> <h:head> <f:facet name="first"> <meta content='text/html; charset=UTF-8' http-equiv="Content-Type"/> <title>PrimeFaces</title> </f:facet> </h:head> <h:body> <p:layout fullPage="true">


Ce code sera modifié en code html compréhensible par le navigateur par la suite.

Pour mettre en place un site dynamique, il faut un CDI/ Clic droit, nouveau package "controleur", nouvelle classe ControleurIndex. Il faut déclarer qu'il s'agit d'un CDI avec l'annotation "@Named" (et "import javax.inject.Named;"). On pourra alors utiliser ses attibuts (par exemple un string chaineTest qui vaudra "bonjour" avec les getters et setters appropriés) dans notre page xhtml avec l'Expression Language #{controleurIndex.chaineTest} (la première lettre du controleur en minuscule car il s'agit d'un objet).


(autres éléments, header, layout, etc...) <h:body> <p:layout fullPage="true"> <p:layoutUnit position="north" size="100" resizable="true" closable="true" collapsible="true"> Voici la valeur de l'attribut chaineTest du controleur : #{controlJeu.chaineTest} Header </p:layoutUnit> (autres éléments, balises fermantes, etc...)


Après avoir cliqué sur "clean and build project" puis sur "run project", une page html s'affichera et devra afficher l'attribut.


Le fichier web.xml définit (entre autres) quels fichiers sont interprétés par java.

Ci-dessous, n'importe quel fichier ("*") présent dans "/faces/" seront interprétés :

<servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>/faces/*</url-pattern> </servlet-mapping>


Si un de vos fichier xhtml n'est pas interprêté, vous pouvez modifier web.xml de la façon suivante :

<servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>*.xhtml</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.xhtml</welcome-file> </welcome-file-list>


C'est aussi dans web.xml qu'on installe des thèmes, précise la page d'accueil, les pages d'erreur, etc.

<context-param> <param-name>primefaces.THEME</param-name> <param-value>le-frog</param-value> </context-param> <welcome-file-list> <welcome-file>index.xhtml</welcome-file> </welcome-file-list> <error-page> <exception-type>javax.faces.application.ProtectedViewException</exception-type> <location>/connexion.xhtml</location> </error-page>




Les objets sont créés dès qu'ils sont appelés dans une vue (par exemple dès qu'on appellera #{controleur.variable} le contrôleur sera créé). La durée de vie des objets managés est indiqué avec une annotation.
-@RequestScoped : durée de vie plus longue, temps d'une requête.
-@ViewScoped : tant qu'on est dans la même page, l'objet n'est pas détruit (javax.faces.view.ViewScoped).
-@SessionScoped : tant qu'on ne ferme pas le navigateur l'objet n'est pas détruit (javax.enterprise.context.SessionScoped).
-@ApplicationScoped : tant que le site existe, l'objet n'est pas détruit. On peut ajouter @ManagedBean(eager=true) pour que l'objet soit créé dès le lancement de l'appli (et pas dès que l'objet est appelé).

Attention ViewScoped, SessionScoped et ApplicationScoped nécessitent un Serializable.

Exemple :

@Named @SessionScoped public class ControlConnexion implements Serializable { //reste de la classe


Pour chaque client une instance du contrôleur est dédié. Si on annote le CDI avec @Singleton, on aura un seul CDI pour tous les clients, avec une durée de vie illimitée.

Exemple de page simple (affichera l'id, le nom, le prénom de l'id renseigné quand on clique sur lire ou bien le créé si on clique sur écrire) :

<p:inputText value="#{controlIndex.etudiant.id}"/> <p:inputText value="#{controlIndex.etudiant.nom}"/> <p:inputText value="#{controlIndex.etudiant.prenom}"/> <p:commandButton value="Lire" id="ajax" update="panel" actionListener="#{controlIndex.lire()}"/> <p:commandButton value="Ajouter" update="panel" actionListener="#{controlIndex.ajouter()}"/>


Avec son contrôleur :

@Named @RequestScoped public class ControlIndex implements Serializable { @Inject private DaoEtudiant daoEtudiant; private Etudiant etudiant; public void lire(){ etudiant=daoEtudiant.rechercherparId(etudiant.getId()); } public void ajouter(){ daoEtudiant.ecrire(etudiant); } //ajouter les getters et setters ci dessous



Exemple de page d'index un peu complexe :

<?xml version='1.0' encoding='UTF-8' ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui"> <f:view contentType="text/html"> <h:head> <f:facet name="first"> <meta content='text/html; charset=UTF-8' http-equiv="Content-Type"/> <title>PrimeFaces</title> </f:facet> </h:head> <h:body> <h:form> <p:poll interval="1" update="panneauJoueur1,panneauJoueur2,panneauJoueur3,panneauJoueur4" /> </h:form> <p:dialog header="Nouveau joueur" id="popupNouveauJoueur" modal="true" widgetVar="popupNouveauJoueur" minHeight="40"> <h:form> <h:panelGrid columns="2" cellpadding="5"> <h:outputText id="champSaisieNom" value="Nom :"/><p:inputText value="#{controlJeu.nomSaisi}"/> <h:outputText id="champSaisiePrenom" value="Prenom :"/><p:inputText value="#{controlJeu.prenomSaisi}"/> <p:commandButton value="Valider" id="boutonValiderNouveauJoueur" update=":msg,:panneauJoueur1,:panneauJoueur2,:panneauJoueur3,:panneauJoueur4" onclick="PF('popupNouveauJoueur').hide();" actionListener="#{controlJeu.createTurn()}"/> </h:panelGrid> </h:form> </p:dialog> <p:layout fullPage="true"> <p:layoutUnit position="north" size="50" resizable="true" closable="true" collapsible="true"> <p:growl id="msg" showDetail="true" /> <h:form> <p:commandButton value="Ajouter un joueur" id="boutonNouveauJoueur" onclick="PF('popupNouveauJoueur').show()"/> <h:outputText id="infosMultijoueurs" value="Votre id : #{controlJeu.getId()}" /> </h:form> <br /> </p:layoutUnit> <p:layoutUnit position="center" id="panneauxJoueurs"> <h:panelGrid columns="2"> <p:panel header="Joueur 1 : #{controlJeu.tabTurn[0].joueur.prenom} #{controlJeu.tabTurn[0].joueur.nom}" closable="#{controlJeu.testerSiBoutonAppartientAuJoueur(0)}" id="panneauJoueur1" style="margin-bottom:20px" visible="#{controlJeu.rendered[0]}"> <p:ajax event="close" listener="#{controlJeu.reagirPanelFerme}" update=":msg" /> <h:panelGrid columns="2"> <h:form> <h:panelGrid columns="3"> <p:graphicImage id="imageDe1" value="img/#{controlJeu.tabImagesDes[0][0]}" /> <p:graphicImage id="imageDe2" value="img/#{controlJeu.tabImagesDes[0][1]}" /> <p:graphicImage id="imageDe3" value="img/#{controlJeu.tabImagesDes[0][2]}" /> <p:commandButton value="Lancer dé 1" id="BoutonLancerDe1" update="imageDe1,BoutonLancerDe1" rendered="#{controlJeu.afficherBouton(0)}" actionListener="#{controlJeu.appuiSurLancer(0,0)}" disabled="#{controlJeu.tabTurn[0].getDeLance(0)}"/> <p:commandButton value="Lancer dé 2" id="BoutonLancerDe2" update="imageDe2,BoutonLancerDe2" rendered="#{controlJeu.afficherBouton(0)}" actionListener="#{controlJeu.appuiSurLancer(0,1)}" disabled="#{controlJeu.tabTurn[0].getDeLance(1)}"/> <p:commandButton value="Lancer dé 3" id="BoutonLancerDe3" update="imageDe3,BoutonLancerDe3" rendered="#{controlJeu.afficherBouton(0)}" actionListener="#{controlJeu.appuiSurLancer(0,2)}" disabled="#{controlJeu.tabTurn[0].getDeLance(2)}"/> <p:commandButton value="Choisir" id="boutonChoisir" update="panneauJoueur1,msg" rendered="#{controlJeu.afficherBouton(0)}" actionListener="#{controlJeu.appuiSurChoisir(0)}" disabled="#{controlJeu.tabTurn[0].tourFini}"/> <p:commandButton value="Valider" id="boutonValider" update="panneauJoueur1,msg" rendered="#{controlJeu.afficherBouton(0)}" actionListener="#{controlJeu.appuiSurValider(0)}" disabled="#{!(controlJeu.tabTurn[0].isJoue())}"/> </h:panelGrid> </h:form> <p:panel header="Stats J1" style="min-height:600;"> Nombre de coups : #{controlJeu.tabTurn[0].calculNombreCoups()}<br /><br /> Score : #{controlJeu.tabTurn[0].score}<br /><br /> Pourcentage : #{(controlJeu.tabTurn[0].victoires/controlJeu.tabTurn[0].calculNombreCoups())*100}%<br /><br /> Nombre de victoires : #{controlJeu.tabTurn[0].victoires}<br /> </p:panel> </h:panelGrid> </p:panel> <p:panel header="Joueur 2 : #{controlJeu.tabTurn[1].joueur.prenom} #{controlJeu.tabTurn[1].joueur.nom}" closable="#{controlJeu.testerSiBoutonAppartientAuJoueur(1)}" id="panneauJoueur2" style="margin-bottom:20px" visible="#{controlJeu.rendered[1]}"> <p:ajax event="close" listener="#{controlJeu.reagirPanelFerme}" update=":msg" /> <h:panelGrid columns="2"> <h:form> <h:panelGrid columns="3"> <p:graphicImage id="imageDe1J2" value="img/#{controlJeu.tabImagesDes[1][0]}" /> <p:graphicImage id="imageDe2J2" value="img/#{controlJeu.tabImagesDes[1][1]}" /> <p:graphicImage id="imageDe3J2" value="img/#{controlJeu.tabImagesDes[1][2]}" /> <p:commandButton value="Lancer dé 1" id="BoutonLancerDe1J2" update="imageDe1J2,BoutonLancerDe1J2" rendered="#{controlJeu.afficherBouton(1)}" actionListener="#{controlJeu.appuiSurLancer(1,0)}" disabled="#{controlJeu.tabTurn[1].getDeLance(0)}"/> <p:commandButton value="Lancer dé 2" id="BoutonLancerDe2J2" update="imageDe2J2,BoutonLancerDe2J2" rendered="#{controlJeu.afficherBouton(1)}" actionListener="#{controlJeu.appuiSurLancer(1,1)}" disabled="#{controlJeu.tabTurn[1].getDeLance(1)}"/> <p:commandButton value="Lancer dé 3" id="BoutonLancerDe3J2" update="imageDe3J2,BoutonLancerDe3J2" rendered="#{controlJeu.afficherBouton(1)}" actionListener="#{controlJeu.appuiSurLancer(1,2)}" disabled="#{controlJeu.tabTurn[1].getDeLance(2)}"/> <p:commandButton value="Choisir" id="boutonChoisirJ2" update="panneauJoueur2,msg" rendered="#{controlJeu.afficherBouton(1)}" actionListener="#{controlJeu.appuiSurChoisir(1)}" disabled="#{controlJeu.tabTurn[1].tourFini}"/> <p:commandButton value="Valider" id="boutonValiderJ2" update="panneauJoueur2,msg" rendered="#{controlJeu.afficherBouton(1)}" actionListener="#{controlJeu.appuiSurValider(1)}" disabled="#{!(controlJeu.tabTurn[1].isJoue())}"/> </h:panelGrid> </h:form> <p:panel header="Stats J2" style="min-height:600;"> Nombre de coups : #{controlJeu.tabTurn[1].calculNombreCoups()}<br /><br /> Score : #{controlJeu.tabTurn[1].score}<br /><br /> Pourcentage : #{(controlJeu.tabTurn[1].victoires/controlJeu.tabTurn[1].calculNombreCoups())*100}%<br /><br /> Nombre de victoires : #{controlJeu.tabTurn[1].victoires}<br /> </p:panel> </h:panelGrid> </p:panel> </h:panelGrid> </p:layoutUnit> </p:layout> </h:body> </f:view> </html>


Récupère le contenu d'un champ et l'utilise en argument :

<h:form id="formulaireChat" > <p:inputText id="champText" value="#{controleurUtilisateur.textTemp}"/> <p:commandButton update="chat,formulaireChat" actionListener="#{controleurChat.ajouterMessage(controleurUtilisateur.utilisateur, param['formulaireChat:champText'])}"/> </h:form>


Dans certain cas, on voudra pouvoir utiliser un input html (et non pas un p:commandButton), il faudra utiliser bundle pour intégrer la fonction :

<input type="submit" value="#{bundle['controlConnexion.connexionRealm']}" />


Pour cibler un élément, on utilise "id". Il y a une notion de portée, par exemple pour cibler un élément a dans un form b, il faut faire ":a:b".

Pour diriger vers une autre page on peut donner le nom de la page en statique <h:commandButton value="Intitulé du lien" action="nomDeLaPageDeDestination (sansXHTML)" /> (on peut ajouter "?faces-redirect=true", ou bien de façon dynamique avec une fonction ou une variable <h:commandButton value="Intitulé du lien" action="#{controleur.fonctionQuiRenvoieUnString()}" />. Il vaut mieux que ça se fasse dynamiquement du côté contrôleur car il est généralement plus simple de modifier celui-ci que toutes les pages xhtml.

On peut aussi faire une redirection dans le code du contrôleur avec FacesContext.getCurrentInstance().getExternalContext().redirect("index.xhtml");. Attention, ça manque un peu de souplesse caron gère les url à la place de Java. Parfois il rajoutera des token derrière une url, de type "panier.xhtml?javax.faces.Token=bucEhCRemJmdo8SUgg%3D%3D" si on veut par exemple accéder à une page sécurisée.

FacesContext permet également d'exécuter les scripts depuis le java. Ci-dessous on affiche un pop-up avec une méthode javascript (show) :

RequestContext requestContext = RequestContext.getCurrentInstance(); FacesContext context=FacesContext.getCurrentInstance(); requestContext.execute("PF('dlg1').show()");


Chaque client aura sa propre représentation de la vue. Dès qu'il arrivera sur la page, un numéro de session lui sera attribué qu'on peut récupérer avec :

FacesContext fCtx = FacesContext.getCurrentInstance(); HttpSession session = (HttpSession) fCtx.getExternalContext().getSession(false);


Si les clients partagent des infos sur la vue (par exemple une variable en statique partagée par tous, ou un contrôleur en singleton où sont tous connectés les clients), les changements sur une vue ne se répercuteront pas tant que la page ne sera pas actualisée. Plusieurs solutions pour actualiser la vue :
-La balise <p:pool>, qui actualise en ajax deséléments de la page toutes les x secondes. <p:poll interval="1" update="panneauJoueur1,panneauJoueur2,panneauJoueur3,panneauJoueur4" />. Très simpleà mettre en place.

-La balise avec du code java, qui enverra un message à tous les clients. Elle ne fonctionnera pas avec toutes les balises.

-En java, on récupère l'id du client et on fait des demandes de mise à jour.

Le composant One menu : un composant pour afficher une liste déroulante. 2 façons de faire, statique ou à la main. Ci-dessous, le selectItem est statique alors que le selectItems est rempli par une liste :

<p:selectOneMenu id="choixCategories" value="#{controleur.numCategorieSelectionnee}" style="width:125px" onchange="submit()" valueChangeListener="#{controleur.maMethode()}"> <f:selectItem itemLabel="-- Choisissez une catégorie --" itemValue="" /> <f:selectItems itemValue="#{controleur.renvoyerNumCategorie(i)}" itemLabel="#{controleur.renvoyerSuperbe(i)}" var="i" value="#{controleur.daoCategorie.renvoyerToutes()}" /> </p:selectOneMenu>

onchange execute du javascript côté client. Ici, la méthode submit() est appelée pour envoyer le formulaire dès que le selectOneMenu est changé. Lorsque la valeur sera changée, valueChangeListener appellera maMethode().

On peut remplir le one menu avec des strings, des int... (puis utiliser la valeur pour faire une recherche en base) mais aussi simuler le remplissage avec des objets en utilisant un "converter".


import javax.faces.convert.Converter; ... @RequestScoped @Named public class ConverterEtudiant implements Converter { @Inject private DaoEtudiant daoe; @Override public Object getAsObject(FacesContext context, UIComponent component, String value) { //retourne un objet en fonction du champ html value (du num d'étudiant) return daoe.rechercher(Integer.parseInt(value)); } @Override public String getAsString(FacesContext context, UIComponent component, Object value) { //remplit le champ "value=" du select html avec le numéro d'étudiant if(value==null){ return ""; } return String.valueOf(((Etudiant)value).getId()); } }


Puis dans le select one menu :

<p:selectOneMenu value="#{cdiControl.e}" converter="#{converterEtudiant}"> <f:selectItems var="etudiant" itemLabel="#{etudiant.getNom()}" value="#{cdiControl.daoEtudiant.getAll()}" />



Autre exemple (en base on a une List "listeNomCategorie" contenant des noms de catégories type "Culture", "Histoire", "Poésie"...) :

<h:selectOneMenu id="categorieRecherche" value="#{controlRecherche.categorieRecherche}" required="true"> <f:selectItem itemLabel="Toutes catégories" itemValue="" /> <f:selectItems itemValue="#{nomCategorie}" var="nomCategorie" value="#{controlRecherche.listeNomCategorie}" /> </h:selectOneMenu>


Attention aux injections SQL ! Ci-dessous différents exemples montrant différentes façons de faire les requêtes :

package modele.access; import java.util.List; import javax.ejb.Stateless; import javax.persistence.Query; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.ParameterExpression; import javax.persistence.criteria.Root; import modele.Eleve; @Stateless public class DaoEleve extends Dao<Eleve> { //une méthode pour renvoyer toutes les banques dans une liste public List<Eleve> liretous() { return em.createNativeQuery("select * from eleve",Eleve.class).getResultList(); } public Eleve rechercherparId(int id) //cherche avec la clef { return em.find(Eleve.class, id); } public List<Eleve> rechercherparNom(String nom) //cherche avec la clef { //Danger d'injection SQL !! return em.createNativeQuery("select * from eleve where nom='"+nom+"'",Eleve.class).getResultList(); } // SQL natif //Avantage : connu et éprouvé //Inconvévient : Syntaxe peut varier légèrement selon le SGBD public List<Eleve> rechercherparNomParams(String nom) //cherche avec la clef { Query requete=em.createNativeQuery("select * from eleve where nom=?1",Eleve.class); requete.setParameter(1, nom); return requete.getResultList(); } // JPQL //Avantage : on reste purement en orienté objet //Inconvénient : difficile de passer des requêtes complexes public List<Eleve> rechercherparNomJPQL(String nom) //cherche avec la clef { Query requete=em.createQuery("select e from Eleve e where e.nom=:nom"); requete.setParameter("nom", nom); return requete.getResultList(); } // NamedQuery/NamedNativeQuery // -la syntaxe est légèrement vérifiée lors du démarrage de l'appli // -regroupées dans les classes les concernants public List<Eleve> rechercherparNomNamed(String nom) //cherche avec la clef { Query requete=em.createNamedQuery("Eleve.findByNom"); requete.setParameter("nom", nom); return requete.getResultList(); } // APICriteria //Avantage : les erreurs de requêtes sont vues au moment de la compilation //Inconvénient : légèrement moins lisible public List<Eleve> rechercherparNomAPICriteria(String nom) //cherche avec la clef { CriteriaBuilder constructeurRequete = em.getCriteriaBuilder(); CriteriaQuery<Eleve> requeteCriteria = constructeurRequete.createQuery(Eleve.class); Root<Eleve> c = requeteCriteria.from(Eleve.class); ParameterExpression<String> p = constructeurRequete.parameter(String.class); requeteCriteria.select(c).where(constructeurRequete.equal(c.get("nom"), nom)); Query requete = em.createQuery(requeteCriteria); return requete.getResultList(); } }


Composants JSF



<f:metadata> <f:viewAction action="#{controleur.afficherMsgConsole()}"/> </f:metadata>
pour lancer une action au chargement de la page. On peut également forcer un EJB Singleton à se lancer au démarrage de l'appli avec l'annotation @Startup.

Pour utiliser du css : <h:outputStylesheet library="css" name="style.css" />

Datatable editable :

<h:form id="mainForm"> <p:growl id="growl" /> <p:dataTable id="bookDataTable" value="#{controlIndex.listTest}" var="produit" editable="true"> <p:column headerText="ID" style="width : 200px ; font-size : 9px">#{produit.id}</p:column> <p:column headerText="Titre"> <p:cellEditor> <f:facet name="output">#{produit.modele}</f:facet> <f:facet name="input"><p:inputText value="#{produit.modele}" /></f:facet> </p:cellEditor> </p:column> <p:column headerText="Actions"><p:rowEditor /></p:column> <p:ajax event="rowEdit" update="bookDataTable :mainForm:growl" listener="#{controlEdition.onRowEdit}" /> </p:dataTable> </h:form>


Attention, si vous utilisez une méthode pour remplir la datatable (<p:dataTable value="#{controleur.maListeGeneree()}">), celle-ci pourra être appelée plusieurs fois. De manière générale, en JSF, un getter peut être appelé plusieurs fois même s'il n’apparaît qu'une seule fois sur la page.

Objets managés / Non-managés


Quand on fait un new() objet, ce dernier n'est pas managé. Par contre si on fait un em.find, c'est à dire qu'on demande à l'entity manager de créer un objet à partir d'éléments présents en base, celui-ci sera managé. Si on demande à un entity manager de manager un objet qui ne l'est pas (ou pas par lui), on peut se retrouver avec une erreur "java.lang.IllegalArgumentException: Entity not managed".
Le lazy loading ne fonctionnera que dans un état managé.

Problème, un EJB stateless (DaoProduit par exemple) s'adapte dynamiquement au nombre de clients, donc les entity managers apparaissent et disparaissent, laissant potentiellement des objets qui ne sont managés par personne. Il faudra utiliser un "merge" sur l'objet (DaoProduit.merge(produit),ce qui fusionne les attributs de l'objets avec ceux présents en base et renvoie un objet managé). Merge se base sur la clef primaire.

Pour transformer un objet managé en non managé (cas rare), utiliser "detach".

Validation en utilisant les annotations


Plusieurs types de validationpossible : en javascript (facileà déjouer), en faisant des échanges vue/contrôleur, ou dans le modèle directement.
Il faudratoujours sécuriser au niveau du modèle. C'est assez simple en JPA.

Une fois le modèle généré à partir des tables, on peut ajouter des annotations :

@Size(max = 32) @Column(name = "nom") @Pattern(regexp="^[a-zA-Z]+$",message="Champs Alphabétique") private String nom;


Lorsqu'on utilisera cette classe pour créer un objet, ses attrbuts devront respecter les annotations. Ainsi, il sera impossible de mettre un numéro dans l'attribut nom.

Faire remonter ces messages d'erreur est très simple avec Primefaces (penser à mettre à jour le formulaire pour que les messages s'affichent) :

Nom : <p:inputText id="nom" value="#{controlInscription.utilisateur.nom}" /> <p:message for="nom"/>


Si on essaie d'entrer un texte avec des chiffres dans le champ nom (ce qui va essayer d'attribuer cette valeur à l'attribut "nom" de notre utilisateur), la balise p:message affichera un message d'erreur "Champs Alphabétique" et empêchera la création de l'objet.

Sécurisation des pages


Plusieurs choix pour resttreindre l'accès à certaines pages de notre site :
-Avec du code dans la page (par eemple ViewAction, qui s'exécute avant le chargement de la page).

-ProtectedViews, très simple à mettre en oeuvre :il seraimpossible d'accéder à une page en tapant son url, mais on pourra toujours y accéder par un lien interne (un token sera généré pour nous permettre d'y accéder). Il suffit de créer un fichier faces-config.xml (clic droit, new, other...) et d'y mettre :


<?xml version='1.0' encoding='UTF-8'?> <faces-config version="2.2" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd"> <protected-views> <url-pattern>/protected/welcomePrimefaces.xhtml</url-pattern> </protected-views> </faces-config>


Dans web.xml, on peut mettre une page d'erreur personnalisée :

<error-page> <exception-type>javax.faces.application.ProtectedViewException</exception-type> <location>/connexion.xhtml</location> </error-page>


-Realm, qui passera par un paramétrage de l'application ET de glassfish, donc 2 niveaux de sécurité. Par contre, il s'appuie sur la base de donnée, à laquelleil faudra apporter une notion d'utilisateurs et de groupes de sécurité. En outre, il exige de respecter des spécifications précises pour les formulaires (pas plus de 2 champs...) qui peuvent être limitantes. Très bon tuto.

Il faudra au moins 2 tables : utilisateur (contenant un nom d'utilisateur et un mot de passe hashé) + une table utilisateur_groupe (contenant un nom d'utilisateur -clef étrangère vers l'utilisateur- et le nom d'un groupe). On ajoute souvent une troisième table "groupe" pour arriver au schéma "utilisateur*---*utilisateur_groupe*---*groupe".
Dans Glassfish, ajouter un realm dans server-config, security, realm. Mettre "jdbcRealm" dans JAAS Context, le chemin JNDI de votre base, le nom de la table contenant les utilisateurs, la colonne contenant le pseudo, la colonne contenant le mot de passe, la table utilisateur_groupe, dans "Group Name Column" la colonne contenant d'utilisateur_groupe contenant le nom des utilisateurs, dans "Assign Groups" la colonne contenant le nom des groupes. Dans "Assign Groups", indiquez le groupe par défaut pour tous les utilisateurs. Dans "Digest Algorithm" et "Password Encryption Algorithm" y renseigner l'algorithme utilisé pour le hash des mots de passe. Indiquez aussi le charset ("utf-8").

Il faut maintenant indiquer quels répertoires seront protégés et selon quelles conditions dans web.xml :

<login-config> <auth-method>BASIC</auth-method> <realm-name>realmTopCommerce2</realm-name> <form-login-config> </form-login-config> </login-config> <security-constraint> <web-resource-collection> <web-resource-name>Default secure pages</web-resource-name> <url-pattern>/protegeesParRealm/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>default_user_role</role-name> </auth-constraint> </security-constraint> <security-role> <role-name>default_user_role</role-name> </security-role>


Puis vous aurez besoin dans le même répertoire d'un fichier glassfish-web.xml contenant :

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE glassfish-web-app PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Servlet 3.0//EN" "http://glassfish.org/dtds/glassfish-web-app_3_0-1.dtd"> <glassfish-web-app error-url=""> <security-role-mapping> <role-name>default_user_role</role-name> <group-name>utilisateur</group-name> </security-role-mapping> </glassfish-web-app>


Si vous crééez un répertoire "protegeesParRealm" avec une page web, un pop up devrait s'afficher vous demandant vos infos.

On peut aussi s'identifier avec un fomulaire personnalisé en changeant "l'auth method" :

<auth-method>FORM</auth-method> <realm-name>realmTopCommerce2</realm-name> <form-login-config> <form-login-page>/connexion.xhtml</form-login-page> <form-error-page>/logonError.jsp</form-error-page> </form-login-config>


Pour récupérer l'utilisateur connecté avec le realm :
request.getRemoteUser()

Pour se connecter, il suffit d'utiliser javax.servlet.http.HttpServletRequest.login (doc oracle):

public String connexionRealm(){ System.out.println("On va chercher "+pseudo); FacesContext context = FacesContext.getCurrentInstance(); HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest(); try { request.login(pseudo, motDePasse); utilisateur=daoUtilisateur.rechercherParPseudo(pseudo); } catch (ServletException e) { System.out.println("Echec du login"); } return "index.xhtml"; }



-Filters, une ancienne technologie.


Pour forcer le https :

<security-constraint> <display-name>ConstraintSSL</display-name> <web-resource-collection> <web-resource-name>protected</web-resource-name> <description/> <url-pattern>/*</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method> <http-method>HEAD</http-method> <http-method>PUT</http-method> <http-method>OPTIONS</http-method> <http-method>TRACE</http-method> <http-method>DELETE</http-method> </web-resource-collection> <user-data-constraint> <transport-guarantee>CONFIDENTIAL</transport-guarantee> </user-data-constraint> </security-constraint>



Connexion à une base de donnée


On parle d'architecture n tiers. On aura la couche client (xhtml), la couche logique (cdi et autres beans java) et enfin la couche d'accès aux données (JPA).

Pour pouvoir gérer les connexions à la base, Oracle amis en place des EJB (Entreprise Java Beans).
Les EJB sont capables de gérer les transactions (plus besoin de faire transaction begin/commit), sont très capables au niveau de la sécurité, ont la possibilité de se délencher automatiquement (par exemple envoyer un mail tous les jours à minuit) et gèrent la concurrence.

Sans EJB, si l'application se connectait directement à la base, tous les clients essaieraient de se connecter en même temps.

Il y a 3 principaux types d'EJB :
-Stateless, le plus utilisé. Plusieurs EJB sont générés (par exemple, pour 8 connexions simultanées, 4 objets EJB seront instanciés par le serveur). C'est très capable en matière de performance, mais les objets sont volatiles, on ne peut pas stocker d'attributs vu qu'un objet peut disparaître rapidement et que plusieurs clients peuvent se partager les EJB).

-Stateful, un EJB sera mis en place pour chaque client. Les données sont conservées, mais c'est gourmand en ressource.

-Singleton, un seul EJB pour tous les CDI. C'est le moins performant mais le plus léger en terme de ressource et un système de file d'attente doit être mis en place par le programmeur. Si le CDI est Singleton, on va mettre l'EJB dans ce mode.

Il en existe d'autre comme EJB timer (pour exécuter les méthodes en différé, très simple, il suffit d'ajouter @Schedule(hour = "11", minute = "28") pour déclencher une méthode à 11h28), EJB web services pour se connecter à des services.

Au niveau du contrôleur, il aura besoin d'un objet DAO, mais on ne peut pas faire "new DAO" comme auparavant vu qu'il est généré automatiquement : il faudra utiliser l'annotation @Inject pour indiquer qu'il s'agit d'un objet managé que l'on veut injecter. Attention, l'injection se fait une fois l'objet construit. Si on essaie d'appeler un objet injecté dans le constructeur, ça ne marchera pas, il faut le faire après la construction. On peut appeler une méthode dès que la construction est terminée avec @PostConstruct.


Exemples :

package dataAccess; import java.io.Serializable; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; //Serializable car le Controleur est en SessionScoped //si on peut sauvegarder le controleur durant toute une session, il faut pouvoir sauver les Dao public class Dao<T> implements Serializable{ //on ne créé pas d'entitymanagerfactory @PersistenceContext(unitName="testConnexionMysqlServeurAppliPU") protected EntityManager em; //pour que les autres classes dao puissent y accéder public void ecrire(T objetEntite) { //pas besoin de em.getTransaction().begin(); em.persist(objetEntite); } public void maj(T objetEntite) { em.merge(objetEntite); } public void supprimer(T objetEntite) { T objettrouve=em.merge(objetEntite); //il met à jour l'objet et le retourne em.remove(objettrouve); //supprime l'objet de la base } }



package dataAccess; import javax.ejb.Stateless; import modele.Etudiant; @Stateless //on doit déclarer qu'il s'agit d'un EJB, stateless en l'occurence NE PAS OUBLIER OU ERREUR " javax.persistence.TransactionRequiredException" public class DaoEtudiant extends Dao { public Etudiant rechercherparId(int id) //cherche avec la clef { return em.find(Etudiant.class, id); } }



package controleur; import dataAccess.DaoEtudiant; import java.io.Serializable; import javax.enterprise.context.SessionScoped; import javax.inject.Inject; import javax.inject.Named; import modele.Etudiant; @SessionScoped @Named public class Controleur implements Serializable{ private Etudiant e; @Inject //permet de récupérer le Dao créé automatiquement (on nous l'injecte) private DaoEtudiant daoEtudiant; public Controleur(){ //Attention on peut pas demander directement au dao //e=daoEtudiant.rechercherparId(1); } public Etudiant getE() { return e; } public void setE(Etudiant e) { this.e = e; } public DaoEtudiant getDaoEtudiant() { return daoEtudiant; } public void setDaoEtudiant(DaoEtudiant daoEtudiant) { this.daoEtudiant = daoEtudiant; } public Etudiant getEtudiantParId(int id){ return daoEtudiant.rechercherparId(id); } }


Envoi de mail


Avec une apli java on utilise l'api JavaMail pour en envoyer. En Java EE, on va utiliser le serveur d'application,c'est à dire Glassfish+JavaMail. On va paramétrer Glassfish pour le faire pointer vers un serveur mail (pour les tests, on peut utiliser "fake smtp server"). Dans la console d'amin de Glassfish (http://localhost:4848/), menu de gauche, javamail session. Entrez le nom jndi de votre choix, l'adresse du serveur (localhost si local), le nom et l'adresse de l'expéditeur. Il faudra ajouter une propriété additionnelle en bas : mail-smtp-port (ou mail.smtp.port) et y indiquer le port de votre serveur smtp.
Selon les serveurs, il peut y avoir plus ou moins de propriétés additionnelles (par exemple, il y en aurait plus d'une dizaine à ajouter si on voulait utiliser gmail).

Pour envoyer le mail, dans le managed bean, les imports :

import javax.mail.Message; import javax.mail.MessagingException; import javax.mail.Session; import javax.mail.Transport; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage;


L'attribut javax.mail.Session où on injecte la ressource créée dans glassfish :

//Adresse JNDI créée dans glassfish qui pointe vers notre serveur SMTP @Resource(name="jmail/mailcommande") private Session session;


Enfin, le code pour envoyer le mail :

MimeMessage message = new MimeMessage(session); try { message.setFrom(new InternetAddress(session.getProperty("mail.from"))); InternetAddress[] address = {new InternetAddress("bob@bob.com")}; message.setRecipients(Message.RecipientType.TO, address); message.setSubject("SUJET DU MAIL"); message.setSentDate(new Date()); message.setText("TEXTE DU MAIL"); Transport.send(message); } catch (MessagingException ex) { ex.printStackTrace(); }


EJB envoyant un message à l'heure voulue :

@Named @Singleton @Startup public class Controleur3 { @Resource TimerService timerService; @PostConstruct public void init(){ ScheduleExpression heureMessage = new ScheduleExpression().hour("16").minute("46"); timerService.createCalendarTimer(heureMessage, new TimerConfig()); } @Timeout public void afficherMessage(Timer timer) { System.out.println("il est 16h40"); } }


Listeners JPA



Se rapprochent des triggers PLSQL. Ne marchent que dans un contexte JavaEE avec un serveur d'application. Une classe entité est écoutée par une classe listener qui va enclencher des traitements selon les évènements. Exemple : une entité stock avec un champ quantité où le listener va afficher un message en fonction de la quantité (on pourrait avoir ce genre de comportement avec des annotations @min(0) pour que la quantité ne soit jamais mise à zéro.
Les listeners sont là pour des contraintes complexes, par exemple, supprimer en base l'article dont la quantité en stock est négative. Pour ajouter un listener on utilise, juste sous l'annotation @Entity, l'annotation @EntityListeners(MaClasseListener.class) ou @EntityListeners({1.class,2.class,3.class}) si on en a plusieurs. Ensuite, on va préciser par annotation le déclenchement de ces méthodes.

Exemple :

public class EtudiantListener { @Resource private SessionContext ctx; // sessionContext permet d'acceder à la transaction ejb, on pourra faire un ctx.setRollbackOnly(); dessus par exemple pour la forcer à ne faire qu'un rollback @PostPersist //indique que la méthode suivante se déclenchera après l'insertion en base public void checknomprenom(Etudiant e) //l'objet en cours de traitement est injecté ici automatiquement { FacesContext context=FacesContext.getCurrentInstance(); context.addMessage(null, new FacesMessage("Etudiant écrit")); //affichage d'un message } }


@PrePersist (se déclenche avant un em.persist), @PreRemove (avant em.remove), @PreUpdate (avant update en base),@PostPersist(après insert en base), @PostRemove (après em.remove), @PostUpdate (après update en base), @PostLoad (après un select ou un refresh).

On peut également se passer d'une classe supplémentaire et directement écrire les annotations dans le modèle.

Héritage JPA



Dans la table utilisateur, on aura un champ "typeUser" qui prendracomme valeur "non-admin" ou "admin".

La classe utilisateur :

@Entity @Inheritance(strategy=InheritanceType.SINGLE_TABLE) //definition du type de stratégie d'héritage @DiscriminatorColumn(name="typeuser",discriminatorType=DiscriminatorType.STRING) //definition de la colonne discriminante et du type de discriminant içi une chaine. @DiscriminatorValue("non-admin") // valeur du discriminant ("non-admin") @Table(name = "utilisateur") @NamedQueries({ .......//les NamedQueries public class Utilisateur implements Serializable {


Dans la classe admin, héritée d'utilisateur, on aura les infos suivantes :

@Entity @DiscriminatorValue("admin") public class Admin extends Utilisateur{


Si on créé un objet de type utilisateur, le champ typeuser sera à la valeur "non-admin" par défaut. Si on créé un objet admin, ce champ sera à "admin".

Ainsi, on retrouvera ces valeurs dans la base :

+----+---------+-----------+ | id | nom | typeuser | +----+---------+-----------+ | 13 | Morane | non-admin | | 14 | Adhmine | admin | | 17 | padmin | non-admin | +----+---------+-----------+


Il y a donc différentes stratégies d'héritages :
@Inheritance(strategy=InheritanceType.SINGLE_TABLE) : on met l'objet parent et les objets hérités dans une seule table, différenciés par un champ (par exemple "typeuser"). Si les objets hérités ont des attributs en plus, des colonnes seront ajoutés. Par exemple, si on a un objet voiture qui hérite de véhicule, on aura un champ "nombre_de_portes" qui ne sera renseigné que pour les voitures et NULL pour les motos).

@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) : chaque classe et classe hérité aura sa table. Une table véhicule avec un champ "marque", une table voiture avec un champ "marque" et "nombre_de_portes"...

@Inheritance(strategy=InheritanceType.JOINED) : chaque class aura sa table mais on aura une table pour les propriétés communes.


REST


Representational State Transfert : Normalise le webservice côté serveur et certain aspects côté client.

Client ---- Webservice ---- Base de donnée

Le webservice est à peu près l'équivalent du DAO (il aura ses propres entity manager,EJB...). Il va fournir des donnés à la demande en respectant un cadre normalisé par REST. On ne se connecte pas directement à la base (ce n'est pas une bonne pratique de laisser la base accessible directement), on se connecte en http au webservice qui va se connecter à la base.

1)Des URL sont utilisées pour se connecter à la base. On pourrait ouvrir un navigateur et taper htp://monserveur/livres afin de récupérer la liste des livres en JSON ou en XML.

2)Les opérations sur la base correspondent aux requêtes HTTP : GET (lecture), POST (écriture), PU (màj), DELETE. Si le webservice détecte un get, il va renvoyer telle ou telle chose;

3)On peut échanger les infos avec différents formats de fichier : JSON, XML... JSON est à préférer car plus compact et dévoile moins d'informations que le XML où les balises décrivent chaque attributs.

4)Des mesures de sécurité peuvent être prises, par exemple en ajoutant un token.

En JavaEE, on va se baser sur la bibliothèque JAX-RS qui est simple et fait un recours massif aux annotations. Dans Netbeans, une fois un projet JavaEE web créé et une connection vers notre basede donnée effectuée, clic droit à la racine du projet et new, other, webservices, restful web service from entity classes.
Un package service sera généré, se basant sur les entités de notre package "modele".
On y trouvera AbstractFacade qui est l'équivalent de la classe abstrait DAO, une classe ApplicationConfig, avec une annotation @javax.ws.rs.ApplicationPath("webresources") indiquant le chemin de l'url (http://monsite/webresources/modele.commande). On trouvera aussi private void addRestResourceClasses(Set<Class<?>> resources) { qui listera les entités.

Enfin, les entités :

@Stateless //indique qu'il s'agit d'un EJB @Path("modele.produit") //le chemin pour y accéder (http://monsite/webresources/modele.produit) public class ProduitFacadeREST extends AbstractFacade<Produit> { @PersistenceContext(unitName = "exoCDI_topCommercePartie2PU") //pour la création de l'entity manager private EntityManager em; public ProduitFacadeREST() { super(Produit.class); } @GET //que se passera-t-il si on fait un HTTP GET (càd si le navigateur appelle une page) @Path("{id}") @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) //les formats de fichier qui seront générés public Produit find(@PathParam("id") Integer id) { return super.find(id); } @DELETE @Path("{id}") public void remove(@PathParam("id") Integer id,@HeaderParam("key") String clef) { if(clef=="clefDeSuppression"){ super.remove(super.find(id)); }else{ System.out.println("la clef n'est pas bonne."); } }


On peut créer nous même des méthodes qui seront appelées selon les paramètres de l'URL, ci-dessous une méthode pour lister les produits selon une catégorie donnée, si on tape l'url "http://monsite/webresources/modele.produit/listerProduitsCategories" :

@GET @Path("{id}/listeProduits") @Produces({MediaType.APPLICATION_JSON}) public List<Produit> listerProduitsCategories(@PathParam("id") Integer id) { return em.createNativeQuery("select * from produit where idCategorie="+id,Produit.class).getResultList(); }


En cas d'erreur "BEGIN_ARRAY but was STRING" c'est peut être que de l'XML est renvoyé à la place du JSON. Enlever MediaType.APPLICATION_XML pour ne garder que @Produces({MediaType.APPLICATION_JSON}.

Le travail du côté client est de générer l'URL, de récupérer les informations concernant l'objet et de le régénérer.

Ci-dessous un exemple du côté de l'application cliente, qui va se connecter à un webservice et récupérer une liste de "catégorie". On utilise les librairies httpclient, httpcore, commons-logging de chez Apache pour faire la requête HTTP puis GSON (librairie google) pour parser le fichier json.


CloseableHttpClient client = HttpClientBuilder.create().build(); HttpGet get = new HttpGet("http://localhost:8080/exoCDI_topCommercePartie2/webresources/modele.categorie"); // creation d'une requete HttpResponse response = null; try { response = client.execute(get); //execution de la requete } catch (IOException ex) { Logger.getLogger(ExoCDI_connexionATopCommerce.class.getName()).log(Level.SEVERE, null, ex); } StatusLine statusLine = response.getStatusLine(); System.out.println("Code réponse du serveur :"+statusLine.getStatusCode()); if(statusLine.getStatusCode() == 200) { // si réponse positive du serveur HttpEntity entity = response.getEntity(); //recuperation sous forme httpentity InputStream content = null; try { content = entity.getContent(); //passage du contenu dans inputStream } catch (IOException ex) { Logger.getLogger(ExoCDI_connexionATopCommerce.class.getName()).log(Level.SEVERE, null, ex); } catch (UnsupportedOperationException ex) { Logger.getLogger(ExoCDI_connexionATopCommerce.class.getName()).log(Level.SEVERE, null, ex); } Reader reader = new InputStreamReader(content); //objet de type reader pour reconstruire l'objet GsonBuilder gsonBuilder = new GsonBuilder(); //objet factory à objet json Gson gson = gsonBuilder.create(); //creation du objet gson listeCategories = new ArrayList<Categorie>(); listeCategories = Arrays.asList(gson.fromJson(reader, Categorie[].class)); //gson fromJson resconstruit des objets à partir d'un inputstream et des métadonées de type Class try { content.close(); //finalisation on ferme le flux } catch (IOException ex) { Logger.getLogger(Controleur.class.getName()).log(Level.SEVERE, null, ex); } System.out.println(listeCategories.get(0).getLibelle()); //on affiche la 1ere categorie }


Android


La majorité des applications Android sont écrites en Java. Elles ne fonctionnent pas avec la machine virtuelle d'oracle : soit elles utilisent la machine virtuel Dalvik (avant android 5), soit elles sont recompilés pour devenir du code natif Android. Ca reste du Java, tout ce qu'il est possible de faire en JavaSE se retrouve sur android. Par contre l'interface graphique n'a rien à voir (plus de Swing)et autre grosse différence, on doit gérer le multitâche lors du développement. Android a également ses propres règles de sécurité. Il faut également tenir compte des caractéristiques propres à un appareil mobile comme le mode d'affichage (portrait/paysage), la taille de l'écran variable,lesoutils comme le gyroscope, la caméra, etc. C'est le multitâche qui reste le plus dur à comprendre et mettre en place.

Dans un projet Android, on trouve un dossier "src" avec les packages java (doivent être nommés mot1.mot2). Le dossier "Gen" (invisible sous Android Studio) contient lui tous les fichiers générés (on y touche pas), on y trouvera la class "R" qui est généré à chaque compilation. Le dossier "Manifest" contient "Android.Manifest", un fichier où on indiquera les classes de l'application, son titre, le type de SDK utilisé, les thèmes et autres réglages.
Dans "res", on trouvera plusieurs choses importantes. D'abord, les ressources images ("drawable") avec des dossiers pour les différentes tailles d'images selon la résolution de l'appareil sur lequel elles seront affichées. Ensuite, le dossier "layout" qui contient les vues (en xml). Enfin, le dossier "values" contenant des XML où sont renseignés les constantes (par exemple les textes affichés sur les boutons).
La liaison entre les vues en xml et le code java est faite avec la classe "R" dans le dossier "gen", une référence vers chaque élément ajouté dans le dossier RES sera ajouté automatiquement à cette classe R pour qu'on puisse y accéder par la suite (par exemple, pour afficher une vue, on fera setContentView(R.layout.ma_jolie_vue);, pour trouver un boutton déclaré dans le xml d'une vue, ce sera findViewById(R.id.MonJoliBouton);).

Le contrôleur est une "activité", il hérite de la classe "activity" (et ces activités devront être déclarées dans Android.Manifest). On redéfinira la classe "onCreate" pour indiquer ce qui se passera au lancement de l'activité (affichage d'une vue, initialisation des variables, création des listeners...). Il y a plusieurs activités pour une appli, quand on passe de l'une à l'autre, l'activité est mise en pause par défaut (mais on pourrait la laisser tourner en arrière plan en redéfinissant des méthodes). Tous les états possibles d'une application sont des méthodes d'activity : onCreate, onStart, onRestart, onResume,onPause, onStop, onDestroy (il faut la stopper avant). Il suffit de faire super.onPause() pour mettre en pause. En cas de long chargement (grosse BDD par exemple), on pourra avoir besoin de redéfinir ces états pour éviter d'interrompre le chargement.

Pour les interactions, il y a 2 façons de faire :
-soit du côté vue, dans le xml, en mettant un onClick sur un bouton :

<Button android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="Cliquez-moi" android:onClick="MaSuperbeMethode" />

C'est très simple mais la vue n'est pas réutilisable et on n'est plus vraiment dans un modèle MVC.

-soit du côté contrôleur, en ajoutant un listener sur un bouton.

Exemple du cours :

//Un listener pour table View.OnClickListener clickButtonTable = new View.OnClickListener() { @Override public void onClick(View actuelView) { // traitement déclenché par le clic sur le bouton Intent intent = new Intent(MainActivity.this, TableConversions.class); startActivity(intent); } }; //on l'attribue au bouton table buttonTable.setOnClickListener(clickButtonTable);


Exemple plus simple sur la doc android :

button = (Button) findViewById(R.id.button_id); button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { // Perform action on click } });


Ca respecte mieux le modèle MVC car la vue est indépendante du contrôleur par contre le code est moins lisible (utilisation de classes anonymes).

On va potentiellement avoir beaucoup de code dans la classe anonyme si on a plusieurs boutons dans la vue. Si on est gêné par ces classes anonymes, on peut faire une classe à part avec tous les listeners dedans.

Fragments


Un fragment est un peu l'équivalent des panels en swing, c'est à dire un système de container qui contient des élements d'interface (boutons, listViews, images, etc). Ils pourront avoir les mêmes layout que les activités (en xml). On peut disposer les fragments un peu comme en veut de façon avec différents types de périphériques. Comme une activité, un fragment possède un cycle de vie.
2 manières de faire des fragments, de façon statique (en dur, avec une référence vers la classe de notre fragment dans le xml de l'activité, facile à mettre en place mais on ne pourra pas retirer le fragment plus tard lors du cycle de vie de l'appli) ou dynamique (en code java, on peut remplacer les fragments en temps réel).
Les Fragments ont un cycle de vie différent des applications cf. L'activity qui a appelé le fragment est passée en paramètre lors du onAttach. On peut la récupérer avec un getActivity.

Pour les fragments dynamiques, ils ne sont pas ajoutés manuellement au "backstack" (la liste d'applis quand on appuie sur "back"), il faut le demander explicitement avec la méthode "addToBackstack".




Exemple minimum de fragment statique (associé à un "fragment_chrono.xml") :

package com.example.aka.exocdi_chronometre; import android.app.Fragment; import android.os.Bundle; import android.support.annotation.Nullable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; public class FragmentChrono extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate le layout (càd récupère le XML et l'utilise comme vue) return inflater.inflate(R.layout.fragment_chrono, container, false); } }


Pour utiliser ce fragment dans la vue (si le chemin vers la classe, ici "com.example.aka.exocdi_chronometre.FragmentChrono" n'est pas trouvé dans android studio, l'écrire puis faire alt+enter pour qu'il créé et reconnaisse la classe fragment automatiquement) :

<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment android:name="com.example.aka.exocdi_chronometre.FragmentChrono" android:id="@+id/headlines_fragment" android:layout_width="match_parent" android:layout_height="match_parent" /> </FrameLayout>


En dynamique, les fragments seront appelés depuis le code java.

Ci-dessous un exemple fonctionnel tiré de la doc android :

package com.example.aka.exocdi_chronometre; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.FragmentActivity; import android.support.v7.app.AppCompatActivity; import android.view.View; public class MainActivity extends FragmentActivity { //attention, si on extends FragmentActivity, il faudra "import android.support.v4.app.Fragment;" dans la classe du fragment @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Check that the activity is using the layout version with // the fragment_container FrameLayout if (findViewById(R.id.layoutQuiContientLeFragment) != null) { // However, if we're being restored from a previous state, // then we don't need to do anything and should return or else // we could end up with overlapping fragments. if (savedInstanceState != null) { return; } // Create a new Fragment to be placed in the activity layout FragmentChrono fragmentChrono = new FragmentChrono(); // In case this activity was started with special instructions from an // Intent, pass the Intent's extras to the fragment as arguments fragmentChrono.setArguments(getIntent().getExtras()); // Add the fragment to the 'fragment_container' FrameLayout getSupportFragmentManager().beginTransaction() .add(R.id.layoutQuiContientLeFragment, fragmentChrono).commit(); } }


Et le fragment :

package com.example.aka.exocdi_chronometre; import android.support.v4.app.Fragment; //ATTENTION, l'import a changé comparé au fragment statique import android.os.Bundle; import android.support.annotation.Nullable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; public class FragmentChrono extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_chrono, container, false); } }


Pour changer de fragment, on peut en ajouter un devant, ou cacher l'ancien avec "hide", ou utiliser replace (attention replace stoppe l'ancien fragment.
Il faut passer par un objet fragmentTransaction car plusieurs fragments ont le risque d'être manipulés par plusieurs objets à la fois.


FragmentHistorique fragmentHistorique = new FragmentHistorique(); FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); // On ajoute le fragment appelé "fragmentHistorique" devant l'ancien fragment transaction.add(R.id.layoutQuiContientLeFragment, fragmentHistorique); //ajoute la transaction au "backstack" -ce qui nous permet de retrouver le précédent fragment avec back transaction.addToBackStack(null); //on peut donner un nom à cet ajout au backstack pour le récupérer plus tard d'une autre façon qu'en appuyant sur back //Effectue la transaction transaction.commit();




Installation sur Linux



Dans tous les cas, il faudra installer le SDK Android. Il peut être téléchargé tout en bas de la page https://developer.android.com/studio/index.html (lien direct vers la version 24.4.1).

(Ce processus d'installation n'a été testé qu'une seule fois étalé sur plusieurs mois : ) Une fois téléchargé, direction le dossier "tools", lancez une console et exécuter ./android update sdk. Installer Android SDK Tools, Android SDK Platform-tools, AndroiD SDK Build-tools + les éléments selon votre la version d'android avec laquelle vous voulez travailler (tout le dossier "Android 7.0 API 24" par exemple).

Pour créer un téléphone émulé, lancer ./android avd.

Netbeans


Il faut utiliser le plugin NBAndroid avec Netbeans 8.1 (la version de novembre 2015 n'a pas fonctionné sur 8.2).

Instructions d'installation.

Dans Netbeans, tools plugin, setting, add "http://nbandroid.org/release81/updates/updates.xml". Puis available plugin, cocher Android, redémarrer netbeans. Puis tools, options, miscellaneous, renseigner le dossier du SDK android. Créer un nouveau projet Android (le nom du package doit être sous la formemot1.mot2). Au démarrage, vous serez invité à choisir un de vos "AVD".

Mieux vaut tout de même utiliser Android Studio.

OrmLite


Un ORM (Object Relational Mapping) léger, utilisé pour fournir un accès à une base de donnée pour une application.

A la différence de JPA, il ne gère pas les transactions, il n'y a pas de gestion de la concurrence (vu qu'il n'y a qu'une seule application qui utilise la base), il n'y a pas de fichier de config -tout se fait depuis le code java, il est "model driven" (=on écrit les models PUIS la base et les tables seront générés automatiquement alors que pour JPA on créé les tables puis on importait depuis MySQL).
Cette approche "model driven" est possible aussi en JPA ("generate tables from entities") mais n'est pas faite en général.

Comme avec JPA, on aura du code java (de type monDao.queryBuilder().where().eq("prenom",prenom).query();, interprété par l'ORM, qu'il transformera en requête SQL. Les deux travaillent avec des annotations :

@DatabaseField(id = true private String name; @DatabaseField(columnName = "passwd", canBeNull = false) private String password;


On ne peut pas avoir de clef concaténée en OrmLite, il faut la créer soit même par concaténation, dans le constructeur surchargé et le setter : clef1Clef2=clef1.toString()+clef2.toString();


Il faudra importer les packages OrmLite dans le projet.

Exemples



Exemple d'application Android (convertisseur euros/francs):

package afpa.cdi.euroFrancs; import android.app.Activity; import android.os.Bundle; import android.text.Editable; import android.text.TextWatcher; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends Activity { private EditText francsEditText; private EditText eurosEditText; /** Méthode appelée lors de la création de l'activité. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //on indique qu'on utilise ressources/values/strings.xml setContentView(R.layout.main); // On récupère les EditText pour les modifier plus tard francsEditText = (EditText) findViewById(R.id.francsEditText); eurosEditText = (EditText) findViewById(R.id.eurosEditText); // On créé un listener qui va surveiller les changements sur le champ du texte TextWatcher surveilleModifTexteEuros = new TextWatcher() //une classe anonyme avec ses méthodes obligatoires { public void beforeTextChanged(CharSequence s, int start, int count, int after) { } public void onTextChanged(CharSequence s, int start, int before, int count) { //on converti l'autre champ if(eurosEditText.hasFocus()){ if(!eurosEditText.getText().toString().equals("") && !eurosEditText.getText().toString().equals(".")){ Long nombreConverti=Math.round(Double.valueOf(eurosEditText.getText().toString())*655); francsEditText.setText(String.valueOf(nombreConverti.doubleValue()/100)); }else{ //si le champ euros est complètement effacé, il faut aussi effacer le champ francs francsEditText.setText(""); } } } public void afterTextChanged(Editable s) { } }; TextWatcher surveilleModifTexteFrancs = new TextWatcher() { public void beforeTextChanged(CharSequence s, int start, int count, int after) { } public void onTextChanged(CharSequence s, int start, int before, int count) { if(francsEditText.hasFocus()){ if(!francsEditText.getText().toString().equals("") && !francsEditText.getText().toString().equals(".")){ Long nombreConverti=Math.round(Double.valueOf(francsEditText.getText().toString())*655); eurosEditText.setText(String.valueOf(nombreConverti.doubleValue()/100)); }else{ //si le champ francs est complètement effacé, il faut aussi effacer le champ euros eurosEditText.setText(""); } } } public void afterTextChanged(Editable s) { } }; // On lui attribue une méthode si il reçoit le focus eurosEditText.addTextChangedListener(surveilleModifTexteEuros); francsEditText.addTextChangedListener(surveilleModifTexteFrancs); //BOUTON RESET // On implémente l’interface OnClickListener OnClickListener clickButton = new OnClickListener() { @Override public void onClick(View actuelView) { // traitement déclenché par le clic sur le bouton // par exemple affichage d’une boite popup Toast.makeText(getBaseContext(), "Reset", Toast.LENGTH_LONG).show(); francsEditText.setText(""); eurosEditText.setText(""); } }; // On récupère le bouton cliqué Button bouton = (Button) findViewById(R.id.boutonReset); // On appelle sa méthode setOnClickListener() bouton.setOnClickListener(clickButton); } }


Le layout main.xml :

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/euros" /> <EditText android:id="@+id/eurosEditText" android:layout_width="fill_parent" android:layout_height="40dip" android:ems="20" android:layout_marginLeft="36dip" android:inputType="numberDecimal" /> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/francs" /> <EditText android:id="@+id/francsEditText" android:layout_width="fill_parent" android:layout_height="40dip" android:ems="20" android:layout_marginLeft="36dip" android:inputType="numberDecimal" /> <Button android:id="@+id/boutonReset" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/reset" /> </LinearLayout>


Les activités sont des classes qui hérite d'Activity, elle doivent être déclarés dans AndroidManifest.xml.

Pour passer d'une activité à une autre, on utilise des intents :

//cecodepeut être dans un OnClickListener par exemple Intent intent = new Intent(MainActivity.this, Resultat.class); intent.putExtra("nomPourRecupererObjetEnvoye",objetEnvoye); //on peut envoyer des paramètres de la façon suivante startActivity(intent);


Pour récupérer des objets envoyés d'une activité à l'autre, on peut écrire dans l'activité de destination : getIntent().getExtras().getString("nomPourRecupererObjetEnvoye");

Certains objets doivent être alimentés par des "adapters", qui sont l'équivalent des DefaultListModel en swing. Ces adapters vont faire l'intermédiaire entre un tableau et un objet (une liste, une combobox) qui utilisera ce tableau. Lorsque le tableau sera mis à jour, l'adapter mettra à jour l'objet dynamiquement.

Ci-dessous, un adapter pour un spinner :
ArrayAdapter<String> adaptateurPourMonObjet = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, monTableau);


Un adapteur pour une listeView :

super.onCreate(savedInstanceState); setContentView(R.layout.activite_principale); //la liste de choses qui seront affichées dans la liste view listTextes=new ArrayList(); //on récupère la liste view maListView = (ListView) findViewById(R.id.maListeView); //on ajoute les éléments voulus listTextes.add("texte1"); listTextes.add("texte2"); listTextes.add("texte3"); //il faut utiliser un adapter final ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, listTextes); //on met l'adapter sur la listeview maListView.setAdapter(adapter);


On ne peut pas avoir de tableau à 2 dimensions dans le XML.

On pourrait faire ([http://stackoverflow.com/questions/6571477/is-it-possible-to-access-a-string-array-item-in-android|exemple]) :

<array name="Unite1"> <item name="nom">Euros</item> <item name="valeurEnEuro">1</item> </array> <array name="Unite2"> <item name="nom">Francs</item> <item name="valeurEnEuro">0.1524</item> </array> <array name="unitesUtilisees"> <item>@array/Unite1</item> <item>@array/Unite2</item> </array>


Puis :

//on récupère le contenu du tableau listant les taux et les unites TypedArray unitesUtilisees = getResources().obtainTypedArray(R.array.unitesUtilisees); tabNomUnites = new String[unitesUtilisees.length()]; tabTaux = new String[unitesUtilisees.length()]; for (int i = 0; i < unitesUtilisees.length(); ++i) { //on récupère l'id de la ressource stockée à la position i du tableau unitesUtilisees //c'est à dire un tableau de string System.out.println("contenu de unitesUtilisees ?"+String.valueOf(getResources().obtainTypedArray(unitesUtilisees.getResourceId(i, 0)))); System.out.println("Quelle unite ?"+String.valueOf(getResources().getResourceEntryName(unitesUtilisees.getResourceId(i, 0)))); TypedArray soustabUnitesUtilisees=getResources().obtainTypedArray(unitesUtilisees.getResourceId(i, 0)); System.out.println("Le contenu de soustabUnitesUtilisees :"+soustabUnitesUtilisees.toString()); //on parcourt le tableau qui vient d'être renvoyé for (int j = 0; j < soustabUnitesUtilisees.length(); ++j) { System.out.println("Nom du champ :"+soustabUnitesUtilisees.getResourceId(j,99)); System.out.println("Contenu du champ :"+String.valueOf((soustabUnitesUtilisees.getString(j)))); } }


Renvoie

I/System.out: contenu de unitesUtilisees ?[3, 5, 2, 0, 0, 0, 16, 1, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0] I/System.out: Quelle unite ?Unite1 I/System.out: Le contenu de soustabUnitesUtilisees :[3, 5, 2, 0, 0, 0, 16, 1, 2, 0, 0, 0] I/System.out: Nom du champ :99 I/System.out: Contenu du champ :Euros I/System.out: Nom du champ :99 I/System.out: Contenu du champ :1


MAIS impossible de récupérer l'attribu "name" dans <item name="nom">Euros</item>. On est donc forcé de respecter un sens (nom de la devise en premier, nom de la valeur en bas).

Donc :

<?xml version="1.0" encoding="utf-8"?> <resources> <string-array name="Unite1"> <item>Euros</item> <item>1</item> </string-array> <string-array name="Unite2"> <item>Francs</item> <item>0.1524</item> </string-array> <array name="unitesUtilisees"> <item>@array/Unite1</item> <item>@array/Unite2</item> </array> </resources>


TypedArray unitesUtilisees = getResources().obtainTypedArray(R.array.unitesUtilisees); tabNomUnites = new String[unitesUtilisees.length()]; tabTaux = new String[unitesUtilisees.length()]; for (int i = 0; i < unitesUtilisees.length(); ++i) { tabNomUnites[i]=getResources().getStringArray(unitesUtilisees.getResourceId(i, 0))[0]; tabTaux[i]=getResources().getStringArray(unitesUtilisees.getResourceId(i, 0))[1]; }


Un exemple de Handler ([https://guides.codepath.com/android/Repeating-Periodic-Tasks#scheduledthreadpoolexecutor|bon tuto]) :

package com.example.aka.exocdi_chronometre; import android.os.Bundle; import android.os.Handler; import android.os.Message; public class HandlerThreadChrono extends Handler { private MainActivity mainActivity; public HandlerThreadChrono(MainActivity ma) { mainActivity=ma; } @Override public void handleMessage(Message msg) { mainActivity.mettreAJourChrono(); } }


Un exemple de thread qui communique avec le handler plus haut :

package com.example.aka.exocdi_chronometre; import android.os.Bundle; import android.os.Message; public class ThreadChrono implements Runnable { private HandlerThreadChrono handlerThreadChrono; private int i; public ThreadChrono(HandlerThreadChrono h) { handlerThreadChrono=h; } public void run(){ //on contacte le chrono pour lui dire d'updater la vue handlerThreadChrono.sendEmptyMessage(0); // System.out.println("ThreadChrono, tour "+(i++)); //on relance dans xxx millisecondes handlerThreadChrono.postDelayed(this,100); } }


Pour faire fonctionner les thèmes dans Android Studio, changer la valeur de theme dans le manifest par android:theme="@style/Theme.AppCompat.

Pager


Le système utilisé en android pour faire défiler des fragments en glissant avec le doigt. Il n'y a pas besoin de fragmentManager si on utilise un pager.

Layout du pager :

<?xml version="1.0" encoding="utf-8"?> <!-- activity_screen_slide.xml --> <android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/idDeMonPager" android:layout_width="match_parent" android:layout_height="match_parent" />


Il faudra une classe pour le pager, héritant de FragmentStatePagerAdapter (ou PagerAdapter selon les cas) :

import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentStatePagerAdapter; public class MaClassePager extends FragmentStatePagerAdapter { private FragmentChrono fragmentChrono; private FragmentHistorique fragmentHistorique; public MaClassePager(FragmentManager fm) { super(fm); System.out.println("Constructeur du fragment"); fragmentChrono=new FragmentChrono(); fragmentHistorique=new FragmentHistorique(); } @Override public Fragment getItem(int position) { //à la position X, on retourne un fragment ou un autre System.out.println("getItem dans le pager"); if(position==0) { System.out.println("On retourne"+fragmentChrono); return fragmentChrono; } return fragmentHistorique; } @Override public int getCount() { return 2; //le nombre de pages dans le fragment } @Override public void notifyDataSetChanged() { //cette méthode est appelée quand on veut rafraichir les fragments super.notifyDataSetChanged(); System.out.println("NotifyDatasetchanged dans le pager"); fragmentHistorique.actualiserHistorique(); } }


Puis dans le MainActivity :

private ViewPager viewPager; private MaClassePager monInstanceDepager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.layout_du_pager);//on utilise le layout du pager viewPager=(ViewPager) findViewById(R.id.idDeMonPager); monInstanceDepager=new MaClassePager(getSupportFragmentManager()); viewPager.setAdapter(monInstanceDepager);



Autre exemple simplifié à l'extrême et fonctionnel :
La class "fragment 1" :

package com.example.aka.testpager; import android.support.v4.app.Fragment; //ATTENTION, l'import a changé comparé au fragment statique import android.os.Bundle; import android.support.annotation.Nullable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; public class Fragment1 extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment1, container, false); } }


La classe fragment 2 est identique.

Le pager :

package com.example.aka.testpager; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentStatePagerAdapter; public class MaClassePager extends FragmentStatePagerAdapter { public MaClassePager(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int position) { //à la position X, on retourne un fragment ou un autre System.out.println("Position : "+position); switch (position) { case 0: return new Fragment1(); case 1: return new Fragment2(); default: return new Fragment1(); //il ne faut pas retourner null sinon crash } } @Override public int getCount() { return 2; //le nombre de pages dans le fragment } @Override public void notifyDataSetChanged() { //cette méthode est appelée quand on veut rafraichir les fragments super.notifyDataSetChanged(); } }


La mainActivity :

package com.example.aka.testpager; import android.support.v4.view.ViewPager; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; public class MainActivity extends AppCompatActivity { private ViewPager viewPager; private MaClassePager monInstanceDepager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.layout_du_pager); viewPager=(ViewPager) findViewById(R.id.idDeMonPager); monInstanceDepager=new MaClassePager(getSupportFragmentManager()); viewPager.setAdapter(monInstanceDepager); } }


Web


Pour donner l'autorisation à l'application d'aller sur internet, dans le manifest, juste avant l'ouverture de la balise "application", ajouter <uses-permission android:name="android.permission.INTERNET" />.

Application utilisant Apache httpclient


Pour utiliser les librairies apache, mettre dans le fichier build.gradle useLibrary 'org.apache.http.legacy' :

android { ... buildTypes { ... } ... useLibrary 'org.apache.http.legacy' }

Il faudra ensuite utiliser un Thread pour les connexions (si on se connecte à internet dans la classe "main", on risque d'obtenir une erreur "NetworkOnMainThreadException").

Ci-dessous 2 classes d'une application fonctionnelle (si on veut que le thread communique avec le min, il faudra rajouter un handler) :

La classe main, qui lance le thread :

package com.example.aka.testrest; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Thread t=new Thread(new ThreadConnexion()); t.start(); } }



La classe ThreadConnexion qui se connecte au serveur et récupère les infos :

package com.example.aka.testrest; import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.util.EntityUtils; public class ThreadConnexion implements Runnable { @Override public void run() { System.out.println("Le threadConnexion run..."); DefaultHttpClient httpclient = new DefaultHttpClient(); try { HttpHost target = new HttpHost("192.168.122.1", 8080, "http"); HttpGet getRequest = new HttpGet("/testWebServiceAndroid"); System.out.println("On se connecte à " + target); HttpResponse httpResponse = httpclient.execute(target, getRequest); HttpEntity entity = httpResponse.getEntity(); System.out.println("----------------------------------------"); System.out.println(httpResponse.getStatusLine()); System.out.println("----------------------------------------"); if (entity != null) { System.out.println(EntityUtils.toString(entity)); } } catch (Exception e) { e.printStackTrace(); } finally { httpclient.getConnectionManager().shutdown(); } } }


Rest


Exemple d'une application REST avec Android. Attention, cette façon de faire est dépréciée depuis le sdk 21.

Du côté serveur :
Créer la base de donnée, créer le projet web java EE, ajoutez une connexion vers votre base, puis ajouter unpackage modele dans le projet (on pourra le supprimer plus tard, il servira juste à générer le Rest), clic droit new "entity classes from database" , ajouter la connexion vers votre base à la liste, générer les classes, puis clic droit sur le projet, new "restful web service from entities classes".

Du côté client (android) :
Tout d'abord, indiquer dans le fichier gradle les librairies que l'on souhaite utiliser (ici gson et org.apache.http.legacy) :

android { ... } buildTypes { ... } useLibrary 'org.apache.http.legacy' } dependencies { ... compile 'com.google.code.gson:gson:2.4' ... }



Ensuite, il faudra l faudra instancier le thread dans la classe main :

package com.example.aka.testrest; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Thread t=new Thread(new ThreadConnexion()); t.start(); } }


Pour l'exemple, on va récupérer un objet de type Etudiant, il nous faut donc une classe Etudiant pour que GSON puisse le construire à partir des infos reçues du serveur :

package com.example.aka.testrest; public class Etudiant { private int id; private String nom; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getNom() { return nom; } public void setNom(String nom) { this.nom = nom; } }


Enfin, le thread qui va se connecter :

package com.example.aka.testrest; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.util.EntityUtils; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class ThreadConnexion implements Runnable { @Override public void run() { System.out.println("Le threadConnexion run..."); DefaultHttpClient httpclient = new DefaultHttpClient(); try { HttpHost target = new HttpHost("192.168.122.1", 8080, "http"); HttpGet getRequest = new HttpGet("/testWebServiceAndroid/webresources/modele.etudiant"); System.out.println("On se connecte à " + target); HttpResponse httpResponse = httpclient.execute(target, getRequest); HttpEntity entity = httpResponse.getEntity(); InputStream content = null; try { content = entity.getContent(); //passage du contenu dans inputStream } catch (IOException ex) { } catch (UnsupportedOperationException ex) { } Reader reader = new InputStreamReader(content); //objet de type reader pour reconstruire l'objet GsonBuilder gsonBuilder = new GsonBuilder(); //objet factory à objet json Gson gson = gsonBuilder.create(); //creation du objet gson List listeEtudiants = new ArrayList<Etudiant>(); listeEtudiants = Arrays.asList(gson.fromJson(reader, Etudiant[].class)); //gson fromJson resconstruit des objets à partir d'un inputstream et des métadonées de type Class try { content.close(); //finalisation on ferme le flux } catch (IOException ex) { } System.out.println("Mon étudiant joli :"+((Etudiant)listeEtudiants.get(0)).getNom()); } catch (Exception e) { e.printStackTrace(); } finally { httpclient.getConnectionManager().shutdown(); } } }


Résultat:

I/System.out: Le threadConnexion run... I/System.out: On se connecte à http://192.168.122.1:8080 D/NetworkSecurityConfig: No Network Security Config specified, using platform default I/OpenGLRenderer: Initialized EGL, version 1.4 D/OpenGLRenderer: Swap behavior 1 E/EGL_emulation: tid 19823: eglSurfaceAttrib(1174): error 0x3009 (EGL_BAD_MATCH) W/OpenGLRenderer: Failed to set EGL_SWAP_BEHAVIOR on surface 0xa24d0840, error=EGL_BAD_MATCH I/System.out: !!!!!!!!!!!!!! Content :org.apache.http.conn.EofSensorInputStream@860a409 I/System.out: Mon étudiant joli :Bob



Autre exemple qui n'est pas deprecated :

package com.example.aka.webservicemeteo.access; import android.os.Message; import android.util.Log; import com.example.aka.webservicemeteo.modele.WeatherObservation; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonObject; import org.json.JSONObject; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.util.Arrays; import java.util.List; public class ThreadGetMeteo implements Runnable { private MonHandler monHandler; public ThreadGetMeteo(MonHandler monHandler) { this.monHandler = monHandler; } @Override public void run() { try { Log.i("suivi","Dans le ThreadGetMeteo, ouverture de l'url pour récupérer l'observation météo..."); URL url = new URL("http://api.geonames.org/findNearByWeatherJSON?lat=47.316667&lng=5.016667&username=levmc"); HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); BufferedReader contenuWebRecu = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); //Log.i("suivi","STREAM RECU" + contenuWebRecu.readLine()); String json = contenuWebRecu.readLine(); Gson gson = new Gson(); //on ne peut pas directement reconstruire un objet WeatherObservation car le webservice //renvoie un json avec WeatherObservation à la racine, c'est ballot //on va donc reconstruire dans une classe intermédiaire JsonObject jsonObject = gson.fromJson(json, JsonObject.class); //puis, depuis cette classe intermédiaire, on va récupérer tous les enfants de "weatherObservation" WeatherObservation weatherObservation = gson.fromJson(((JsonObject)jsonObject.get("weatherObservation")), WeatherObservation.class); Log.i("suivi","Reconstruction de l'observation météo à partir du JSON reçu..."+weatherObservation); contenuWebRecu.close(); //finalisation on ferme le flux Log.i("suivi","Exemple de weatherobservation :"+weatherObservation); Log.i("suivi","Ville :"+weatherObservation.getStationName()); Log.i("suivi","Température :"+weatherObservation.getTemperature()); Message message=new Message(); //on met l'objet weatherObservation dans le message à destination du handler message.obj=weatherObservation; //on envoie le message au handler monHandler.sendMessage(message); } catch (Exception e) { //il faudrait plutôt catcher différentes exceptions ici //"pas de réseau ?" "pas de réponse ?" "mauvaise réponse ?" Message message=new Message(); message.obj=e.getLocalizedMessage().toString(); //on envoie le message au handler monHandler.sendMessage(message); Log.i("suivi","Exception :"+e.getMessage()); }finally { Log.i("suivi","Terminé !"); } } }


Avec son handler :

package com.example.aka.testpersothreads; import android.os.Bundle; import android.os.Handler; import android.os.Message; public class MonHandler extends Handler { private ActivitePrincipale activitePrincipale; public MonHandler(ActivitePrincipale ma) { activitePrincipale=ma; System.out.println("Creation du handler"); } @Override public void handleMessage(Message msg) { System.out.println("Un message a été reçu : "+msg.toString()); activitePrincipale.modifInterface(); } }


Et le main :

package com.example.aka.webservicemeteo; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.TextView; import com.example.aka.webservicemeteo.access.MonHandler; import com.example.aka.webservicemeteo.access.ThreadGetMeteo; public class ActivitePrincipale extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_activite_principale); MonHandler monHandler=new MonHandler(this); Thread t=new Thread(new ThreadGetMeteo(monHandler)); t.start(); } public void changerTexteCentralPar(String nouveauTexte) { TextView texteCentral= (TextView) findViewById(R.id.texteCentral); texteCentral.setText(nouveauTexte); } }



Divers


Maven


Maven permet de gérer les dépendances à distance, en les récupérant sur un serveur distant. Au lieu d'intégrer les librairies manuellement (importation de jar), on va donner l'URL d'un dépôt Maven où seront récupérés les fichiers.

Maven est disponible sous forme de plugin pour Netbeans et intégré par défaut (bundled). Tools, options, java, onglet Maven.
Pour créer un projet Maven: new project, Maven, java application. Entrer le nom du projet, éventuellement le numéro de version et un group id. Un projet va se créer, il faudra modifier le fichier pom.xml pour ajouter des librairies.

Exemple de pom.xml où a été rajouté la librairie DesignGridLayout :

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.mycompany</groupId> <artifactId>mavenproject2</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <!-- https://mvnrepository.com/artifact/net.java.dev.designgridlayout/designgridlayout --> <dependencies> <dependency> <groupId>net.java.dev.designgridlayout</groupId> <artifactId>designgridlayout</artifactId> <version>1.5</version> </dependency> </dependencies> </project>


Une fois le fichier sauvé, DesignGridLayout sera téléchargé et placé dans le dossier dependency et utilisable tout de suite.