:: Enseignements :: Master :: M1 :: 2022-2023 :: Java Avancé ::
![[LOGO]](http://igm.univ-mlv.fr/ens/resources/mlv.png) |
Projet de Java Avancé Master 1 - 2022
|
Exercice 1 - CloneWar
Le but du projet
Clone est d'écrire une application web qui analyse
des fichiers jar (Java Archive) pour détecter des codes communs (on parle de clones).
L'application
CloneWar est composée d'un
back-end écrit en Java offrant
différents services REST permettant d'accéder aux informations de l'analyse d'une archive
et d'un
front-end écrit en JavaScript affichant ces informations et en particulier les codes sources considérés comme des clones.
L’application doit permettre...
-
... d'ajouter un nouvel artefact. Un artefact est constitué de deux archives différentes,
l'archive "main" contient le bytecode (les .class) de l'artefact et l'archive "source" contient le code source associé (les .java).
Les méta-données de l'archive "main" (le numéro de version, les développeurs, etc...) sont extraites du pom.xml de déploiement.
-
... d'indexer un artefact, en calculant pour chaque groupe d'instructions (bytecodes)
de l'archive "main", un hash et d'y associer un nom de fichier ainsi qu'un numéro de ligne.
-
... d'afficher pour un artefact, tous les clones présents dans les autres artefacts ainsi que dans l'artefact lui-même.
Un calcul de score doit être affiché pour indiquer le pourcentage de code commun entre les artefacts.
De plus, pour chaque clone, il existe une vue qui affiche les codes sources correspondants sous forme d'un "diff" en couleur.
En termes d'interface graphique, votre application doit être composée de deux écrans.
-
La première page sert d'écran d'accueil. Elle affiche l'ensemble des artefacts disponibles. Pour chacun, elle indique les artefacts les plus proches en fonction du score (seulement quelques uns, pas tous !).
Pour cet écran, uniquement les informations essentielles
d'un artefact doivent être affichées : le nom de l'artefact, l'URL correspondante,
la date d'ajout de l'artefact, si une analyse/indexation de l'artefact est en cours (
avec une barre de progression si possible !).
-
La seconde page affiche, pour un artefact donné, l'ensemble des méta-données disponibles ainsi
que les clones détectés dans chaque autre artefact, par ordre de taille des clones (nombre d'instructions clonées).
Pour chaque clone, un "diff" des parties des codes sources correspondants doit être affiché visuellement,
avec le code de l'artefact courant à gauche et le code du clone à droite,
à la façon dont un IDE affiche les différences entre deux codes.
Pour détecter les clones, on va utiliser l'algorithme de
Karp-Rabin
mais au lieu de le faire fonctionner sur des chaînes de caractère, on vous demande de le faire fonctionner
sur les instructions en bytecode.
L'idée est que pour chaque suite d'instructions de bytecode, on va calculer un
hash et
stocker dans une base de données, l'association de ce
hash avec le fichier et la ligne
de la première instruction de la suite d'instructions.
Détecter les clones revient à regarder si il n'existe pas deux hashs égaux correspondant à deux parties
de code distinctes. Il faut aussi regrouper les zones communes pour que si il y a plusieurs hashs
successif qui sont égaux, on indique à l'utilisateur qu'il y a un seul clone et pas plusieurs.
Enfin, si vous n'utilisez pas un hachage parfait, il peut aussi y avoir des collisions, deux zones
ont la même valeur de hash mais la suite d'instructions est pas la même, il faut dans ce cas, ne pas
remonter les zones comme étant des clones.
Pour que l'on puisse reconnaître des clones indépendamment du nom des variables, des champs, des méthodes ou
des classes, on va abstraire le flot d'instructions.
Par exemple,
Toy toy = new Toy("buzz");
int b = Integer.parseInt(toy.name());
correspond en bytecode à
NEW Toy
DUP
LDC "hello"
INVOKESPECIAL Toy.<init>(Ljava/lang/String;)V
ASTORE 1
ALOAD 1
INVOKEVIRTUAL Toy.name ()Ljava/lang/String;
INVOKESTATIC Integer.parseInt (Ljava/lang/String;)I
ISTORE 2
on peut estimer que la constante "HELLO" ou le nom Integer.parseInt() est important pour la détection tandis que
Toy,
name ou le type des variables locales ne le sont pas, et donc abstraire le code comme ceci
NEW
DUP
LDC "hello"
INVOKESPECIAL <init>
STORE 1
LOAD 1
INVOKEVIRTUAL
INVOKESTATIC Integer.parseInt
STORE 2
La façon d'abstraire ci-dessus est un exemple, c'est à vous de trouver comment abstraire le bytecode pour détecter les clones correctement.
Si vous abstrayez trop le code, vous aurez plein de faux positifs, c'est à dire l'algorithme trouvera des clones mais un humain qui
regarderait le code source ne serait pas d'accord. Ou, au contraire, vous aurez des faux négatifs, si vous n'abstrayez pas assez, c'est à dire des clones visibles pour un humain qui ne seront pas détectés par l'algorithme.
Vous êtes aussi libres de choisir comment doit fonctionner exactement l'algorithme qui calcule les valeurs de
hash,
pourvu que vous utilisiez le principe du
rolling hash.
Si l'algorithme de détection de clone ne marche pas, vous serez sévèrement punis, être capable d'assurer que votre application détecte bien les clones est un objectifs important.
Pour cela, on vous demande d'avoir au moins 20 exemples de codes qui sont des clones ou pas des clones,
avec des tests unitaires (utilisant JUnit 5) automatiques qui montrent que votre algorithme fonctionne de façon satisfaisante.
On vous demande, de plus, sur Maven Central, de récupérer les artefacts ("main" et "source") des libraries
junit 5, jackson, Google Guava, JAXB, Log4j 2, Slf4J
et ASM et de tester votre projet avec.
Note : indexer des jars devrait être rapide, si votre code est lent, ou n'a pas assez de mémoire en indexant l'artefact Guava, ce n'est pas normal !
Vous devez veillez à ce que votre algorithme ne soit pas consommateur de temps ou de mémoire.
Pour lire le bytecode, vous allez utiliser la librairie
ASM 9.4. Voici un exemple
var finder = ModuleFinder.of(Path.of("name_of_your.jar"));
var moduleReference = finder.findAll().stream().findFirst().orElseThrow();
try(var reader = moduleReference.open()) {
for(var filename: (Iterable<String>) reader.list()::iterator) {
if (!filename.endsWith(".class")) {
continue;
}
try(var inputStream = reader.open(filename).orElseThrow()) {
var classReader = new ClassReader(inputStream);
classReader.accept(new ClassVisitor(Opcodes.ASM9) {
private static String modifier(int access) {
if (Modifier.isPublic(access)) {
return "public";
}
if (Modifier.isPrivate(access)) {
return "private";
}
if (Modifier.isProtected(access)) {
return "protected";
}
return "";
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
System.err.println("class " + modifier(access) + " " + name + " " + superName + " " + (interfaces != null? Arrays.toString(interfaces): ""));
}
@Override
public RecordComponentVisitor visitRecordComponent(String name, String descriptor, String signature) {
System.err.println(" component " + name + " " + ClassDesc.ofDescriptor(descriptor).displayName());
return null;
}
@Override
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
System.err.println(" field " + modifier(access) + " " + name + " " + ClassDesc.ofDescriptor(descriptor).displayName() + " " + signature);
return null;
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
System.err.println(" method " + modifier(access) + " " + name + " " + MethodTypeDesc.ofDescriptor(descriptor).displayDescriptor() + " " + signature);
return new MethodVisitor(Opcodes.ASM9) {
@Override
public void visitInsn(int opcode) {
System.err.println(" opcode " + opcode);
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
System.err.println(" opcode " + opcode + " " + owner+ "." + name + descriptor);
}
// + the other visit methods to get all the opcodes
};
}
}, 0);
}
}
}
Technologies à utiliser
-
Vous devez utiliser Maven comme outil de build et IntelliJ comme IDE,
et la version 19 de Java.
-
Pour tester vos services REST, vous pouvez utiliser
Postman
ou tout autres clients capables de faire des requêtes REST.
-
Les test unitaires Java doivent être effectués avec JUnit 5.9.1, vous pouvez vous référer au
guide d'utilisation.
Chaque classe classe Java doit avoir une classe de test correspondante (pour au moins 80%) du projet.
-
Pour lire le bytecode la librarie ASM 9.3. Vous pouvez vous référer au
guide d'utilisation.
-
Pour la sérialisation/dé-sérialisation JSON des requêtes, vous utiliserez une API de parsing JSON
Jackson 2.13.4.2
-
Pour l'extraction de "truc"(TM) dans le code source, vous utiliserez des expressions régulières
avec le package java.util.regex.
Pour implanter les différents services REST, votre application doit utiliser une des technos
(c'est moi qui choisi pas vous) ci-dessous.
Attention à bien faire la différence entre les versions AOT ou JVM et les versions
synchrone et asynchrone (reactive) des APIs !
Pour l'API REST, les entrées et les sorties JSON doivent utiliser des records si possible.
Pour la mapping Object / Relational, c-a-d voir une ligne d'une table de BDD comme un objet Java.
Il y a deux implantations, une à base d'Hibernate qui peut être utilisée directement soit par
l'intermédiaire de la spécification JPA (Java Persistance API ou Jakarta Persistence API pour les versions
plus récente). Et une à base de JDBI.
Pour le
front-end web, vous avez besoin d'un framework graphique
Note : le front-end doit être "buildé" aussi en utilisant
Maven (un seul POM pour front et le back),
vous aurez peut-être besoin de plugin Maven specifique pour cela.
Note2: vous avez besoin de
npm pour la partie build mais pas à l'exécution !
Vous pouvez de plus, utiliser une librairie spéciale pour la gestion des graphes pourvu quelle
soit adaptée à votre framework (pas de react-graph si vous devez utiliser svelte).
Attention: lors du déploiement, vous ne devez pas utiliser nodejs comme serveur Web car vous avez
déjà un serveur Web qui sert l'API donc il peut aussi servir les pages statiques de votre librarie JS.
Bien sûr, pour le build, vous pouvez utiliser nodejs et npm pour construire votre application web.
Pour vous aidez à avoir de belles pages, vous allez aussi utiliser une bibliothèque qui vous aide
pour la partie CSS
L'application a besoin d'une base de données mais vu le volume de données, pas forcément d'une
"vrai" base de données, nous utiliserons donc des bases de données
embedded.
L'intérêt d'une BDD
embedded est qu'elle est prête à l'emploi directement à partir d'un jar.
Il y a deux façon d'accéder à une BDD en Java, en utilisant le
Driver JDBC ou le
DataSource JDBC.
On vous demande d'utiliser le
DataSource car la gestion des connections à la BD est automatique.
Dans le cas où vous utilisez JPA / Hibernate, il vous faut aussi ajouter une dépendence sur Dialect correspondant
à votre base de donnéees car chaque base de données parle un SQL un petit peu différent.
Attention, ces peuvent être utiliser aussi comme des BDDs classiques extérieurs à l'application,
ce n'est pas ce qui nous intéresse ici, on veut la version
embedded !
Et on ne veut pas que la base de donnée disparaisse quand on quitte l'application !
REST API documentation
Nous allons documenter l'API REST de votre
back-end en utilisant le format Open API 3.
Note : il y a deux façon d'utiliser Open API, soit on l'utilise comme un générateur qui génère le squelette
de l'API soit dans l'autre sens, on extrait les valeurs des classes Java. On va utiliser la seconde version,
comme cela, la documentation de l'API sera toujours à jour avec le code.
Sécurité :
-
Pas de HTTPS pour ce projet (c'est mal) mais c'est pour vous aider à débugger !
-
Les entrées des services web au niveau de l'URL ou de la partie JSON doivent être validées
et les sorties doivent être "escapées" pour éviter les injections de code.
-
Il n'y a aucune raison que le login/mdp de la BDD soit en dur dans votre code !
Binômes avec les technos qui doivent être utilisées
Binome | SERVER | PERSISTENCE | OPENAPI? | DB | FRONT | UI
ABEELACK-REGUEME | Helidon MP Reactive | JPA reactive | OpenAPI | Apache Derby | angular | bootstrap
ABIB-SEDDAR | Spring MVC | JPA | non | H2 | vue | bootstrap
AGNETTI-SAIDI | Helidon SE Reactive | DBClient | OpenAPI | H2 | react | tailwind
AGONSE-ATTRAX | Spring Reactive | JPA reactive | OpenAPI | SQLite | react | tailwind
AHAMMAD-POMBO | Spring MVC | JPA | non | SQLite | vue | bootstrap
ANCEL-CROHARE | Quarkus | JPA | non | SQLite | vue | tailwind
AOUNALLAH-BOURENNANE | Quarkus | Hib. + panache | non | SQLite | vue | bootstrap
ARBAOUI-LYLY_IENG | Quarkus | JPA | non | SQLite | angular | tailwind
AVRON-HATHAT Avron | Quarkus Reactive | Hib. + panache | OpenAPI | SQLite | react | bootstrap
BARDIN-LOUSADO | Quarkus | JPA | non | SQLite | angular | tailwind
BENAIBOUCHE-HAMADI | Spring MVC | JPA | OpenAPI | SQLite | vue | bootstrap
BENDIAF-ENNOUCHI | Spring MVC | JPA | non | SQLite | angular | bootstrap
BENMALEK-DOS_SANTOS | Spring 6 Reactive | JPA (jakarta) | OpenAPI | SQLite | vue | bootstrap
BENREGUIG-VILARINHO | Spring MVC 6 + Loom | JPA (jakarta) | OpenAPI | Apache Derby | react | tailwind
BOUKA-SIMON | Quarkus | Hib. + panache | non | H2 | react | tailwind
BOULET-DOS_SANTOS | Helidon SE | DBClient | OpenAPI | SQLite | svelte | bulma
COLLET-EYNARD | Helidon SE | Jdbi | OpenAPI | H2 | react | tailwind
COLLET-LE_DUFF | Spring 6 Reactive | JPA (jakarta) | OpenAPI | Apache Derby | svelte | tailwind
CROS-MAZYRAC | Spring Reactive | JPA reactive | OpenAPI | HyperSQL | react | tailwind
DA_COSTA-LIU DA | Micronaut | JPA | non | SQLite | vue | tailwind
DE_JESUS-RICHARD | Helidon NIMA | DBClient | OpenAPI | SQLite | react | bulma
DEBATS-SIM | Spring MVC | JPA | non | H2 | vue | bootstrap
DOS_SANTOS-KAMDOM | Quarkus Reactive | JPA reactive | OpenAPI | HyperSQL | vue | tailwind
DOUKI-LEDOUX | Spring MVC 6 + Loom | JPA (jakarta) | OpenAPI | HyperSQL | svelte | bulma
FOULON-LAGIER | Helidon SE Reactive | DBClient | OpenAPI | HyperSQL | svelte | bulma
FRICHE-ETIENNE | Quarkus | Jdbi | non | SQLite | react | tailwind
FROIDURE-BENMECHICH | Spring MVC | JPA | non | H2 | angular | bootstrap
GARCIA-KINGUE | Micronaut Reactive | JPA reactive | OpenAPI | HyperSQL | react | bulma
GAUDET-JEAN | Helidon NIMA | Jdbi | OpenAPI | H2 | svelte | tailwind
GENNEVOISE-THEPHARAT | Quarkus | JPA | non | H2 | angular | tailwind
HAIDAMOUS-JAILLARD | Helidon MP | Jdbi | non | H2 | vue | bootstrap
JOUVENOT-MENAA | Micronaut Reactive | JPA reactive | OpenAPI | H2 | angular | tailwind
KY-NODA_HODA | Quarkus | JPA | non | H2 | vue | tailwind
LAGIER-NGUYEN | Helidon MP Reactive | JPA reactive | OpenAPI | SQLite | vue | bootstrap
LE_CHECH-VONG | Helidon MP | JPA | non | SQLite | react | tailwind
MACKE-PEREZ | Helidon MP | JPA | non | SQLite | react | tailwind
MONTEIRO-THORSTEINSSON Spring Reactive | JPA reactive | OpenAPI | H2 | vue | bulma
OSMANI-WANG | Spring MVC | JPA | non | SQLite | angular | bootstrap
PHAM_DIEU-SOM | Quarkus Reactive | Hib. + panache | OpenAPI | Apache Derby | angular | bulma
QUACH-TOURE | Micronaut | Jdbi | non | H2 | react | bootstrap
RAMAROSON-TELLIER | Spring 6 Reactive | JPA (jakarta) | OpenAPI | SQLite | react | bulma
STEPHANELY-VONG | Micronaut | JPA | non | H2 | react | bootstrap
Calendrier des rendus.
Soutenance intermédiaire (bêta) :
Toutes les parties de l'architecture doivent être présentes et communiquer entre elles.
Il peut y avoir des bugs, c'est pas grave. Fonctionnellement, seule la partie incrémentale
du calcul des métriques et des badges n'est pas demandée.
Description des tests (TODO !)
Soutenance finale :
TODO !
Pour vous aider, si vous ne respectez pas les indications de "sudden death" suivantes,
votre projet sera considéré comme mort et noté 0.
Pour la partie Java, le programme doit être écrit en utilisant correctement les différents concepts
vus lors du cours de Java Avancé (sous-typage, polymorphisme, lambdas, classes internes,
exceptions, types paramétrés, annotations, collections, entrées/sorties).
-
Une des technologies que votre projet utilise n'est pas celle requise pour votre binome
-
Il ne doit pas y avoir de warnings lorsque l'on compile avec javac -Xlint:all.
-
Dans un module, les packages d'implantation ne doivent pas être exportés et
requires transitive doit être utilisé là où c'est nécessaire.
-
Il ne doit pas y avoir de raw types, de @SuppressWarning non justifié, de cast non justifié.
-
Le principe d'encapsulation et la programmation par contrat doivent être respectées.
-
Il ne doit pas y avoir de champs ou méthodes protected.
-
Il ne doit pas y avoir d'instanceof/if...else sur des types là où il est possible
d'utiliser le polymorphisme ou le pattern matching.
-
Chaque interface devra être nécessaire.
Une interface possède 0 ou 1 méthode (sinon justifiée).
-
Aucune classe abstraite ne doit être publique ou utilisée comme un type.
-
Chaque méthode devra être appelée (pas de code mort).
-
Aucune méthode ne doit faire plus de 10 lignes sans une vraie justification.
-
Il est interdit d'utiliser des champs static typés par un objet (pas de variables globales),
seules les constantes (static final) de type primitif sont autorisées (et utiliser l'injection de dépendence SVP).
-
Le fichier POM.xml ne doit pas contenir de dépendances non listées dans ce document
où ayant une autre version que la version demandée (à part les dépendances de dépendances).
© Université de Marne-la-Vallée