vendredi 30 juillet 2010

Rétro-conception UML de classes par introspection java


Objectif

Utiliser les mécanismes d’introspection java pour afficher un diagramme de classes UML.

Introduction

Hier je me suis demandé s’il était faisable de générer un diagramme UML à partir d’une classe de base Java. Après une nuit blanche passée à plancher sur le problème, voici la première version d’un outil développé en 24 heures pouvant se révéler fort pratique et qui va bientôt concurrencer doxygen ou umlGraph ;)

Pseudo cahier des charges

  • Traiter toutes les classes dépendantes d’une classe mère ( ou classe de base )
  • Ne pas introspecter les packages java natifs ( commençant par java, javax et sun )
  • Afficher les superClasses, les interfaces et les classes utilisées par les attributs de classe
  • Afficher un diagramme UML global au format PNG à partir de l’outil graphviz

Améliorations ( version 1 )

  • Traiter toutes les classes d’une arborescence ( package ou racine )
  • Discriminer les interfaces des classes normales
  • Mettre les superclasses au dessus des classes spécialisées

Améliorations ( version 2 )

  • Faire des sous-graphes par packages
  • Gérer en objet les éléments Graphviz
  • Paramètres gérés : afficher les détails, détailler les interfaces, afficher les superclasses

Améliorations possibles

  • Traiter des jar
  • Générer un fichier petal rose
  • Remplir un fichier de propriétés avec les informations ( quasiment déjà intégré depuis la version 0 )

Diagramme UML de l’outil ( version 0 )

Diagramme UML de l’outil ( version 1 )

Diagramme UML utilisé pour les tests

Diagrammes UML utilisés pour les tests ( version 2 )

 

Pour approfondir les sujets

samedi 17 juillet 2010

comparatif de quelques extracteurs de PDF écrits en java


Objectif

Comparer 3 extracteurs de PDF écrits en java : pdfbox 1.2.0, pdfrenderer et bfopdf.

Introduction

Toujours pour récupérer des données issues de pdf, il faut extraire des images ou plus généralement des pages à partir des pages de pdf. Pour ce faire, j’ai testé 3 solutions gratuites permettant de le faire : pdfbox 1.1.0 puis 1.2.1 puis finalement 1.2.0, pdfrenderer et un produit contraint gratuitement : bfopdf.
40 pdf téléchargés et 8 pdf générés avec PDFCreator ont été utilisés pour les tests : 40 pdf issus des sites revell et monogram représentant des notices d’instructions de maquettes plastiques, plus 7 fichiers générés par PDFCreator à partir d’un même fichier, un arc-en-ciel initialement en png,  converti en  jpg, gif, bmp 1 bit ( 2 couleurs), bmp 4 bits ( 16 couleurs ), bmp 8 bits ( 256 couleurs ) et 24 bits ( « couleurs » ), et un utilisant toutes les images.

Protocole de tests

  • Télécharger les jar depuis les sites respectifs
  • Créer un projet eclipse avec 3 packages, pour chaque package,
  • Créer une classe qui liste les pdf du répertoire en question et qui appelle une classe ( thread ) d’extraction des pages ( png, rgb ou argb, 72 dpi ) généralement trouvée dans les cookbooks, tutoriaux et exemples. Ceci est dû à la non-gestion des exceptions java dans certains cas.
  • Analyser les résultats qualitativement et quantitativement selon le tableau ci-dessous

Critère
Pdfbox 1.2.0
pdfrenderer
Bfopdf 2.1.11
Observation
Vitesse d’exécution
19
11
10
Minutes
Pages générées
491
433
503
Certaines l’étant partiellement suite aux cas non gérés dans la norme pdf ( ps compressé ) ou des exceptions
Précision des dessins



Bfopdf ne génère qu’en 72 dpi, pdfbox et pdfrenderer, paramétrables,  ont été contraints à ce facteur limitant
Png ok
Ok
Ok
Ok

Jpg ok
Ok
Ok
Ok

Gif ok

Ok
Ok

Bmp 2 couleurs

Ok
Ok

Bmp 16 couleurs

Ok
Ok

Bmp 256 couleurs

Ok
Ok

Bmp couleurs
Ok
Ok
Ok

