Série 10 – Flots et cryptographie : corrigé

Introduction

Le code du corrigé est disponible sous la forme d'une archive Zip. Les solutions aux différents exercices sont brièvement discutées ci-dessous.

Exercice 1 – flot « OU exclusif »

La classe XOrInputStream est très simple à écrire : conformément au patron Composite, elle étend la classe abstraite InputStream et est paramétrisée par les deux flots qu'elle combine, nommés s1 et s2.

La méthode read du flot combiné doit simplement obtenir le prochain octet de chacun des deux flots. Si l'un des deux flots est épuisé, alors le flot combiné l'est aussi. Sinon, son prochain octet est le OU exclusif des deux octets lus. La méthode read ressemble donc à ceci :

public int read() throws IOException {
    int b1 = s1.read();
    int b2 = s2.read();
    if (b1 == -1 || b2 == -1)
        return -1;
    else
        return b1 ^ b2;
}

La méthode close quant à elle est triviale et ne fait rien d'autre que fermer les deux flots combinés :

public void close() throws IOException {
    s1.close();
    s2.close();
}

Au moyen de ce flot combiné il est facile d'écrire un programme décodant le ficher encrypté donné. On obtient alors le texte du télégramme Zimmermann.

Exercice 2 – Itérateur pour flots

L'adaptateur de flot d'entrée en objet « itérable » (c-à-d la classe InputStreamToIterable) ne contient que la méthode iterator qui retourne un itérateur ressemblant beaucoup à l'itérateur filtrant de la série 8. En effet, pour savoir que retourner depuis sa méthode hasNext, l'itérateur de l'adaptateur est obligé de tenter de lire le flot qu'il adapte, tout comme l'itérateur filtrant devait obtenir un élément de l'itérateur filtré. Si le flot fournit une valeur différente de -1, alors cette valeur est la prochaine que doit retourner la méthode next et il faut donc la stocker dans un champ de l'adaptateur (nommé next).

Pour savoir si le champ next est valide, un autre champ booléen (isNextValid) est utilisé, exactement comme pour l'itérateur filtrant. Pour plus de détails quant au fonctionnement de ces deux champs, reportez vous au corrigé de la série 8.

La méthode hasNext est plus complexe que celle de l'itérateur filtrant car elle doit gérer les potentielles exceptions levées par read et elle doit fermer le flot dès sa fin atteinte. Pour ce faire, les appels aux méthodes read et close sont englobés dans un bloc try qui assure que le booléen isNextValid ne soit jamais vrai en cas d'erreur, ce qui signale la fin du flot. Cette dernière est aussi signalée lorsque le flot retourne l'entier -1, comme attendu. Dans ce dernier cas, le flot est immédiatement fermé. A noter que cette fermeture provoquera la levée d'une exception lors des prochains appels à hasNext (s'il y en a), car la méthode read appliquée à un flot fermé a ce comportement. Cela dit, cette exception sera traitée correctement et hasNext retournera faux comme elle le doit.

public boolean hasNext() {
    if (!isNextValid) {
        try {
            int nextAsInt = inStream.read();
            if (nextAsInt != -1) {
                isNextValid = true;
                next = (byte)nextAsInt;
            } else
                inStream.close();
        } catch (IOException e) {
            assert !isNextValid;
        }
    }
    return isNextValid;
}

A noter qu'avec cette solution le flot n'est pas fermé si read lève une exception, ce qui est discutable mais simplifie passablement le code.

La méthode next est quant à elle rigoureusement identique à celle de l'itérateur filtrant de la série 8, si ce n'est que son type de retour est Byte et pas un type générique :

public Byte next() {
    if (!hasNext())
        throw new NoSuchElementException();
    isNextValid = false;
    return next;
}