Commit 2185eba1 authored by Marek Trtik's avatar Marek Trtik
Browse files

iteration-08 assignment

parent 3d431310
Loading
Loading
Loading
Loading
+77 −80
Original line number Diff line number Diff line
## Zadání iterace 07
## Zadání iterace 08

**Cvičení je zaměřené na práci s kolekcemi.**
**Cvičení je zaměřeno na práci s uspořádanými kolekcemi a lambda výrazy.**

1.  Vytvořte třídu `CollectionPolygon`, která bude rozšiřovat třídu `SimplePolygon`, podobně jako třída `ArrayPolygon`.
    Jediný rozdíl je v tom, že vrcholy n-úhelníku nebudou uloženy v poli, ale ve vhodné kolekci.
V předchozích iteracích jsme vytvořili několik variant jednoduchých n-úhelníků, jejichž topologie (pořadí propojení vrcholů hranami) byla dána pořadím vrcholů.
Nyní vytvořme n-úhelník s pojmenovanými vrcholy.
Topologie bude dána abecedním pořadím názvů vrcholů.
Změnou názvů vrcholů můžeme snadno změnit topologii n-úhelníku.

	*   Vytvořte konstruktor, který jako vstupní parametr přijme **pole** vrcholů
(podobně jako třída `ArrayPolygon`). Vrcholy však uloží do vhodné kolekce.
Podívejme se na příklad. Na následujícím obrázku je vlevo n-úhelník se šesti vrcholy.
Čísla u vrcholů představují pořadí, ve kterém byly definovány, a toto pořadí určuje topologii.
Pokud je topologie dána pojmenováním vrcholů, pak stejného výsledku dosáhneme pojmenováním vrcholů 1–6 písmeny A–F (obrázek uprostřed).
Přejmenování vrcholů však může zcela změnit topologii, aniž by se změnilo samotné pořadí vrcholů n-úhelníku (obrázek vpravo).

        > Při volbě mezi seznamem a množinou mějte na paměti, že topologie n-úhelníku je dána pořadím vrcholů a že je povoleno mít více vrcholů se stejnými souřadnicemi (u jednoduchého n-úhelníku se hrany nesmí protínat, ale mohou se dotýkat).
![topology](images/09a.png)

		> Proměnné by měly být typu rozhraní, tj. `List` místo `ArrayList`, `Set` místo `HashSet`.
1.  Definujte přirozené uspořádání třídy `Vertex2D` podle metody `equals()`,
    tj. řazení podle souřadnice X vzestupně a v případě shody podle souřadnice Y vzestupně.
    > Pro primitivní typy existují statické porovnávací metody, například `Double.compare`.

    *   Stejně jako dříve chceme zabránit vytvoření mnohoúhelníku bez vrcholů. Konstruktor proto kontroluje, zda vstupní pole není prázdné (**prázdné**, `null` nebo vyplněné `null` objekty). Pokud je prázdné, konstruktor vyhodí výjimku `IllegalArgumentException` s popisem.
2.  Vytvořte `VertexInverseComparator` pro třídu `Vertex2D` v balíčku `cz.muni.fi.pb112.project.comparator`.
    Komparátor bude řadit vrcholy **sestupně**, nejprve podle souřadnice X sestupně a v případě shody podle Y sestupně.

		> Poznámka: Tentokrát výjimka `IllegalArgumentException` nastává pouze v případě, že je vstupní pole prázdné (ve srovnání s předchozí iterací).
3.  Použijte **seřazenou mapu** k vytvoření třídy `LabeledPolygon`, která bude rozšiřovat třídu `SimplePolygon`.
    Tato třída bude podobná třídám `ArrayPolygon` a `CollectionPolygon`, s tím rozdílem, že vrcholy budou uloženy pod svými názvy.

    *   Definujte metody rovnosti. Dva mnohoúhelníky jsou stejné, pokud jsou stejné vrcholy na stejných indexech, tj. mají stejné souřadnice vrcholů ve stejném pořadí.
    Vrcholy jsou pojmenovány libovolným textovým řetězcem (obvykle jedním písmenem) a *název vrcholu je v rámci n-úhelníku jedinečný*.
    N-úhelník však může obsahovat dva různě pojmenované vrcholy se stejnými souřadnicemi
    (viz situace v příkladu výše).

		> Kolekce z Java API mají definovanou rovnost. A mají ji definovanou rozumně. Využijte toho.
    Pořadí vrcholů v n-úhelníku je dáno jejich pojmenováním (lexikograficky vzestupně).

		> Pro zjednodušení vynecháváme situaci, kdy jsou mnohoúhelníky geometricky stejné, ale liší se pořadím vrcholů. V takovém případě jsou považovány za různé.
    Třída bude mít **privátní** konstruktor s jedním parametrem – mapou vrcholů a jejich popisků.

    *   Přidejte do třídy metodu `CollectionPolygon withoutLeftmostVertices()`, která vrátí nový mnohoúhelník bez nejlevějších vrcholů (může jich být více, viz např. obdélník).
        Původní mnohoúhelník zůstává nezměněn. Pokud nový mnohoúhelník po odebrání nejlevějších vrcholů již neobsahuje žádné vrcholy, metoda vrátí `null`.
	    > Tato metoda slouží k procvičení práce s kolekcemi. Nehledejte v ní žádný jiný význam.
    Třída nebude děditelná (bude `final`).

