Étiquette : Java

Vincent Lecomte
[Java] Filtre sur liste avec les streams

[Java] Filtre sur liste avec les streams

L’une des grosses améliorations de Java 8, c’est ce qu’on appelle les streams. C’est un nouveau pattern de manipulation de données et cela permet de se passer notamment des itérateurs que l’on connait. On peut les utiliser sur des collections ou même des tableaux.

On prend la classe suivante comme exemple :

public class MyObject
{
 private String sId;
 private String sName;
 
 // ...
}

Une méthode nous retourne alors une liste qui reprend des objets caractérisés par leur nom et un identifiant. On voudrait filtrer la liste pour ne récupérer que les objets nommés “chair” par exemple.

Stream<MyObject> lo_s = lo_listObj.stream().filter(
 myObj -> myObj.getName().equals("chair")
);

Le paramètre de la méthode filter() est un prédicat (type Predicate). Le code ci-dessus renvoie un stream que l’on peut ensuite manipuler pour retourner une collection ou bien un seul objet. Par exemple on pourrait utiliser la méthode collect() pour renvoyer une nouvelle liste.

List<Myobject> lo_list = lo_s.collect(Collectors.toList());

On peut aussi retourner le premier objet qui correspond au filtre, avec findFirst(). Cela retourne un objet de type Optional.

Optional<MyObject> lo_opt = lo_s.findFirst();

Plusieurs opérations sont possibles sur cet objet. On peut savoir s’il y a bien un résultat avec la méthode isPresent(), ou bien retourner directement le résultat ou une autre valeur si aucun objet n’avait été trouvé lors du filtre.

MyObject lo_result = lo_opt.orElse(lo_defaultObject);

La méthode orElse() renvoie la valeur qui avait été trouvée et si elle n’est pas présente, alors elle renvoie un autre objet du même type.

if (lo_opt.isPresent())
{
 MyObject lo_result = lo_opt.get();
}

L’exemple ci-dessus montre comment récupérer l’objet uniquement s’il est présent et pour également éviter l’exception NoSuchElementException. Cela peut être un raccourci à la méthode orElse(), tout dépend de quoi on a besoin en retour et ce qu’on va en faire.

[Java] Mémo – Validation de beans

[Java] Mémo – Validation de beans

En Java, il est possible d’utiliser la validation de beans pour permettre d’en valider son contenu, donc les différents membres de la classe. Par exemple, si on a une classe “Personne“, qui contient un nom et un prénom, on voudrait valider que ceux-ci ne dépassent pas une certaine taille ou même qu’ils respectent une expression régulière. Plutôt que de créer des méthodes visant à vérifier chaque élément un à un, on va se baser sur des annotations.

Prenons la classe suivante comme référence pour ce 1er exemple :

public class Person
{
    @Size(min = 1, max = 5, message = "cod: btw. 1 and 5")
    private String      cod;
 
    @Size(max = 60, message = "firstName: Length max 60")
    private String      firstName;

    @NotNull(message = "birthdate: mandatory")
    private LocalDate   birthdate;
 
    // ...
}

On utilise l’annotation Size pour définir la taille maximum qu’une chaine peut avoir. Si on ne spécifie pas de minimum, cela veut dire que la chaine peut être vide. A chaque fois on spécifie le paramètre “message” pour indiquer quel sera le message retourné lors de la validation.

Pour récupérer les erreurs sous forme de chaine :

Person lo_p = new Person();
Set<ConstraintViolation<T>> lo_violations = 
 Validation.buildDefaultValidatorFactory()
  .getValidator()
  .validate(lo_p);
for (ConstraintViolation<T> lo_violation : lo_violations)
{
    System.out.println(lo_violation.getMessage());
}

Ce code affiche les messages en fonction des champs qui sont en erreur par rapport à l’objet qu’on donne en paramètre de la méthode validate().

Si notre classe contient une liste d’objets que l’on veut aussi valider, ou un objet d’une autre classe, il faut rajouter une annotation Valid pour qu’il soit pris en compte lors de la validation.

