====== 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("
===== 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.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 ''
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 ''
@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
@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