Vous allez développer la partie métier d’une petite application Java de gestion de documents.
L’interface graphique vous est fournie. Votre travail consiste à comprendre et utiliser :
L’objectif n’est pas de faire un joli écran. L’objectif est de comprendre comment on structure proprement une application.
Dans votre projet, il faut modifier votre fichier module (pour la partie graphique):
module GestionDocument { requires java.desktop; }
Une application permet à un utilisateur de :
L’entreprise souhaite que l’application puisse évoluer facilement :
Vous devez donc concevoir une architecture simple mais évolutive.
Vous devez compléter la partie métier d’une application de gestion de documents.
Le programme devra manipuler au minimum deux types de documents :
TextDocument
LogDocument
L’interface graphique est déjà fournie. Vous ne devez pas vous concentrer sur le code Swing.
À la fin du TP, vous devez être capable d’expliquer :
src/ ├── exception/ │ └── DocumentException.java │ ├── model/ │ ├── AbstractDocument.java │ ├── TextDocument.java │ └── LogDocument.java │ ├── repository/ │ ├── DocumentRepository.java │ ├── InMemoryDocumentRepository.java │ └── FileDocumentRepository.java -> fourni │ ├── service/ │ └── DocumentService.java │ └── ui/ └── DocumentApp.java -> fourni aussi c'est l'ihm
AbstractDocument
Créer une classe abstraite AbstractDocument contenant les attributs communs à tous les documents :
id
title
content
createdAt
Cette classe devra :
this.id = UUID.randomUUID().toString();
this.dateCreation = LocalDateTime.now();
getType()
format()
Créer 2 classes qui héritent de AbstractDocument :
TEXT
LOG
Créer une interface DocumentRepository qui définit le contrat suivant :
DocumentRepository
Pour le moment on va utiliser la classe mère Exception. qui est la classe mère.
void save(AbstractDocument document) throws Exception; AbstractDocument findById(String id) throws Exception; List<AbstractDocument> findAll() throws Exception;
Créer une classe InMemoryDocumentRepository qui implémente DocumentRepository.
InMemoryDocumentRepository
Cette classe stocke les documents dans une collection.
Utiliser une interface pour permettre l’utilisation de plusieurs implémentations différentes sans modifier le reste de votre programme.
La classe FileDocumentRepository est fourni ci-dessous :
FileDocumentRepository
package fr.formation.semaine2.repository; import java.io.IOException; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import fr.formation.semaine2.exception.DocumentException; import fr.formation.semaine2.model.AbstractDocument; import fr.formation.semaine2.model.LogDocument; import fr.formation.semaine2.model.TextDocument; /** * Ici, je garde les conventions de nommage. * Cette classe permet de stocker nos documents dans un fichier */ public class FileDocumentRepository implements DocumentRepository { private final Path dossier; public FileDocumentRepository(String nomDossier) throws IOException { this.dossier = Paths.get(nomDossier); Files.createDirectories(dossier); } @Override public void save(AbstractDocument document) throws DocumentException { try { Path fichier = dossier.resolve(document.getId() + ".txt"); String data = document.getType() + "\n" + document.getTitre() + "\n" + document.getContenu(); Files.writeString(fichier, data); } catch (IOException e) { throw new DocumentException("Erreur lors de la sauvegarde du document", e); } } @Override public AbstractDocument findById(String id) throws DocumentException { try { Path fichier = dossier.resolve(id + ".txt"); if (!Files.exists(fichier)) { return null; } List<String> lignes = Files.readAllLines(fichier); if (lignes.size() < 3) { throw new DocumentException("Le fichier du document est invalide"); } String type = lignes.get(0); String titre = lignes.get(1); String contenu = String.join("\n", lignes.subList(2, lignes.size())); if ("LOG".equals(type)) { return new LogDocument(titre, contenu); } return new TextDocument(titre, contenu); } catch (IOException e) { throw new DocumentException("Erreur lors de la lecture du document", e); } } @Override public List<AbstractDocument> findAll() throws DocumentException { List<AbstractDocument> documents = new ArrayList<>(); try (DirectoryStream<Path> stream = Files.newDirectoryStream(dossier, "*.txt")) { for (Path path : stream) { String filename = path.getFileName().toString(); String id = filename.substring(0, filename.length() - 4); AbstractDocument doc = findById(id); if (doc != null) { documents.add(doc); } } } catch (IOException e) { throw new DocumentException("Erreur lors de la lecture du dossier", e); } return documents; } }
Cette classe permet de :
.txt
Vous devez créer une nouvelle classe qui implémente aussi DocumentRepository.
Ici, les exceptions sont importantes : les accès aux fichiers peuvent échouer !
Créer une classe DocumentException. Elle servira à encapsuler les erreurs de lecture/écriture de fichiers avec un message plus compréhensible.
DocumentException
Exemples :
Erreur lors de la sauvegarde du document
Erreur lors de la lecture du document
Créer une classe DocumentService chargée de :
DocumentService
Le service devra manipuler AbstractDocument et DocumentRepository.
C’est ici que le polymorphisme doit apparaître.
Le code ci-dessous vous est donné pour tester votre travail. Vous n’avez pas à le réécrire.
package fr.formation.semaine2.gestion.ui; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Font; import java.util.List; import javax.swing.BorderFactory; import javax.swing.DefaultListModel; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import fr.formation.semaine2.model.AbstractDocument; import fr.formation.gestion.repository.DocumentRepository; import fr.formation.gestion.repository.FileDocumentRepository; import fr.formation.gestion.repository.InMemoryDocumentRepository; import fr.formation.gestion.repository.JsonDocumentRepository; import fr.formation.gestion.service.DocumentService; public class DocumentApp extends JFrame { private final DocumentRepository memoryRepository = new InMemoryDocumentRepository(); private DocumentRepository fileRepository; // private DocumentRepository jsonRepository; // si on voulait figer le mode de sauvegarde //private final DocumentService service = new DocumentService(new InMemoryDocumentRepository()); // ici on pourra choisir dynamiquement private DocumentService service; private final JComboBox<String> repositoryBox = new JComboBox<>(new String[]{"Mémoire", "Fichier TXT"}); private final JComboBox<String> typeBox = new JComboBox<>(new String[]{"TEXT", "LOG"}); private final JTextField titleField = new JTextField(25); private final JTextArea contentArea = new JTextArea(8, 30); private final JTextArea displayArea = new JTextArea(); private final JButton applyStorageButton = new JButton("Appliquer le stockage"); private final JButton saveButton = new JButton("Enregistrer"); private final JButton refreshButton = new JButton("Rafraîchir"); private final JButton clearButton = new JButton("Vider la saisie"); private final DefaultListModel<AbstractDocument> listModel = new DefaultListModel<>(); private final JList<AbstractDocument> documentList = new JList<>(listModel); public DocumentApp() { super("Mini gestionnaire de documents"); initRepositories(); initService(); initComponents(); buildLayout(); bindEvents(); refreshDocuments(); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setSize(1100, 650); setLocationRelativeTo(null); } private void initRepositories() { try { fileRepository = new FileDocumentRepository("data/txt"); // jsonRepository = new JsonDocumentRepository("data/json"); } catch (Exception e) { JOptionPane.showMessageDialog( this, "Erreur lors de l'initialisation des stockages : " + e.getMessage(), "Erreur", JOptionPane.ERROR_MESSAGE ); } } private void initService() { service = new DocumentService(memoryRepository); } private void initComponents() { repositoryBox.setPreferredSize(new Dimension(150, 28)); typeBox.setPreferredSize(new Dimension(120, 28)); applyStorageButton.setPreferredSize(new Dimension(180, 28)); saveButton.setPreferredSize(new Dimension(140, 30)); refreshButton.setPreferredSize(new Dimension(120, 30)); clearButton.setPreferredSize(new Dimension(140, 30)); contentArea.setLineWrap(true); contentArea.setWrapStyleWord(true); displayArea.setEditable(false); displayArea.setLineWrap(true); displayArea.setWrapStyleWord(true); displayArea.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 13)); documentList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); } private void buildLayout() { setLayout(new BorderLayout(10, 10)); JPanel northPanel = buildNorthPanel(); JSplitPane centerSplitPane = buildCenterPanel(); add(northPanel, BorderLayout.NORTH); add(centerSplitPane, BorderLayout.CENTER); } private JPanel buildNorthPanel() { JPanel globalPanel = new JPanel(new BorderLayout(0, 8)); globalPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 0, 10)); JPanel storagePanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 5)); storagePanel.setBorder(BorderFactory.createTitledBorder("Mode de stockage")); storagePanel.add(new JLabel("Stockage :")); storagePanel.add(repositoryBox); storagePanel.add(applyStorageButton); JPanel formPanel = new JPanel(new BorderLayout(8, 8)); formPanel.setBorder(BorderFactory.createTitledBorder("Saisie d'un document")); JPanel firstLine = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 5)); firstLine.add(new JLabel("Titre :")); firstLine.add(titleField); firstLine.add(new JLabel("Type :")); firstLine.add(typeBox); JScrollPane contentScrollPane = new JScrollPane(contentArea); contentScrollPane.setPreferredSize(new Dimension(200, 140)); JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 5)); buttonsPanel.add(saveButton); buttonsPanel.add(refreshButton); buttonsPanel.add(clearButton); formPanel.add(firstLine, BorderLayout.NORTH); formPanel.add(contentScrollPane, BorderLayout.CENTER); formPanel.add(buttonsPanel, BorderLayout.SOUTH); globalPanel.add(storagePanel, BorderLayout.NORTH); globalPanel.add(formPanel, BorderLayout.CENTER); return globalPanel; } private JSplitPane buildCenterPanel() { JScrollPane listScrollPane = new JScrollPane(documentList); listScrollPane.setBorder(BorderFactory.createTitledBorder("Liste des documents")); JScrollPane displayScrollPane = new JScrollPane(displayArea); displayScrollPane.setBorder(BorderFactory.createTitledBorder("Contenu du document")); JSplitPane splitPane = new JSplitPane( JSplitPane.HORIZONTAL_SPLIT, listScrollPane, displayScrollPane ); splitPane.setDividerLocation(350); splitPane.setResizeWeight(0.35); JPanel wrapper = new JPanel(new BorderLayout()); wrapper.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); wrapper.add(splitPane, BorderLayout.CENTER); JSplitPane outer = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); outer.setLeftComponent(listScrollPane); outer.setRightComponent(displayScrollPane); outer.setDividerLocation(350); outer.setResizeWeight(0.35); return outer; } private void bindEvents() { applyStorageButton.addActionListener(e -> { changeRepository(); refreshDocuments(); }); saveButton.addActionListener(e -> saveDocument()); refreshButton.addActionListener(e -> refreshDocuments()); clearButton.addActionListener(e -> clearForm()); documentList.addListSelectionListener(e -> { if (!e.getValueIsAdjusting()) { AbstractDocument selected = documentList.getSelectedValue(); if (selected != null) { displayArea.setText(selected.format()); } } }); } private void changeRepository() { String choice = repositoryBox.getSelectedItem().toString(); if ("Fichier TXT".equals(choice)) { service = new DocumentService(fileRepository); } // else if ("Fichier JSON".equals(choice)) { // service = new DocumentService(jsonRepository); // } else { service = new DocumentService(memoryRepository); } JOptionPane.showMessageDialog( this, "Stockage actif : " + choice, "Information", JOptionPane.INFORMATION_MESSAGE ); } private void saveDocument() { try { String title = titleField.getText().trim(); String content = contentArea.getText().trim(); String type = typeBox.getSelectedItem().toString(); if (title.isEmpty()) { JOptionPane.showMessageDialog(this, "Le titre est obligatoire."); return; } if (content.isEmpty()) { JOptionPane.showMessageDialog(this, "Le contenu est obligatoire."); return; } service.createAndSave(type, title, content); clearForm(); refreshDocuments(); JOptionPane.showMessageDialog(this, "Document enregistré avec succès."); } catch (Exception e) { JOptionPane.showMessageDialog( this, "Erreur lors de l'enregistrement : " + e.getMessage(), "Erreur", JOptionPane.ERROR_MESSAGE ); } } private void refreshDocuments() { try { listModel.clear(); List<AbstractDocument> documents = service.getAll(); for (AbstractDocument doc : documents) { listModel.addElement(doc); } displayArea.setText(""); } catch (Exception e) { JOptionPane.showMessageDialog( this, "Erreur lors du chargement des documents : " + e.getMessage(), "Erreur", JOptionPane.ERROR_MESSAGE ); } } private void clearForm() { titleField.setText(""); contentArea.setText(""); typeBox.setSelectedIndex(0); titleField.requestFocus(); } public static void main(String[] args) { SwingUtilities.invokeLater(() -> { DocumentApp app = new DocumentApp(); app.setVisible(true); }); } }
Document
JsonDocument
Si vous terminez en avance, vous pouvez :
JsonDocumentRepository
Ne cherchez pas à faire compliqué.
Un bon code pour ce TP est un code :