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ł:
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) } }
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.
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.
Atrybut content
to treść akapitu.
setContent()
zmienia treśćwriteHTML()
powinna umieszczać treść pomiędzy znacznikam <p>…</p>
Dodaj na podstawie diagramu
ParagraphWithList
ma dodatkowy atrybut - listę typu UnorederedList
; można ją wypisać wewnątrz znaczników <p>…</p>
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
<ul> ... ... </ul>
Podobnie ListItem
ma wypisywać tekst pomiędzy znacznikami <li>…</li>
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.
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.
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.
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:
Dla przypomnienia <to:jest:tag att=“a to jest atrybut”>
a tag zamykający wygląda tak </to:jest:tag>
.
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):
@org.junit.Test
Formalnie, adnotacje deklaruje się jak interfejsy i umieszcza w pakietach
Dodając adnotacje zadbaj, aby importować elementy z pakietu
javax.xml.bind.annotation
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);
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
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@XmlRootElement public class Document { ... }
Zobacz, co zostało zapisane do pliku cv.xml
@XmlElement String title; @XmlElement List<Section> sections = new ArrayList<>(); @XmlElement Photo photo;
Wykonaj program i spróbuj zidentyfikować i naprawić błąd…
Pole url
odwzorujemy w atrybut
@XmlAttribute String url;
Tytuł jest zapewne krótki - odwzorujemy go w atrybut, natomiast listę akapitów (paragraphs
) w XmlElement
Zastąp w odpowiednim miejscu @XmlElement
przez @XmlElement(name=“section”)
i @XmlElement(name=“paragraph”)
.
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
Zawartość pliku XML powinna wyglądać następująco
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <document> <title>Jana Kowalski - CV</title> <section title="Wykształcenie"> <paragraph> <content>2000-2005 Przedzkole im. Królewny Snieżki w Warszawie</content> </paragraph> <paragraph> <content>2006-2012 SP7 im Ronalda Regana w ...</content> </paragraph> <paragraph> <content>...</content> </paragraph> </section> <section title="Umiejętności"> <paragraph xsi:type="paragraphWithList" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <content>Umiejętności</content> <list> <item> <content>C</content> </item> <item> <content>C++</content> </item> <item> <content>Java</content> </item> </list> </paragraph> </section> <photo url="..."/> </document>
Znaczników <content>
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<Paragraph> paragraps = new ArrayList<>() ;
Wtedy można zrezygnować z @XmlSeeAlso({ParagraphWithList.class})
przed klasą Paragraph.
Oczekiwany wynik
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <document> <title>Jana Kowalski - CV</title> <section title="Wykształcenie"> <paragraph>2000-2005 Przedzkole im. Królewny Snieżki w Warszawie</paragraph> <paragraph>2006-2012 SP7 im Ronalda Regana w ...</paragraph> <paragraph>...</paragraph> </section> <section title="Umiejętności"> <paragraph-with-list>Umiejętności <list> <item>C</item> <item>C++</item> <item>Java</item> </list> </paragraph-with-list> </section> <photo url="..."/> </document>
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.