**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).

> 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
LabeledPolygonpolygon=newLabeledPolygon.Builder()
.addVertex("A",newVertex2D(2,5))
.addVertex("B",newVertex2D(3,1))
.addVertex("C",newVertex2D(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.

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