Table of Contents
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
Photo
Klasa Photo
jest raczej prosta
public class Photo { Photo(String url){ this.url =url; } String url; void writeHTML(PrintStream out){ out.printf("<img src=\"%s\" alt=\"Smiley face\" height=\"42\" width=\"42\"/>\n",url) } }
Document
Klasa powinna mieć następujące atrybuty:
String title; Photo photo; List<Section> 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<Paragraph> 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<p>…</p>
- Możesz też dodać konstruktor
Inne klasy...
Dodaj na podstawie diagramu
ParagraphWithList
ma dodatkowy atrybut - listę typuUnorederedList
; można ją wypisać wewnątrz znaczników<p>…</p>
lub po znaczniku zamykającym.UnorederedList
przechowuje na liście elementyListItem
ListItem
zawiera wyłącznie tekst
Klasa UnorederedList
została wprowadzona, ponieważ będzie odpowiedzialna za wypisywanie
<ul> ... ... </ul>
Podobnie ListItem
ma wypisywać tekst pomiędzy znacznikami <li>…</li>
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.
Więcej informacji na temat języka HTML: https://www.w3schools.com/html/
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 <img src=“jan-kowalski.png” alt=“Smiley face” height=“42” width=“42”/>
?
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("<img")); 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 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 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
com.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 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 jest określana terminem marshaling, zwłaszcza w kontekście serializacji obiektów w celu przesłania ich przez sieć, patrz 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<Paragraph> 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ę
danilopianini.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<Paragraph> 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