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

A tak diagram klas

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("<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ę 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>

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 <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

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 <to:jest:tag att=“a to jest atrybut”> a tag zamykający wygląda tak </to:jest:tag>.

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<Section> 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

<?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.

​po/lab4.txt · Last modified: 2017/11/16 13:35 (external edit)
CC Attribution-Share Alike 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0