2.  Všimněte si, že konstruktory tříd `ArrayPolygon` a `CollectionPolygon` jsou si velmi podobné. 
    Oba kontrolují správnost vstupního pole. Liší se pouze způsobem uložení vrcholů.
    Implementujte následující rozhraní (pro více informací viz JavaDoc daného rozhraní).

	*   Ve společné nadtřídě `SimplePolygon` vytvořte konstruktor a přesuňte do něj společný kód.
	    Konstruktor `SimplePolygon` tedy kontroluje správnost vstupního pole. V případě chyby vyhodí výjimku, a tím skončí celý proces vytváření mnohoúhelníku. Pokud je vše v pořádku, řízení je předáno konstruktoru podtřídy. Konstruktory podtříd naopak zavolají konstruktor `SimplePolygon` a poté uloží vrcholy do pole nebo kolekce. Pokud konstruktor `SimplePolygon` selže s výjimkou, selže automaticky i konstruktor podtřídy. Není tedy nutné se tím v podtřídách zabývat. Práci s výjimkami si procvičíme podrobněji příště.
    Metody z rozhraní `Polygon`:
    *   `Vertex2D getVertex(int index)` vrací `index`-tý vrchol s ohledem na pořadí dané pojmenováním vrcholů.
        Například pokud máme vrcholy „A“, „B“ a „C“, pak je nulový vrchol „A“,
        první vrchol „B“, druhý vrchol „C“, třetí vrchol opět „A“ (modulo) atd.
    *   `int getNumVertices()` vrací počet vrcholů v kolekci.

        > Abstraktní třída `SimplePolygon` může mít konstruktor, ale nelze ji přímo instancovat.
    Implementujte rozhraní `Labelable`:
    *   `Vertex2D getVertex(String label)` vrací souřadnice vrcholu pojmenovaného `label`.
        Metoda vyhodí `IllegalArgumentException`, pokud takový vrchol neexistuje.
    *   `getLabels()` vrací kolekci názvů vrcholů uspořádaných lexikograficky **vzestupně**.
    *   `getLabels(Vertex2D vertex)` vrací všechny názvy vrcholů se souřadnicemi `vertex`.
        Pokud takový vrchol neexistuje, vrátí prázdnou kolekci.

		> Existuje statická metoda `Arrays.asList` pro převod pole na seznam. 
    Implementujte rozhraní `Sortable`:
    *   `Collection<Vertex2D> getSortedVertices()` vrací vrcholy seřazené podle přirozeného uspořádání bez duplicit.
    *   `Collection<Vertex2D> getSortedVertices(Comparator<Vertex2D> comparator)` přijímá libovolný komparátor 2D vrcholů a vrací vrcholy seřazené podle tohoto komparátoru bez duplicit.

		> Metody `List.of` i `Arrays.asList` vracejí nemodifikovatelnou kolekci. Pro úpravy je nutné vytvořit novou kolekci.
    Nakonec metoda `Collection<Vertex2D> duplicateVertices()` vrací množinu vrcholů, které jsou v polygonu uloženy vícekrát pod různými názvy.

    *   Ve třídě `CollectionPolygon` vytvořte druhý konstruktor, který jako parametr přijme *seznam* vrcholů. Opět je nutné zkontrolovat, že máme alespoň jeden vrchol, takže tento konstruktor také zavolá konstruktor nadtřídy vytvořený v předchozím bodě.