public class PremObj
{
    @Size(min = 1, max = 20, message = "id: btw. 1 and 20")
    private String                  id;
 
    @Valid
    @NotNull(message = "liste_sousobj: Mandatory")
    private List<UnSousObj>  liste_sousobj;
 
    // ...
}

Chaque objet de la liste sera donc analysé. C’est aussi valable si on avait mis un seul objet de type “UnSousObj“. La classe que l’on référence doit évidemment posséder diverses annotations de validation.

Un champ peut également être validé sur base de valeurs d’une énumération. On peut ainsi utiliser l’annotation Enum (sur une variable de type String). Le paramètre obligatoire “enumClass” permet de spécifier la classe qui servira à la validation.

D’autres annotations utiles :

  • Pattern : sur une chaine, permet de faire en sorte qu’elle respecte une expression régulière.
  • Min : indique la valeur minimale d’un nombre.
  • Max : indique la valeur maximale d’un nombre.

Liens

Liste des annotations disponibles

[Java] Gestion des erreurs à l’aide d’une servlet

[Java] Gestion des erreurs à l’aide d’une servlet

Dans votre application web, il est possible de réaliser une gestion d’exceptions – notamment pour intercepter ServletException – en passant par une servlet. Par défaut, les erreurs telles que le code HTTP 500 sont gérées par le serveur et renvoient vers une page par défaut. L’idée est donc de changer ce comportement en renvoyant par exemple une information en JSON ou en redirigeant vers une JSP.

On va donc créer une classe qui hérite de HttpServlet. Celle-ci devra implémenter des fonctions pour gérer les différentes méthodes HTTP (exemple : PUT, GET, POST…).

public class MaServletHttp extends HttpServlet 
{
    public void doGet(HttpServletRequest request, 
        HttpServletResponse response) throws IOException 
    {
        // On gère l'erreur...
    }
}

Comme on peut le constater on a donc créé une fonction qui sera exécutée lorsque la méthode GET est utilisée. Typiquement lorsqu’on appelle une URL et qu’on spécifie un ensemble de paramètres dans celle-ci. Il faudra donc ajouter les autres fonctions comme doPost() – généralement appelée lors d’un envoi de formulaire – ou doPut().On remarque aussi l’annotation @WebServlet qui permet de définir tout un ensemble d’informations utiles, notamment l’ “URL pattern”. On peut soit spécifier cette valeur directement comme dans l’exemple ci-dessus, soit en spécifiant les attributs de la manière suivante.

@WebServlet(
        name = "maBelleServlet",
        description = "Gestion d'exceptions",
        urlPatterns = "/maServletHttp"
)
public class MaServletHttp extends HttpServlet 
{
    // Code de la servlet...
}

Certains attributs de la servlet ne sont utiles que pour les outils utilisés lors du développement (par exemple, description ou encore smallIcon). Ces attributs sont décrits dans la documentation de JavaEE 7.Il faut ensuite modifier le fichier web.xml pour spécifier dans quels cas la servlet doit être appelée pour gérer les exceptions ou les différents code d’erreur. Dans l’exemple ci-dessous on va faire en sorte que la servlet soit appelée en cas d’erreur HTTP 500.

<web-app ... >
    <error-page> 
        <error-code>500</error-code> 
        <location>/maServletHttp</location> 
    </error-page>
</web-app>

On a donc ajouté un élément “error-page” dans le fichier descriptif de l’application. Pour cet élément on a spécifié le code d’erreur grâce à l’élément “error-code” et l’emplacement de la servlet (qui correspond au URL pattern défini précédemment dans l’annotation de la classe Java) représenté par l’élément “location“.

A la place de l’élément “error-code” on peut aussi spécifier “exception-type” et indiquer les erreurs que la servlet devra intercepter. On y spécifie alors le canonical name de la classe d’exception visée (ex : java.lang.NullPointerException).