Taille d’une page de référence sortie en png
72
177
153
Ko ( en rgb ou en argb )
Argb = rgb + luminosité
Page 11 de la notice du t-80 ud référencé 03078.pdf chez revell
595 * 842 pixels
Qualité des textes
moyenne
ok
ok
En 72 dpi. Ils sont mieux écrits en 98 dpi.
Qualité des couleurs
Contrastés
Grillage trop contrasté
Trop contrasté manque de détails
Grillage monochrome noir
Manque de contraste
Grillage acceptable
Pour les détails, voir comment ressort le grillage du moteur à l’arrière de la tourelle, le grillage ressort différemment
Utilisabilité de l’image
ok
ok
Limitée …
Un gros « DEMO » rose s’affiche en surimpression avec la version gratuite. A traiter pour l’enlever. A voir pour le faire.
Rendu des dessins incomplets
Pavés noirs, images en négatif, des pixels défectueux
Pages vierges, dessins absents
R.A.S !

Sources fournis
oui
oui
Non

Verbosité des messages d’erreurs
très verbeux
Assez peu verbeux
Peu verbeux
Souvent les messages annoncent les fonctions PS non gérées ou les traces d’erreur java

Quelques compléments

  • La dernière version stable de pdfbox au moment de l’écriture de ce billet et buggée. Elle est sensée corrigée la version 1.2.0 pour du texte mais elle restitue nettement moins d’images, d'où l’utilisation de l’avant dernière version.
  • 72 dpi est la définition sur Mac, les PC utilisent 98 dpi, et pdfbox est bien meilleur avec une plus grande définition.
  • Bfopdf indique sur son site que le tiff en 200 dpi monochrome est excellent pour être faxé
  • Pdfrenderer ne fournit pas de n° de version pour ses jars. Au mieux, nous avons accès au svn de l’application.
  • Il faut utiliser une poignée de jar externes en plus de pdfbox lui-même pour utiliser les classes. Il y a peut être une incompatibilité de versions qui expliquerait les défaillances de pdfbox 1.2.1
  • Pdfrenderer, qui ne fait que visualiser des pdf, peut être utilisée avec des applets avec l’api Graphics2D.

Pour approfondir les sujets

Pdf guide de référence : http://www.adobe.com/devnet/pdf/pdfs/PDFReference.pdf ( entre autre pour le codage des opérateurs )

pdfbox
pdfrenderer
bfopdf




vendredi 9 juillet 2010

Ponce Pilate s'en serait lavé les mains

Introduction

Encore un bon court article dans le « Cerveau et Psycho » de ces vacances : comment choisir entre 2 possibilités quasi-équivalentes mais chronophages ?

Ponce Pilate s’en serait lavé les mains ?

Tout le monde connaît la réaction du préfet (procurateur) de Judée. Même si certains disent que cela n’est qu’une légende.
Cependant d’après un article tiré de « Sciences » [S.Lee & N.Schwarz, Sciences, 2010, vol 328, p 709] le fait de se laver les mains permettrait de se libérer l’esprit et de pouvoir discriminer son jugement entre 2 choix autant chronophages qu’à faible valeur ajoutée sinon plus, du style :
  • Poulet ou viande à midi ?
  • Chewing-gum aux fruits rouges ou aux fruits des bois ?
  • Entrée porte de gauche ou porte de droite ?

Du symbolisme au cas pratique

Honnêtement, qui pourrait croire que ce genre de comportement (se laver les mains est synonyme de purification de la conscience) permettrait d’enfin se décider en tout état de cause ?
A vous de faire le test !

Pour approfondir les sujets

Cerveau et Psycho : n° 40 juillet/août 2010 p 10

vendredi 28 mai 2010

copie iso-fonctionnelle de données Oracle avec java


Objectif

Copier le schéma Scott d’Oracle sur le Schéma d’un user nouvellement créé. En utilisant les outils de migration standard Oracle et un très simple programme de copie.

Introduction