4. Vytvořte **vnořenou** pomocnou třídu `Builder`, tj. `LabeledPolygon.Builder`.
   Tato třída se bude starat o vytvoření polygonu.
    *   Třída bude implementovat rozhraní `Buildable`.
    *   Metoda `Builder addVertex(String label, Vertex2D vert)` uloží vrchol pod zadaným názvem.
        Název ani vrchol nesmí být `null`, jinak metoda selže s vhodnou výjimkou.
        Pokud již v n-úhelníku existuje vrchol pod daným názvem, bude nahrazen novým.
    *   Metoda `LabeledPolygon build()` vrátí novou instanci `LabeledPolygon` naplněnou vrcholy.
    *   Použití:
        ```java
        LabeledPolygon polygon = new LabeledPolygon.Builder()
             .addVertex("A", new Vertex2D(2, 5))
             .addVertex("B", new Vertex2D(3, 1))
             .addVertex("C", new Vertex2D(1, 3))
             .build();
        ```

		> Pro převod kolekce na pole existuje metoda `toArray`, která jako argument přijímá nové pole.
5.  Pokud jste implementaci provedli bez chyb, pak se po spuštění třídy `Draw` na obrazovce vykreslí polygon s pojmenovanými vrcholy podobný prostřednímu polygonu výše.  
    ![](images/draw08.png)

3.  Vytvořte třídu `ColoredPolygon`, která vezme libovolný existující mnohoúhelník a přidá novou vlastnost: barvu.

    *   Konstruktor přijímá mnohoúhelník typu `Polygon` a barvu typu `Color`.
    *   Třída obsahuje gettery pro dané atributy `getPolygon` a `getColor`.
    *   Dva barevné mnohoúhelníky jsou stejné, pokud obsahují (logicky) stejný mnohoúhelník a stejnou barvu.

4.  Vytvořte třídu `Paper`, která implementuje rozhraní `Drawable`. Tato třída simuluje papír, na který lze kreslit barevné mnohoúhelníky. Simulace spočívá v tom, že se nic přímo nekreslí, ale ukládají se mnohoúhelníky určené k vykreslení spolu s barvou do kolekce jako objekty `ColoredPolygon`.
	
	> Poznámka: Třída `ColoredPolygon` je mezikrok k tzv. návrhovému vzoru *Decorator*. Přidává nové vlastnosti (zde barvu) k existujícímu objektu vložením mezilehlého objektu (`ColoredPolygon`) mezi klientský kód (`Paper`) a původní objekt (libovolný `Polygon`). Třída `ColoredPolygon` však musí také implementovat rozhraní `Polygon`, aby byla skutečným dekorátorem.
	
    Pokud kreslíme stejný mnohoúhelník (se stejnou barvou) na papír dvakrát, uloží se pouze jednou. Mnohoúhelníky se na papír kreslí barevně a každý mnohoúhelník má pro jednoduchost právě jednu barvu. Výchozí barva je černá.
	
    *   První konstruktor bude bez parametrů.
    
    *   Další konstruktor přijme parametr typu `Drawable` a zkopíruje kolekci nakreslených mnohoúhelníků.
    
    *   `changeColor(color)` změní barvu, ve které budou následující mnohoúhelníky kresleny (tj. s jakou barvou budou ukládány). Výchozí barva nové instance papíru je černá. 
    
    *   `drawPolygon` „nakreslí“ (tj. uloží) mnohoúhelník na papír s barvou nastavenou v předchozí metodě.
	    Pokud takový mnohoúhelník (stejné barvy a tvaru) již na papíře existuje (v kolekci), je ignorován. Pokud je nastavena bílá barva, mnohoúhelník se nekreslí (neukládá), protože by na bílém papíře stejně nebyl viditelný.

    *   `erasePolygon(polygon)` odstraní mnohoúhelník z papíru (odebere jej z kolekce).

    *   `eraseAll()` odstraní z papíru všechny mnohoúhelníky.

    *   `getAllDrawnPolygons()` vrátí všechny barevné mnohoúhelníky.

		> Zachovejte zapouzdření. Neumožněte externímu kódu modifikovat seznam vrácený getterem! Getter musí vracet kolekci jako **nemodifikovatelnou**. To je obecné pravidlo, nejen pro metodu `getAllDrawnPolygons()`. Pro „přepnutí“ kolekce do nemodifikovatelné podoby použijte statické metody `Collections.unmodifiableXXX`. Nemodifikovatelnou kolekci nemusíme vracet pouze tehdy, pokud v metodě vytvoříme a vrátíme kopii původní kolekce.
		
    *   `uniqueVerticesAmount()` vrátí počet vrcholů na papíře bez duplicit.
    
    *   Více informací naleznete v JavaDocu třídy `Drawable`.

5.  Spuštění třídy `Draw` nakreslí barevný dům  
    ![](images/draw07.png).