Une fois le fichier de configuration XML modifié, on peut s’attaquer au code de la servlet. Aussi bien lorsqu’on redirige vers une JSP qu’une servlet, des attributs contenant des informations à propos de l’erreur qui se produit, sont définis au niveau de la requête et peuvent donc être rapatriés dans le code.

@WebServlet("/maServletHttp")
public class MaServletHttp extends HttpServlet 
{
    public void doGet(HttpServletRequest request, 
        HttpServletResponse response) throws IOException 
    {
        Integer li_st = (Integer) request.getAttribute(
                "javax.servlet.error.status_code"
        );

        Throwable lo_e = (Throwable) request.getAttribute(
                "javax.servlet.error.exception"
        );

        String ls_svlt = (String) request.getAttribute(
                "javax.servlet.error.servlet_name"
        );
        
        // Envoi de la réponse après.
        // Voir ci-dessous.
    }
}

Voici le tableau des attributs disponibles (source : tutorialspoint).

L’attribut qui contient le nom de la servlet est intéressant car il s’agit de la source, là où l’erreur s’est produite. Cela signifie qu’en fonction du nom on peut imaginer un traitement différent en retour.L’exemple ci-dessous renvoie simplement du JSON au client après une erreur dans un Web Service.

@WebServlet("/maServletHttp")
public class MaServletHttp extends HttpServlet 
{
    public void doGet(HttpServletRequest request, 
        HttpServletResponse response) throws IOException 
    {
        // Récupération des attributs avant.
        // Voir ci-dessus.
        if (ls_svlt.equals("webserviceServlet"))
        {
            response.setContentType("application/json");
            response.getWriter().print(
                "{ key1: 'val1', key2: 'val2' }"
            );

            response.getWriter().flush();
        }
    }
}

On peut également modifier le code de retour (par exemple, si le code était 500, on peut le redéfinir au niveau de l’objet HttpServletResponse).

Sources

Baeldung
CodeJava.net
LogicBig
Oracle
Tutorialspoint

[Java] Evaluer une expression avec Groovy

[Java] Evaluer une expression avec Groovy

Apache Groovy est à la base un langage de programmation pouvant s’intégrer à des logiciels développés pour la plateforme Java. Les librairies qu’il propose peuvent être importées dans un projet, notamment pour diverses raisons comme le besoin d’évaluer dynamiquement une expression.

En téléchargeant les binaires de la version stable 2.4.12, vous trouverez dans le sous-répertoire “lib” un JAR – groovy-2.4.12.jar – à intégrer dans votre application. La méthode qui nous intéresse se trouve dans le package “groovy.util.*”. Il s’agit de Eval.me().

Integer res = (Integer) Eval.me(" 2 * 4 + 2");

Cela va tout simplement retourner 10 et le stocker dans la variable.

Il existe également trois variantes, qui permettent de passer jusqu’à 3 variables automatiquement nommées “x”, “y” et “z”.

Eval.x(2, " x * 4 + 2")

On donne en premier paramètre la valeur de “x” ensuite, il suffit de donner l’expression qu’on veut évaluer.

Si on creuse un peu, cette méthode réalise ceci :

Binding bind = new Binding(); 
bind.setVariable("x", 2); 
GroovyShell sh = new GroovyShell(bind); 
sh.evaluate(" x * 4 + 2");

Cela signifie qu’on peut évidemment définir plus de variables que prévu… Ouf! Et on peut même explicitement renvoyer une variable différente selon une condition remplie. Sympa non ?

[Java] JPA : Fetch dans une requête JPQL

[Java] JPA : Fetch dans une requête JPQL

Quand on crée des entités avec la Java Persistence API, on peut spécifier des jointures qui se présentent alors sous forme d’objets liés à d’autres entités, plutôt que sous la forme de variables comme des chaines ou des Long. Voici un exemple de relation entre un client et ses factures.

