====== Laboratorium 4/5 - 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.addPhoto("..."); cv.addSection("Wykształcenie") .addParagraph("2000-2005 Przedszkole im. Królewny Snieżki w ...") .addParagraph("2006-2012 SP7 im Ronalda Regana w ...") .addParagraph("..."); cv.addSection("Umiejętności") .addParagraph( new ParagraphWithList().setContent("Umiejętności") .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")); Jeżeli masz czas i ochotę możesz zastosować 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 ===== Zakładamy, że utworzony programowo dokument został zapisany w postaci pliku HTML. Co jednak, gdybyśmy chcieli mieć możliwość zapisu i późniejszego odczytu zawartości dokumentu? Jednym z wygodnych rozwiązań jest zastosowanie JAXB (Java Architecture for XML Binding), biblioteki pozwalającej na zapis złożonej struktury obiektów w pamięci w formacie XML (//ang. marshal//) i późniejsze odtworzenie struktury obiektów z dokumentu XML (//ang. unmarshal//). Aby zapis i odczyt był mozliwy musi być spełnionych kilka warunków: *musimy **oznaczyć** elementy, które chcemy zapisać (można też wymusić użycie specyficznych tagów lub atrybutów XML). Czyli raczej //oznaczamy// niż //programujemy//, ale oszczędzamy bardzo dużo czasu! *ponieważ przy odczycie tworzone są nowe puste obiekty na podstawie tagów, może być wymagane dodanie standardowych konstruktorów (tam gdzie są obecne konstruktory z parametrami). Dla przypomnienia '''' a tag zamykający wygląda tak ''''. ==== Adnotacje ==== Biblioteka JAXB używa specyficznego mechanizmu języka - adnotacji (//ang. annotations//) [który chyba nie będzie omawiany na wykładzie ???] Adnotacje mają postać ''@Identifier'' lub ''@Identifier(key=value[,otherkey=othervalue])'' i są umieszczane w kodzie programu, zazwyczaj przed definicją klasy, deklaracją atrybutu lub definicją metody. Na przykład: @Override public String toString(){ return ""; } Adnotacje nie mają bezpośredniego wpływu na wykonanie kodu, ale mogą być wykorzystywane przez zewnętrzne narzędzia przetwarzające kod (zarówno źródłowy, jak i skompilowany): * kompilator * biblioteki, które na podstawie adnotacji dowiadują się, w jaki sposób przetwarzać obiekty, na przykład: * wypełniać ich atrybuty danymi zapisanymi w zewnętrznych źródłach (dokumentach, bazach danych) * zapisywać obiekty w specyficznych formatach * wybierać specyficzne elementy, np. funkcje służące do testowania ''@org.junit.Test'' * wiązać wywołania metod i ich parametry z interfejsem sieciowym Formalnie, adnotacje deklaruje się jak interfejsy i umieszcza w pakietach :!: ** Dodając adnotacje zadbaj, aby importować elementy z pakietu ''javax.xml.bind.annotation'' ** ==== Metody do zapisu i odczytu ==== Dodaj w klasie ''Document'' metody do zapisu i odczytu public void write(String fileName){ try { JAXBContext jc = JAXBContext.newInstance(Document.class); Marshaller m = jc.createMarshaller(); m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); FileWriter writer= new FileWriter(fileName);; m.marshal(this, writer); } catch (JAXBException ex) { ex.printStackTrace(); } catch (IOException ex) { ex.printStackTrace(); } } public static Document read(String fileName){ try { JAXBContext jc = JAXBContext.newInstance(Document.class); Unmarshaller m = jc.createUnmarshaller(); FileReader reader = new FileReader(fileName); return (Document) m.unmarshal(reader); } catch (JAXBException ex) { ex.printStackTrace(); } catch (FileNotFoundException ex) { ex.printStackTrace(); } return null; } Dodaj także w funkcji ''main()'' kod, który zapisuje (i odczytuje) dokument cv.write("cv.xml"); //Document cv2 = Document.read("cv.xml"); //cv2.writeHTML(System.out); === Dla użytkowników Java 9 (zazwyczaj na laptopach) === Java SE 9 nie widzi części modułów Java EE, w tym JAXB. 1. W IntelliJ w dialogu //Project Structure// - ustaw **Project language Level** : 8 - lambdas, type annotations 2. Zalecanym obejściem (w [[https://blog.codefx.org/java/java-9-migration-guide/]]) jest dodanie opcji uruchamiania programu --add-modules java.se.ee W IntelliJ nalezy je wpisac w oknie //Run -> Edit Configurations -> pole **VM options**// ==== Dodajemy adnotacje ==== W następnych etapach będziemy modyfikowali kod (dodawali adnotacje) i sprawdzali, co zmienia się w pliku ''cv.xml'' Najczęściej używane będą następujące adnotacje *''@XmlRootElement'' - oznaczenie klasy będącej korzeniem drzewa XML *''@XmlElement'' - oznaczenie, że atrybut ma zostać zapisany jako element XML (jako nazwa znacznika XML zostanie użyta nazwa atrybutu) *''@XmlElement(name="xyz")'' - podana jest nazwa znacznika ''xyz'' *''XmlAttribute'' atrybut (pole klasy) zostanie zapisany jako atrybut XML ==== Oznacz Document jako XMLRootElement ==== @XmlRootElement public class Document { ... } Zobacz, co zostało zapisane do pliku ''cv.xml'' ==== Oznacz atrybuty klasy Document ==== @XmlElement String title; @XmlElement List
    sections = new ArrayList<>(); @XmlElement Photo photo; Wykonaj program i spróbuj zidentyfikować i naprawić błąd... ==== Photo ==== Pole ''url'' odwzorujemy w atrybut @XmlAttribute String url; ==== Section ==== Tytuł jest zapewne krótki - odwzorujemy go w atrybut, natomiast listę akapitów (''paragraphs'') w XmlElement ==== Poprawiamy nazwy znaczników XML ==== Zastąp w odpowiednim miejscu ''@XmlElement'' przez ''@XmlElement(name="section")'' i ''@XmlElement(name="paragraph")''. ==== Nie widać listy w cv.xml? ==== 1. Uzupełnijmy więc znaczniki w ''ParagraphWithList'', UnorderedList oraz ''ListItem'' Prawdopodobnie dalej lista w ''cv.xml'' nie będzie widoczna... 2. Poinformuj, JAXB o klasach potomnych ''Paragraph'' @XmlSeeAlso({ParagraphWithList.class}) public class Paragraph { ... i usuń ewentualne błędy. 3. Możesz przyjrzeć się znacznikom i poprawić nazwy według uznania ==== Odchudzamy XML ==== Zawartość pliku XML powinna wyglądać następująco Jana Kowalski - CV
    2000-2005 Przedzkole im. Królewny Snieżki w Warszawie 2006-2012 SP7 im Ronalda Regana w ... ...
    Umiejętności C C++ Java
    Znaczników '''' jest trochę za dużo? 1. Zastąp w adontacje ''@XmlElement'' przed ''content'' w '''ListItem'' i ''Paragraph'' przez ''XmlValue''. 2. Niestety :-( elementy listy znikają... Należy poprawić adnotacje w Section -- wskazać, że lista ''paragraphs'' może zawierać różne typy obiektów @XmlElements(value= { @XmlElement(name = "paragraph", type = Paragraph.class), @XmlElement(name = "paragraph-with-list", type = ParagraphWithList.class) }) List paragraps = new ArrayList<>() ; Wtedy można zrezygnować z ''@XmlSeeAlso({ParagraphWithList.class})'' przed klasą Paragraph. Oczekiwany wynik Jana Kowalski - CV
    2000-2005 Przedzkole im. Królewny Snieżki w Warszawie 2006-2012 SP7 im Ronalda Regana w ... ...
    Umiejętności C C++ Java
    Uff... Każdy framework ma swoje adnotacje i aby osiągnąć zamierzony efekt nieraz konieczne będzie czytanie obszernej dokumentacji i oczywiście StackOverflow. Tak zazwyczaj będzie przy programowaniu usług sieciowych (//web service//), korzystaniu z //Hibernate// oraz //Spring// podczas kolejnych zajęć na 3 roku.