### Cílový UML diagram tříd:

![UML diagram tříd](images/07-class-diagram.jpg)
![UML class diagram](images/08-class-diagram.jpg)
+34 −103
Original line number Diff line number Diff line
package cz.muni.fi.pb112.project.demo;

import cz.muni.fi.pb112.project.geometry.CollectionPolygon;
import cz.muni.fi.pb112.project.geometry.ColoredPolygon;
import cz.muni.fi.pb112.project.geometry.Paper;
import cz.muni.fi.pb112.project.geometry.LabeledPolygon;
import cz.muni.fi.pb112.project.geometry.Polygon;
import cz.muni.fi.pb112.project.geometry.Vertex2D;

@@ -25,51 +23,6 @@ public final class Draw extends JFrame {
    private static final int HALF_WIDTH = PANEL_WIDTH / 2;
    private static final int HALF_HEIGHT = PANEL_HEIGHT / 2;

    private static final Color POLYGON_COLOR = Color.MAGENTA;

    private static final Polygon WALLS =
            new CollectionPolygon(
                    new Vertex2D[]{
                            new Vertex2D(-150, -150),
                            new Vertex2D(150, -150),
                            new Vertex2D(150, 150),
                            new Vertex2D(-150, 150),
                    });

    private static final int A = 45;
    private static final int B = 100;

    private static final Polygon WINDOW_LEFT = new CollectionPolygon(
            new Vertex2D[]{
                    new Vertex2D(-A, A),
                    new Vertex2D(-B, A),
                    new Vertex2D(-B, B),
                    new Vertex2D(-A, B),
            });

    private static final Polygon WINDOW_RIGHT = new CollectionPolygon(
            new Vertex2D[]{
                    new Vertex2D(A, A),
                    new Vertex2D(B, A),
                    new Vertex2D(B, B),
                    new Vertex2D(A, B),
            });

    private static final Polygon DOOR = new CollectionPolygon(
            new Vertex2D[]{
                    new Vertex2D(-30, -150),
                    new Vertex2D(30, -150),
                    new Vertex2D(30, -20),
                    new Vertex2D(-30, -20),
            });

    private static final Polygon ROOF = new CollectionPolygon(
            new Vertex2D[]{
                    new Vertex2D(-150, 150),
                    new Vertex2D(150, 150),
                    new Vertex2D(0, 250),
            });

    private Graphics graphics;

    /**
@@ -100,19 +53,17 @@ public final class Draw extends JFrame {
    private void paintScene(Graphics g) {
        graphics = g;

        Paper paper = new Paper();
        paper.changeColor(cz.muni.fi.pb112.project.geometry.Color.BLUE);
        paper.drawPolygon(DOOR);
        paper.changeColor(cz.muni.fi.pb112.project.geometry.Color.GREEN);
        paper.drawPolygon(WALLS);
        paper.changeColor(cz.muni.fi.pb112.project.geometry.Color.RED);
        paper.drawPolygon(WINDOW_LEFT);
        paper.drawPolygon(WINDOW_RIGHT);
        paper.changeColor(cz.muni.fi.pb112.project.geometry.Color.BLACK);
        paper.drawPolygon(ROOF);
        LabeledPolygon polygon = new LabeledPolygon.Builder()
                .addVertex("A", new Vertex2D(-100, -100))
                .addVertex("D", new Vertex2D(100, 100))
                .addVertex("F", new Vertex2D(-100, 100))
                .addVertex("C", new Vertex2D(100, -100))
                .addVertex("B", new Vertex2D(0, 0))
                .addVertex("E", new Vertex2D(0, 0))
                .build();

        paintCross();
        paintPaper(paper);
        paintPolygon(polygon);
    }

    private void paintCross() {
@@ -121,55 +72,35 @@ public final class Draw extends JFrame {
        graphics.drawLine(HALF_WIDTH, 0, HALF_WIDTH, PANEL_HEIGHT);
    }

    private void paintPaper(Paper paper) {
        for (ColoredPolygon cp : paper.getAllDrawnPolygons()) {
            paintColoredPolygon(cp);
        }
    }

    private void paintColoredPolygon(ColoredPolygon coloredPolygon) {
        paintPolygon(coloredPolygon.getPolygon(), convertColor(coloredPolygon.getColor()));
    }
    private void paintPolygon(Polygon polygon) {

    private void paintPolygon(Polygon polygon, Color color) {
        int arraySize = polygon.getNumVertices() + 1;
        int[] yVertex = new int[arraySize];
        int[] xVertex = new int[arraySize];
        int[] yVertex = new int[polygon.getNumVertices() + 1];
        int[] xVertex = new int[polygon.getNumVertices() + 1];

        for (int i = 0; i < arraySize; i++) {
            Vertex2D vertex = polygon.getVertex(i);
            xVertex[i] = PANEL_WIDTH - ((int) Math.rint(HALF_WIDTH - vertex.getX()));
            yVertex[i] = (int) Math.rint(HALF_HEIGHT - vertex.getY());
        for (int i = 0; i <= polygon.getNumVertices(); i++) {
            xVertex[i] = PANEL_WIDTH - ((int) Math.rint(HALF_WIDTH -
                    polygon.getVertex(i % polygon.getNumVertices()).getX()));
            yVertex[i] = (int) Math.rint(HALF_HEIGHT - polygon.getVertex(i % polygon.getNumVertices()).getY());
        }

        graphics.setColor(color);
        graphics.drawPolygon(xVertex, yVertex, arraySize);
        graphics.setColor(Color.BLUE);
        graphics.drawPolygon(xVertex, yVertex, polygon.getNumVertices() + 1);

        if (polygon instanceof LabeledPolygon) {
            LabeledPolygon labeledPolygon = (LabeledPolygon) polygon;

            graphics.setColor(Color.RED);
            int j = 0;
            int labelDistance = 15;
            for (String label : labeledPolygon.getLabels()) {
                int x = (xVertex[j] < HALF_WIDTH) ? xVertex[j] + labelDistance : xVertex[j] + labelDistance;
                int y = yVertex[j];
                graphics.drawString(label, x, y);
                j++;
                labelDistance *= -1;
            }

    /**
     * Converts enum Color to real color enum.
     *
     * @param color enum specific type
     * @return converted color type
     */
    private Color convertColor(cz.muni.fi.pb112.project.geometry.Color color) {
        switch (color) {
            case WHITE:
                return Color.WHITE;
            case YELLOW:
                return Color.YELLOW;
            case ORANGE:
                return Color.ORANGE;
            case RED:
                return Color.RED;
            case BLUE:
                return Color.BLUE;
            case GREEN:
                return Color.GREEN;
            case BLACK:
                return Color.BLACK;
            default:
                return POLYGON_COLOR;
        }

    }

}
+23 −0
Original line number Diff line number Diff line
package cz.muni.fi.pb112.project.geometry;