@Entity @Table(name = "CLIENT", schema = "maDb")
public class Client implements Serializable 
{ 
	@Column(name = "CLI_ID_CLIENT") 
	private Long l_idClient; 
	
	@OneToMany(mappedBy = "objClient") 
	private List<Facture> objListFactures; 
	
	// ... 
} 

@Entity @Table(name = "FACTURE", schema = "maDb")
public class Facture implements Serializable 
{ 
	@Column(name = "FAC_ID_FACTURE")
	private Long l_idFacture; 
	
	@ManyToOne @JoinColumn(name = "FAC_ID_CLIENT") 
	private Client objClient; 
	
	// ... 
}

Ce qu’on a fait :

  • Dans l’entité Client, on a créé une liste de factures, en spécifiant le tag @OneToMany (relation 1-N soit un client peut avoir plusieurs factures) avec pour attribut mappedBy le nom de la variable Client qui a été ajoutée dans notre entité Facture.
  • Dans cette dernière, on doit spécifier la relation dans l’autre sens, donc avec le tag @ManyToOne (relation N-1, soit plusieurs factures pour un même client).
  • On doit également ajouter le tag qui indiquera sur quelle colonne on effectue la jointure afin que la relation fonctionne (@JoinColumn + attribut name). Il faut indiquer le nom de la colonne tel que spécifié dans la base de données.

Par défaut, quand on récupérera plusieurs clients, leurs factures ne seront pas immédiatement chargées. C’est ce qu’on appelle “Lazy Loading” qui est le comportement par défaut quand on ne spécifie pas l’attribut fetch au niveau de la relation.

Afin de récupérer les factures lorsqu’on récupère un client, nous avons plusieurs choix qui se présentent à nous :

Spécifier l’attribut FETCH

On peut spécifier au niveau de la relation, l’attribut fetch avec la valeur EAGER. Cela signifie que pour chaque client on récupérera toujours ses factures, ce qui n’est probablement pas ce que l’on souhaite (attention aux temps d’accès et aux accès en cascade s’il y a d’autres entités liées de cette manière).

@OneToMany(mappedBy = "objClient", fetch = FetchType.EAGER) 
private List<Facture> objListFactures;

Accéder à la liste

Lorsqu’on récupère l’objet client depuis la base de données dans notre Session Bean, on peut accéder à la liste pour que son contenu soit chargé.

Client cliFromDb = ... ;
cliFromDb.getObjListFactures().size();

Créer une requête JPQL

On crée une requête avec la syntaxe JPQL, ce qu’on appelle des “Named queries“. Elles sont définies soit dans une méthode que vous aurez pris soin d’écrire, soit directement au niveau de l’entité, à l’aide des tags adéquats.

