====== 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("
===== Document =====
Klasa powinna mieć następujące atrybuty:
\n",url)
}
}
String title;
Photo photo;
List
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
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 ''
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 ''
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 ''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 [[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 jest 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
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