Ziirish's Home :: Blog

Ziirish's Pub

 
 

Aujourd'hui, je discutais d'introspection avec un collègue, et je me suis dit que ça pourrait en intéresser certains ici (ou pas).

Qu'est-ce que l'introspection ? C'est grosso-modo la capacité de se voir de l'intérieur.

Du temps où j'étais "développeur", j'ai utilisé cette technique, parce qu'à l'époque déjà, j'étais un sacré fainéant :) Je m'étais donc mis en tête de développer le bout de code le plus générique possible pour m'en servir à toutes les sauces par la suite. De ce que j'ai compris, ça se rapproche du data-driven de Python. Ici je vais vous présenter une implémentation en Java.

Prenons la classe Container suivante. Elle ne sert que de "conteneur".


public class Container {
    private Test t;
    
    public Container (Test t) {
        this.t = t;
    }
    
    public Test getTest () {
        return this.t;
    }
}

Puis une classe Test qui contiendra notre jeu de données.


public class Test {
    String name;
    int age;
    Test son;
    
    public Test (String name, int age, Test son) {
        this.name = name;
        this.age = age;
        this.son = son;
    }
    
    public String toString () {
        return super.toString() + ": " + name + " (" + age + ")";
    }
    
    public Test getSon () {
        return this.son;
    }
}

Et enfin, le code qui va procéder à l'introspection.


import java.util.*;
import java.lang.reflect.*;

public class Main {
    /**
     * On "introspecte" notre liste.
     * @param list La liste à parcourir
     * @param separator Le séparateur entre deux "objets" de la liste
     * @param func La fonction a appeler à chaque tour de boucle
     * @return La chaîne de caractères qui servira à remplir notre div
     * @throws SecurityException
     * @throws NoSuchMethodException
     * @throws IllegalArgumentException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    private static <T> String introspecte (List <T> list, String separator, String func) 
        throws SecurityException, NoSuchMethodException,
        IllegalArgumentException, IllegalAccessException,
        InvocationTargetException {
    
        final int tailleInt = 100 * list.size();
        int i = 1;
        // notre buffer représentant le contenu de la div
        StringBuffer valuesStr = new StringBuffer(tailleInt);
        // On parcourt notre liste
        for (T localObj : list) {
            Method method = null;
            T localTmpObj = localObj;
            // on split sur . pour appeler successivement différentes fonctions
            String[] funcTab = func.split("[.]");
            for (int j=0; j<funcTab.length; j++) {
                // On récupère notre méthode. Ici, comme on récupère un getter, il n'y a pas de paramètre à lui donner, d'où le 'null'
                method = localTmpObj.getClass().getMethod(funcTab[j], null);
                // On invoque ensuite notre méthode, sauf si on est arrivé au dernier getter
                if (j<funcTab.length-1) {
                    localTmpObj = (T) method.invoke(localTmpObj, (Object[])null);
                }
            }
            // Pour la fin, on invoque de manière "particulière" parce qu'on veut une String que l'on ajoute directement à notre buffer
            if (method.invoke(localTmpObj, (Object[])null) != null) {
                valuesStr.append(method.invoke(localTmpObj, (Object[])null).toString());
            }
            if (i<list.size()) {
                valuesStr.append(separator);
            }
            i++;
        }
    
        return valuesStr.toString();
    }
    
    public static void main (String[] args) {
        System.out.println("Hello World");
        Test t1 = new Test("blah",19, null);
        Test t2 = new Test("blih",18, t1);
        Test t3 = new Test("bloh",20, t2);
        Container c1 = new Container(t1);
        Container c2 = new Container(t2);
        Container c3 = new Container(t3);
        List<Container> list = new ArrayList<Container>();
        list.add(c1);
        list.add(c2);
        list.add(c3);
        try {
            System.out.println(Main.introspecte(list," | ","getTest"));
            System.out.println(Main.introspecte(list," ","getTest.getSon"));
        } catch (Exception e) {
            System.err.println (e);
        }
    }
 
}

Il ressort de cela un code certes un peu plus complexe, mais réutilisable (pratiquement) à volonté.

En réalité, je l'ai utilisé dans le cadre d'un projet J2EE, j'y avais défini un tag basé sur cette fonction, ce tag étant utilisé dans tous nos écrans de consultation.

Malheureusement, personne n'a semblé très intéressé par tant de puissance autour de moi, du coup, je suis devenu admin !

Mais franchement, ce genre de code incarne pour moi la sexytude absolue.

Résultat :


$ java Main      
Hello World
Test@1034bb5: blah (19) | Test@7f5f5897: blih (18) | Test@4cb162d5: bloh (20)
 Test@1034bb5: blah (19) Test@7f5f5897: blih (18)

Au final, c'est quelque chose de totalement adapté à de la consultation, et ça évite d'avoir à écrire un foreach par type d'objet, soit 40 foreach si on a 40 types.