@NamedQueries({ 
	@NamedQuery( name = "Client.findClientByPkWithFactures",
		query = "select c from Client c 
			left join FETCH c.objListFactures 
			where c.l_idClient = :idClient") 
})

Exemple de classe qui définit des fonctions pour la gestion des clients. Ces fonctions seront appelées depuis notre Session Bean.

public class ClientManager 
{ 
	private EntityManager em; 
	
	public ClientManager(EntityManager em) 
	{ 
		this.em = em; 
	} 
	
	public Client findClientByPkWithFactures(Long pId) 
	{ 
		Query query = em.createNamedQuery( 
			"Client.findClientByPkWithFactures"
		).setParameter("idClient", pId);

		Client res = (Client) query
			.getSingleResult(); 
		return res; 
	} 
}

Bon développement !

[Java] Appel de méthode avec l’API Reflection

[Java] Appel de méthode avec l’API Reflection

Vous avez par exemple deux classes qui possèdent la même méthode (même nom, même signature) mais au moment de l’exécution, vous souhaitez choisir dynamiquement laquelle appeler à l’exécution. L’API Reflection permet d’effectuer cela de manière plutôt simple.

Supposons la classe suivante, “MaClasse1” dans laquelle on a écrit une fonction “getHello“, qui prend en paramètres une chaine de caractère, et un nombre quelconque dans un objet de type Long. La fonction retournera C1= suivi du contenu de la chaine. Dans l’exemple nous n’afficherons pas le nombre passé en second paramètre.

public class MaClasse1 
{
	public String getHello(String text, Long nbTest) 
	{ 
		return "C1=" + text;
	}
}

Nous créerons deux classes supplémentaires, appelées “MaClasse2” et “MaClasse3“, qui implémenteront la même fonction en retournant cette fois C2 ou C3, tous deux suivis du contenu du paramètre.

Class myClass = ...; 
Object instClasse = myClass.newInstance(); 
Class paramTypes[] = new Class[2]; 
paramTypes[0] = String.class; 
paramTypes[1] = Long.class; 
Method laMethode = myClass.getMethod(
	"getListeDestinataire",  
	lo_parameterType
); 

Object paramVal[] = new Object[2]; 
paramVal[0] = ...; 
paramVal[1] = 2L; 
System.out.println(
	laMethode.invoke(instClasse, paramVal)
);

Ce qu’on a fait :

  • Instancier un objet de classe en spécifiant (par exemple, dans une fonction), la classe (ex : MaClasse1.class) ou avec Class.forName(“Class FQN”).
  • Créer un tableau contenant les types de paramètres. Ce sera un tableau de classes. Dans notre cas, ce sont des objets classiques, mais on peut aussi utiliser Class.forName().
  • On crée un objet de type Method, en utilisant getMethod() de l’objet Class (pas l’instance). On passe en paramètres le nom de la fonction, et le tableau de types.
  • Créer un tableau, cette fois qui contiendra des éléments Object.
  • Spécifier les valeurs des paramètres.
  • Appeler la méthode invoke() de l’objet Method, avec en paramètres l’instance de la classe et les valeurs des différents paramètres.

Ainsi, si on écrit :

Class myClass = MaClasse2.class; 
// ... 
paramVal[0] = "test";

Lors de l’affichage, on obtiendra “C1=test”.

Dans ce petit bout de code on a donné les valeurs aux différents objets en “dur”… Il ne vous reste plus qu’à adapter tout ça 😉 Bon amusement !

[Java] Log d’un repository SVN

[Java] Log d’un repository SVN

Tout d’abord, télécharger SVNKit sur svnkit.com (lien ici). Choisir la version “standalone” puis décompresser les librairies qui se trouvent dans “lib”, à l’aide de WinRAR ou 7zip par exemple.

Ajoutez ensuite les différentes archives JAR à votre projet. Clic droit sur le projet dans Eclipse -> Properties -> Java Build Path, onglet Libraries. Cliquez sur le bouton “Add Library“, choisissez “User Library”. Indiquez un nom et sélectionnez les fichiers précédemment extraits.

Dans le code, il faut réaliser les manipulations suivantes :

DAVRepositoryFactory.setup();

On indique que l’on va interroger un repository SVN dont on renseignera l’URL, en HTTP ou HTTPS.

String ls_url = "http://xxx.com/svn/mySVN";
String ls_username = "user"; 
String ls_password = "password123";

Il faut ensuite définir quelques variables dont l’URL, le nom d’utilisateur et le mot de passe. Dans le code d’exemple ils sont en dur mais on peut imaginer les récupérer par une saisie utilisateur.

On va maintenant s’identifier.

sUrl = SVNEncodingUtil.autoURIEncode(ls_url); 
SVNRepository repo = SVNRepositoryFactory.create( 
	SVNURL.parseURIEncoded(sUrl)
); 

ISVNAuthenticationManager authManager = 
	SVNWCUtil.createDefaultAuthenticationManager( 
		ls_username, 
		ls_password.toCharArray()
	);

repo.setAuthenticationManager(lo_authManager);

On doit donc :

  • Instancier un objet SVNRepository à l’aide de la fabrique SVNRepositoryFactory. On passe en paramètre un objet SVNURL.
  • Créer un gestionnaire d’authentification à l’aide de la classe SVNWCUtil. On passe en paramètre de la fonction le nom d’utilisateur et le mot de passe (ce dernier étant passé sous forme de tableau de caractères).
  • Une fois celui-ci créé, on le lie au repository.

On peut savoir quelle est la dernière révision en appelant:

SVNDirEntry entry = repo.info(".", -1);

Pour récupérer les entrées entre deux révisions on procède comme suit :

Collection colLogEntries = null; 
colLogEntries = repo.log(
	new String[] {""}, 
	null, 
	lStartRevision, 
	lEndRevision, 
	true, 
	true
);

On déclare d’abord une collection dans laquelle on pourra stocker le résultat. Ensuite, on appelle la fonction “log” sur notre repository précédemment créé. On passe en paramètre un tableau vide (chemins cible), la valeur nulle pour le second paramètre (qui attend une collection), la révision de début, la révision de fin, et deux flags (“changed paths” – pour lister tout ce qui a été modifié, et “strict node”). Voir plus de détails dans la documentation.

Astuce : on peut aussi utiliser getDatedRevision(Date date) pour retrouver un numéro de révision correspondant à la date donnée en paramètre, ou du moins proche de celle-ci.

Parcourir la collection récupérée.

for(SVNLogEntry entry : colLogEntries) 
{ 
	System.out.println(
		logEntry.getRevision()
	); 
	
	System.out.println(
		logEntry.getAuthor()
	); 
	
	//... 
}

On peut afficher différentes informations (auteur, message, numéro de révision, date). Dans cette boucle, on peut en rajouter une autre :

Set changedPathsSet = 
	logEntry
		.getChangedPaths()
		.keySet(); 
for (String sFullPath : changedPathsSet) 
{ 
	SVNLogEntryPath entryPath = 
		(SVNLogEntryPath) logEntry.getChangedPaths()
			.get(sFullPath); 
	System.out.println(
		entryPath.getPath()
	); 
	
	System.out.println(
		entryPath.getType()
	); 
	
	//...
}

Ce qui va permettre d’afficher les chemins des fichiers modifiés et le “type” de modification (M = modified, A = added, D = deleted). Reste à afficher tout cela de manière bien plus sympa que dans une simple console…

Et pour fermer proprement la session :

repo.closeSession();

Voilà, c’est tout pour aujourd’hui !

[Java] Regrouper plusieurs PDF en un seul

[Java] Regrouper plusieurs PDF en un seul

Avec la librairie PDFBox, vous pouvez regrouper plusieurs documents PDF en un seul, de manière assez simple. Dans les exemples que je vais donner ici, j’ai utilisé la version 2.0.0-RC2. Vous la retrouverez sur la page de téléchargement du site web. Si vous utilisiez une version 1.x, n’hésitez pas à consulter le guide de migration et la liste de dépendances.

Si vos fichiers ne sont pas protégés contre l’ouverture et/ou la modification alors le code est relativement simple à mettre en place.

PDFMergerUtility ut = new PDFMergerUtility();
ut.addSource(FICHIER1); 
ut.addSource(FICHIER2); 
ut.addSource(FICHIER3); 
ut.setDestinationFileName(NOM_FICHIER); 
ut.mergeDocuments();

Nous utiliserons une instance de la classe PDFMergerUtility, du package “org.apache.pdfbox.multipdf” (ceci vous sera utile pour l’import). Il faut ensuite ajouter chaque fichier source afin de constituer la liste des fichiers à regrouper. Le paramètre de la fonction addSource() peut être une chaine de caractères représentant le chemin du fichier, un objet de type File, ou un objet de type InputStream.

Une fois tous les éléments ajoutés, il faut spécifier le nom du fichier destination avec setDestinationFileName(). Cette fonction est disponible pour notre objet PDFMergerUtility précédemment créé. Ensuite, comme le montre l’exemple, on demande à ce que les documents soient tous regroupés. On effectue cette action avec la fonction mergeDocuments().

Si par contre vos fichiers sont protégés contre la modification (par exemple, à l’aide d’un mot de passe), alors il faudra procéder de manière légèrement différente.

final java.util.ArrayList tobeclosed = 
	new java.util.ArrayList<>(); 
try (PDDocument dest = new PDDocument()) 
{ 
	PDDocument src1 = PDDocument.load(fic1,password); 
	src1.setAllSecurityToBeRemoved(true); 
	src1.save(fic1); 
	merge.appendDocument(dest, src1); 
	tobeclosed.add(src1); 
	
	PDDocument src2 = PDDocument.load(fic2,password); 
	src1.setAllSecurityToBeRemoved(true); 
	src1.save(fic2); 
	merge.appendDocument(dest, src2); 
	tobeclosed.add(src2); 
	
	//... 
}

On a tout d’abord déclaré un tableau – nommé “tobeclosed” – auquel on ajoutera tous les documents PDF qu’on souhaite regrouper, car ils vont être ouverts pour pouvoir être ajoutés à tour de rôle. Il ne faut pas les fermer avant que le document final ne soit sauvegardé. Pour chaque document, on déclare donc une instance de la classe PDDocument, et on appelle la fonction load() de celle-ci pour lancer le chargement. La fonction prendra en paramètre une variable de type File et le mot de passe.

On fait ensuite en sorte d’enlever toutes les protections du document. Pour cela l’objet dispose d’une fonction appelée setAllSecurityToBeRemoved(). En paramètre, on lui donne un booléen dont la valeur est Vrai. On sauve ensuite le document, à l’aide de save() en spécifiant à nouveau l’objet File.

Le document sauvé, on va ajouter le document aux autres, à l’aide de la fonction appendDocument() de notre objet PDFMergerUtility. Les 2 paramètres sont de type PDDocument. On donne, dans l’ordre, le document de destination et ensuite le document source à ajouter. Ensuite, il faut ajouter ce dernier dans le fameux tableau qu’on a créé au tout début (voir l’exemple).

Une fois tous les fichiers sélectionnés…

dest.save(f);

Le document de destination doit être sauvegardé – dest.save(). Il faut ajouter cette instruction au code d’exemple précédent, avant l’accolade de fermeture de la clause “try“.

Un petit conseil : si vous devez laisser la protection active dans les fichiers d’origine, alors il vaut mieux passer par des copies. Dans mon cas je copie les PDF dans le répertoire temporaire de l’utilisateur, puis j’effectue le traitement de regroupement. Petite astuce pour obtenir le répertoire temporaire (à partir du JDK 7) :

String tmpFolder = System.getProperty("java.io.tmpdir");

Bon développement !

[Java] Parser alternatif avec PDFBox 1.8

[Java] Parser alternatif avec PDFBox 1.8

Dans les versions de PDFBox 1.8, un parser “non-séquentiel” a été mis en place, mais celui-ci n’est pas utilisé par défaut. Ainsi, au lieu d’utiliser la fonction “load()“, comme expliqué dans le billet du 26 mai 2015, on peut utiliser “loadNonSeq()” pour charger le document PDF. En fait cela est vivement recommandé, d’après ce qu’on lit un peu partout sur internet. Dans mon cas, j’obtenais une erreur “incorrect header check” lorsque j’utilisais l’ancien parser pour certains fichiers. En utilisant la seconde fonction ci-dessus, j’ai pu régler le problème. Exemple :

try (
	PDDocument document = PDDocument.loadNonSeq(f,null)
) { 
	document.silentPrint(printJob); 
}

Lors de l’exécution, j’ai rencontré une petite erreur de classe non trouvée ; la classe mentionnée était en lien avec Bouncy Castle. En effet, PDFBox utilise les algorithmes de cryptage et décryptage de ces librairies plutôt que d’en implémenter de nouvelles. S’il vous manque autre chose, consultez la page “dependencies“.Dans la version 2, le nouveau parser remplace complètement l’ancien.

[Java] Jsoup HTML Parser

[Java] Jsoup HTML Parser

Il arrive parfois que l’on retrouve des fichiers dans un format Excel qui ne le sont pas vraiment. En y regardant de plus près et même si l’extension est “xls“, on peut tomber nez-à-nez avec un fichier HTML qui contient un tableau. Dans notre cas il a fallu transformer le fichier suivant en un fichier CSV (séparateur “;”). Avec Jsoup, c’est beaucoup plus facile à réaliser ! Il va “parser” le contenu du fichier donné en paramètre… il ne reste plus qu’à manipuler et traiter les différents éléments.

<html xmlns:x="urn:schemas-microsoft-com:office:excel"> 
<table border="1" columns="3"> 
 <tr> 
  <th>Header1</th> 
  <th>Header2</th> 
  <th>Header3</th> 
 </tr> 
 <tr> 
  <td>LaDonnee1</th> 
  <td>LaDonnee2</th> 
  <td>LaDonnee3</th> 
 </tr> 
</table> 
</html>

Ensuite nous allons simplement utiliser l’API Jsoup pour manipuler les différents éléments afin de tout mettre dans un fichier CSV (encodage ANSI). Ici chaque ligne du tableau en HTML va être copiée dans le CSV avec, entre chaque “colonne”, le séparateur “;”. Une ligne dans le fichier texte = une ligne du tableau HTML.

File input = new File(args[0]); 
File outpt = new File(args[1]); 
Document doc = Jsoup.parse(input, "UTF-8"); 
Elements tbl = doc.select("tr"); 
try (FileOutputStream fos = new FileOutputStream(outpt); 
	BufferedWriter bw = new BufferedWriter( 
		new OutputStreamWriter(fos,"UTF-8"))) 
{ 
	for (Element trb : tbl) 
	{ 
		Elements td = trb.children(); 
		i = 1; 
		for (Element tdb : td) 
		{ 
			if (i==1) 
			{ 
				bw.write(tdb.text()); 
			}
			else 
			{ 
				bw.write(";"+tdb.text()); 
			} 
			
			i++; 
		} 
		
		bw.newLine(); 
	} 
}

En résumé, voici ce qu’on a réalisé :

  • Parsing du document avec la fonction Jsoup.Parse(). Les paramètres sont le fichier source et le jeu d’encodage (charset). Elle renverra une exception si le fichier n’a pas pu être chargé.
  • Sélection de tous les éléments “tr” avec la méthode Document.select(). Celle-ci doit être appelée directement par l’instance qui a été créée ci-dessus.
  • On va créer un flux de sortie pour le fichier destination (FileOutputStream) et l’objet qui va nous permettre d’y écrire (BufferedWriter). On en profite pour déterminer le jeu d’encodage de caractères.
  • On parcourt l’ensemble des lignes à l’aide de l’instruction “for”.
  • On récupère les éléments “enfants”.
  • Parmi ces éléments on va considérer qu’il s’agit des lignes. Il faut vérifier plus attentivement mais il semblerait que la méthode “children()” renvoie les éléments qui devraient logiquement apparaitre selon les spécifications de la syntaxe. En effet, durant mes tests, j’ai vu l’élément “tbody” apparaitre, or il n’existe pas dans mon fichier source.
  • Parcourir l’ensemble des cellules (instruction “for”).
  • Écriture des données des cellules à l’aide de la fonction bw.write().
  • On rajoute une nouvelle ligne dans notre CSV avant le prochain tour de boucle à l’aide de la méthode bw.newLine().
  • Les ressources sont automatiquement libérées avec cette syntaxe de “try” Rien n’empêche d’écrire tout ça à l’ancienne!

Et voilà… bien sûr, cela reste très basique et ce code pourrait être amélioré afin de répondre à d’autres cas plus spécifiques. Bon développement et bonne découverte à tous.