Copier des bases de données entre différents supports, c’est possible à condition que les types de données soient compatibles.
Si l’on regarde et exécute les outils d’import export d’Oracle ( cf. http://oracle.developpez.com/guide/sauvegarde/generalites/ par exemple ) , nous pouvons faire très simple … ou très compliqué selon le cas.
Il faut prendre en compte pas mal de choses :
  • Les séquences ( pour ceux qui ont envie de gérer les séquences ).
  • concernant Access, il faut différencier les types Integer et Counter ( java.sql.Types.4 ), qui est un Integer auto-incrémenté
  • les types non-compatibles ( voir les autres billets )
  • Les objets non compatibles sql92 et sql99 comme les LOB et tous les autres non connus par Java.
  • etc.
Pour rester simple, voici expliqué un bout de code simple et concis permettant de gérer la recopie du schéma Scott d’Oracle.

Procédure

  • Exporter le schéma Scott
  • Créer le schéma testJdbc
  • Importer le schéma Scott dans testJdbc
  • Vérifier la présence des tables
  • Lancer la classe java de recopie

Exporter le schéma Scott

Taper en ligne de commande sous Windows :
exp scott/tiger file=/schemaScottVide.dmp rows=n

Créer l’utilisateur testJdbc

Taper en ligne de commande sous Windows :
sqlplus system/manager
drop user testjdbc cascade;
create user testjdbc identified by testjdbc;
grant connect , resource  to testjdbc;
grant CREATE SESSION to testjdbc;
conn testjdbc/testjdbc
exit

Importer le schéma Scott dans testJdbc

Taper en ligne de commande sous Windows :
imp testjdbc/testjdbc file=/schemaScottVide.dmp commit=y fromuser=scott touser=testjdbc

Vérifier la présence des tables

Taper en ligne de commande sous Windows :
sqlplus testjdbc/testjdbc
select table_name from cat

Le code permettant la copie

package speh.jdbc.test.exec;

import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Vector;

import speh.jdbc.core.DBUtilities;
import speh.jdbc.core.UneBdD;

public class CopySchemaScottPreparedStatement
{
      //      ===============================================================================    
      DBUtilities dbi = null ;
      DBUtilities dbo = null ;
      Vector types ;
      ResultSet rsIn ;
      PreparedStatement pstmt;
      //      ===============================================================================    
      public CopySchemaScottPreparedStatement( String _in , String _out , boolean _mustDeleteBefore , String _tables[] )
      {
            //
            // connexions schemas in et out
            //
            System.out.println ( "connexion" ) ;
            UneBdD bddIn = new UneBdD()._getByAlias( _in , true ) ;
            dbi = new DBUtilities(bddIn) ;
            UneBdD bddOut = new UneBdD()._getByAlias( _out , true ) ;
            dbo = new DBUtilities(bddOut) ;
            //
            // vider contenus si demande
            // remarque : vider sens inverse insert cause FK
            //
            if ( _mustDeleteBefore )
            {
                  for ( int i = _tables.length -1  ; i > -1  ; i-- )
                  {
                        System.out.println( "delete " + _tables[i] + " ..." ) ;
                        boolean b = dbo._executerDeleteTable( _tables[i] , true ) ;
                  }
            }
            //
            // recuperer structure types jdbc ( java.sql.Types.xxx )
            // copier tuple par tuple, champ par champ
            //
            int nbTables = 0 ;
            for ( int i = 0 ; i < _tables.length ; i++ )
            {
                  System.out.println( "copy " + _tables[i] + " ..." ) ;
                  _structureTable( _tables[i] ) ;
                  if ( types.size() > 0 )
                  {
                        _copyTable( _tables[i] ) ;
                        nbTables++ ;
                  }
            }
            System.out.println( "tables traitées : " + nbTables ) ;
            //
            // deconnexions schemas in et out
            //
            dbi._deConnexion() ;
            dbo._deConnexion() ;
            System.out.println ( "déconnexion" ) ;
      }
      //      ===============================================================================    
      void _structureTable( String _tableName )
      {
            //
            // recuperer structure types jdbc ( java.sql.Types.xxx )
            // teste pas catalogue et schema : peut y avoir ambiguite
            //
            types = new Vector() ;
            try
            {
                  DatabaseMetaData meta;
                  meta = dbi.conn.getMetaData();
                  ResultSet rs = meta.getColumns(null, null, _tableName, null);
                  while ( rs.next() )
                  {
                        int type = rs.getInt("DATA_TYPE") ;
                        types.add(type) ;
                  }
                  rs.close();
            }
            catch (SQLException e)
            {
                  e.printStackTrace();
                  System.err.println( "STOP." ) ;
                  System.exit(0) ;
            }
      }
      //      ===============================================================================    
      void _copyTable( String _tableName )
      {
            //
            // cree un PreparedStatement pour faciliter copy
            // et eviter les transcodages de champs affiches
            //
            String sql1 = "INSERT INTO " ;
            String sql2 = " VALUES( ? " ;
            String sql3 = " )" ;
            String sqlRepet = ", ? " ;
            String sqlInsert = sql1 + _tableName + sql2 ;
            for ( int i = 1 ; i < types.size() ; i++ )
            {
                  sqlInsert = sqlInsert + sqlRepet ;
            }
            sqlInsert = sqlInsert + sql3 ;

            //
            // lecture toute la table en entree
            //
            String selectSql = "select * from " + _tableName ;
            rsIn = dbi._executerSelect(selectSql) ;
            try
            {
                  pstmt = dbo.conn.prepareStatement(sqlInsert);
                  //
                  // recopier tuple par tuple, champ par champ
                  //
                  int nbTuples = 0 ;
                  while (rsIn.next())
                  {
                        for ( int ix = 1 ; ix < types.size() + 1 ; ix++ )
                        {
                              int type = Integer.parseInt(types.elementAt(ix-1).toString()) ;
                              switch (type)
                              {
                              case java.sql.Types.DECIMAL: // 3
                                    pstmt.setBigDecimal( ix , rsIn.getBigDecimal(ix) );
                                    break;
                              case java.sql.Types.VARCHAR: // 12 
                                    pstmt.setString( ix , rsIn.getString(ix) ); 
                                    break;
                              case java.sql.Types.TIMESTAMP: // 93 
                                    pstmt.setTimestamp( ix , rsIn.getTimestamp(ix) ); 
                                    break;
                              default: System.out.println("pas trouvé type # " + type); System.exit(0) ; break;
                              }
                        }
                        pstmt.executeUpdate();
                        nbTuples++ ;
                  }
                  System.out.println("   ... tuples traités : " + nbTuples ) ;
            }
            catch (SQLException e)
            {
                  e.printStackTrace();
                  try
                  {
                        dbo.conn.rollback() ;
                  }
                  catch (SQLException e1)
                  {
                        e1.printStackTrace();
                  }
                  System.out.println("STOP.") ;
                  System.exit(0) ;
            }
      }
      //      ===============================================================================    
      public static void main( String args[] )
      {
            //
            // alias des schemas in et out
            // tableau des tables a traiter dans l'ordre des insert
            // qui respecte les FK pre-existantes
            //
            String in = "jdbcOracleMabase" ;
            String out = "jdbcOracleTestJdbc" ;
            String tables[] = {"ACCOUNT","BONUS","RECEIPT","SALGRADE","DEPT","EMP" } ;

            CopySchemaScottPreparedStatement appli = new CopySchemaScottPreparedStatement(in, out, true , tables) ;
            appli.toString() ;
            System.out.println ( "Traitement terminé." ) ;
      }
      //      ===============================================================================    
}

Quelques commentaires

  • les classes DBUtilities et UneBdD ne sont là que pour gérer l’interface avec les bases de données déclarées dans un fichier properties
  • l’utilisation d’un PreparedStatement est bien pratique parce qu’elle permet d’éviter d’écrire en toutes lettres les ordres INSERT dans les formats propriétaires – notamment aux dates –
  • il faut rapprocher les types java/jdbc gérés des méta-données et les ordres resultSet.getXXX et setXXX correspondant
  • les ordres resultSet.setNull n’ont pas à être gérés en valorisant directement avec pstmt.setString( ix , rsIn.getString(ix) );
  • les tables à copier sont écrites dans l’ordres des contraintes de clés étrangères à vérifier, les plus contraintes à la fin.

Trace de la sortie

connexion
charge les bd recensées dans bin/speh/jdbc/sgbd.properties
  +- charge jdbcMysqlScottTiger
  +- charge jdbcOracleMabase
  +- charge jdbcOracleTestJdbc
  +- charge jdbcSqlServerAdventureWorks
  +- charge odbcAccess2000
  +- charge odbcAccess97
  +- charge odbcAccessDeluxecd
  +- charge odbcDbase
  +- charge odbcDbaseSourcedbase
  +- charge odbcMabase
  +- charge odbcSqlServerScottTiger
delete EMP ...
 rc = 14
delete DEPT ...
 rc = 4
delete SALGRADE ...
 rc = 5
delete RECEIPT ...
 rc = 1
delete BONUS ...
 rc = 0
delete ACCOUNT ...
 rc = 5
copy ACCOUNT ...
   ... tuples traités : 5
copy BONUS ...
   ... tuples traités : 0
copy RECEIPT ...
   ... tuples traités : 1
copy SALGRADE ...
   ... tuples traités : 5
copy DEPT ...
   ... tuples traités : 4
copy EMP ...
   ... tuples traités : 14
tables traitées : 6
déconnexion
Traitement terminé.

Pour approfondir les sujets


lundi 24 mai 2010

Installer SQL Server et tester les types java jdbc


Objectif

Installer SQL Server express 2005 et copier le schéma Scott d’Oracle

Introduction

Utiliser une vraie base de données chez Microsoft, c’est possible : SQL Server ( ex Sybase pour ceux qui connaissent l’histoire (http://en.wikipedia.org/wiki/Microsoft_SQL_Server de codename yukon ;) pour la version 2005 donc ).
Autant essayer cette dernière avec le driver jdbc fourni par Microsoft ( il y en a autre chez sourceforge (http://jtds.sourceforge.net/ )

Installer « sql server express 2005 »

Pour ce faire, aller sur le site de Microsoft, télécharger la base de données et toutes les dépendances nécessaires ( i.e. mdac ). Je conseille fortement l’installation de SSMSEE « sql server management studio express edition » pour gérer la base de données installée, l’utilisation de la base de données « adventureWorks » étant pour le moins hasardeuse sans configuration.
Pour jdbc, il faut télécharger « sqljdbc_1.2.2828.100_fra.exe » qui est en fait un zip à décompresser. Récupérer le jar ( sqljdbc.jar ) et placer le fichier « sqljdbc_auth.dll » dans le répertoire [win]/system32 … Un peu de configuration sur les connexions réseau et redémarrez, cela pourrait fonctionner correctement.

Lister les types de données compatibles java

Lister les types de données pour les bases installées permet de voir plusieurs choses :

  • Il existe un type de données sql_variant qui rappelle fortement le type variant utilisé avec VB.
  • Parmi les types dates, SQL Serveur ne reconnaît que le type « timestamp », pas les types « date » et « time » ( voir plus loin pour les problèmes liés )
  • Le type long binaire s’appelle « image »


Essayer de copier le schéma Scott

Pour ce faire, l’outil que j’ai développé va très bien ( en théorie ),
Choisir la base en entrée, celle en sortie ( l’outil affiche les tables liées, on peut facilement supprimer des tables existantes ) et cliquer sur « copier »
Trois tables sont copiées ( « account », « bonus » et « dept » ), l’outil stoppe sur le problème de conversion du format « date » de la colonne « hiredate » de la table « emp ».

Résolution du problème

Pour résoudre le problème, il faut forcer une conversion du type 91 oracle « date » dans le type 93 Sql Server « timestamp », ce qui avait déjà été fait pour la gestion des types numériques dans les bases Dbase ( type 8 = java Double )

Pour approfondir les sujets





mardi 11 mai 2010

transcodage de données inter-bases


Objectif

Transcoder des types de données de bases de données à base de données.

Introduction

Ces derniers temps, je m’occupe à un projet auquel je pense depuis une bonne dizaine d’années : un convertisseur de bases de données multi-bases en java. Sql Server utilise un produit spécifique dénommé DTS pour migrer des bases.
Parler de l’application et de ses fonctionnalités fera l’objet d’un autre commentaire, mais pour l’instant, regardons le seul problème qui me reste à gérer : les transcodages de données inter-bases.

Les types de données jdbc / sql / java

Depuis l’élaboration de jdbc v 1.0, il y a correspondance entre la plupart des types de données java et leurs types SQL ( ou jdbc ) correspondants. Le problème tient au fait que les types de données jdbc/sql ne portent pas le même nom, ni le même format.
Problème 1 : le type jdbc n° 8 : le DOUBLE, NUMERIC sous DBASE, FLOAT avec Oracle et REAL [(m,d)] sous MySql. L’exploration des méta-données permet de récupérer les éventuels paramètres. Pour gérer le transcodage, il suffit de connaître la correspondance des types de champs entre la base source et la base destination.
Problème 2 : les types n’existant pas dans certains SGBD comme les LOB avec l’antique base de données fichier mono-table DBASE.
Etc.

Image illustrant le problème

L’image ci-dessous reprend les types de données présents ( donc pas forcément tous représentés ) dans l’éventuel schéma utilisateur.
Cet exemple affiche les colonnes dataType jdbc, type java, sous Access, sous DBase, sous MySQL, et sous Oracle.

Images illustrant la solution

Utiliser un PreparedStatement en java serait illusoire, étant donné que la définition de la table n’est pas hard-codée en java, néanmoins, l’utilisation des ordres DDL SQL standards fonctionne très bien comme l’illustre les ordres ci-dessous avec une des tables du schema Scott des bases de données Oracle :
CREATE TABLE EMP ( EMPNO DECIMAL(4,0) ,  ENAME char(10) ,  JOB char(9) ,  MGR DECIMAL(4,0) ,  HIREDATE DATETIME ,  SAL DECIMAL(7,2) ,  COMM DECIMAL(7,2) ,  DEPTNO DECIMAL(2,0) , primary key ( EMPNO ) )
Et
insert into EMP values ('7369' , 'SMITH' , 'CLERK' , '7902' , '1980-12-17 00:00:00.0' , '800.00' ,  null  , '20' )


Pour approfondir les sujets