/**
 * This interface represents a builder pattern.
 * In other words, it separates the complex construction of an object from its representation.
 * When the object is ready to be built, {@link #build()} method is called.
 * <p>
 * Every implementation of this interface should contain at least one method to fill up the builder with the data.
 *
 * @param <T> represents a type which should be built.
 * @author Marek Sabo
 */

public interface Buildable<T> {

    /**
     * Method is called when the builder object is filled with data.
     *
     * @return new built object
     */
    T build();
}
+36 −0
Original line number Diff line number Diff line
package cz.muni.fi.pb112.project.geometry;

import java.util.Collection;

/**
 * Interface representing basic methods for labeling vertices.
 *
 * @author Marek Sabo
 */
public interface Labelable {

    /**
     * Get vertex stored under given label in a polygon.
     * If label does not exists, IllegalArgumentException is thrown.
     *
     * @param label label under which the vertex is stored
     * @return vertex with given label
     */
    Vertex2D getVertex(String label);

    /**
     * Method returns the labels of vertices in a polygon.
     * The labels are sorted in the ascending order lexicographically.
     *
     * @return collection of labels in ascending order
     */
    Collection<String> getLabels();

    /**
     * Finds all labels corresponding to given vertex.
     *
     * @param vertex vertex which labels are searched
     * @return collection of corresponding labels
     */
    Collection<String> getLabels(Vertex2D vertex);
}
+29 −0
Original line number Diff line number Diff line
package cz.muni.fi.pb112.project.geometry;

import java.util.Collection;
import java.util.Comparator;

/**
 * Interface representing vertices sorting.
 *
 * @author Marek Sabo
 */
public interface Sortable {

    /**
     * Method returns the vertices of this polygon in ascending order
     * defined by he natural ordering of {@link Vertex2D} class.
     *
     * @return sorted vertices
     */
    Collection<Vertex2D> getSortedVertices();

    /**
     * Method returns the vertices of this polygon in ascending order
     * defined by given comparator.
     *
     * @param comparator comparator object used to determine the ordering
     * @return sorted vertices
     */
    Collection<Vertex2D> getSortedVertices(Comparator<Vertex2D> comparator);
}
Loading