====== Laboratorium 4 - piszemy CV ====== Zaimplementujemy kilka klas, które pozwolą na utworzenie strukturalnego dokumentu - i wypełnimy przykładowymi danymi tak, aby powstało CV. Dokument będzie miał: * tytuł * zdjęcie * składał się z kilku sekcji * sekcje będą zawierały akapity (paragraph) * Szczególnym typem akapitu będzie akapit zawierający listę * całość ma być zapisywana w formacie HTML (który zapewne jest dobrze znany) ===== Analiza struktury ===== Tak wygląda dokument {{:po:dokument-wzor.png?400|}} A tak diagram klas {{:po:klasy-do-cv.png?400|}} Zaimplementuj kolejne klasy ===== Photo ===== Klasa ''Photo'' jest raczej prosta public class Photo { Photo(String url){ this.url =url; } String url; void writeHTML(PrintStream out){ out.printf("\"Smiley\n",url) } } ===== Document ===== Klasa powinna mieć następujące atrybuty: String title; Photo photo; List
sections = new ArrayList<>(); oraz metody: Document setTitle(String title){ this.title = title; return this; } Document setPhoto(String photoUrl){ // ??? return this; } Section addSection(String sectionTitle){ // utwórz sekcję o danym tytule i dodaj do sections return ???; } Document addSection(Section s){ return this; } void writeHTML(PrintStream out){ // zapisz niezbędne znaczniki HTML // dodaj tytuł i obrazek // dla każdej sekcji wywołaj section.writeHTML(out) } Możesz dodać konstruktor ustalający tytuł dokumentu. ===== Section ===== Sekcja powinna mieć następujące atrybuty: String title; List paragraps = new ArrayList<>() ; oraz metody Section setTitle(String title){} Section addParagraph(String paragraphText){} Section addParagraph(Paragraph p){} void writeHTML(PrintStream out){} Możesz dodać konstruktor ustalający tytuł sekcji. ===== Paragraph ===== Atrybut ''content'' to treść akapitu. *Metoda ''setContent()'' zmienia treść *Metoda ''writeHTML()'' powinna umieszczać treść pomiędzy znacznikam ''

...

'' *Możesz też dodać konstruktor ===== Inne klasy... ===== Dodaj na podstawie diagramu *''ParagraphWithList'' ma dodatkowy atrybut - listę typu ''UnorederedList''; można ją wypisać wewnątrz znaczników ''

...

'' lub po znaczniku zamykającym. *''UnorederedList'' przechowuje na liście elementy ''ListItem'' *''ListItem'' zawiera wyłącznie tekst Klasa ''UnorederedList'' została wprowadzona, ponieważ będzie odpowiedzialna za wypisywanie Podobnie ''ListItem'' ma wypisywać tekst pomiędzy znacznikami ''
  • ...
  • '' ===== Zbuduj dokument z CV ===== Spróbuj dobrać funkcje tak, aby budowało się w miarę wygodnie. Na przykład tak: Document cv = new Document("Jana Kowalski - CV"); cv.setPhoto("https://upload.wikimedia.org/wikipedia/commons/thumb/7/71/Calico_tabby_cat_-_Savannah.jpg/1200px-Calico_tabby_cat_-_Savannah.jpg"); cv.addSection("Wykształcenie") .addParagraph("2000-2005 Przedszkole im. Królewny Snieżki w ...") .addParagraph("2006-2012 SP7 im Ronalda Regana w ...") .addParagraph( new ParagraphWithList().setContent("Kursy") .addListItem("Języka Angielskiego") .addListItem("Języka Hiszpańskiego") .addListItem("Szydełkowania") ); cv.addSection("Umiejętności") .addParagraph( new ParagraphWithList().setContent("Znane technologie") .addListItem("C") .addListItem("C++") .addListItem("Java") ); Możesz wygenerować swoje CV lub wyimaginowanej osoby, ale zadbaj, aby znalazło się w nim wystarczająco dużo informacji. ===== Wygeneruj kod HTML strony ===== Powinno to być jedno wywołanie, jak (wypisujące na konsoli) cv.writeHTML(System.out); lub (wpisujące do pliku) cv.writeHTML(new PrintStream("cv.html","ISO-8859-2")); // lub cv.writeHTML(new PrintStream("cv.html","UTF-8")); Wygeneruj poprawny składniowo dokumenty HTML. Możesz zastosować zdefiniowane w nagłówku style CSS, aby zapewnić ładne formatowanie. ===== Napisz testy ===== Głównym celem klas jest generacja kodu w HTML. Testy powinny więc sprawdzać głównie poprawność generacji: obecność znaczników i danych w wygenerowanym kodzie... Co może być sprawdzone dla wyjścia typu ''Smiley face''? Przykład implementacji public class PhotoTest { @org.junit.Test public void writeHTML() throws Exception { String imageUrl = "jan-kowalski.png"; // Utwórz strumień zapisujący w pamięci ByteArrayOutputStream os = new ByteArrayOutputStream(); PrintStream ps = new PrintStream(os); // Utwórz obiekt i zapisz do strumienia new Photo(imageUrl).writeHTML(ps); String result = null; // Pobierz jako String try { result = os.toString("ISO-8859-2"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } //System.out.println(result); // Sprawdź, czy result zawiera wybrane elementy assertTrue(result.contains("")); assertTrue(result.contains("src=")); assertTrue(result.contains(imageUrl)); } } Napisz testy sprawdzające poprawność generacji kodu dla **trzech** wybranych klas. ===== Zapis i odczyt dokumentu w formacie JSON ===== W poprzedniej wersji [[po:lab4_old]] częścią zadania był zapis i odczyt danych dokumentu w formacie XML. Obsługa XML za pomocą biblioteki JAXB została wycofana z dystrybucji Java SE. Jest częścią Java EE (//Enterprise Edition//), gdzie wykorzystywana jest do obsługi protokołu [[https://en.wikipedia.org/wiki/SOAP|SOAP]] służącego do zdalnego wywołania procedur. Zamiast XML użyjemy więc formatu JSON. ==== Dodaj bibliotekę Gson do projektu ==== Spośród dostępnych bibliotek do obsługi formatu JSON [[https://www.baeldung.com/java-json]] użyjemy Gson *Wybierz opcję //Project Structure -> Libraries//, a następnie przycisk + (New project library) *Wybierz //From maven// i podaj nazwę biblioteki ''google.code.gson''. Następnie aktywuj lupę (wyszukiwanie) i wybierz wersję, np. 2.10.1 Generalnie, InteliJ ma (może miewał?) czasem problemy ze znajdywaniem bibliotek. Warto sprawdzić wersje w [[https://mvnrepository.com/artifact/com.google.code.gson/gson|Maven Repository]]. Z nieznanych powodów InteliJ nie jest w stanie ich znaleźć, ale jest w stanie pobrać i zainstalować. ==== Serializacja ==== Serializacja to proces zamiany obiektów na format służący do przechowywania lub transferu danych. Często też w języku angielskim jets określana terminem //marshaling//, zwłaszcza w kontekście serializacji obiektów w celu przesłania ich przez sieć, patrz [[https://en.wikipedia.org/wiki/Marshalling_(computer_science)|Wikipedia]] Dodaj kod w klasie Dokument String toJson(){ Gson gson = new GsonBuilder().setPrettyPrinting().create(); return gson.toJson(this); } Wypisz zwrócony tekst. Efekt powinien być podobny do poniższego: { "title": "Jana Kowalski - CV", "photo": { "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/7/71/Calico_tabby_cat_-_Savannah.jpg/1200px-Calico_tabby_cat_-_Savannah.jpg" }, "sections": [ { "title": "Wykształcenie", "paragraphs": [ { "content": "2000-2005 Przedszkole im. Królewny Snieżki w ..." }, { "content": "2006-2012 SP7 im Ronalda Regana w ..." }, { "list": { "items": [ { "content": "Języka Angielskiego" }, { "content": "Języka Hiszpańskiego" }, { "content": "Szydełkowania" } ] }, "content": "Kursy" } ] }, { "title": "Umiejętności", "paragraphs": [ { "list": { "items": [ { "content": "C" }, { "content": "C++" }, { "content": "Java" } ] }, "content": "Znane technologie" } ] } ] } ==== Deserializacja ==== Dodaj w klasie Dokument statyczną funkcję zamieniająca tekst na obiekt dokument Document fromJson(String jsonString){ Gson gson = new GsonBuilder().create(); return gson.fromJson(jsonString, Document.class); } Wywołaj tę funkcje i przeanalizuj zawartość odtworzonego dokumentu. Na przykład zapisz w formacie HTML lub JSON. **Wniosek**: obiekty klasy ''ParagraphWithList'' nie zostały poprawnie wczytane. ==== Obsługa hierarchii klas ==== Hierarchie klas są na ogół problemem podczas deserializacji. Jeżeli w klasie ''Section'' lista akapitów jest zdefiniowana jako ''List paragraphs = new ArrayList<>();'' biblioteka spodziewa się obiektów klasy bazowej, a nie potomnej. Tworzy więc obiekty klasy bazowej i ignoruje listę będącą atrybutem ''ParagraphWithList'' Jednym z rozwiązań jest jawne przekazanie informacji o typie. *Dodaj do projektu bibliotekę ''danilopanini.gson.extras''. Dla gson 2.10.1 gson-extras w wersji 0.2.1 zadziałało poprawnie. *Podczas serializacji przekaż informacje o istniejącej hierarchii klas String toJson(){ RuntimeTypeAdapterFactory adapter = RuntimeTypeAdapterFactory .of(Paragraph.class) .registerSubtype(Paragraph.class) .registerSubtype(ParagraphWithList.class); Gson gson = new GsonBuilder().registerTypeAdapterFactory(adapter).setPrettyPrinting().create(); return gson.toJson(this); W tym przypadku wynik powinien być następujący: { "title": "Jana Kowalski - CV", "photo": { "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/7/71/Calico_tabby_cat_-_Savannah.jpg/1200px-Calico_tabby_cat_-_Savannah.jpg" }, "sections": [ { "title": "Wykształcenie", "paragraphs": [ { "type": "Paragraph", "content": "2000-2005 Przedszkole im. Królewny Snieżki w ..." }, { "type": "Paragraph", "content": "2006-2012 SP7 im Ronalda Regana w ..." }, { "type": "ParagraphWithList", "list": { "items": [ { "content": "Języka Angielskiego" }, { "content": "Języka Hiszpańskiego" }, { "content": "Szydełkowania" } ] }, "content": "Kursy" } ] }, { "title": "Umiejętności", "paragraphs": [ { "type": "ParagraphWithList", "list": { "items": [ { "content": "C" }, { "content": "C++" }, { "content": "Java" } ] }, "content": "Znane technologie" } ] } ] } ==== Ostateczna wersja ==== *Analogicznie skonfiguruj Gson w przypadku deserializacji *Przetestuj sekwencję operacji: - Serializacja do formatu JSON - Deserializacja - Powtórna serializacja do formatu JSON *i sprawdź, czy wyniki (1) i (3) są identyczne. Możesz to zrealizować jako test jednostkowy - ale powinieneś też sprawdzić, czy tekst obejmuje pełną zwartość ''ParagraphWithList''. ==== Pliki do przesłania ==== *Kod Java *Plik HTML zawierający wydrukowane CV *Plik w formacie JSON