České vysoké učení technické v Praze Fakulta elektrotechnická Katedra počítačů
Diplomová práce
Pokročilý editor grafů Bc. Martin Šach
Vedoucí práce: Ing. Ivan Šimeček, Ph.D. Studijní program: Elektrotechnika a informatika, strukturovaný, Navazující magisterský Obor: Výpočetní technika 12. května 2010
Poděkování Chtěl bych vyjádřit poděkování vedoucímu své diplomové práce panu Ing. Ivanu Šimečkovi, Ph.D. za cenné připomínky a rady k obsahu práce. Dále bych chtěl poděkovat svým nejbližším za velkou trpělivost a podporu při studiu a tvorbě práce. Děkuji!
Prohlášení Prohlašuji, že jsem práci vypracoval samostatně a použil jsem pouze podklady uvedené v přiloženém seznamu. Nemám závažný důvod proti užití tohoto školního díla ve smyslu §60 Zákona č. 121/2000 Sb., o právu autorském, o právech souvisejících s právem autorským a o změně některých zákonů (autorský zákon).
V Praze dne 6. 5. 2010
...........................................................
Abstract This work represents the result of many and many days and nights of thinking over the problem of realization of an advanced editor of graphs and the realization itself. This editor enables simulation of graph algorithms.
Abstrakt Tato práce prezentuje výsledek mnoha dnů a nocí strávených úporným přemýšlením o problému realizace pokročilého editoru grafů a realizací samotnou. Tento editor umožňuje simulování grafových algoritmů.
Obsah 1 Úvod.................................................................................................................................................1 2 Popis problému, specifikace cíle......................................................................................................4 2.1 Hlavní cíle práce.......................................................................................................................4 2.2 Požadavky na program..............................................................................................................4 3 Analýza.............................................................................................................................................7 3.1 Požadavky na realizaci..............................................................................................................7 3.1.1 Použití programovacího jazyku Java................................................................................7 3.1.2 Systémové nároky.............................................................................................................8 3.1.3 Spouštění z různých médií a OS.......................................................................................9 3.1.4 Využívání OOP..................................................................................................................9 3.1.5 Strukturalizování kódu....................................................................................................10 3.1.6 Odolávání výjimkám.......................................................................................................10 3.1.7 Vytváření omezení...........................................................................................................11 3.2 Požadavky na práci s grafy.....................................................................................................11 3.2.1 Kreslení grafů..................................................................................................................11 3.2.2 Editace nakreslených grafů.............................................................................................12 3.2.3 Ukládání grafů do souborů..............................................................................................13 3.2.4 Načítání grafů ze souborů...............................................................................................14 3.2.5 Generování grafů.............................................................................................................15 3.2.6 Algoritmy pro práci s grafy.............................................................................................17 3.2.7 Zobrazování grafů...........................................................................................................19 Požadavek: Grafy lze zobrazovat různými způsoby.............................................................19 Požadavek: Stejný graf může být najednou zobrazen různými způsoby.............................20 Požadavek: Změna učiněná na jednom zobrazovači grafu se projeví na ostatních zobrazovačích grafu.............................................................................................................20 3.3 Požadavky na jazyk a nastavení .............................................................................................21 3.3.1 Pamatování si uživatelského nastavení ..........................................................................21 3.3.2 Lokalizace programu.......................................................................................................22 3.3.3 Výchozí nastavení...........................................................................................................23 Obecně k výchozímu nastavení............................................................................................23 Shlukování výchozího nastavení .........................................................................................24 3.3.4 Základní text...................................................................................................................24 3.4 Požadavky na GUI..................................................................................................................24 3.4.1 Základní funkcionality v nástrojové liště........................................................................24 3.4.2 Různé způsoby zobrazování obsahu...............................................................................25 3.4.3 Stavový řádek..................................................................................................................26 3.4.4 Startovní splash screen....................................................................................................26 3.5 Další požadavky......................................................................................................................27 3.5.1 Podpora modifikovatelnosti sebe sama...........................................................................27 3.5.2 Podpora rozšířitelnosti sebe sama...................................................................................27 3.5.3 Podpora přidání nové funkcionality do menu.................................................................29 3.5.4 Přidání nápovědy od uživatele .......................................................................................29 3.5.5 Výpis zpráv o chybách / výjimkách................................................................................30 3.6 Java a reflexe..........................................................................................................................31 4 Popis implementace........................................................................................................................33 4.1 Třídy, soubory.........................................................................................................................33 4.2 Spuštění Programu..................................................................................................................38 4.3 Hlavní okno.............................................................................................................................38
4.4 Ukládání uživatelského nastavení...........................................................................................40 4.5 Načítání uživatelského nastavení............................................................................................41 4.6 Jazykové nastavení.................................................................................................................43 4.6.1 Řešení lokalizace.............................................................................................................43 4.6.2 Lokalizování do nového jazyka......................................................................................45 4.7 Stavy programu.......................................................................................................................45 4.7.1 Přidání uzlu.....................................................................................................................46 4.7.2 Stisknutí tlačítka myši nad plátnem................................................................................47 4.8 Logování.................................................................................................................................47 4.9 Stavový řádek.........................................................................................................................48 4.10 Sebereflexe............................................................................................................................49 4.10.1 Seznam tříd, které jsou v programu dostupné...............................................................49 4.10.2 Vytvoření instance konkrétní třídy................................................................................51 4.11 Možnosti zobrazování obsahu...............................................................................................51 4.12 Grafy a jejich zobrazování....................................................................................................53 4.12.1 Princip řešení.................................................................................................................53 4.12.2 Grafický vs. textový pohled..........................................................................................55 4.12.3 Manuální vs. automatické vykreslování........................................................................56 4.12.4 Zobrazení grafu v jiném pohledu..................................................................................57 4.13 Manage okno.........................................................................................................................57 4.14 Nápověda..............................................................................................................................59 4.15 Rozšíření...............................................................................................................................60 4.15.1 Princip řešení.................................................................................................................60 4.15.2 Jak přidat nový add-on..................................................................................................61 4.16 Přidání položky do menu......................................................................................................62 4.17 Ukládání a načítání grafů......................................................................................................63 4.17.1 Ukládání grafů...............................................................................................................63 4.17.2 Načítání grafů................................................................................................................64 4.18 Simulace algoritmů...............................................................................................................65 4.18.1 „Needs“ metody............................................................................................................66 4.18.2 Krok pro inicializaci prostředí......................................................................................66 4.18.3 Další přípravné kroky....................................................................................................67 4.18.4 Třída Algorithm a její konstruktor................................................................................67 4.18.5 Krok pro start samotného kroku....................................................................................68 4.18.6 Metoda createMySteps()...............................................................................................68 4.18.7 Kroky............................................................................................................................69 4.18.8 Panel pro simulování algoritmů....................................................................................69 4.18.9 Příklad algoritmu pro simulaci......................................................................................71 4.18.10 Další příklady..............................................................................................................73 4.18.11 Příznaky a staré hodnoty uzlů a hran..........................................................................74 4.19 Generátor grafů.....................................................................................................................74 4.19.1 Základní principy..........................................................................................................74 4.19.2 Dostupné generátory.....................................................................................................76 5 Testování.........................................................................................................................................77 5.1 HW nároky..............................................................................................................................77 5.2 Spouštění v různých OS a JDK...............................................................................................77 5.3 Testování funkčnosti SystemInfoFunctions............................................................................77 5.3.1 Vytváření nových projektů..............................................................................................78 5.3.2 Spouštění z konzole / příkazové řádky...........................................................................78 5.4 Testování funkčnosti ostatních částí programu.......................................................................79
5.5 Testy použitelnosti..................................................................................................................81 6 Podobné programy..........................................................................................................................82 6.1 Pokročilý editor grafů.............................................................................................................82 6.2 uDraw(Graph) 3.1...................................................................................................................83 6.3 yEd Graph Editor 3.5..............................................................................................................83 6.4 Applety....................................................................................................................................83 6.5 Graph Magics 2.1....................................................................................................................83 6.6 Platforma NetBeans................................................................................................................84 7 Závěr...............................................................................................................................................85 7.1 Náměty na vylepšení...............................................................................................................85 7.2 Zhodnocení.............................................................................................................................87 8 Seznam použité literatury...............................................................................................................88 A Seznam použitých zkratek..............................................................................................................90 B Uživatelská příručka.......................................................................................................................91 Spouštění programu.......................................................................................................................91 C Obsah CD........................................................................................................................................92
Seznam obrázků Obrázek 1.1: Ukázka zachycující práci s programem vyvinutým v rámci BP (převzato z [2])...........2 Obrázek 3.1: Různé způsoby reprezentace grafu (převzato z [10])....................................................11 Obrázek 3.2: Indikace, kde je kurzor myši.........................................................................................12 Obrázek 3.3: Graf pro uložení (převzato z [11])................................................................................14 Obrázek 3.4:Nabídka tříd, které implementují dané rozhraní\...........................................................19 Obrázek 3.5: Možný způsob načtení nastavení..................................................................................23 Obrázek 3.6: Panel s grafem zobrazeným v záložce a okno pro výběr barvy zobrazené ve vnitřním okně....................................................................................................................................................25 Obrázek 3.7: Obecné schéma, jak při skládání řešit podkomponenty................................................27 Obrázek 3.8: Pohled na nastavení plug-inů v NetBeans IDE 6.7.1....................................................28 Obrázek 3.9:Deaktivování stavového řádku může řešit jen záměna objektů.....................................31 Obrázek 4.1: Ilustrace k popisu třídy GraphicalGraphView..............................................................36 Obrázek 4.2: Načtení programu.........................................................................................................38 Obrázek 4.3: Struktura hlavního okna................................................................................................39 Obrázek 4.4: Jak hlavní okno dědí od javax.swing.JFrame...............................................................39 Obrázek 4.5: Interface Settable..........................................................................................................40 Obrázek 4.6: Načítání nastavení.........................................................................................................41 Obrázek 4.7: Příklad tříd, pomocí kterých se načte nastavení pro GraphViewCircles......................42 Obrázek 4.8: Třídy související s načítáním........................................................................................43 Obrázek 4.9: Stavy AddNodeState a AddNodeNoState.....................................................................46 Obrázek 4.10: TimeLog jako jedna z tříd, kterými lze realizovat log................................................48 Obrázek 4.11: Metody třídy SystemInfoFunctions............................................................................50 Obrázek 4.12: Třídy pro vizualizaci obsahu pomocí záložek............................................................52 Obrázek 4.13: Graf a jeho pohledy.....................................................................................................53 Obrázek 4.14: Třídy potřebné pro realizaci překreslovačů u GraphicalGraphView..........................54 Obrázek 4.15:Graf a uzly v něm.........................................................................................................55 Obrázek 4.16: Hierarchie tříd pohledů...............................................................................................56 Obrázek 4.17: Třída okna pro nastavení komponent..........................................................................57 Obrázek 4.18: Třídy realizující okno pro správu komponent.............................................................58 Obrázek 4.19: Registr add-onů...........................................................................................................58 Obrázek 4.20: Téma nápovědy: Grafy...............................................................................................59 Obrázek 4.21: Třída okna pro správu add-onů...................................................................................61 Obrázek 4.22: Okno pro správu doplňků...........................................................................................62 Obrázek 4.23: Třídy související s položkami v menu........................................................................63 Obrázek 4.24: Třídy používané při ukládání grafů.............................................................................64 Obrázek 4.25: Předci třídy BFS..........................................................................................................65 Ilustrace 4.26: ....................................................................................................................................65 Obrázek 4.27: Třída reprezentující.....................................................................................................66 Obrázek 4.28: Panel pro krokování algoritmů...................................................................................70 Obrázek 4.29: Třídy, které se podílí na realizaci generátoru..............................................................75
1
Úvod
Informatika, stále populárnější pojem, který lze v podstatě považovat stále za pojem moderní, je věda o zpracování informací. A to nejenom na počítačích. Jako počítačová věda je to pojem, který zahrnuje řadu důležitých věcí, disciplín a oborů. A objevit tu můžeme i teorii grafů, disciplínu, která by měla stát jako takový hlavní zastřešovací pojem nad touto prací. O teorii grafů se v [1, str. 18] píše: „Bez pojmů a postupů teorie grafů si lze informatiku jen stěží představit. Využívání grafů a grafových algoritmů prostupuje nejen teoretický základ oboru, ale tvoří neodmyslitelnou součást i tak prakticky zaměřených oblastí, jakou představují např. programovací techniky nebo počítačové sítě“. Tedy teorie grafů není jen zastřešujícím pojmem nad touto prací. Je to hlavně i jedna z důležitých věd, s kterými by měl student informatiky přijít do styku. A pokud možno si toho z jejího studia odnést co nejvíce. Základem této teorie jsou grafy. Jde o matematické modely, kterých je hojně využíváno právě v informatice. Většinou bývají zakreslovány jako shluk koleček propojených čárkami. Kolečkům se říká uzly, čárkám hrany. Vzniklý útvar v řadě případů nepůsobí vůbec složitě. Grafy bývají využívány k abstrakci reálného problému. Skutečný problém se namapuje na graf, ve kterém uzly najednou představují např. města, a hrany silnice mezi takovými městy. Problém, který pro takové město (graf) chceme řešit, tu pak představuje grafový algoritmus, který by měl být nad naším grafem vykonán.. Teorie grafů tu ale není jen od toho, aby nám říkala, že reálné problémy by šlo abstrahovat pomocí grafů. Kromě grafů, jak je zakreslovat či reprezentovat, bylo vymyšleno již nespočetně grafových algoritmů, pomocí kterých lze řešit nejrůznější problémy. Jmenovat můžeme např. Jarníkův algoritmus pro nalezení minimální kostry grafu, Dijkstrův algoritmus pro nalezení nejkratší cesty z jednoho uzlu do uzlů ostatních či Fordův-Fulkersonův algoritmus pro výpočet maximálního toku v síti. Když si dokážeme náš problém namapovat na graf, můžeme se dostat ke zjištění, že pro řešení takového problému lze využít již vymyšlený algoritmus. U toho budeme znát jeho asymptotickou složitost a pseudokód, což nám usnadní vyřešení našeho problému. Vše se nese v duchu: „Proč vymýšlet znova něco, co už bylo dávno vymyšleno?“ To by bylo několik úvodních vět k problematice teorie grafů. Více např. v [1]. Nyní bude ještě následovat několik vět k účelu této práce. Cíle konkrétněji budou ještě rozebrány v kapitole 2. 1
Jak již bylo řečeno tato práce se věnuje teorii grafů. Cílem je poskytnutí podpory pro studium teorie grafů, o což jsem se snažil již v roce 2007 ve své práci bakalářské. Ano. V této diplomové práci navazuji na svou práci bakalářskou [2], která se věnovala stejnému tématu. Tímto tématem bylo vytvoření softwaru, pro „hraní“ si s grafy. Slovo hraní je v uvozovkách, jelikož ho nelze chápat doslova. Uživatel v takovém programu mohl vytvářet grafy a simulovat si na nich nabídnuté algoritmy.
Obrázek 1.1: Ukázka zachycující práci s programem vyvinutým v rámci BP (převzato z [2])
2
Hlavním cílem této diplomové práce je zrovna tak vytvoření softwaru pro takové „hraní si“ s grafy. Napadá mě spojení „Škola hrou“. Idea je taková, že když si uživatel vzniklého programu vytvoří graf, něco v něm změní a má možnost sledovat, jaké taková změna vyvolala konsekvence, tak to na něj může míti výchovný efekt. Přitom platí, že se předešlá práce nekopíruje. Ani se na ní nenavazuje v implementačním slova smyslu. Tři roky odstupu, sebereflexe, ujasnění si, co jsou klady a zápory předešlé práce, to jsou ty základní stavební kameny, které vedly k rozhodnutí, že program, který bude vytvářený v rámci této diplomové práce (dále DP), bude vytvářen úplně od znova. Záměrem je být obecnější. Bylo by chybné předpokládat, že je možné vytvořit nějaký dokonalý a nepřekonatelný program sloužící jako učební pomůcka pro studium grafů. Vždycky je možné vytvořit něco lepšího. Za prvé se může např. něco změnit. Za druhé může být zapomenuto nebo nestiženo v takovém programu něco implementovat. Za třetí implementace některých částí může být chybná, nepřesná, časově neoptimální, nebo jen mírně jiná, než by člověk po čase chtěl. Člověk je bytost chybující a stane se, že se v hotovém programu objeví zásadní nedostatek, i když se testuje sebevíc. A konec konců ještě za čtvrté: V průběhu času se může vyskytnou potřeba po přidání nějaké nové funkcionality. Všechny tyto úvahy mě vedly k názoru, že je vhodnější program pro tuto DP koncipovat více obecnějším způsobem. Jako takovou platformu, kterou by nemělo být tak těžké pozměňovat nebo do ní něco přidávat. To o softwaru napsaném v rámci BP vůbec neplatí a změny by se v něm prováděly poměrně složitě. A to by bylo pro úvod asi vše. Uvedli jsme si několik vět k problematice teorie grafů, uvedli jsme si několik vět k účelu této práce, a konkrétnější budeme v následujících kapitolách. Co se týče související terminologie, tak nejzákladnější používané termíny tu na rozdíl od BP vysvětlovány nebudou. Snahou je se držet terminologie použité v [1]. Tudíž pokud čtenář například neví, co je to ohodnocená hrana nebo co je to smyčka, tak viz. zde.
3
2
Popis problému, specifikace cíle
2.1
Hlavní cíle práce
Úplně nejzákladnějším cílem této práce je vytvoření programu, který by mohl být v ideálním případě využívaný jako učební pomůcka ke studiu teorie grafů. To už bylo uvedeno v úvodu. V této kapitole vše dostatečně rozvedeme. Implementovaný program bude v rámci tohoto textu nazýván jednoduše programem. Hlavní funkcionality programu pravděpodobně budou zjevné ze zadání této DP. Přesto si je v kapitole 2.2 v rámci všech učiněných požadavků vyjmenujeme a v kapitole 3 podrobně probereme. Co se týče textu této DP, tak zde je hlavním cílem přiblížit řešení programu. Tedy jeho implementaci. Uváděny tu pochopitelně nebudou zdrojové kódy, protože ty by samy o sobě čtenáři nic zásadního neřekly. A ani by tu pro ně nebyl dostatek prostoru, protože zdrojového kódu bylo napsáno skutečně dost. Mnohem zásadnější je pro mě nastínit logiku řešení jednotlivých části. Jak je program myšlenkově uchopen. Jak je navrhnut a jak je sestaven. Jaký v něm panuje vnitřní řád. Co je pro jeho obsluhu potřeba. Jak ho nastavovat, měnit či ovládat. A proč to tak je. Rád bych tu nastínil, v čem se takové řešení vyznačuje, jaké má výhody a jaké z něj plynou důsledky. A to všechno by mělo být obsahem následujících řádek.
2.2
Požadavky na program
Tato kapitola vyjmenovává ve strukturalizované formě požadavky / cíle, o kterých bylo rozhodnuto, že je program bude splňovat. Jde tedy o funkcionality, které program i splňuje. Tyto funkcionality plynou jednak ze zadání této DP, a pak také z požadavků, které jsem si učinil sám. Učinil jsem tak po zkušenostech z BP a po analýze celého problému. Tyto požadavky budou v kapitole 3 blíže rozvedeny a analyzovány. Zde je pouze jejich seznam, aby bylo možné si snadno učinit přehled o základních možnostech programu. Jestli se tu vyjmenovává požadavků zbytečně moc, nebo jestli tu měla býti zmíněna ještě nějaká další funkčnost, která by mohla býti považována za požadavek na program, těžko posoudit. Jde o záležitost subjektivního vnímání. V následujícím seznamu jsem se snažil mimo jiné o vyjmenování všech základních a nejdůležitějších věcí, jejichž diskutování by mohlo pomoci k pochopení vnitřní logiky výsledku. Celé uchopení programu je samozřejmě od učiněných požadavků dosti odvislé. 4
Požadavky na realizaci: 1.
Program je naprogramovaný v programovacím jazyku Java
2.
Program zbytečně neplýtvá výpočetními prostředky.
3.
Předpokládá se spouštění z různých operačních systémů a z různých úložných médií.
4.
Program využívá možností objektově orientovaného programování.
5.
Program se snaží obecně použitelné / společné funkcionality vyčleňovat do zvláštních tříd / metod.
6.
Program není ukončen kvůli jednoduché chybě / výjimce, která se vyskytne během běhu programu.
7.
Program zbytečně nevytváří nějaká omezení, která by znesnadňovala budoucí vývoj.
Požadavky na práci s grafy: 8.
Uživatel může kreslit grafy.
9.
Uživatel může editovat nakreslené grafy.
10.
Uživatel může uložit graf do souboru.
11.
Uživatel může načíst graf ze souboru.
12.
Uživatel si může nechat vygenerovat graf.
13.
Uživatel může graf ovlivnit zadaným algoritmem napsaným v programovacím jazyku Java.
14.
Grafy lze zobrazovat různými způsoby.
15.
Stejný graf může být najednou zobrazen různými způsoby.
16.
Změna učiněná na jednom zobrazovači grafu se projeví na ostatních zobrazovačích grafu.
17.
Program lze rozšiřovat o podporu nových formátů, do kterých lze grafy ukládat, či ze kterých lze grafy načítat.
Požadavky na jazyk a nastavení: 18.
Program si pamatuje uživatelské nastavení.
19.
Program je možné spustit v různých světových jazycích.
20.
Program bude spuštěn ve výchozím nastavení, pakliže načtení uživatelského nastavení selže.
21.
Výchozí nastavení je pokud možno shluknuto do jednoho místa.
22.
Program poskytne základní text v situacích, kdy se nepodaří načíst text lokalizovaný.
5
Požadavky na GUI: 23.
Program zpřístupňuje základní funkcionality v nástrojové liště.
24.
Zobrazovaný obsah, kterým v našem případě jsou vykreslené grafy, lze uživateli zpřístupňovat různými způsoby – např. jako jednu ze záložek v panelu záložek, či jako okénko.
25.
Uživateli se může zobrazovat stavový řádek, který obsahuje základní informace o tom, co se právě v programu děje.
26.
Před startem programu se zobrazuje splash screen (okno indikující načítání programu).
Další požadavky: 27.
Program se snaží podpořit uživatelskou modifikovatelnost sebe sama.
28.
Program se snaží podpořit uživatelskou rozšířitelnost sebe sama.
29.
Program usnadňuje přidání nové funkcionality do menu.
30.
Program může umožnit přidání nápovědy pro uživatele.
31.
Program umožňuje výpis zpráv o chybách / výjimkách.
6
3
Analýza
Účelem této kapitoly je podrobně zanalyzovat požadavky, které byly heslovitě vyjmenovány v kapitole 2.2. Stejnojmenná kapitola byla i v mé BP, kde mimo jiné stojí věta: „Analýza a návrh řešení nebývá jednoduchý úkol, protože její úspěšné vykonání vyžaduje detailní znalost řešené úlohy, dobrou znalost prostředků, které by bylo možné k řešení úlohy využít, a konec konců i talent pomocí těchto vědomostí vyfiltrovat vhodné řešení, toto řešení dokázat popsat a tento popis umět obhájit.“ Asi nebude žádným překvapivým tvrzením, když uvedeme, že tato věta platí i zde. Má-li se zvýšit pravděpodobnost, že jakýkoli implementovaný program bude kvalitní, analýza řešení je potřebná vždy. Jinak pak vznikají různá ad hoc řešení, pomocí kterých se programátor snaží vyřešit problém, který se objevil. Když se nad vším člověk zamyslí dopředu, na řadu případných problémů se přijde už během analýzy. Jak už bylo uvedeno v úvodu, v analýze a návrhu řešení jsem mohl vycházet ze zkušeností s implementací podobného programu, který vznikl v rámci mé BP. Tehdy se člověk zabýval ještě takovými věcmi, jako bylo například zvolení vhodného programovacího jazyku, v kterém práci implementovat. Tehdy vyhrál programovací jazyk Java (konkrétně Java 5.0 od tehdejších Sun Microsystems) v kombinaci s obecným značkovacím jazykem XML.
3.1
Požadavky na realizaci
3.1.1 Použití programovacího jazyku Java Požadavek 1: Program je napsaný v programovacím jazyku Java. Zvolit Javu či jiný programovací jazyk? Od této volby jsem byl v rámci této práce odstíněn, protože volba Javy je součástí zadání. Asi netřeba připomínat skutečnosti, že Java je po celém světě hodně rozšířená a že programy vytvořené v Javě jsou nezávislé na architektuře, přenositelné a objektově orientované. I proto bych Javu znovu volil i dnes. Více o ní případně viz. v [3]. V rámci analýzy a návrhu řešení bych především pojednal o verzi Javy, kterou jsem se rozhodl použít. Stejně jako v případě BP jsem myslel na Javu 5.0 od Sun Microsystems. Již několik let je sice dostupná i verze 6, ale tu případný uživatel nemusí mít nainstalovánu. K Javě 6 jsem se chtěl uchýlit jen v případě, pokud by mě k tomu nutila nějaká šikovná knihovna, kterou by šlo v programu využít. Nestalo se tak. Další skutečností, která souvisí přímo s programovacím jazykem, je ta, že jsem se v rámci 7
analýzy rozhodl, že nebudu využívat deprecated metody. To jsou takové metody, které je doporučeno nepoužívat, protože k nim nejspíš existují lepší alternativy. V Javě jsou přítomny hlavně kvůli zpětné kompatibilitě. Díky té programy napsané pro určitou verzi Javy lze spouštět i ve verzích novějších. To je podstatná skutečnost, která měla na volbu Javy 5.0 nemalý vliv. Finální stav je tak takový, že program je napsaný v 5. verzi Javy, spustitelný v jejím JRE, a použité metody nejsou označeny jako deprecated ani v Javě 6.
3.1.2 Systémové nároky U požadavku 2 bylo uvedeno, aby program zbytečně neplýtval výpočetními prostředky. Jde o poměrně obecný požadavek, který by měl signalizovat, že jsem si vědom toho, že počítače, na kterých program poběží, mají limitované možnosti. Dostupná paměť tu nebude nekonečná, takže není vhodné si do ní ukládat každou zbytečnost. A počet instrukcí, které počítač bude schopen za časový úsek provést, bude také konečný. V rámci BP jsem v souvislosti se systémovými nároky napsal několik vět o rozdílu mezi interpretovanými a kompilovanými jazyky. Tentokrát bych tu místo toho jen odkázal na systémové nároky využitého virtuálního stroje [4]. Od těch by mělo býti snahou se příliš nevzdalovat. Objekty se v programu mohou vytvářet až v případě potřeby. Po použití se mohou hned odstranit, aniž by se čekalo až na konec programu. A některé časté časově náročné operace mohou být naopak urychleny ukládáním před vypočtených hodnot. Mohou, mohou, mohou – slova vypovídající o tom, že se o systémových nárocích bavíme v obecnější rovině. Důvod je ten, že na vyvíjený program nahlížím jako na „knihovnu rozumných řešení“, kde není prvořadým cílem efektivnost, ale nabízená funkcionalita, poskytnutá míra rozšiřitelnosti atd. Jako příklad lze uvést následující kód (předpokládá existenci třídy A s bezparametrovým konstruktorem): A a1 = new A(); A a2 = (A) A.class.newInstance(); Jak se uvádí v [5], je výkonnostní rozdíl mezi tím, jestli vytvoříme instanci třídy pomocí operátoru new, nebo jestli k tomu použijeme metodu newInstance() třídy Class. Použití new je rychlejší a pro většinu situací naprosto správné. Na druhou stranu, jak bude diskutováno v textu ještě dále, při použití newInstance() můžeme za běhu načíst i třídy, které při spuštění programu ani neexistovaly. To může být např. třída nějakého plug-inu. A to je velice zajímavé chování. Hodí se 8
a tím nás nutí do výkonnostního kompromisu. A podobných příkladů bychom tu mohli uvádět ještě několik. V této souvislosti bych tu dále připomenul skutečnost, že cílem programu je býti učební pomůckou. V takových programech bývají spouštěny spíše menší úlohy, a víc než rychlost se cení názornost. Nám tak stačí, aby se program při běžné práci na běžných počítačích zbytečně nesekal. Tolik k systémovým nárokům.
3.1.3 Spouštění z různých médií a OS Mohlo by se zdát, že je tady tento bod zmiňován zbytečně, protože jednou ze základních vlastností Javy je, že se snaží býti platformově nezávislá. Přesto se jen s takovým tvrzením nemůžeme spokojit. Existují totiž skutečnosti, na které je nutné v době implementace pamatovat, pokud chceme maximalizovat míru bezkonfliktnosti při spouštění na různých OS a z různých médií. A proto tento speciální požadavek. Mezi možné problémy patří následující: •
Odlišné znaky pro odřádkování.
•
Odlišný typ lomítka, které se využívá k popisu cesty ke konkrétnímu souboru v souborovém systému.
•
Reálná možnost toho, že program bude uložen na takovém médiu, na kterém nebude možné uložení dalších souborů. V Javě jsou k dispozici funkce, které nám umí vrátit správný znak pro odřádkování a správné
lomítko do cesty souboru. Nemusíme se přitom starat o detekci toho, s jakým OS se vůbec pracuje. Mechanismus vyhazování výjimek zase umožňuje, že programátor na každém rizikovém místě v programu nemusí testovat, jestli platí určitá skutečnost. Místo toho předpokládá korektní chování, a na zvláštním místě ošetří možnost, že ke korektnímu chování nedošlo. Např. ve zmiňované situaci s ukládáním souborů zkusíme soubor uložit, a když to nebude možné, zareagujeme na vyhozenou výjimku.
3.1.4 Využívání OOP Java je objektově orientovaný jazyk a proto se může zdát jako zbytečné, když si vytváříme požadavek 4, aby se využívalo možností OOP. Záleží ale na chápání celého problému. V Javě lze krásně napsat program, který moc z možností, které OOP nabízí, nevyužívá. To jsou např. abstrakce, zapouzdření, skládání, dědičnost a polymorfismus. O nich více např. v [6]. Když jsem se zamýšlel nad programem, který vznikl v rámci BP a hledal v něm nedostatky, 9
jedním z učiněných závěrů bylo právě to, že nebylo využito možností, které OOP nabízí. V programu se např. neustále pomocí podmínek testovalo, jestli platí nějaký stav, aby se případně provedla určitá operace. Takové věci šlo ale řešit např. jen lepší strukturalizací kódu a využitím polymorfismu. A takto by se dalo pokračovat dále. Proto jsem zavedl tento požadavek. Mělo by se více využívat dědění k tomu, aby se nové funkcionality vytvořily pozměněním funkcionalit starších. Dále komplexnější celky mohou být jednoduše tvořeny pomocí skládání z celků jednodušších apod. Pokud chceme mít program obecně navržen, aby ho bylo možné co nejjednodušeji modifikovat a rozšiřovat, jsou podobné postupy nadmíru vhodné.
3.1.5 Strukturalizování kódu U požadavku 5 se píše o vyčleňování obecně použitelných funkcionalit do zvláštních metod. Jde o další z obecných požadavků, který by se dal zařadit do rad, jak vhodně programovat. Zde ho uvádíme jako jednu ze skutečností, na které by měl člověk v rámci implementace pamatovat. Pokud bude člověk stejný kód shlukovat (v případně parametrizované podobně), má to pro něj mimo jiné následující důsledky: •
Nemusí se psát stejná věc vícekrát.
•
V případě chybné implementace lze provést změny jen na jednom místě.
•
Jednodušeji se testuje funkčnost.
3.1.6 Odolávání výjimkám V Javě je přítomný mechanismus výjimek [7], díky kterému lze lépe reagovat na nestandardní a chybové chování programu. Výjimka může být vyvolána např. v případě, když není na zadané adrese nalezen požadovaný soubor, když se pokoušíme dělit nulou nebo když se v poli snažíme přistoupit k hodnotě na indexu, který v poli vůbec není. K výjimce za běhu programu zkrátka může dojít poměrně snadno. A nebylo by pochopitelně vhodné, aby v takovém případě program hned skončil. Tedy pokud existuje alternativa, jak se zachovat v případě nezdaru. A ta existuje v podstatě vždy, když budeme uvažovat i takové možnosti, jako jsou dialogová okna s varovným výpisem. Proto můžeme požadovat, aby si pokud možno žádná výjimka nevynutila ukončení programu. V Javě platí, že vzniklé výjimky lze odchytávat a ve speciálních blocích na ně reagovat. Snahou by mělo býti takového chování využít a výjimky odchytit. A je-li to nutné, nabídnout 10
alternativní možnost pro běh programu. Program by při vzniklé výjimce nemusel korektně fungovat v určité své části, ale sám o sobě by měl neustále zůstávat běhu schopný.
3.1.7 Vytváření omezení U požadavku 7 se píše o tom, aby nebyla zbytečně vytvářena omezení, která by znesnadňovala budoucí vývoj. Z našeho pohledu jde o další z požadavků, které jsou poměrně obecné. Skrývat se pod ním může psaní kódu, které lze samo o sobě označit za obecné. Ke komponentám, u kterých by byla představitelná i jiná implementace, lze vytvářet rozhraní, pomocí kterých by se z vnějšku skrývalo, s instancí jaké třídy se konkrétně pracuje. Dále není nutné hned využívat prostředků / knihoven, které by samy od sebe dosti omezovaly možnosti, které byly do doby jejich využití dostupné. Pokud se nějaká taková knihovna vyskytne, nešlo by k ní nalézt méně náročnou alternativu? Nešlo by pro uložení použít formát, který je dostupný na všech platformách? A nešlo by nabídku možností např. v nějaké roletce odvodit od stavu v systému či od uživatelsky definovaných mezních hodnot - tak aby nabídka nemusela být vytvořena napevno? To všechno jsou otázky, který si člověk musí během vývoje pokládat, a také si je pokládal. Podařilo se mi tak např. zachovat možnost spouštět program s pomocí JRE 5.0, i když jsem kvůli tomu musel oželet několik řešení funkčních jen v Java 6 a vytvořit si místo toho řešení jiné.
3.2
Požadavky na práci s grafy
3.2.1 Kreslení grafů U požadavku 8 na kreslení grafů nelze uvést nic zásadně jiného, než bylo uvedeno již v BP. Aby si uživatel mohl kreslit, je potřeba, aby zvolený programovací jazyk podporoval grafický výstup na obrazovku. To Java umožňuje prostřednictvím knihoven AWT a JFC (Swing), které jsou součástí Java Core API (sada základních API). Dostupné jsou základní grafické prvky jako je např. elipsa, úsečka a obdélník. Ty nám usnadňují vykreslování a další základní práce. Programátor se tak sám nesnaží pomocí jednotlivých bodíků „vybodíkovat“ útvar, který by měl uživateli připomínat např. zmíněnou elipsu. Toto je ale základní chování, které bychom čekali od každého programovacího Obrázek 3.1: Různé způsoby reprezentace grafu (převzato z [10]) 11
jazyku, kde lze něco kreslit. Zmínit bych chtěl proto hlavně takové funkcionality, díky kterých nám grafický objekt umí sdělit, jestli se zadaný bod v grafickém kontextu [8] nachází uvnitř nebo vně jeho plochy. Díky tomu tak např. u instance třídy java.awt.geom.Ellipse2D (elipsa) umíme relativně jednoduše zjistit, jestli na nad ní bylo stisknuto tlačítko myši. Vyjadřovat si to sami, jestli zadaná souřadnice leží uvnitř obsahu nějakého objektu, který nemá tvar zrovna čtverce, by byl již poněkud obtížnější matematický problém. O AWT a Swingu více např. v [8]. O kreslení geometrických útvarů v Javě pojednává [9].
3.2.2 Editace nakreslených grafů Požadavek 9: Uživatel může editovat nakreslené grafy. Zde začněme tím, co si konkrétně pod tímto požadavkem představit. Tak za prvé jde už o samotnou možnost, že si uživatel může grafy nakreslit ručně. Tedy že má nějaké plátno, v něm si zvolí libovolný bod, nad ním klikne myší, v místě kliknutí se objeví např. kolečko reprezentující uzel apod. To je základ. A k tomu chceme, aby uživatel, lidský tvor, který může chybovat a rozmýšlet se, měl možnost nakreslené objekty odstraňovat nebo nějak modifikovat. Např. u uzlu můžeme chtít změnit jeho pozici, velikost, barvu, ohodnocení. Tedy pokud takové parametry uzel vůbec má. Grafy lze totiž uživateli vizuálně prezentovat různými způsoby [10], což se snaží naznačit obrázek 3.1. O zobrazování grafů různými
Obrázek 3.2: Indikace, kde je kurzor myši
způsoby bude dále pojednáno v kap. 3.2.7. Jestli v textu kapitoly 3.2.1 nebylo patrno, proč byly oslavovány funkcionality, pomocí kterých může uživatel relativně jednoduše zjistit, jestli nebylo např. kliknuto myší nad daným grafickým objektem, tak nyní by už to snad býti vidět mohlo. Podobné funkcionality se pro potřeby grafické editace hodí náramně. Při kliknutí na plátno, kde je graf nakreslen, nebudeme vědět, zda bylo kliknuto na nějaký grafický útvar nebo ne. Plátno pouze vykresluje obrazy daných grafických objektů, a neví o nich nic detailního. My si proto musíme ukládat, které objekty chceme na plátno vykreslit, a když je nad plátnem např. stisknuto tlačítko myši, tak se objektů zeptáme, jestli náhodou kurzor myši nebyl v době stisknutí nad jejich plochou. Když ano, vykonáme potřebnou akci.
12
3.2.3 Ukládání grafů do souborů Požadavek na ukládání vytvořeného obsahu je jedním z nejzákladnějších požadavků, se kterým se člověk setká skoro u každého programu. Proč plýtvat nad něčím časem, když veškeré vynaložené úsilí bude ztraceno v době ukončení programu? Tato úvaha asi nikoho nepřekvapí, jen jsem ji uvedl jako úvod k celému problému. S problémem ukládání vytvořených grafů se člověk samozřejmě potýkal už v rámci BP. Byla potřeba vytvořit nebo najít již standardizovaný formát, pomocí kterého by bylo možné zachytit co nejvíce z vytvořeného obsahu, aby si pak takový obsah mohl uživatel znovu (pokud možno v nezměněné podobě) načíst. V rámci BP jsem čtenáře seznámil s formáty GraphML [11], GXL [12], XGMML [13] a GML [14]. První tři jmenovaní zástupci jsou XML formáty s jasně definovanou strukturou, čtvrtý je jeden ze zástupců starších formátů, které vznikly ještě před příchodem XML. Jak lze možná z vět uvedených výše vycítit, formátů pro ukládání grafů bylo vytvořeno relativně dost. Zmíněné formáty patří k jedněm z těch významnější formátů. A minimálně jeden ze zmíněných XML formátů by měl program podporovat. Ideálně GraphML, který je nejnovější. To je jediný požadavek na podporovaný formát, který jsem v rámci analýzy a návrhu řešení učinil. Měl jsem k tomu důvod, který snad bude patrný z následujících odstavců. Jedním z cílů programu je uživatelská rozšířitelnost. Když člověk přemýšlí nad tímto termínem, může dojít stejně tak jako já k závěrům, že by se takováto rozšířitelnost měla týkat i formátů, do kterých by bylo možné graf ukládat. Když jsem přišel k tomuto názoru, další na řadě byly úvahy, jak zařídit, aby měl program nějakou základní nabídku formátů, do kterých by umožnil ukládat. Tuto nabídku by bylo možné případně rozšiřovat, či dokonce podporu některého formátu odstraňovat. Tyto úvahy vedly až k závěrům, že v Javě půjde s využitím Java Reflection API zařídit, aby uvažování, který formát je v základní nabídce a který je nějakým speciálním způsobem načítaný doplněk, splynulo. Program může být naprogramovaný i tak, že nebude uživatelský ani programátorský rozdíl v tom, jestli člověk pracuje s formátem (třídou, která daný formát zastupuje), který byl v programu přítomný už v době publikování této práce, a nebo byl dopsán až někdy pak. Takového chování lze s Java Reflection API dosáhnout snadno. Jak je vše konkrétně řešené, si lze přečíst v kapitole 4.17. Krátké pojednání k Java Reflection API lze najít v kap. 3.6. Příklad uložení grafu do GraphML (převzatý z [11]) následuje:
13
Uložení grafu z obrázku 3.3 do formátu GraphML:
<default>yellow <node id="n0"> green <node id="n1"/> <node id="n2"> blue <node id="n3"> red <node id="n4"/> <node id="n5"> turquoise <edge id="e0" source="n0" target="n2"> 1.0 Obrázek 3.3: Graf pro uložení (převzato z [11]) <edge id="e1" source="n0" target="n1"> 1.0 <edge id="e2" source="n1" target="n3"> 2.0 <edge id="e3" source="n3" target="n2"/> <edge id="e4" source="n2" target="n4"/> <edge id="e5" source="n3" target="n5"/> <edge id="e6" source="n5" target="n4"> 1.1
3.2.4 Načítání grafů ze souborů Když může uživatel ukládat grafy do speciálních formátů, které umožní zpětnou rekonstrukci ukládaného grafu, mělo by býti možné umožnit i samotnou rekonstrukci takto uloženého grafu. Takové chování se může zdát intuitivní, přesto mu věnuji stejně jako v rámci BP speciální kapitolu. Připadá mi totiž vhodné ještě zmínit několik skutečností.
14
Tak za prvé mi v rámci různých případů užití, které jsem si během analýzy chování programu zkoušel navrhnout, připadalo rozumné, aby načítání grafů a ukládání grafů byly na sobě nezávislé. Tím myslím skutečnost, že by teoreticky mohlo býti možné i to, že by graf uměl ukládat do úplně jiných formátů, než z kterých by uměl načítat. Tento příklad je po funkční stránce samozřejmě úplně nevhodný, ale de facto by mohl být v obecně koncipovaném programu reálný. Vše lze demonstrovat na celkem reálné situaci, kdy by program uměl ukládat grafy do pomyslných formátů A, B a C, z těchto formátů by grafy uměl i načítat, a navíc by uměl načítat grafy i z nějakého speciálního formátu, kterému zde budeme říkat formát D. Jde de facto o možnost pro import, se kterým se můžeme v některých programech setkat jako s oddělenou funkčností od otevírání. Náš program by proto nějaké speciální funkčnosti jako Export do a Import z nezaváděl. Místo toho tu budeme mluvit jen o formátech, do kterých lze ukládat a o formátech, z kterých lze načítat. Je přitom vhodné, aby obě tyto množiny pokrývaly stejné formáty. Pokud to tedy bude možné. Druhou skutečnost, kterou bych zde chtěl uvést, můžeme nazvat např. GUI podporou načítání, která se tu hodí ještě více než u ukládání, ale i tam by měla býti dostupná. Myslí se tím skutečnost, aby se grafy po stisknutí tlačítka Uložit samy neukládaly s výchozím názvem do nějakého implicitního úložiště, kde by případně automaticky přepsaly starší stejnojmenné soubory. Stejně tak by bylo krajně nepohodlné, kdyby jedinou možností, jak by mohl uživatel uložit nebo načíst graf do konkrétního místa úložného prostoru, byla nutnost zapsat ručně cestu k takovému nově vytvářenému souboru. V GUI aplikacích jsou uživatelé zvyklí na chování, že při kliknutí na uložit / otevřít, se jim zobrazí okénko, v kterém snadno mohou nadefinovat jméno souboru a cestu k takovému souboru. A právě takové chování budeme chtít stejně jako v BP i nyní. V Javě k podpoře vytváření takovýchto okýnek slouží třída JFileChooser z package javax.swing.
3.2.5 Generování grafů Vygenerování grafu lze dosáhnout různými způsoby. Tím se myslí, že pro takovou funkcionalitu může existovat samostatně fungující program, stejně tak jako nějaká funkcionalita uvnitř hlavního programu. Dále se může lišit podoba generovaného obsahu a úroveň takového generovaného obsahu. Generováním lze například vytvořit soubor s obsahem, s kterým by si uměl poradit některý
15
z „načítačů“ grafů diskutovaných v kapitole 3.2.4. Jinou možností je v rámci běhu programu vygenerovat graf, a hned ho zobrazit. U pokročilého generátoru by vygenerované grafy neměly být úplně náhodné a měly by obsahovat rozumně rozvržené uzly rozmístěné např. v nějakém navoleném tvaru. Zde by bylo možné definovat si počty hran a uzlů, jejich tvar, barvu, ohodnocení, a jmenovat by se tu dalo dál. Možností, jak si hrát s generátorem, je zkrátka mnoho. U programu, jehož snahou je obecnější návrh, je problematické určovat, jak by takový generátor měl vypadat a co všechno by měl umět. Ideální by mohlo býti, kdyby uměl všechno, ale něco podobného jsme už řešili v úvodu. Co je všechno? A nevyskytne se jednou něco, co by bylo potřeba do generátoru přidat? A co kdyby se po čase v generátoru objevila nějaká chyba? Bude snadné ji opravit? Ne, myšlenka nějakého komplexního generátoru, v jehož možnostech by prostřednictvím zaškrtávání různých možností bylo nepřesně řečeno všechno, se mi už od samotného začátku nelíbila. Takový generátor by se implementoval složitě, nebyl by tak uživatelsky přívětivý pro člověka, kterému by stačila jen drobnost, a o obecnosti a rozšířitelnosti také těžko něco konkrétního soudit. Ty by byly plně odvislé od implementačního zvládnutí takového komplexního generátoru. Místo komplexního generátoru jsem proto začal uvažovat o něčem, co by šlo nazvat jako okno pro výběr generátorů. Myšlenka je de facto stejná jako v případě ukládání a načítání grafů. Neměl by být činěn žádný konkrétní požadavek, co by měl takový generátor umět. Jednomu uživateli by se grafy mohly hodit v podobě takové, druhému v podobě jiné. Jenom v rámci předmětu Paralelní systémy a algoritmy by studenti určitě ocenili řadu různých generátorů – jeden by mohl umět generovat mřížky o zadaných rozměrech, druhý krychle o zadaných dimenzích apod. Místo toho jsem proto shledal jako vhodnější, aby si šlo vytvářet generátory libovolně, a jak by se takový generátor choval a co by generoval, by bylo jen a jen na něm. Uživatel by si mohl otevřít (jak bylo výše označeno) okno pro výběr generátorů, zde by si vybral konkrétní generátor, v něm by využil možnosti, které tento generátor nabízí a vygeneroval si s ním nový graf. To by bylo okno pro výběr generátorů. Z předešlého povídání vyplývá další skutečnost. Uživatel by měl mít možnost si dopsat vlastní generátor. Ten by mělo jít co nejjednodušeji připojit k programu takovým způsobem, aby se nový generátor zobrazoval v okně pro výběr generátorů. Když si člověk vytvořil tento cíl, znělo to už poměrně složitě, ale stále se tu jedná o to samé jako v případě ukládaní grafů. Tam se uvádělo využití Java Reflection API. Zde lze celý problém řešit obdobně. Uživatel si např. ve vývojovém nástroji NetBeans vytvoří nový projekt, do knihoven si přidá JAR soubor našeho programu, a aniž by v něm musel něco měnit, vytvoří si novou javovskou 16
třídu, která implementuje zadané rozhraní. K této třídě dopíše chování metod, a když projekt spustí, tak se mu pro výběr generátorů objeví i nově vytvořený generátor. Ten lze normálně použít. V pozadí za vším je vyhledání tříd, které jsou v programu dostupné. V případě takového řešení opět těžko hovořit o tom, který z nabízených generátorů je pevnou částí programu a který přidaný doplněk. Všechny generátory si budou rovnocenné, jen jeden se v nabídce zobrazí jako prvně vybraný. Odstranění třídy, která daný generátor představuje, pak znamená, že se takový generátor v nabídce generátorů již nebude nabízet. K podobnému chování by mohlo stačit i udělat třídu jako abstraktní, a myslitelné jsou i jiné možnosti. Konkrétní chování je popsáno v kapitole 4.19, kde se již uvažuje vytvořený kód.
3.2.6 Algoritmy pro práci s grafy Požadavek 13: Uživatel může graf ovlivnit zadaným algoritmem v programovacím jazyku Java. K této funkcionalitě úvodem asi tak, že by to měla býti jedna z nejdůležitějších změna oproti programu implementovanému v rámci BP. To, že se objevila již v zadání, plyne z určité základní analýzy, která proběhla před konkretizováním zadání této práce. Původní program implementovaný v rámci BP byl takového ražení, že jsme tu měli plátno pro kreslení grafů, a vedle něj byl k dispozici panel simulátoru, který nabízel několik základních algoritmů, které bylo možné na vytvořeném grafu simulovat. Všechny tyto algoritmy byly zapsány přímo jako součást panelu tohoto simulátoru. Přidání nového algoritmu pro simulování pak bylo možné takovým způsobem, že si uživatel potřebný kód napsal třeba do nějaké funkce, a následně ho ručně přidal do potřebných míst. Tak, aby se v roletce s nabídkou algoritmů zobrazoval i algoritmus nový a bylo možné ho v pořádku spouštět. Takových vynucených změn kvůli přidání nového algoritmu nebylo vůbec málo. Dále se tu programátor musel při některých operacích starat o nastavení správných hodnot v různých místech programu. Pro vývojáře tedy nic příjemného. Byla tu ale ta výhoda, že si nově přidávaný algoritmus šlo zapsat přímo v Javě. O té jsme tu již uváděli, že ji ovládá spousta programátorů. Zauvažujme se na chvíli, že by simulační algoritmy byly programovány v něčem jiném, než v Javě a že by s celým programem nesdílely jedno stejné běhové prostředí. Úvahy učiněné v rámci analýzy podobné myšlenky vedly k následujícím závěrům: •
Programátorům algoritmů je vhodné poskytnout programovací jazyk s pokud možno známou syntaxí, aby se nemuseli učit něco nového. Nutnost učit se pracovat s novým 17
programovacím jazykem, by mohla řadu lidí odradit, aby vůbec takovou práci zkoušeli. •
Vytvoření vlastního programovacího jazyku (jedno s jakou syntaxí) pro tvorbu simulačních algoritmů by programátora odstínilo od samotného běhové prostředí. Vznikla by nutnost si vytvořit vlastní syntaktický analyzátor a další související prostředky, přičemž by vlastně ve výsledku jen docházelo k mapování příkazů z takového programu na volání metod našeho programu. Čím jednodušší by takový programovací jazyk byl, tím bychom pravděpodobně byly odstíněni od více dostupných funkcionalit v programu, protože by chybělo potřebné mapování. Některé věci by tak nemusely býti možné a při potřebě přidání nového chování, které nebylo pokryto, by mohla vyvstat nutnost modifikace jazyku a pravděpodobně i nemalého množství s ním souvisejícího kódu. Že při takovém doplňování dojde k vytvoření chyb, je reálné.
•
Řešení použité v BP, tedy řešení, kdy simulační kód využívá stejné běhové prostředí, protože sám je částí programu, je výhodné v tom, že programátor simulačního algoritmu není v ničem omezen. Dostupné mu jsou všechny veřejně přístupné třídy a metody programu, a dále i samotné možnosti Javy a vývojových prostředí, které jsou pro ni dostupná..
•
Řešení použité v BP má řadu nevýhod souvisejících s úpravou kódu, jak již bylo diskutováno. Není vhodné kvůli novým algoritmům měnit cokoli ve zdrojových kódech programu. Když si píšeme program v Javě, tak taky kvůli němu neděláme změny ve vývojovém nástroji, v kterém takový program píšeme (např. v NetBeans). To by byly základní podněty, díky kterým mi volba použití samotné Javy pro zapisování
simulačních algoritmů připadala rozumná. Snahami dalších analýz a návrhů chování proto bylo zajistit, aby takové simulování netrpělo stejnými (zmiňovanými) nedostatky, které se vyskytly v rámci BP. V úvahách nad výsledným chováním jsem se tak postupně dostal k řešení, kde se opět využívá Java Reflection API. O tom, pro připomenutí, byla řeč již v souvislosti s ukládáním grafů (kap.3.2.3) a generováním grafů (kap. 3.2.5). Simulační algoritmus může být třída, která implementuje konkrétní rozhraní. V nabídce dostupných algoritmů, které lze v programu simulovat, pak lze nabízet všechny třídy, které implementují zadané rozhraní a jsou aktuálně v programu dostupné. Díky tomu by se programátor takových algoritmů nemusel vůbec starat o přidávání svých výtvorů do potřebných míst tak, aby se zobrazily v nabídce. Tato nabídka by se namísto toho generovala automaticky. 18
Zároveň (druhá důležitá věc) celý program by měl být koncipován tak, aby se uživatel/programátor, mohl soustředit převážně na algoritmus samotný, a o ostatní věci, jako je vykreslování, různé stavové proměnné programu apod. se nemusel starat. I když i tyto možnosti by tu byly. Samotný program lze tak chápat jako takové běhového prostředí pro simulaci. Programátor píšící simulační algoritmy v něm nemusí řešit, jak implementovat potřebné struktury, jak zajistit, aby se mu na obrazovce vykreslila hrana, na jejímž jednom konci je šipka atd. Místo toho se soustředí se hlavně na to, jak co nejvýchovněji zapsat kód, pomocí kterého by šlo ilustrovat, jak např. funguje algoritmus pro prohledávání grafu do šířky.
Obrázek 3.4:Nabídka tříd, které implementují dané rozhraní\
3.2.7 Zobrazování grafů Požadavek: Grafy lze zobrazovat různými způsoby.
Grafy lze zobrazovat různými způsoby. Toto je obecně napsaný požadavek, za kterým se skrývá možná nejvíce práce. Záleží, co všechno si člověk pod takovým požadavkem představí. O programu vytvořeném v rámci BP se takto mluvit nedalo. A přitom lze nad takovým chováním uvažovat jako o jednom z těch, u kterých by se v případě vydařené implementace dalo mluvit o výchovném efektu. Ale konkrétněji. Byla tu řeč o tom, že grafy lze zobrazovat různými způsoby. Uváděn byl graf vykreslený s pomocí čárek a koleček, stejně tak graf, který je reprezentovaný maticí sousednosti. Možností je nespočet. V pozadí za vším je graf tvořený množinou uzlů, množinou hran a informacemi o tom, že daná hrana se nachází mezi uzlem tím a tím, a že je orientovaná a ohodno19
cená hodnotou 7.Údaje, jak je který uzel široký, tu už chybí, proto v každém vyobrazení grafu může být taková šířka úplně jiná. Pakliže si uživatel bude moci graf zobrazit v různých podobách, je reálná taková situace, že prostřednictvím změn činěných v jednom zobrazení, může studovat principy, na kterých funguje zobrazení jiné. Zobrazení grafů budou dále v textu nazývána termíny jako pohledy, vizualizace či vykreslovače. Modelový příklad: Nakreslím si graf jako směsici čar a koleček (tedy uzlů a hran) a zobrazím si jeho matici sousednosti, která představuje jen jiné zobrazení toho samého. Při pohledu na takovou matici vidím např. tabulku, v které jsou nuly a jedničky. Následně ze zobrazení tvořeného čárkami a kolečky vymažu jednu čárku (hranu). Když si nyní pro takový graf znovu nechám zobrazit matici sousednosti, vidím, že se tu jedna jednička změnila na nulu. A právě takové chování budeme chtít po programu. Konkrétnější budeme v kapitole 4.12. Požadavek: Stejný graf může být najednou zobrazen různými způsoby
Uváděli jsme, že by bylo vhodné, aby bylo možné graf zobrazovat různými způsoby. Nic ale nebylo vyřčeno o tom, co se skrývá pod různými způsoby. Nemuselo tak jasně vyplynout, že takto zobrazovat by mělo být možné různé pohledy najednou. Tedy aby uživatel nemusel zavřít jednu vizualizaci grafu, aby mohl vidět jinou vizualizaci grafu apod.. Hlavním důvodem je pochopitelně výchovný efekt. Abychom mohli mít různé vizualizace zobrazené vedle sebe a porovnávat je. Požadavek: Změna učiněná na jednom zobrazovači grafu se projeví na ostatních zobrazovačích grafu.
A nyní k poslední důležité věci, která souvisí s vizualizací grafů a která by mohla zesilovat výchovný efekt na uživatele programu. Jak plyne z názvu tohoto požadavku, grafy nechceme vizualizovat jen různými způsoby a zobrazovat si tyto vizualizace najednou, ale budeme chtít, aby se změny učiněné v jedné vizualizaci okamžitě projevovaly v ostatních vizualizacích stejného grafu. Jak je vše řešeno konkrétně, bude diskutováno až v rámci řešení. Nyní už jen několik vět o tom, na jaké změny by vizualizace grafů měly být schopné vzájemně reagovat. Když jsem se nad vším zamýšlel, připadalo mi rozumné, nic konkrétního v rámci tohoto požadavku nespecifikovat. Pohledy na grafy mohou být různé, může být časem přidán nějaký nový, ten může být specifický něčím, co nebylo dosud uvažováno atd. Proto je potřeba s tímto počítat. Konkrétnější požadavky ale činit nebudeme. Každá vizualizace grafu si jakoby rozhodne sama, na co chce reagovat, a na co ne. 20
3.3
Požadavky na jazyk a nastavení
3.3.1 Pamatování si uživatelského nastavení Další z požadavků, který se vyskytl a byl řešen již v rámci BP, je ukládání uživatelského nastavení. Vždyť je to uživatelsky příjemné, když si program zapamatuje nějaké moje poslední funkční nastavení a při dalším spuštění mi ho nabídne. Například proč pokaždé uživatele nutit, aby si okno programu velikostně nastavil na přibližně polovinu velikosti monitoru, a místo toho mu pokaždé nabízet okno přes celou obrazovku? Je relativně jednoduché zařídit, aby si program při ukončování zapamatoval, že jeho hlavní okno mělo naposledy pozici [x ; y], šířku š a výšku v, a při dalším spuštění si tyto údaje načíst a hlavní okno podle nich nastavit. Takového ukládání lze docílit např. s využitím XML. Aplikační data se uloží do zvláštního souboru, který má definovanou strukturu, a pak si je odsud zase načteme. Odlišit od sebe jednotlivá data lze např. unikátními názvy jednotlivých elementů, které je obsahují. O Javě lze říci, že pro práci s XML obsahuje poměrně dobrou podporu. Program vzniklý v rámci BP ukládal nastavení rovněž do XML souboru, ale situace tam byla taková, že tento XML soubor byl pro celý program pouze jeden a s přibývajícím počtem údajů, u kterých vyvstávala potřeba po jejich ukládání, se stával čím dál tím více nepřehledný. Hlídání unikátnosti názvů elementů tu tak bylo komplikovanější. Po startu programu docházelo hned k načítání všech dat, i když některá z nich byla potřebná jen v hodně řídkých případech. Byla tedy většinou načítaná úplně zbytečně. Použít i nyní podobné chování u programu, kde se předpokládá jeho větší míra modifikovatelnosti a náhrady některých částí řešením jiným, mi připadalo problematické. Zvlášť, když uvážíme, že vznikat mohou i přídavné funkcionality, se kterými se původně vůbec nepočítalo. V takovém případě používat pro ukládání nastavení jen jeden soubor, není výhodné. Místo toho, pokud má být řešení dostatečně obecné, mi připadá vhodné, aby si načítání nastavení zajišťovala každá komponenta, která může být z programu nějak vyčleněna, sama. Když se taková komponenta načítá, načte si svoje a jen svoje nastavení, nastaví se podle něj, a v okamžiku, když se taková komponenta ukončuje / zavírá, si hned svoje nastavení uloží, abychom si ho nemuseli pamatovat např. až do doby, kdy bude ukončován celý program. Vede to sice k tomu, že se během běhu programu otevírá řada malých souborů a čte se / ukládá se jejich obsah, ale výhodou je, že se každá komponenta stará jen o své vlastní nastavení a přidává-li uživatel komponentu novou, nemusí víceméně řešit situaci ve zbytku programu.
21
3.3.2 Lokalizace programu Pokud máme program, který si lze přeložit do jiného světového jazyka, než pro který byl napsán, aniž bychom museli provádět změny ve zdrojovém kódu, je zajisté možné takové chování okomentovat pozitivními výrazy. A takové chování chceme i od našeho programu. Když se člověk nad vším zamyslí, tak situace je tu podobná jako u ukládání nastavení. Opět se tu dostáváme k řešení, kdy máme texty ve zvláštních souborech, z kterých si je načítáme pomocí unikátních klíčů. Když bych zase odkázal na program vzniklý v rámci BP, situace tu byla taková, že tu existoval jeden soubor pro všechny texty, které se v daném jazyce mohly v programu objevit (i v některých méně běžných případech). Výjimkou byly jen texty pro jednotlivé simulační algoritmy, které byly umístěny ve vlastních souborech. Vše tedy vedlo k tomu, že na začátku byly do paměti načteny všechny texty, a ty se pak v případě potřeby vypisovaly. U menších programů lze podobné řešení tolerovat. U větších programů a u programů, které jsou složeny z menších celků, které se mohou různě zaměňovat, přidávat a odstraňovat, mi opět připadá vhodnější a obecnější, když si každá komponenta bude zajišťovat lokalizaci svého textu sama. Situace je tu obdobná jako u načítání nastavení, takže veškeré mínusy vyjmenované v kap. 3.3.1 zde znovu jmenovat nebudu. Místo toho bych uvedl dvě věci, které tu jsou jinak: 1. Světové jazyky jsou různé. V případě ukládání nastavení bylo navrhováno, aby každá samostatná komponenta měla vlastní XML soubor, do kterého by si své nastavení ukládala. Zde každá komponenta má vlastní soubor s jazykovým nastavením, ale jazyků je ale mnoho. Každá komponenta by při takovém řešení měla tolik jazykových souborů, kolik jazyků podporuje. V programu by přitom nemělo být nijak definováno, které jazyky to mohou být, protože v průběhu času může vzniknout požadavek po podpoře nového světového jazyku. A měnit kvůli tomu zdrojové kódy by bylo opět nešťastné. Rozumnější je si upravovat aktuální jazyk pomocí některého souboru s uloženým nastavením. Při startu programu by se načetl údaj o tom, jaký jazyk se má používat pro výpis zpráv, a dle tohoto údaje by si pak jednotlivé komponenty načítaly lokalizované výpisy. 2. I když by šlo pro tuto funkcionalitu opět využít XML, Java pro podobné potřeby poskytuje mocnější mechanismus. Tím je Internacionalizace zvaná jako i18n. Jde o skupiny tříd, pomocí kterých lze přizpůsobovat jazyk a chování aplikace zvyklostem, které jsou pro daný jazyk a danou zem běžné. Například v různých státech se formát zapsaného časového údaje může lišit. My až takové detaily v rámci práce na programu řešit nebudeme. To už je takové 22
chování, které lze zdokonalovat v budoucnu. Ale pro lokalizaci výpisů i18n využijeme. Bylo tak činěno už v BP.
3.3.3 Výchozí nastavení Obecně k výchozímu nastavení
Mluvili jsme o načítání uživatelského nastavení a o načítání lokalizovaných textů programu ze zvláštních souborů. Jenomže nic nebylo řečeno o tom, jak by měl program fungovat v případech, kdy by takové načítání selhalo. Zde jsem se nakonec rozhodl neměnit způsob chování oproti způsobu, kterým se choval program vytvořený v rámci BP. Ale konkrétně: 1. Před prvním spuštěním programu není žádné uživatelské nastavení dostupné. Nebylo by to ani logické. Jednotlivé komponenty se pokusí načíst soubor se svým nastavením, ale nenajdou ho, a tak se spustí s nastavením výchozím. Výchozí nastavení je tedy dostupné vždy, když není načten soubor s uživatelským nastavením. 2. Pokud je dostupný soubor s uživatelským nastavením, ale jsou v něm jen některé z údajů, kterými lze ovlivnit vzhled a chování spouštěné komponenty, tak části, pro které je dostupné uživatelské nastavení, se načtou s tímto nastavením, a části, pro které uživatelské nastavení není dostupné, se načtou s nastavením výchozím. 3. Každá komponenta si zajišťuje načtení a uložení vlastního nastavení sama, takže oba body 1) a 2) uvedené výše nemusí pro některou z komponent platit.
Obrázek 3.5: Možný způsob načtení nastavení 23
4. Obdoba bodů 1 – 3 ) platí i pro jazykové nastavení. V programu je vhodné poskytovat i základní text, kdyby náhodou někdo některý ze souborů s lokalizovanými výpisy odstranil, nebo z něj odstranil jen některou z dvojic klíč – text. Vše nastiňuje obrázek 3.5. Ten byl vytvořen v našem programu. Shlukování výchozího nastavení
Byla řeč o výchozím nastavení. Jak se má komponenta chovat a vypadat, když není ještě uloženo uživatelem modifikované nastavení? Uvádělo se, že jako odpověď na tyto problémy slouží nastavení výchozí. To samozřejmě musíme nějakým způsobem v programu definovat. V rámci tohoto požadavku, který chápu jako jeden z těch obecných, chci jen zmínit skutečnost, že je vhodné, když se takové výchozí nastavení bude shlukovat na co nejméně míst. Při implementaci je potřeba takto uvažovat. Důvod? Usnadnění případných změn takového nastavení. Vzhledem k tomu, že se různé části programu mohou měnit, je takové usnadnění na místě. Konkrétní hodnoty některých parametrů v závislosti na aktuálním stavu může vracet třeba i funkce. Kromě nějakých proměnných s výchozími hodnotami proto můžeme mít i funkce, jejíchž chování bychom mohli nazývat jako výchozí chování apod.
3.3.4 Základní text Cílem této kapitoly je opět zdůraznit, že něco podobného, co bylo zmiňováno u výchozího nastavení, platí pro výchozí texty. Význam takového chování spočívá v tom, že kdyby se uživateli nepodařilo načíst jeho lokalizované výpisy, tak stále uvidí aspoň „něco“. Když toto „něco“ bude v Angličtině, je tu šance, že nejspíše středoškolsky či vysokoškolsky vzdělaný uživatel programu bude stále nabízenému textu rozumět. Tedy v rámci toto odstavce zmiňme hlavně tu skutečnost, že by výchozím jazykem měl být jazyk anglický. Jinak pro problematiku základního textu neplatí nic zvláštního oproti problematice výchozího nastavení.
3.4
Požadavky na GUI
3.4.1 Základní funkcionality v nástrojové liště Toto je jeden z požadavků, který by mohl zpříjemnit uživatelovu práci s programem. Už je to zvyklostí, že programy, s kterými pracujeme, poskytují nejpodstatnější funkcionality v nástrojových lištách, aby je uživatelé nemuseli hledat např. někde v menu, kde je dostupných funkcionalit mnohem více. A to je i motivace k tomuto požadavku. V Javě se nástrojové lišty vytvářejí např.
24
pomocí třídy javax.swing.JToolBar, se kterou se pracuje podobně jako s jinými swingovskými třídami. Co se týče toho, jaké panely nástrojů mají být dostupné a co všechno by v nich mělo být, tak to neupřesňujeme. Nutno ale počítat s tím, že počet takových panelů a jejich obsah by mohl být časem měněn.
3.4.2 Různé způsoby zobrazování obsahu V programu, který vznikl v rámci BP, se grafy zobrazovaly v záložkách, jejichž přidávání v Javě umožňuje instance třídy JTabbedPane z balíku javax.swing. Šlo o řešení, kdy se všechny grafy zobrazovaly na jednom místě. Při vhodném pojmenování v nich byl celkem přehled, i když jich bylo vytvořeno např. deset najednou. Jen ale nebylo možné vidět dva různé grafy najednou. A to je problematické, když si vzpomeneme na požadavky diskutované v kap. 3.2.7 – tedy že grafy lze vizualizovat více způsoby najednou. Proto se člověk dostal k úvahám, že grafy bude nutné zobrazovat spíše v oknech. Mají to být ale okna hlavní, nebo budou stačit jen vnitřní? A nebude těžší si v zobrazeném obsahu udržovat pořádek, když si uživatel bude muset okna různě posouvat, zvětšovat a zmenšovat, aby vidět to, co zrovna chce?
Obrázek 3.6: Panel s grafem zobrazeným v záložce a okno pro výběr barvy zobrazené ve vnitřním okně
25
A tak jsem se i díky těmto úvahám dostal k závěru, ke kterému je tu nyní psáno několik vět. Ideálnější by totiž mohlo být, kdyby si uživatel v programu vybral, jak se mu vizualizovaný obsah bude zobrazovat. Jestli to bude v záložce, nebo jestli to bude v okně. A jaké případně má toto okno být? Aby bylo něco takového možné, tak se s tím musí samozřejmě dopředu počítat a musí k tomu být postaveny vhodné programové základy, protože takové chování není jen problematikou přidání do menu jednoho tlačítka a napsání akce, co se v případě kliknutí na toto tlačítko má provádět. Blíže v kapitole 4.11, která pojednává o konkrétní realizaci.
3.4.3 Stavový řádek Stavový řádek je jakási obdoba logu (kap. 3.5.5), která se vyznačuje tím, že se do ní nevypisují zprávy chybové, ale zprávy informační. Ty by převážně novému uživateli mohly pomoci v počátečním zorientování s prací v programu. V nástrojích Microsoft Office se v podobné úloze vyskytuje např. ona známá mluvící sponka. Na podobnou funkcionalitu došlo už v rámci BP. Uživateli se v malé lištičce ve spodní části programu zobrazovaly informace, že je právě možné přidávat uzly, že je nyní možné přidávat hrany apod. Program implementovaný jako součást DP, by se měl chovat stejně. Rozdíl bude v tom, že v DP by mělo býti možné si jednoduše zaměnit objekty, pomocí kterých se výpisy zpráv realizují. Při vhodném strukturování kódu to není nic těžkého a z programu se díky tomu částečně stává skládačka. Toto chování bude ještě diskutováno v textu dál u jiných funkcionalit.
3.4.4 Startovní splash screen Splash screeny, tedy okna s indikací, že dochází k načítání programu, poskytuje nejeden z dnešních programů, s kterými v rámci práce s počítačem přijde běžný uživatel do styku. Příkladem může být spouštění samotného OS, kde informace o tom, že dochází ke spouštění programu, bývají běžně přítomné. Někteří uživatelé si již na podobné chován zvykli. Kdyby se program dlouho načítal, na obrazovce se nic opticky neměnilo a nebyla by přítomná informace o tom, že dochází k načítání, mohlo by dojít k závěru, že program nefunguje. To samozřejmě nechceme, a proto přítomnost splash screenu bude příjemným uživatelským doplňkem s čistě informativními účely. V programu bude, ten ale bude běhu schopný i bez něco. V Javě je pro tyto účely v základním API dostupná třída JProgressBar z balíku javax.swing.
26
3.5
Další požadavky
3.5.1 Podpora modifikovatelnosti sebe sama O rozšiřitelnosti zde již bylo napsáno nemálo vět. Připomeňme, že se např. předpokládají situace jako u ukládání grafů, kdy si programátor připíše vlastní „ukládač“ a ten bude dostupný, aniž by bylo nutné na některých místech zdrojového kódu programu něco měnit. Vyvstat samozřejmě ale může i situace, kdy si uživatel poví, že se mu např. současné řešení stavového řádku nelíbí a že si napíše třídu, s jejíž pomocí si vytvoří stavový řádek vlastní (kap. 3.4.4). V takovém případě již bude jistý zásah do zdrojového kódu, nebo například jen do souborů s uživatelským nastavením, nutný. Implementačními snahami by mělo být vytvářet program takovým způsobem, aby se kvůli případným změnám muselo modifikovat co nejméně míst v programu. Ideálně by šlo o záměnu instance jedné třídy instancí třídy jiné – a to na co nejméně místech v programu. Když se např. od zmiňovaného stavového řádku bude vyžadovat, aby implementoval dané rozhraní, a v jiných místech kódu se s ním pracovalo právě přes toto rozhraní, je to dobrý základ k řešení takového problému. Další věcí, co by mohl program poskytovat, by mohla být komponenta, pomocí které by si podobné náhrady těch nejzákladnějších komponent / objektů mohl uživatel jen naklikat, díky čemuž by byl odstíněn od nudnosti provádět v kódu programu změny pomocí přepisu několika málo míst v kódu. V kapitole 4.13 je nazvána jako Manage okno. Při ručních přepisech totiž může snadno dojít k nějakému opomenutí, a vytvoření chyb / nekonzistentností. Díky přítomnosti nastavovacího okénka by se uživatel případných změn nemusel tolik bát.
Obrázek 3.7: Obecné schéma, jak při skládání řešit podkomponenty
3.5.2 Podpora rozšířitelnosti sebe sama Mohlo by se zdát, že tento požadavek byl již diskutován v kap. 3.5.1, ale není tomu úplně tak. Jde tu totiž o to, aby bylo umožněno přidávat a v programu nějak jednoduše aktivovat / spouštět 27
funkcionality, se kterými původně nebylo vůbec počítáno. Jednou z možností samozřejmě je, že si uživatel otevře zdrojový kód programu, na pár míst dopíše kód vlastní, a nová funkcionalita je na světě. Takový postup je ale v podstatě možný u každého programu, ke kterému je dostupný zdrojový kód, takže asi nepřekvapí konstatování, že zrovna toto pod tímto požadavkem skryto není. Tam se skrývá chování, díky kterému do programu novou funkcionalitu (de facto třídu) přidáme či zaregistrujeme, aniž by bylo nutné ve zdrojovém kódu něco měnit. Potom, co tu už několikrát bylo zmíněno Java Reflection API, asi již nepřekvapí, že i takového chování je možné s jeho pomocí dosáhnout. V programu musí být komponenta (okno s nabídkou možností), které umí vyhledat a nabídnout třídy, které lze využít. U simulace algoritmů takto byly nabízeny k simulaci algoritmy, které byly napsány v třídách, jež rozšiřovaly dané rozhraní. U ukládání grafů byly dle stejného principu vyhledány a nabídnuty třídy, které umožňují uložení grafu (rozšiřují jiné rozhraní). A tady v rámci diskutovaného požadavku 28 mluvíme o funkcionalitě, která by de facto uměla to samé, jen by zahrnovala všechno ostatní, co se nedalo vyčlenit do nějakých speciálních funkčních kategorií. Vzniklé třídy, které se v ní budou využívat, tak nebudeme nazývat jako simulační algoritmy či jako „ukládače grafů“, ale například jako rozšíření / plug-iny. A to je pojem, se kterým se setkáme v nejednom programu, a jestli někdo z dosavadního povídání nepochopil, o čem je řeč, tak nyní snad již chápe. Dále, co se ještě plug-inů týče, pokládám za vhodné, aby u nich bylo možné definovat, jestli jsou zrovna aktivní nebo ne. Aby nemuselo docházet např. k odstranění / modifikace třídy, když bychom plug-in, který se s její pomocí realizuje, nechtěli mít po startu programu aktivní. Aktivní plug-iny by se přitom po spuštění programu automaticky spustily. V oknu, které by nabízelo všechny dostupné plug-iny, by měla být možnost nastavit, který z plug-inů chceme mít aktivovaný a který ne. Něco podobného je např. v NetBeans IDE.
Obrázek 3.8: Pohled na nastavení plug-inů v NetBeans IDE 6.7.1 28
3.5.3 Podpora přidání nové funkcionality do menu Mluvili jsme o modifikovatelnosti a rozšířitelnosti. Psalo se tu o tom, že se počítá s tím, že si uživatel bude moci dopisovat vlastní třídy a zakomponovat je do programu. Díky tomu lze na celý tento SW nahlížet jako na spustitelnou knihovnu tříd. A v takové je možné hned několik možností, jak přidávat nové funkcionality do menu. Za prvé nás asi napadne možnost, že např. třída, pomocí které by bylo realizované hlavní okno programu, by uměla vrátit instanci třídy javax.swing.JMenu. Tou se v javovských programech menu běžně realizuje. Když by se uživatel k takovému objektu dostal, využíval by stejné metody, pomocí kterých by jinde v kódu docházelo k přidání defaultních položek do menu. Takové chování lze nazvat jako základní, ale asi bych u něj místo slovíčka usnadňuje použil jen slovo umožňuje. Pod slovem usnadňuje si představím jakoukoli možnost, díky které bych jako programátor musel napsat méně kódu, než by bylo nutné, kdybychom postupovali jako v případě popsaném u základní možnosti. Když jsem si toho uvědomil, začal jsem o takových funkcionalitách uvažovat jako o speciálních případech plug-inů, o kterých pojednala kapitola 3.5.2. Možné jsou pochopitelně i jiné možnosti. Přídavné položky do menu by tedy byly taky takové plug-iny. Plug-iny, které navíc do menu přidávají tlačítko, pomocí kterého se vyvolává zadaná akce. V souvislosti s plug-iny připomínám, že byla řeč i o komponentě, kde by si uživatel naklikal, jaké plug-iny chce mít v programu aktivní. Pro přídavné položky do menu by tedy platilo obdobné.
3.5.4 Přidání nápovědy od uživatele Nápověda je funkcionalita, u které není zásadní, aby se v programech vyskytovala, ale bylo by zdvořilé, kdyby pro ní program poskytoval podporu. Důvod je ten, že i kdyby se program snažil o intuitivním ovládání, které by spočívalo v napodobování chování a vzhledu od podobných funkcionalit v některých známých programech, tak i přesto uživatelé nemusí tušit, jak program ovládat. Každý uživatel se totiž během své práce s počítači setkal s různými programy a jako intuitivní by označil jiné chování. Lidské vnímání je subjektivní, a právě proto poskytování aspoň menší nápovědy není od věci. V rámci vývoje programu by proto mělo být snahou jakousi podporu pro nápovědu vytvořit. Jako primární přitom nepokládám skutečnost, aby každá funkcionalita měla k dispozici přehlednou nápovědu, ale možnost takovou nápovědu k programu přidávat a různě si jí modifikovat. Důvod? I pod pojmem přehledná nápověda si každý představí něco jiného. 29
Kdybych byl konkrétnější, tak jde hlavně o to, aby se např. při stisknutí klávesy F1 otevřelo okno s dostupnými tématy k nápovědě. Tato témata a jejich obsah mohou být různě přidávány, uzpůsobovány a odstraňovány. Opět je vlastně ve všem využito Java Reflection API, o které již byla řeč například u požadavku na ukládání grafů. V případě nápovědy zde přibývá skutečnost, že i texty s nápovědou by mělo býti možné nějakým způsobem lokalizovat. Přitom nelze spoléhat jen na soubory properties využívané v rámci i18n, protože realizovat nějaký pokročilejší text s nápovědou je spíše vhodné pomocí HTML souboru. V HTML souborech se mohou vyskytovat různé ilustrační obrázky apod. Konkrétněji v kapitole 4.14, která popisuje výsledné řešení.
3.5.5 Výpis zpráv o chybách / výjimkách Pod požadavkem na výpis zpráv o chybách a výjimkách si můžeme představit funkcionalitu, která je v některých programech nazývaná jako log nebo logovací soubor. V našem případě se budeme držet jen pojmu log, protože ukládání obsahu nemusí být nutností. Motivací k logu je skutečnost, že může dojít k nějakému nekorektnímu chování, aniž by bylo patrné, z jakého důvodu k němu došlo. Pakliže by člověk v kódu jen ošetřoval možné výjimky a neposkytoval informace o tom, že k vyvolání nějaké výjimky vůbec došlo, může být enormně obtížné identifikovat zdroj problémů. Na druhou stranu zase není možné někde hodně viditelně vypisovat informace o tom, že došlo k nestandardní situaci / nekorektnímu chování / chybě. Uživatel by v takovém případě mohl nabít dojmu, že je program nefunkční, i když by došlo např. k nějaké drobnosti. To, že někde chybí přídavný soubor není ani chyba samotného programu. Když se podíváme do jiných programů, kde je log dostupný, tak tam je většinou implementovaný takovým způsobem, že k logování dochází na pozadí a uživatel se s výpisem obsahu logu, takové pomyslné černé skříňky programu, seznámí až po jeho vyžádání. A to např. kliknutím na danou možnost v menu nebo otevřením konkrétního souboru na disku. Takové chování pokládám osobně za rozumné. Na druhou stranu uváděli jsme zde snahy, ve kterých se snažíme býti obecní. Že by uživatel měl mít možnost ovlivňovat, jak se bude výsledný program chovat. A log je příkladem funkcionality, která stojí poměrně stranou od zbytku programu, takže by tam možnost zaměnitelnosti / změny chování měla být dostupná tím spíše. Po těchto úvahách mi přišlo vhodné na log nahlížet jen jako na objekt, který budou mít ostatní objekty realizující jednotlivé komponenty programu k dispozici, a kterému budou moci
30
posílat text k zaznamenání do logu. Text by to teoreticky mohl být jakýkoli. Ideálně ale upozorňující na výjimky. Pro informativní účely slouží stavový řádek diskutovaný v kap. 3.4.3. Jak bude log vypadat, kam si bude zaslané zprávy ukládat a jak je bude zobrazovat, by mělo býti až na konkrétní realizaci. Krásně díky takové realizaci pak můžeme řešit situaci, kdy chceme mít logování vypnuté. Místo toho, abychom testovali, jestli je logování zapnuté, může funkci logu plnit objekt, který de facto nic nedělá (přijme text a ignoruje ho). Obdobného je možno dosáhnout i u stavového řádku.
Obrázek 3.9:Deaktivování stavového řádku může řešit jen záměna objektů
3.6
Java a reflexe
Součástí Javy je Java Reflection API [15]. Jde o třídy a metody, díky kterým můžeme za běhu získávat informace o třídách a jejich proměnných a metodách, aniž by bylo nutné znát tyto informace v době kompilace. Stejně tak je možné vytvářet za běhu instance od zadaných tříd. Reflexe je poměrně pokročilý rys Javy. Několik faktů k tomuto tématu, které lze v rámci implementace využít a kterých skutečně využito bylo, v bodovité formě následuje (plus související): •
Všechny referenční typy dědí v Javě od třídy java.lang.Object.
•
Pro každý objekt JVM vytváří instanci třídy java.lang.Class, prostřednictvím které lze za běhu zjišťovat informace o daném objektu.
•
Prostřednictvím Class objektu lze vytvořit novou instanci třídy, kterou Class objekt reprezentuje – stačí zavolat jeho metodu newInstance().
•
U každého objektu lze volat metodu getClass(), díky čemuž jsme schopni zjistit, od jaké třídy je objekt instancí.
•
Pokud máme k dispozici místo instance typ, lze Class objekt získat pomocí volání .class. Např. pro třídu MainFrame získáme Class objekt takto: Class c = MainFrame.class;
31
•
Známe-li celý název třídy, lze pro ní získat Class objekt pomocí statické metody forName(String název) třídy Class. Příklad pro třídu MainFrame: Class c = Class.forName(“windows.MainFrame”);
•
Instance třídy Class nereprezentuje pouze třídu v běžícím programu. Reprezentovat může i rozhraní.
•
Třídy do JVM načítá classloader [16], přičemž standardní classloader hledá třídy v CLASSPATH. To je argument nastavitelný přes příkazový řádek či proměnné prostředí. Tento argument JVM informuje o tom, kde má hledat uživatelem definované třídy.
•
Class objekt mimo jiné poskytuje metodu boolean isAssignableFrom(Class C), která odpovídá na to, jestli daná třída je stejná jako třída C. Případně, jestli není jeho nadtřídou. Pojem třída v této větě zahrnoval i rozhraní.
•
Další metodou, kterou Class objekt nabízí, je metoda String getName(). Tato metoda vrací celé jméno třídy – tedy i včetně názvů balíčků.
32
4
Popis implementace
Účel této kapitoly je snad zřejmý z jejího nadpisu. Budeme se bavit o tom, jak je program implementován. Kódu v rámci implementace bylo napsáno hodně, tak tu samozřejmě nebudeme rozebírat úplně vše. Zaměříme se hlavně na to nejdůležitější, přičemž se dotkneme požadavků, které byly diskutovány v rámci kapitoly 3. V třetí kapitole jsem udělali obecný úvod do mnoha souvisejících problémů. To, že tam o mnoha věcech bylo nastíněno, jak by mohly býti řešeny, znamená, že tak řešeny skutečně byly. Proto si diskusi k některým menším problémům už odpustíme. O této a o předešlé kapitole by mělo platit, že to jsou nejdůležitější kapitoly v rámci této práce. Tyto kapitoly by případnému rozšiřovaleli funkčností implementovaného programu měly pomoci nejvíce.
4.1
Třídy, soubory
V kapitole nazvané Rozhraní a třídy, kterou jsem v rámci BP [2] psal, jsem použil větu: „Program se skládá z 20 veřejných tříd a 3. rozhraní“. Tato věta už může znít z dnešního pohledu komicky. Nový program totiž naproti tomu obsahuje 27 neprázdných balíčků (packages). Balíčky v Javě jsou jakési složky pro shluknutí tříd a jiných souborů určených pro podobné účely. Tříd a rozhraní je jinými slovy v novém programu mnohem víc. Popisovat účel jednotlivých, stejně tak jako dalších souborů, proto nebudeme. Bylo by to na dlouho a jako čtení by to nemuselo býti vůbec záživné. V rámci této kapitoly proto bude učiněn komentář jen k nejdůležitějším třídám a balíčkům. Soubory programu jsou obsaženy např. v následujících balících: •
algorithm – Balík určený pro třídy, které souvisí se simulací grafových algoritmů. Nachází se tu mimo jiné třídy: ◦ AlgorithmPanel – panel, který nabízí roletku s nabídkou algoritmů, které jsou pro simulování dostupné. Kromě této roletky tu dále jsou tlačítka, která je nutno při simulaci využívat, a „posouvátko“ pro nastavování délky časového kroku. ◦ DFS – Třída, pomocí které se zajišťuje simulování procházení grafu do hloubky. Každý algoritmus má třídu vlastní, toto je jen typický zástupce. V programu mohou existovat i jiné třídy, pomocí kterých se simuluje stejný problém. To kdybychom chtěli stejnou věc simulovat / vysvětlovat jinak.
33
◦ Algorithm – Abstraktní třída, od které je vhodné dědit, pakliže si píšeme algoritmus vlastní. Při použití této třídy je programátor odstíněn od spousty potřebných věcí, které už v rámci této třídy zařízené jsou. •
api – Balíček pro rozhraní, která se v programu vyskytují. Umístění některých by bylo logické i v jiných balíčcích. Např. v tomto balíčku je rozhraní Algorithm určené pro simulační algoritmy,místo toho aby bylo v balíčku algorithm.. Ke konečnému umístění vedlo rozhodnutí, že by všechna rozhraní měla býti shluknuta do jednoho balíčku - právě tohoto. Cíl: Usnadnit přemýšlení, kde se které rozhraní nachází. Jejich účel by přitom měl být patrný z názvů, o což jsem se obecně pokoušel v celém programu.
•
basic – Package, ve kterém jsou nejzákladnější třídy programu, které se nehodilo umístit jinam. Jmenovat lze hlavně následující třídy: ◦ FactoryDefault – V této třídě je shluknuta velká část výchozího nastavení. Pod tím si můžeme představit takové věci jako výchozí locale, výchozí třídu, která se používá pro vytvoření stavového řádku, nebo například i takový údaj, jakým je úhel pro šipku použitou u vykreslené orientované hrany. ◦ GlobalData – Třída s funkčností úložiště základních dat. Když při startu dojde k vytvoření hlavního okna programu, tak reference na objekt, který ho realizuje, je uložena právě zde. Přes tuto třídu se k hlavnímu oknu i dostáváme, pakliže k němu potřebujeme přistoupit z jiných částí programu. Dále jsou tu umístěny např. log a locale objekt. Locale objekt je objekt, který nás informuje, jaký světový jazyk v programu právě používáme. ◦ Menu – Jak název této třídy napovídá, jde o třídu, jejíž instance pak v hlavním okně programu představuje menu. Dochází tu mimo jiné k seskládání menu z jednotlivých položek, které jsou pak uživateli nabízeny. ◦ Start – Třída s metodou main, u které se předpokládá, že přes ní program bude spouštěn. Main metodu má v programu hned několik tříd.
•
components.statusPanel – V této package jsou umístěny třídy, pomocí kterých lze v programu realizovat stavový řádek. V tuto chvíli jsou takové třídy dostupné tři (NoStatusPanel, StatusPanelSimple, StatusPanelWithHistory). Kromě těchto tříd je tu umístěna ještě abstraktní třída StatusPanel, která zařizuje základní věci a od které ostatní třídy určené pro stavovou řádku mohou dědit. Podobný postup, kdy je vytvořena abstraktní 34
třída pojímající základní vlastnosti a chování, je ve zdrojových kódech využit hned několikrát. •
components.splitpanel – Obdoba balíku components.statusPanel. V tomto balíku jsou umístěny třídy, které lze používat pro splitpanel. To je panel, pomocí kterého lze okno programu rozdělit až na dvě části. Možné je mít i jen jednu část, jak použité slovíčko až napovídá.
•
components.toolbars – Další obdoba balíku components.statusPanel. V tomto balíku jsou umístěny třídy, pomocí kterých se realizují lišty v nástrojové liště, a další související třídy. Dále tu jsou umístěny obrázky, které se používají pro ikony tlačítek, která se do nástrojové lišty vkládají.
•
functions – Balík s třídami, které poskytují statické metody zajišťující různé funkce, které lze využít v různých částech programu. Příkladem je např. třída ColorFunctions, jejímž účelem je pomáhat při práci s barvami. Tato třída obsahuje např. metodu getRandomColor() která umí vrátit náhodnou barvu.
•
generate – Balík sdružující třídy pro realizaci generátoru grafů. Obsaženy tu jsou mimo jiné třídy následující: •
GeneratorWindow – Okno, které nabízí nabídku možných generátorů a z kterého se vygenerování příslušného grafu vyvolává.
•
ShapeGenerator -Příklad generátoru. Tento obsahuje detailní obrázky grafů, které může případně vygenerovat.
•
graph – V této package jsou třídy, které souvisí již se samotnými grafy. Typickými zástupci jsou např. tyto třídy: ◦ NodeBasic – představuje datovou reprezentaci uzlu. Obdobou je třída EdgeBasic, jejíž instance mají představovat datovou reprezentaci hrany grafu. Bude ještě konkretizováno v kapitole 4.12. ◦ EdgeQuad – Nadstavba nad datovou reprezentaci hrany, která je již spojená s vizualizací. Pomocí EdgeQuad lze vykreslovat hrany, které mohou být zaoblené. Jde tu o třídu, která dědí od java.awt.geom.QuadCurve2D. Díky tomu jsou k dispozici metody, díky kterým lze zjistit, jestli zadaný bod z prostoru leží pod vykreslenou hranou. EdgeQuad jako grafická reprezentace hrany musí implementovat rozhraní EdgeGraphical. Pro grafické reprezentace uzlů existuje obdoba v podobě rozhraní NodeGraphical. 35
◦ GraphBasic – Třída určená pro realizaci modelu grafu. Obsahuje seznamy objektů implementujících rozhraní Edge a Node. Takovými objekty jsou objekty NodeBasic a EdgeBasic. Jde tedy o datovou reprezentaci grafu, ve které se neříká nic o tom, jak graf vykreslovat. ◦ GraphPresenterBasic – Třída pro realizaci presenteru. To je objekt, který řídí správné vykreslování
stejného
grafu
v
různých
pohledech.
Podrobněji
bude
diskutováno v kap. 4.12. ◦ GraphViewCircles – Objekt, který umí graf vykreslovat jako kolečka propojená čárkami. Kolečka jsou uzly, čárky hrany. Konkrétně k tomuto účelu využívá instance tříd EdgeQuad a NodeCircle. ◦ GraphViewRectangles – Obdoba třídy GraphViewCircles. Rozdíl mezi těmito třídami je v tom, že zde jsou uzly vykreslovány jako čtverečky. Je to díky tomu, že místo instancí třídy NodeCircle tu jsou využívány instance třídy NodeRectangle. Obě tyto třídy implementují zmiňované rozhraní NodeGraphical. ◦ GraphViewAdjacencyMatrix – Zobrazovač grafu, který graf zobrazuje jako matici sousednosti. Jde o tzv. negrafický pohled na graf, ve kterém se graf nevykresluje obrázkem, ale kde se vypisuje jako vhodně formátovaný text. Možné je pro tyto účely využít i tabulky. ◦ GraphicalGraphView – Abstraktní třída, od které dědí třídy GraphViewCircles a GraphViewRectangles. Sdružuje operace, které jsou společné pro grafické vykreslovače grafů. Seznamy pro grafické reprezentanty hran a uzlů jsou právě zde. Můžeme si to dovolit díky tomu, že se v těchto seznamech očekává cokoli, co implementuje rozhraní EdgeGraphical (v případě hran) a NodeGraphical (v případě uzlů).
Obrázek 4.1: Ilustrace k popisu třídy GraphicalGraphView
36
◦ AbstractGraphView – Abstraktní třída, od které dědí třídy GraphicalGraphView a GraphViewAdjacencyMatrix. Sdružuje nejzákladnější operace, které jsou společné pro zobrazovače grafů. Grafické zobrazovače lze proto chápat jako nadstavbu k negrafickým (textovým) zobrazovačům. ◦ NodeCircle – Obdoba EdgeQuad pro uzly. Dědí se od java.awt.geom.Ellipse2D, díky čemuž by mohlo býti možné uzly zobrazovat jako elipsy. V tuto chvíli je využíváno pro vykreslování uzlů s kruhovým obvodem. ◦ Repainter – Třída, pomocí které si zobrazovače zajišťují vyvolání vykreslení jejich obsahu. •
help - Balík pro umístění tříd, které slouží k realizaci okna s nápovědou i pro realizaci konkrétních nápověd.
•
help.web – Do tohoto balíku se umísťují HTML soubory, které obsahují text s konkrétní nápovědou
a
které
se
uživateli
na
vyžádání
zobrazují
v
instanci
třídy
javax.swing.JEditorPane, kterou obsahuje okno pro výpis nápověd. •
io.graph.formats – Třídy určené pro ukládání grafů a pro jejich načítání, jsou ukládány právě sem. Pro příklad lze jmenovat třídu AdjacencySaver, která umí uložit vybraný graf do souboru s příponou .adjacency. Obsahem tohoto souboru je text vhodně proložený mezerami – matice sousednosti.
•
languages – Package, kam byly umístěny properties soubory s lokalizovanými texty, které se v programu používají.
•
log – Package pro třídy, které souvisí s realizací logu. Diskutováno je v kapitole 4.8.
•
manage – V tomto balíku jsou třídy, které souvisí s realizací manageru tříd. O tom pojednává kapitola 4.13. Zmínit lze např. třídu ManageClassesWindow, pomocí které se realizuje okno obsahující možnosti pro nastavování.
•
states – Program se během svého běhu může nacházet v různých stavech. Třídy související s jejich realizací jsou ukládány právě do tohoto balíčku. Ze zástupců jmenujme např. třídy AddNodeState a AddNodeNoState.
•
tests – V době vzniku programu byly vytvářeny různé standardní třídy, pomocí kterých byly testovány potřebné skutečnosti. Tyto třídy byly ve zdrojových kódech programu ponechány, i když v rámci jeho běhu nebudou vůbec využity. Umístěny jsou právě zde. 37
•
visualise – Balíček určený pro třídy, které souvisí s požadavkem 24. Obsah lze díky nim zobrazovat různými způsoby – v záložkách, ve vnitřním okně či v hlavním okně.
•
windows – Package vytvořené pro různá okna, která se nehodilo umístit jinam. Je zde např. třída MainFrame, pomocí které se realizuje hlavní okno programu.
•
windows.components -V tomto balíku jsou umístěny třídy určené pro různé menší komponenty okna – např. pro ikonu pro přidání uzlu.
4.2
Spuštění Programu
Program se spouští prostřednictvím třídy Start, kde je statická metoda main. Hned na začátku dochází k zobrazení splash screenu, který informuje o tom, že probíhá načítání. Následuje pokus o načtení základního uživatelského nastavení, které je využito při vytvoření hlavního okna a jeho součástí. Po vytvoření hlavního okna se ještě načtou a spustí případné doplňky. O těch více v
kap. 4.15. Po
spuštění doplňků se splash screen zavírá a uživateli je zobrazeno hlavní okno. Pokud bychom se zaměřili konkrétně na první spuštění programu, tak při něm dojde k výpisu řady textů do logu. Důvod? V tuto chvíli neexistující soubory s uživatelským nastavením. To se vytvoří až po prvním spuštění programu.
4.3
Hlavní okno
Hlavní okno programu je tvořeno instancí třídy MainFrame a je složeno z několika komponent. Ty mohou ve výsledku být realizovány instancemi různých tříd. Obrázek 4.3 vše znázorňuje
Obrázek 4.2: Načtení programu
z důvodů, aby byla ulehčena případná orientace v kódu. Když bychom se podívali na jednotlivé součásti okna, tak k těm lze uvést následující: •
ToolBarsPanel je panel, do kterého jsou umisťovány nástrojové lišty. Jejich pořadí je odvislé od toho, v jakém pořadí byly přidány.
•
SplitPanel je panel, který může být rozpůlen. Vkládány do něj jsou mohou být i vnitřní okna. Např. okno pro výběr barvy, okno s nabídkou algoritmů pro simulaci, či samotné okno s kreslenými grafy. U těch už oproti předešlým možnostem záleží, jak si přejeme zobrazovat obsah – jestli jako jako panel v panelu záložek, nebo jako vnitřní / hlavní okno. 38
•
StatusPanel je panel s funkcí stavového řádku.
•
Desktop je panel pro umísťování hlavního obsahu. Vložen je do něj SplitPanel.
Obrázek 4.3: Struktura hlavního okna Poslední informace, která tu v souvislosti s hlavním oknem bude uvedena, se zabývá tím, díky jaké třídě z Java Core API je okno vůbec realizováno. Tou třídou je třída javax.swing.JFrame. Od té nedědí přímo třída MainFrame, ale její vnitřní třída InnerMainWindow. Instance této vnitřní třídy je pak využíváno. Takového strukturování kódu je využito i na jiných místech v programu. Důvodem je navenek skrytí metod, které jsou pro objekty JFrame dostupné, a zprostředkované zpřístupnění metod jen některých. U tříd, které implementují zadané rozhraní, si tak můžeme ohlídat, aby zvenčí byly volány jen ty metody, které vnucuje rozhraní. Takové hlídání se hodí, pokud využití jedné třídy může být nahrazeno využitím jiné třídy. Množství možných problémů klesá.
Obrázek 4.4: Jak hlavní okno dědí od javax.swing.JFrame
39
4.4
Ukládání uživatelského nastavení
Jak bylo rozváděno v rámci kapitoly 3.3, v programu je možné ukládat uživatelské nastavení. Děje se tak do souboru, ale ne jednoho. Nastavení se ukládá do více souborů, jelikož si každá komponenta zajišťuje správu svého nastavení sama. Sama si při svém vytváření nastavení načte, a sama si ho při konci své činnosti uloží. Třídy, jejichž instance se takto chovají, povětšinou implementují rozhraní Setable, které třídám vnucuje metody loadSettings() a saveSettings(). Co se týče ukládání, tak to probíhá do XML souborů. Využívány jsou k tomu (prostřednictvím třídy functions.XML) třídy z package org.w3c.dom. Při ukládání si komponenta, která se chystá některá svá data uložit, z těchto dat sestaví DOM (Document Object Model) [17]. Ten je následně uložen. DOM je objektově orientovaná reprezentace XML. Jde o API, které
Obrázek 4.5: Interface Settable
umožňuje přístup k obsahu XML dokumentu a jeho modifikaci. Příklad typického použití následuje: 1. Je zavolána metoda saveSettings() u objektu, jehož nastavení chceme uložit (např. MainFrame). 2. V metodě je vytvořena instance třídy Document, ve které dojde k sestavení stromu údajů, které pak budou namapovány do XML souboru. Ten má rovněž stromovou strukturu. Typicky se ve zdrojových řádcích setkáme s přiřazením: org.w3c.dom.Document doc=XML.newDomXmlDocument(); 3. Po vytvoření objektu Document dochází k vytvoření elementů a k nastavování jejich hodnot. Tyto elementy jsou přidány do stromu. 4. Volá se: XML.saveDOMIntoXMLFile(„název souboru“,doc); Co se ukládání nastavení dále týče, tak zde může dojít k situaci, kdy je program uložen na médiu, kam není povolen zápis. Např. na CD. V takovém případě ukládání jeho nastavení selže. Výsledkem pak jsou upozorňující informace v logu, které zůstanou uživateli utajeny, pokud si obsah logu nenechá zobrazit. Program ale běží úspěšně dál, jako kdyby k žádným výjimkám nedošlo.
40
4.5
Načítání uživatelského nastavení
K načítání nastavení by šlo využít opět API pro práci s DOM. Při jeho použití by dle načtených dat byla vytvořena stromová struktura objektů, které by reprezentovaly původní XML dokument. K jednotlivým uzlům této struktury by pak šlo přistupovat a získávat či modifikovat jejich hodnoty. Použití takového řešení by bylo sice programátorsky jednoduché, ale existuje zde nevýhoda, spočívající ve vysokých požadavcích na systémové zdroje – a to hlavně v případě rozsáhlých dokumentů. Navíc my potřebujeme uživatelská data pouze načíst, abychom si je následně uložili jinam, takže vytváření nějaké stromové struktury objektů je pro nás zbytečné. A tak je pro ukládání využíván SAX (Simple API for XML) [18]. To je API, které umožňuje sériový přístup k XML. Dokument je při použití tohoto API zpracováván jako
proud
dat,
během
jehož
načítání
dochází
k vyvolávání událostí. Například když se narazí na novou počáteční značku elementu, tak je vyvolána
Obrázek 4.6: Načítání nastavení obecně
událost, že byla načtena nová počáteční značku elementu. Obdobné pro koncovou značku elementu, obsah elementu, komentář atd. Programátorovi stačí potřebné z těchto událostí ošetřit. A právě takto to při načítání v programu funguje. Konkrétně si stačí hlídat jen koncovou značku elementu. Tedy pokud XML dokument obsahuje hodnoty pro nastavení v tělu elementů a ne např. v hodnotách případných atributů. V takovém případě lze pro načítání s výhodou využít napsané třídy basic.LoaderOfSettings, která pokrývá společné operace a od které stačí dědit. Jak probíhá načítání nastavení konkrétně, se snaží zastihnout obrázky 4.6 a 4.7. Máme tu jako příklad načítání nastavení zobrazovač GraphViewCircles. Algoritmus pro načítání lze slovně popsat takto (místo konkrétních názvů tříd se uvádí jména zkrácená): 1. Při vytváření objektu G dochází k zavolání jeho metody loadSettings() 2. V těle metody loadSettings() je vytvořena instance třídy S. Tato instance objektu S poslouží k tomu, že objektu G vrátí hodnoty s nastavením. Tyto hodnoty jsou nyní nastaveny na výchozí hodnoty.
41
3. V konstruktoru objektu S je vytvořena instance od vnitřní třídy M. Třída M dědí od třídy LoaderOfSettings, což je parser XML dokumentu, ve kterém je obsah elementů postupně ukládán pro proměnné s názvem text. 4. V překryté metodě endElement(...), kterou objekt M vlastní, jsou dle názvů značek koncových elementů přepisovány konkrétní hodnoty z nastavení, přičemž se počítá s možností vyskytnutí výjimek. 5. Poté, co je načítání dokončeno, dochází v metodě loadSettings() objektu G k využívání načtených (a přes metody objektu S vrácených hodnot). 6. Načtení dokončeno. Pakliže došlo k vyskytnutí nějaké nestandardní situace, projeví se to jen na obsahu logu.
Obrázek 4.7: Příklad tříd, pomocí kterých se načte nastavení pro GraphViewCircles
42
Dále ještě připomeňme, že: • •
4.6
Při prvním spuštění programu soubory s nastavením neexistují, tudíž není odkud načítat. Pokud je program uložen na nezapisovatelném médiu, tak se nezdaří ukládání načítání, tudíž ani není odkud načítat.
Jazykové nastavení
4.6.1 Řešení lokalizace V programu lze změnit jazyk, ve kterém se uživateli zobrazují textové výpisy, aniž by byl nutný zásah do zdrojového kódu. Částečně o tom byla řeč v 3.3. V této kapitole se na vše podíváme konkrétně. V principu se tu střetáváme se stejným chováním jako v případě načítání nastavení. Místo SAX parseru, o kterém byla řeč u nastavení, zde pracujeme s ResourceBundle objektem, který je základním kamenem i18n v Javě.
Obrázek 4.8: Třídy související s načítáním jazyku u menu Vše funguje tak, že existuje obyčejný plain text soubor s příponou properties, ve které jsou řádky dvojic klíč – hodnota. Tedy např.: 43
title = Výběr barvy new = NOVÁ A právě takovýto soubor si načteme pomocí ResourceBundle objektu, a pak se pomocí metody getString snažíme získávat prostřednictvím klíčů jednotlivé hodnoty. Postup načítání je obdobný jako u nastavení. Máme rozhraní Languable, které třídám vnucuje metodu loadLanguage(). V této metodě může např. docházet k vytvoření objektu, který vrátí texty pro komponentu. Ty buď budou základní (výchozí), nebo lokalizované. Záleží na tom, jestli se podaří načíst lokalizované zprávy. Možná je i varianta, kdy v lokalizovaném souboru chybí potřebná řádka / klíčové slovo. To by pak výsledné texty byly částečně lokalizované a částečně původní (tj. uvedené přímo ve zdrojovém kódu). U takového řešení lokalizace programu se setkáváme mimo jiné s objektem Locale, což je objekt, který obsahuje kódy zvoleného státu a jazyku. Využíván je při získání konkrétního ResourceBundle objektu. Dle Locale a zadaného jména souboru dochází k načtení zpráv ze správného properties souboru. Co se týče načítání zpráv ze správného properties souboru, tak situace zde je poněkud složitější. Když neexistuje properties soubor pro konkrétní Locale a tím pádem není pro něj možné vytvořit ResourceBundle, tak není ještě vše ztraceno. Automaticky se totiž ještě zkouší vytvořit ResourceBundle pro podobná Locale. Příklad z [19] následuje. Autoři se v tomto příkladu snažili v prostředí, jehož výchozí Locale bylo en_US, vytvořit ResourceBundle objekt pro Locale fr_CA_UNIX. Název souboru měl být ButtonLabel. Potřebné soubory neexistovaly, takže postupně docházelo k pokusům o vytvoření ResourceBundle objektu se zprávami z těchto souborů: ButtonLabel_fr_CA_UNIX.properties ButtonLabel_fr_CA.properties ButtonLabel_fr.properties ButtonLabel_en_US.properties ButtonLabel_en.properties ButtonLabel.properties
Pokud by neexistoval ani jeden z uvedených souborů, tak by došlo k vyvolání výjimky. Na ty v programu pamatujeme. Výjimka se odchytí, do logu je uložena informace o neúspěšném načítání 44
zpráv, a uživatel uvidí zprávy základní.
4.6.2 Lokalizování do nového jazyka Předpokládejme, že chceme lokalizovat program do Francouzštiny. Podaří se nám to, když se přidržíme následujících kroků: 1. Najdeme si umístění stávajících properties souborů. Pakliže program máme k dispozici jako JAR balík, musíme do něho. Properties soubory s jazykem jsou ve složce languages. V tuto chvíli by tam měly být převážně soubory pro Locale cs nebo cs_CZ. 2. Postupně si budeme otevírat soubory, které jsou nyní dostupné pouze pro Češtinu a budeme si k nim vytvářet ekvivalenty pro francouzské Locale. Klíče, které jsou povětšinou v Angličtině, zůstanou v těchto souborech identické, ale český text nahradí jeho francouzský překlad. 3. Pakliže už byl jednou program spuštěn, měl by existovat ve složce settings soubor GlobalData.xml. V tomto souboru jsou elementy language a country. Obsahem elementu language může být např. text cs, a obsahem elementu country text CZ. Text v language přepíšeme na fr, a element country může třeba vymazat. 4. Po opětovném spuštění programu budou texty načítány pro Locale fr.
4.7
Stavy programu
Stavy programu jsou mechanismem, díky kterému lze v programu signalizovat a detekovat, v jakém kontextu ho chceme právě používat. Např. můžeme mít stav slovně vyjádřitelný jako „V tuto chvíli lze přidávat uzly“, což má za příčinu, že „V tuto chvíli nelze přidávat hrany“ (slovně vyjádřený jiný stav). Stavy programu lze v programu využívat dvěma možnými způsoby: 1. Můžeme si pomocí statické metody getActiveState() třídy states.State zjistit, jaký je aktuální stav. V podmínce si pak ověřit, jestli konkrétní stav je např. stav pro přidávání uzlů, a pakliže ano, provede se potřebná operace. 2. Lze využít mapy s názvem states ve třídě GlobalData. V té se nachází všechny stavy, ve kterých se může program někdy vyskytnout. Pro každý stav přitom platí, že má aktivní a neaktivní variantu. Ty jsou realizovány 2 speciálními třídami, přičemž jedna dědí od druhé. Tyto třídy mají několik stejnojmenných metod, které se ale liší tělem.
45
Obrázek 4.9: Stavy AddNodeState a AddNodeNoState
Nyní si k oběma možným způsobům využití stavů uvedeme modelový příklad:
4.7.1 Přidání uzlu Máme grafický vykreslovač grafu a chceme do něj přidat nový uzel. Popis situace, ve které se využívá mapa stavů, následuje: 1. Uživatel klikne na ikonu uzlu a aktivuje tím v celém programu přidávání uzlů. Metoda getActiveState() třídy states.State nyní vrací referenci na nový objekt AddNodeState. Stejný objekt je nyní dostupný i pomocí klíče add_node v mapě states ve třídě GlobalData. V této mapě není jediný. Ostatní možné stavy tu mají svou neaktivní variantu – tedy např.
46
AddEdgeNoState či MoveNoState. Neaktivní varianty ostatních stavů se vytváření automaticky při aktivování jakéhokoli stavu. 2. Uživatel uvolnil tlačítko myši nad kreslícím plátnem. V kódu, který v tuto chvíli událost ošetřuje, si z mapy ve třídě GlobalData vyžádáme stav pro přidávání uzlů a pošleme zprávu jeho metodě addNode. Kdyby tento stav byl ve své neaktivní variantě (AddNodeNoState), tak se nic neděje (metoda má prázdné tělo), ale vzhledem k tomu, že máme k dispozici aktivní variantu, tak v metodě addNode dojde k vytvoření uzlu a jeho přidání do příslušného grafu. Pozn.: Mapa se používá kvůli tomu, aby bylo možné do programu přidávat nové stavy, aniž by se ve zdrojových kódech programu muselo něco měnit. Statická metoda getStateAddNode() v GlobalData tak slouží jen ke zjednodušení přístupu ke stavu určenému pro přidávání uzlů. Dostat se k němu tedy lze i jinak.
4.7.2 Stisknutí tlačítka myši nad plátnem Druhý příklad, jak je možné stavy používat, se používá opět u plátna. A to v době, kdy se nad plátnem stiskne tlačítko myši. V tuto chvíli je možných několik možností podle toho, jaký je aktuální stav. Všechny tyto možnosti pracují s plátnem. Proto se využije statická metoda třídy states.State, která nám vrátí aktuální stav. Následuje větvení, kde podle třídy, od níž je stav instancí, vykonáváme různé operace. Např. pokud je stav typu AddEdgeState, tak je započato přidávání hrany.
4.8
Logování
O logu již bylo řečeno dosti. Např. i to, že není nutno si pod ním hned představovat logovací soubor, protože ukládat obsah logu není vůbec nutností. V této kapitole si jen krátce představíme jeho řešení. Řešení logu, konkrétně jednoho jeho zástupce v podobě třídy TimeLog, zobrazuje obrázek 4.10. Je na něm zakreslena situace, kde máme objekt log, na nějž je dostupná reference ze třídy GlobalData. Tomuto objektu jsou pomocí volání GlobalData.addToLog(zpráva) předávány z libovolných míst programu varovné výpisy. Co se týče konkrétní realizace, tak platí, že log může být poměrně jednoduše realizován různými způsoby. Je to zástupce komponent programu, u kterých toto jde, a tak si na jeho příkladu vše nastíníme.
47
Abychom si vytvořili vlastní log, tak si stačí vytvořit třídu, uvést si u ní implements api.Log, napsat si těla vnucených metod, a máme log. To, která třída se pro realizaci logu finálně použije, je uvedeno v uživatelském / výchozím nastavení. Když bychom chtěli změnit chování programu takovým způsobem, aby se pro vytváření logu používala právě naše třída, lze toho docílit několika způsoby: •
Změnou ve zdrojovém kódu, a to ideálně ve třídě FactoryDefault, přes kterou lze ovlivňovat výchozí chování.
•
Změnou
v
souboru
s
nastavením
pro
GlobalData. V souboru settings/GlobalData.xml je element logclass, jehož obsahem je název třídy, která se pro realizaci logu používá. Do těchto míst můžeme zadat celé jméno úplně jiné třídy. •
Změnou prostřednictvím okna pro správu komponent, kde si lze třídu pro realizaci logu změnit prostřednictvím roletky s nabídkou dostupných možností. O oknu pro správu Obrázek 4.10: TimeLog jako jedna z tříd, kterými lze realizovat log
komponent pojednává kapitola 4.13.
4.9
Stavový řádek
Stavový řádek je na tom podobně jako log. Slouží k výpisu stavových informací a informativních zpráv. Oproti logu jde o komponentu obsaženou v hlavním oknu, v němž se při povolení také zobrazuje. Případná záměna jinou realizací se proto děje prostřednictvím souboru s nastavením hlavního okna. Tedy souboru settings/MainFrame.xml. Možné jsou ale i ostatní způsoby uvedené v kapitole 4.8. Těmito způsoby jsou úprava kódu ve třídě FactoryDefault, či (ideálně) změna prostřednictvím okna pro správu komponent. Stavový řádek je komponenta, která nebude v hlavní okně vidět, pokud pro její realizaci použijeme třídu obdobnou třídě components.statusPanel.NoStatusPanel. Tato třída je využívána 48
z menu, kde existují možnosti Zobrazit / Skrýt stavový řádek. Ve skutečnosti se ale použitím těchto možností nemění viditelnost stavového řádku, ale objekt, kterým je stavový řádek realizován (obrázek 3.9). Programu to vnucuje nutnost si pamatovat, která třída byla pro realizaci stavového řádku použita, když byl stavový řádek ještě viditelný. V tomto se stavový řádek od logu liší. V souvislosti s uvedeným názvem souboru MainFrame.xml uveďme ještě krátkou poznámku, že název souboru plyne z používané konvence. Podle té se soubory XML a properties pojmenovávají stejně jako třída, které se týkají. Dosti se tím usnadňuje testování a případné změny, protože v souborech je větší přehled. Většina objektů realizujících stavové řádky žádné takové soubory nepotřebuje.
4.10 Sebereflexe V rámci analýzy bylo mnohokrát zmíněno Java Reflection API, o kterém nakonec pojednala kapitola 3.6. Jde o pokročilou možnost Javy, které bylo v rámci implementace využito takovým způsobem, že na ní stojí většina důležitých funkcionalit. V této kapitole odkryjeme k tomuto tématu další informace.
4.10.1 Seznam tříd, které jsou v programu dostupné Ve zdrojových kódech je dostupná třída functions.SystemInfoFunctions. V této třídě se mimo jiné nachází statická metoda getClasses(), která vrací seznam názvů tříd, které jsou v programu dostupné. Jde o třídy, které byly napsány nad rámec standardního API a vyhledané v místě, kam ukazuje CLASSPATH. Nabízeny jsou i třídy z JAR souborů, které se tu nachází. K
hledání
tříd
dochází
konkrétně
v
adresářích,
které
nám
vrátí
volání
System.getProperty("java.class.path"). Cesta k těmto adresářům by měla býti uvedená jako absolutní. Uvádí se to tu z toho důvodu, že při nevhodných údajích v CLASSPATH nebudou v programu dostupné všechny možnosti, které jsou postaveny na údajích, které vrací metody třídy SystemInfoFunctions. Těmito údaji jsou povětšinou plné názvy tříd dostupných v systému, nebo jim odpovídající Class objekty. Uvedená třída System, plným názvem java.lang.System, je součástí Java Core API. Její metoda getProperty slouží ke zjišťování systémových vlastností. Parametrem je klíč. Když bychom se opět věnovali metodám třídy SystemInfoFunctions, tak za zmínku stojí hlavně tyto: •
getSystemProperties() - Vypíše dostupné informace o systému.
49
•
getSortedClasses() - Vrátí abecedně seřazené (dle názvů) uživatelem vytvořené třídy. Uvažuje i obsah JAR souborů.
•
getSortedClassesExcept(List
třídy) - Vrací to samé jako getSortedClasses, až na třídy, které obsahuje seznam třídy.
•
getSortedClassesThatImplements(Class rozhraní) – Seřazené dle názvů vrátí všechny třídy, které implementují zadané rozhraní. Uvažuje i obsah JAR souborů. Jde z vnějšku o nejvíce využívanou metodu z této třídy. Když čtenář upomene na řadu funkcionalit diskutovaných v části s analýzou,kde bylo zmiňováno Java Reflection API, možná si uvědomí, že přesně toto je ta metoda, co se k realizaci takového chování hodí.
•
getClassesFromJARs(List jars) – vrátí třídy, které jsou obsaženy v zadaných JAR souborech.
Obrázek 4.11: Metody třídy SystemInfoFunctions 50
4.10.2 Vytvoření instance konkrétní třídy Některé třídy programu realizují komponenty, u kterých se předpokládá, že by je mělo být možné jednoduše nahradit. A to jinou realizací toho samého. Příkladem je např. zmiňovaný stavový řádek, který je součástí objektu MainFrame. V něm se vytváří potom, co si MainFrame načte vlastní nastavení. Celý algoritmus lze slovně popsat takto: 1. Při načítání nastavení je načten název třídy, pomocí které se má stavový řádek realizovat. 2. Zkouší se získat Class objekt pro načtený název třídy (Class.forName(název)). 3. Kdyby cokoli selhalo, použije se výchozí třída, kterou vrací metoda FactoryDefault.getStatusPanelClass(). Tato třída mezi třídami skutečně existuje. 4. Class objekt, který jsme získali, zavolá svou metodu .newInstance(). Takový postup vyžaduje, aby měla třída, od níž instanci vytváříme, bezparametrový konstruktor. 5. Vytvořený objekt se přetypuje na api.StatusPanel a použije se. 6. Případné výjimky se ošetřují takovým způsobem, aby se v logu zobrazila informační zpráva a aby byl stavový řádek aspoň realizován třídou, která je uvedená jako výchozí Popsaný algoritmus je s různými modifikacemi ve zdrojovém kódu programu přítomný hned na několika místech.
4.11 Možnosti zobrazování obsahu V kapitole 3.4.2 jsme hovořili o možnosti, že by zobrazovaný obsah mohl být zobrazován různými způsoby – ve vnitřním okně, ve vnějším okně nebo v záložce. V této kapitole popíšeme, jak je vše realizováno. Využijeme k tomu obrázku č. 4.12. Obsah, který lze v programu zobrazovat, implementuje interface s názvem Content, za kterým se většinou skrývají instance tříd JPanel a JLabel z balíku javax.swing. Vizualizace obsahu probíhá přes factory objekt (tovární objekt), který se nachází v objektu MainFrame. Factory objekt si od hlavního okna vyžádáme a prostřednictvím jeho metody addContent mu předáme náš obsah, který chceme zobrazit. Další chování je odvislé od toho, jaký factory objekt se v hlavním okně nachází konkrétně (TabVisualiserFactory, MainFrameVisualiserFactory, InternalFrameVisualiserFactory), ale v principu i tak jsou následující kroky podobné. Factory objekt vytvoří zobrazovač (budeme ho nazývat vizualizérem) a předá mu obsah. V případě TabVisualiserFactory tak dojde k přidání nové záložky do panelu záložek, který se v tuto chvíli v hlavním okně zobrazuje. V případě 51
InternalFrameVisualiserFactory dojde k vytvoření vnějšího okna a k jeho přidání do desktop panelu hlavního okna. A v případě MainFrameVisualiserFactory dojde k vytvoření hlavního okna (představovaného MainFrameVisualiserem) a k jeho zobrazení. Factory objekty si musí udržovat informace o tom, jaké vizualizéry zobrazují, aby mohla být umožněna případná záměna factory jednoho typu za factory jiného typu. Co se týče různého vycentrování obsahu, nebo aby příliš velký obsah byl scrollovatelný, tak takové chování si ošetřují vizualizéry, takže se o něj u námi přidávaného obsahu nemusíme starat. U factory objektů by ještě stálo za zmínku, že si evidují, který vizualizér je právě vybraný. Takové chování se hodí pro ukládání grafů a pro simulaci grafových algoritmů. Zde je factory objekt požádán, aby nám vrátil aktuálně vybraný vizualizér, a s tím pak pracujeme, pokud došlo ke splnění základních podmínek.
Obrázek 4.12: Třídy pro vizualizaci obsahu pomocí záložek Obrázek 4.13: Třídy potřebné pro realizaci překreslovačů u GraphicalGraphView
52
4.12 Grafy a jejich zobrazování 4.12.1 Princip řešení A nyní ke grafům. U těch jsme si říkali, že je chceme zobrazovat různými způsoby, přičemž toto zobrazování různými způsoby by mělo být možné najednou a změna v jednom pohledu by se měla případně projevit i pohledech ostatních. Popsané chování je možné a je řešeno způsobem, který naznačuje obrázek. 4.13. Graf, který můžeme nazývat modelem, je oddělen od formátovacích dat. Ty obsahuje pohled, což je komponenta, která graf zobrazuje, přičemž možnosti zobrazování jsou různé (grafický vs. textový výstup). Při řešení, kdy se graf zobrazuje jako kolečka propojená čárkami, pohled obsahuje objekty, které zapouzdřují uzly a hrany z modelu a přidávají k nim formátovací informace, což jsou především výška, šířka a pozice v prostoru. Tyto objekty obohacené o formátovací data obsahují reference na své neformátované protějšky, a to především z důvodů provádění změn.
Obrázek 4.14: Graf a jeho pohledy 53
Nad pohledy stojí jakýsi řadič (nazývaný presenter), který synchronizuje obsah jednotlivých pohledů s modelem. Model je součástí tohoto presenteru. Pakliže v jednom pohledu dojde ke změně v zobrazení (např. bylo pohnuto uzlem A), tak dojde k provedení změny v daném pohledu P a k informování presenteru, že v pohledu P došlo k posunu uzlu A na pozici [x;y] (předává se neformátovaný uzel, a ne jeho formátovaná varianta). Presenter v takovém případě informuje všechny ostatní pohledy, že došlo k posunu uzlu A na pozici [x;y]. A teď pozor. Je jen na pohledu, jak na takovéto upozornění zareaguje – jestli uzel, který v jeho vykreslení odpovídá uzlu A, posune na pozici [x;y], nebo jestli neprovede např. vůbec nic. Na příkladu posunu uzlu bylo demonstrováno chování ukázkové chování presenteru. To je podobné pro spoustu dalších interakcí s pohledy. A právě v reakcích na jednotlivá upozornění se mohou různé pohledy lišit, i když vizuálně mohou působit úplně stejně. Uživatel si tak může s využitím dědění od některého ze současných pohledů napsat pohled vlastní, kde si upraví reakci na nějaké upozornění dle svých potřeb.
Obrázek 4.15:Graf a uzly v něm
54
4.12.2 Grafický vs. textový pohled Termíny použité v nadpisu tohoto odstavce mohou být zmatečné, proto radši uveďme: Grafickým pohledem na graf se myslí např. graf vykreslený pomocí čar a koleček, a textovým (negrafickým) graf zobrazený prostřednictvím matice sousednosti. Ta může být také vykreslena, ale vizuálně bude na uživatele spíš působit tak, že někdo vzal klávesnici a napsal na ní pár čísel. Tolik k těmto pojmům. Pokud se podíváme na zobrazovače grafu obecně, tak pro ty platí, že implementují rozhraní GraphView. V programu je přítomná abstraktní třída AbstractGraphView, ve které je doplněno tělo k těm metodám, které jsou společné pro grafické i textové pohledy. Textové pohledy by měly právě od této třídy dědit. Je to i případ třídy GraphViewAdjacencyMatrix, která by měla sloužit jako ukázkový příklad. Co se týče grafických pohledů, tak u těch pro pohledy přibývá ještě jeden předek. A to třída GraphicalGraphView, od které dědí pohledy, které v rámci použitého programového slangu lze označovat za pohledy grafické. V programu od této třídy dědí např. třída GraphViewCircles. Realizováno je tu plátno, na jehož plochu se kreslí. U tohoto plátna jsou ošetřeny události myši, pomocí kterých se často informuje presenter o různých změnách. Ty presenter zpracuje a vyvolává potřebné metody u ostatních pohledů, aby se aktualizovaly.
Obrázek 4.16: Hierarchie tříd pohledů 55
4.12.3 Manuální vs. automatické vykreslování Vykreslovače grafů představované instancemi tříd, které implementují rozhraní api.GraphView, umožňují přepínání mezi automatickým a manuálním překreslováním obsahu. Důvod? Např. při generování grafu chceme vytvořit několik uzlů a hran najednou, a nechceme, aby se po každém přidání jedné hrany (uzlu) graf zbytečně překresloval. Vše vypadá tak, že každý zobrazovač grafu obsahuje objekt Repainter (překreslovač), přes který se volá překreslování plátna s grafem, když dojde k nějaké změně. Pokud je tento objekt automatickým překreslovačem, tak se plátno s grafem skutečně překreslí. Pokud ne, nic se nestane. Kromě překreslovacího objektu obsahují třídy zobrazovačů ještě metodu repaint(), pomocí které lze graf překreslit úplně vždy, protože s překreslovacím objektem nemá vůbec nic společného. Díky této metodě můžeme tedy grafy překreslovat i tehdy, když je aktivováno manuální překreslování.
4.12.4 Zobrazení grafu v jiném pohledu U zobrazených grafů se nachází tlačítko, prostřednictvím kterého se uživateli nabídne popup menu, přes které si lze specifikovat třídu, jejíž instance zobrazí aktuální graf také. Využito je tu statických metod zmiňované třídy SystemInfoFunctions diskutované v kap. 4.10. Uživateli jsou nabídnuty aktuálně dostupné pohledy, v kterých je možné graf zobrazit (včetně těch, v kterých graf už zobrazen je).
Obrázek 4.17: Registr add-onů 56
4.13 Manage okno Manage okno neboli okno pro správu komponent. Jde o okno, ve kterém si uživatel myší najede na komponentu, kterou chce spravovat, a v nabídnuté roletce si vybere třídu, prostřednictvím které chce, aby se taková komponenta realizovala. Opět je tu využíváno metod ze třídy SystemInfoFunctions. V nabídce jsou nabízeny pouze aktuálně dostupné třídy, pro které platí, že implementují zadané rozhraní a nejsou abstraktní.
Obrázek 4.18: Třída okna pro nastavení komponent Této funkcionality se týká celý balík manage. ManageClassesWindow, které nastavovací okno realizuje, obsahuje instanci třídy javax.swing.JTabbedPane. To je panel pro záložky. Ty jsou v oknu v tuto chvíli dvě. Obsahem jedné je instance třídy GraphPanelForManage a obsahem druhé OtherPanelForManage. Jde o třídy, které dědí od třídy javax.swing.JSplitPane, takže jejich instance jsou zobrazeny jako panel rozpůlený na dvě poloviny. V levé polovině je nabídka možností, v pravé polovině detail zvolené možnosti. Třída ManageClassWindow má realizovat vnitřní okno, u kterého stačí, když bude zobrazeno maximálně jednou. Proto je jeho konstruktor privátní a nová instance se vytváří pomocí veřejné statické metody jen tehdy, pokud již nějaká instance neexistuje (návrhový vzor Singleton). Podobně jsou na tom i jiná okna v programu.
57
Obrázek 4.19: Třídy realizující okno pro správu komponent
58
4.14 Nápověda Nápověda aneb Poskytnutí pomocných informací uživateli. Po vzoru jiných programů tu jde o položku v menu, pomocí které lze vyvolat okno s možnostmi, které lze nazývat tématy nápovědy. Témata nápovědy jsou vyhledána pomocí metod z třídy SystemInfoFunctions. Jde tedy o využití Java Reflection API. Jako téma nápovědy poslouží každá neabstraktní třída, která implementuje rozhraní Help (nebo toto rozhraní implementuje některý z předků takové třídy). Při vytváření vlastního tématu nápovědy
lze
využít
dědění
od
třídy
AbstractHelp, která celé vytváření zjednodušuje. Tato třída je napsána takovým způsobem, že předpokládá načítání textů s nápovědou z HTML souborů. Obecně existují dvě možnosti, jak řešit téma nápovědy. Lze buď text načítat ze zadaného HTML souboru, nebo po vzoru standardního načítání zpráv lze využít properties souborů a ResourceBundle objektu. Okno pro výpis textu s nápovědou totiž obsahuje instanci třídy JEditorPane, která umožňuje jak zobrazování HTML souborů, tak výpisy obyčejných textů. U témat s nápo-
Obrázek 4.20: Téma nápovědy: Grafy
vědou se s tím počítá a tvůrce nápovědy si může vybrat, jak chce objekt JEditorPane využít. Třída AbstractHelp je řešena takovým způsobem, že obsahuje metody protected boolean isHTMLFile() a protected boolean isSimpleText(). Metoda isHTMLFile() tu vrací hodnotu true, a druhá zmiňovaná metoda vrací false. Hodnota true v isHTMLFile() říká, že do objektu JEditorPane chceme načíst HTML soubor s nápovědou, a true v isSimpleText() říká, že do objektu JEditorPane chceme načítat nápovědu z properties souboru. V třídách, které od AbstractHelp podědí, lze chování těchto metod změnit. Dokonce je možné to, aby v obou metodách byla hodnota 59
true. V takovém případě má přednost zobrazení HTML souboru s nápovědou jako pokročilejší z obou možností. Co se týče textů s nápovědou, tak ty lze lokalizovat. U řešení s properties soubory se neděje nic jiného, než bylo popisováno v kapitole 4.6. U řešení s HTML soubory je vše již zajímavější. HTML soubor poskytuje statický text, proto musí pro každý podporovaný jazyk existovat zvlášť. Názvy takovýchto souborů se shodují, jen se mění jejich umístění dle aktuálního Locale. Je-li aktuální Locale cs_CZ, tak soubory s nápovědou budou hledány v adresáři help > web > cs. Neuspěje-li načtení zde, tak po vzoru práce s properties soubory dochází k pokusu o načtení požadovaného souboru ze složky help > web. Pokud i zde dojde k nezdaru, je dané téma bez textu s nápovědou.
4.15 Rozšíření 4.15.1 Princip řešení Rozšíření aneb Add-ony, jak jsme zavedli v textu dříve. Jde o funkcionality, které se budou s programem automaticky spouštět, pakliže je uvedeme do stavu „Aktivní“. Jak je vše konkrétně řešeno, si vysvětlíme s pomocí obrázku 4.20. Když bychom vše zjednodušeně okomentovali: Program obsahuje registr add-onů (dále registr). Ten je dostupný přes třídu GlobalData.
•
K vytváření tohoto registru dochází při startu programu. •
Registr obsahuje seznam add-onů, které mohou být ve stavu aktivní / neaktivní. Aktivní add-on běží, neaktivní neběží, ale jako instance od určité třídy je v programu přítomen.
•
V registru si během běhu programu můžeme zaregistrovat nový add-on.
•
Prostřednictvím registru lze všechny add-ony najednou aktivovat / deaktivovat.
•
Když dochází k ukončení běhu registru, což se děje s koncem běhu programu, dochází k uložení nastavení registru. Konkrétně se děje přesně ve stylu, jakým probíhá ukládání nastavení jiných částí programu (kapitola 4.4). Do XML souboru s nastavením jsou uloženy názvy těch tříd, od kterých jsou v registru přítomné aktivní instance.
•
Během spouštění registru dochází přesně k opačnému procesu – dojde k načtení XML souboru s nastavení, načteme si názvy tříd, od kterých budeme chtít vytvořit instance, a tyto instance vytvoříme. Máme tak objekty, které představují jednotlivé add-ony. Tyto add-ony
60
aktivujeme.
4.15.2 Jak přidat nový add-on V jednotlivých bodech uvedených výše bylo nastíněno, jak probíhá načítání add-onů. Tedy add-onů, co už byly v programu nějak dříve registrovány. V rámci této podkapitoly bude popsáno, jak se v programu registruje úplně nový add-on. Nejdůležitější třídy, pomocí kterých je takového chování dosaženo, znázorňuje obrázek 4.21.
Obrázek 4.21: Třída okna pro správu add-onů
Když opět vše rozvedeme: •
V programu je možné otevřít okno, které slouží ke správě add-onů. Toto okno představuje instance třídy RegisterWindow.
•
RegisterWindow si prostřednictvím metod, které nabízí třída SystemInfoFunctions, zjistí, jaké třídy, které implementují rozhraní api.AddOn, jsou dostupné. Dále si z registru add-onů vyžádá add-ony, které registr registruje. Výsledkem sjednocení a vyhodnocení těchto údajů je nabídka, kterou zachycuje obrázek 4.22.
•
Změna učiněná v nabídnutém okně se odpovídajícím způsobem projevuje v registru.
61
Obrázek 4.22: Okno pro správu doplňků
4.16 Přidání položky do menu V rámci analýzy byla v kapitole 3.5.3 diskutována možnost přidání si nové položky do menu, přičemž byla uvedena motivace k takovému chování a bylo popsáno, jak by vše mělo přibližně fungovat. Pozorný čtenář tak tuší, že umístnění této kapitoly právě pod kapitolou popisující doplňky, není náhodné. Ale konkrétněji. V package api se nachází interface MenuItem. Pokud libovolná třída tento interface implementuje, lze o jejích instancích mluvit jako o položkách do menu. Tyto položky jsou určeny k tomu, aby uživatel prostřednictvím nich mohl ze spuštěného programu vyvolat nějakou blíže nespecifikovanou akci. Jak se snaží nastínit obrázek 4.23, tak pro položky menu platí: •
MenuItem je pouze specializovaný typ add-onu, kterému se přidává akce, která se vykoná, když se klikne na odpovídající položku v menu.
•
Položky menu je možné aktivovat / deaktivovat jak v oknu pro správu add-onů (RegisterWindow), tak i ve speciálním okně, které je obdobou RegisterWindow, ale oproti němu zobrazuje jen dostupné třídy rozšiřující rozhraní MenuItem. V programu toto okno realizuje instance třídy MenuItemWindow.
•
Jak je vidět i na obrázku 4.22, položky menu se zobrazují v menu pod možností Přídavky. Text Přídavky se v menu nezobrazuje, pokud není v programu aktivní žádný přídavek.
62
Obrázek 4.23: Třídy související s položkami v menu
4.17 Ukládání a načítání grafů 4.17.1 Ukládání grafů Jak přibližně ukládání grafů funguje, bylo nastíněno v kapitole 3.23, proto se v rámci kapitoly podíváme hlavně na zúčastněné třídy a na funkce, které plní. Vše zachycuje obrázek 4.24. Základem pro ukládání jakéhokoli obsahu v programu je třída functions.IOFunctions. Když uživatel klikne např. v nástrojové liště na tlačítko Uložit, zavolá se statická metoda save() zmiňované třídy. Zde se zavolá metoda save() u vybraného obsahu (pokud nějaký je). Následuje otevření okna, prostřednictvím kterého si lze naklikat, kam chceme obsah uložit a jaké má být jméno souboru. V případě grafů je využíváno objektu GraphFilter, který vrátí přípony podporovaných formátů.
63
Obrázek 4.24: Třídy používané při ukládání grafů GraphFilter obsahuje 2 mapy. Klíčem do nich je přípona souboru (malými písmeny) a obsahem reference na objekt, který s daným formátem umí pracovat. Jednou z map je mapa pro objekty, prostřednictvím kterých lze grafy ukládat. Během běhu programu dochází obdobně jako u jiných funkcionalit k vyhledání dostupných tříd. Ty musí implementovat rozhraní GraphSaver. Od těchto tříd se vytvoří instance a uloží se do mapy. Z té se konkrétní objekt, prostřednictvím kterého se graf uloží, vybere výhradně na základě přípony souboru. Co se týče podporovaných formátů, dostupná je podpora pro formát GraphML. Uložit lze graf i jako matici sousednosti (přípona .adjacency) a jako matici incidence (přípona incidence).
4.17.2 Načítání grafů Načítání grafů je na tom podobně jako ukládání grafů. Opět se využívá tříd IOFunctions a GraphFilter. Objekty, prostřednictvím kterých lze grafy načítat, jsou instance třídy implementující rozhraní GraphLoader. To stejně tak jako rozhraní GraphSaver dědí od rozhraní GraphFormat. Načtené grafy se zobrazují v zobrazovači, který je nastaven jako výchozí. To je takový zobrazovač, který se zobrazí, když klikneme v nástrojové liště na možnost Vytvořit nový graf. V tuto chvíli by to měla být instance třídy GraphViewCircles.
64
4.18 Simulace algoritmů V této kapitole se konečně dotkneme samotného simulování algoritmů. Jako jeho základní kameny lze jmenovat následující skutečnosti: Neexistuje
•
žádný
speciální
jazyk,
prostřednictvím kterého se grafové algoritmy zapisují. Grafové algoritmy jsou obyčejné třídy,
•
které využívají možností tříd, na kterých je celý program postaven. Nabídka algoritmů k simulaci není vytvo-
•
řena napevno, ale generuje se automaticky podle aktuálně dostupných tříd s algoritmy pro simulaci. A k těmto základním kamenům nyní
Ilustrace 4.26:
podrobněji. Každý algoritmus, který lze prostřednictvím programu simulovat, představuje třída, která implementuje rozhraní api.Algorithm. Rozhraní třídám
mimo
runNextStep(),
jiné
vnucuje
prostřednictvím
metodu které
se
algoritmus krokuje. Interface Algorithm sám o sobě příliš zajímavý není a slouží hlavně k vygenerování nabídky všech dostupných algoritmů. Po vzoru dříve popisovaných problémů se k simulování nabídnou pouze třídy, které tento interface implementují. Co se týče metod, tak těch má tento interface jen několik. Ty souvisí převážně s krokováním, a ne se simulací samotnou. Obrázek 4.25: Předci třídy BFS 65
Taková jednoduchost je ale nevhodná, pakliže po uživateli / programátorovi nechceme, aby se musel detailně seznamovat s celým prostředím, nad kterém jeho programy poběží. A proto vznikla třída Algorithm, která rozhraní Algorithm implementuje. Od té je při vytváření vlastní třídy, která by měla představovat algoritmus pro simulování, doporučeno dědit. Třída Algorithm poskytuje uživateli řadu metod, které by se mu mohly při programování jeho algoritmu hodit. Na obrázku 4.25 nejsou ani zdaleka vidět všechny. Cílem těchto metod je odstiňovat uživatele od úkonů, které by si musel složitěji napsat sám. V rámci následujících podkapitol bude uvedeno několik příkladů.
4.18.1 „Needs“ metody Jednou z metod, které třída Algorithm obsahuje, je např. metoda protected boolean needsStartNode(). Jde o zástup-
Obrázek 4.27: Třída reprezentující jeden krok algoritmu
ce metod, které zjednodušeně můžeme nazývat jako needs metody. Smyslem těchto metod je informovat o potřebě na konkrétní vlastnost grafu. Např. metoda needsStartNode() odpovídá na otázku, jestli algoritmus, který se prostřednictvím instance třídy simuluje, potřebuje znát startovní uzel. Needs metody ve třídě Algorithm mají jednoduché tělo – pouze vrací false. V třídách, které od třídy Algorithm dědí, lze některé z těchto metod v případě potřeby překrýt.
4.18.2 Krok pro inicializaci prostředí Krok pro inicializaci je stav, který bude každý algoritmus (napsaný v třídě dědící od třídy Algorithm) automaticky obsahovat, pakliže si nepřekryje metodu protected boolean isInitStep(). V rámci tohoto kroku se zkontroluje, jestli je vybrán nějaký graf a jestli má potřebné vlastnosti (přihlíží se např. k některým needs metodám). V tomto kroku se nastaví členské proměnné jako visualiser, graph, view, graphPresenter atd., pomocí kterých máme v rámci algoritmu přístup k vybranému pohledu či ke grafu, který zobrazuje. V rámci tohoto kroku se dále aktivuje stav programu nazývaný Prealgorithm, během kterého nelze měnit graf, ale lze v něm vybírat uzly a hrany. Dále se tu aktivuje ruční překreslování vizualizovaných grafů. O tom byla zmínka v kapitole 4.12.3. Ruční překreslování je potřeba před 66
další prací s programem zase změnit na automatické, proto program uživatele nutí k tomu, aby ukončil simulování algoritmu, než začne program používat k jiným činnostem.
4.18.3 Další přípravné kroky Kromě kroku pro inicializaci prostředí lze automaticky vytvořit ještě několik dalších kroků. V konstruktoru třídy Algorithm se volá metoda addPrepareSteps(), v jejímž těle se testují návratové hodnoty zbylých needs metod a případně se s jejich pomocí přidávají nové kroky. Testována je tu např. hodnota, kterou vrací metoda needsStartNode(). Pokud je návratovou hodnotou true, volá se metoda addStateNeedsStartNode(), která zařídí přidání kroku, v rámci kterého se kontroluje, jestli je vybraný v grafu jeden uzel. Pokud ne, zobrazuje se dialogové okno s informací, že je potřeba vybrat uzel. Zmiňování názvů zúčastněných metod by se mohlo zdát jako přílišné zacházení do podrobností, ale rád bych na těchto metodách zdůraznil překrývání metod, na kterém je celá simulace postavena. Překrývat lze totiž na několika místech, abychom dosáhli požadovaného chování. Například se místo metody needsStartNode() může překrýt metoda addPrepareSteps(). Ta nemusí dělat vůbec nic, a programátor si místo ní může např. jen zavolat metodu addStepNeedsStartNode(). A nebo si může krok, během kterého dojde k výběru startovního uzlu, napsat úplně sám.
4.18.4 Třída Algorithm a její konstruktor Díky předešlé kapitole může využívání možností třídy Algorithm působit složitě, ale smysl třídy Algorithm je tím poměrně dobře nastíněn. Ta je zkrátka jen pomocník, který uživateli může ušetřit psaní spousty kódu. A to různými způsoby. Je ale jen na uživateli, jak ji využije, a jestli vůbec. Je to ale doporučeno i za cenu překrývání některých metod. Cílem při realizaci simulování algoritmů nebylo vytvářet nějaká zbytečná omezení. Ani nebyly učiněny snahy, aby byly prostřednictvím třídy Algorithm pokryty úplně všechny funkčnosti, které by se při psaní nějakého nového algoritmu mohly objevit. Celé simulování totiž může vypadat jakkoli. Pro program je potřebné jen to, aby šlo algoritmus pro simulaci krokovat. Jak si ale v rámci některých kroků algoritmu uživatel ošetří, aby byl zvolen např. počáteční uzel, je jen na uživateli. Třída Algorithm nabízí řešení v podobě diskutované metody addStepNeedsStartNode() a je dostupno několik možností, jak tuto metodu zavolat. Při využití třídy Algorithm je vhodné vědět, jak vypadá tělo jejího jediného konstruktoru, aby programátor nebyl překvapen nečekaným chováním instancí odvozené třídy. Konstruktor třídy 67
obsahuje kód, který je uvedený za tímto odstavcem. Metoda createMySteps(), jejíž volání je v kódu obsaženo, je diskutována v samostatné kapitole. Konstruktor třídy Algorithm: public Algorithm(){ this.loadLanguage(); if(isInitStep()) addInitStep(); addPrepareSteps(); createMySteps(); }
4.18.5 Krok pro start samotného kroku V rámci třídy Algorithm se můžeme setkat ještě s jedním důležitým stavem. Jde o startovní stav , a vyvolává se voláním metody protected void addStartAlgorithmState(). Tato metoda se volá na konci metody prepareSteps(). Tento krok má návratovou hodnotu ve skipMe() nastavenou na true a jeho smysl je takový, že program uvádí do stavu Algorithm. V tomto stavu již s vizualizovaným grafem nelze provádět vůbec nic. Důvod? Můžeme mít např. označeny nějaké uzly a hrany, a když bychom myší klikli někam na plátno, tak by se při stavech Prealgorithm a Move mohli odznačit. Na obrázku 4.28 tento krok představuje možnost s řadou pomlček napsaných za sebou. Napsáno tu není nic konkrétního, protože krokování se na tomto kroku nezastaví. A to z toho důvodu, že zmiňovaná metoda skipMe() vrací hodnotu true.
4.18.6 Metoda createMySteps() Metoda createMySteps() je abstraktní metoda třídy Algorithm. V odvozených třídách je vhodné právě zde zařídit vytvoření vlastních kroků, z kterých se bude algoritmus pro simulaci skládat. A to prostřednictvím volání metod, kde každá metoda zařídí vytvoření jednoho kroku. Takové řešení se hodí z toho důvodu, že můžeme chtít vytvořit simulaci, která se bude lišit od jiné simulace jen v jednom kroku. Je-li původní simulace realizována prostřednictvím třídy A, a nová simulace má být realizována prostřednictvím třídy B, tak jen stačí, aby třída B dědila od třídy A a překryla některou její metodu / metody. Vše bude demonstrováno na příkladech v kapitole 4.18.10.
68
4.18.7 Kroky Jak v případě třídy Algorithm a jejích potomků vypadá jeden krok, lze vidět na obrázku 4:27. Jde o instance anonymních tříd, jejichž základem je třída Step. Všechny instance třídy Step (dále označované jen jako kroky) se samy přidají do ArrayListu (seznam realizovaný pomocí pole), který obsahuje instance třídy Algorithm. Krok zná svoji pozici v tomto seznamu, a kromě toho obsahuje text, který informuje o tom, co se v rámci tohoto kroku provádí. Co se v kroku provádí je v těle metody code(). Z dalších stojí za zmínku následující: •
public boolean skipMe() - Pokud vrací true, tak se po proběhnutí tohoto kroku automaticky zavolá krok další. Defaultně vrací false.
•
public boolean stopTimerAfterCode() - Pokud vrací true, tak se zastaví automatické krokování (pokud je zapnuté). Defaultně vrací false.
4.18.8 Panel pro simulování algoritmů K simulaci algoritmů dochází prostřednictvím komponenty, kterou můžeme nazývat jako panel pro simulování algoritmů Jde o panel, který se skládá ze 4 základních částí: •
bloku kroků, ze kterých se průběh algoritmu skládá
•
roletky pro výběr algoritmu,
•
tlačítek pro krokování
•
posuvníku pro nastavení časové prodlevy mezi 2 kroky při automatickém krokování algoritmu. Několik poznámek k vyjmenovanému následuje:
Blok kroků, ze kterých se průběh algoritmu skládá Jde o instanci třídy javax.swing.JList. Obsah tvoří textové zprávy jednotlivých kroků, ze kterých se vybraný algoritmus skládá. O tom, jaká položka je vybrána, vypovídá index kroku, který je zrovna na řadě.
Roletka pro výběr algoritmu Tato roletka obsahuje název třídy, od které se vytvoří instance, když se v roletce změní hodnota. V nabídce jsou pouze třídy, které implementují rozhraní Algorithm. Z vytvořeného objektu se vytáhnou zprávy pro blok kroků a jako krok, který je nyní na řadě, se označí krok s indexem 0.
69
Co se týká obsahu roletky, tak tím jsou skutečně jen názvy tříd, od kterých se vytvoří instance. Pokud bychom tu chtěli mít nějaké „hezčí“ výpisy, museli bychom sem vkládat „nějaké“ objekty s překrytou metodou toString(). Tím si vše děláme složitější.
Tlačítka pro krokování Funkčnost těchto tlačítek nebude asi překvapivá, přesto ji uveďme. •
Pomocí tlačítka Krok se vyvolá provedení kroku, který je v bloku kroků zrovna označen.
•
Pomocí tlačítka Auto-krok se spouští automatické provádění kroků – jako kdyby počítač po určité době klikl na tlačítko Krok za uživatele. Ke krokování je využita instance třídy javax.swing.Timer.
•
Pomocí tlačítka Konec se ukončuje provádění algoritmu. Algoritmy mohou končit krokem, kde se volá metoda end(), pomocí které se zajišťuje obdobné. Po stisknutí tohoto tlačítka se v programu aktivuje stav Přesunů a u vizualizátorů grafu se opět aktivuje automatické vykreslování.
Obrázek 4.28: Panel pro krokování algoritmů
70
4.18.9 Příklad algoritmu pro simulaci Zdrojový kód Následující kód je přepisem obsahu třídy BFS, která se v balíku algorithm nachází. Jak název napovídá, jde o třídu, prostřednictvím jejíž instance se simuluje procházení grafu do šířky. Kód uvádím bez komentářů a importů. Naopak jsem do něj přidal číslování řádků, aby šlo do zdrojového kódu v dalším textu snáze odkazovat. 1
public class BFS extends Algorithm{
2
public Color openColor=Color.CYAN;
3
public Color freshColor=Color.WHITE;
4
public Color closedColor=Color.BLACK;
5 6
@Override
7
protected boolean needsStartNode(){
8 9
return true; }
10 11
@Override
12
public void createMySteps(){
13
step3();
14
step4();
15
step5();
16
step6();
17
step7();
18
}
19 20
protected void step3(){
21
new Step("Nevybrané uzly se označí jako FRESH"){
22
public void code(){
23
startNode.setColorInAlgorithm(openColor);
24
openNode(startNode,openColor);
25
setAllNodesAsFresh(freshColor);
26
nextStep();
27
}
28 29
}; }
30 31 32 33
protected void step4(){ new Step("Expandování vybraného OPEN uzlu"){ public void code(){
34
openNodeNeightbourReflectEdgeOrientation(openColor);
35
repaint();
71
36
nextStep();
37
}
38
};
39
}
40 41
protected void step5(){
42
new Step("Uzavření prozkoumaného uzlu"){
43
public void code(){
44
closeNode(closedColor);
45
repaint();
46
nextStep();
47
}
48
};
49
}
50 51
protected void step6(){
52
new Step("Vytáhnutí nejstaršího uzlu z OPEN k prozkoumání"){
53
public void code(){
54
List opens=getOpenNodes();
55
if(opens.isEmpty()) nextStep();
56
else{
57
selectNode(opens.get(0));
58
repaint();
59
goToStep(getExpandSelectedIndex());
60
}
61
}
62
};
63
}
64 65
protected int getExpandSelectedIndex(){
66
return 4;
67
}
68 69
protected void step7(){
70
new Step("Neexistuje žádný OPEN uzel. Konec."){
71
public void code(){
72
end();
73
}
74
};
75 76
} }
72
Několik poznámek k uvedenému kódu •
Na obrázku 4.28, kde je okno s panelem pro simulování algoritmů, je v panelu vybraný právě tento algoritmus.
•
V kódu si lze na řádcích 6 – 9 povšimnou překryté metody needsStartNode() prostřednictvím které je do algoritmu přidán krok pro výběr startovního uzlu.
•
Vlastní kroky jsou vytvořeny v metodách step3() - step7(). Každý krok je vyčleněn do samostatné metody, aby bylo jednodušší provádění změn ve třídách, které dále dědí od třídy BFS (bude ještě diskutováno).
•
V kódu je využíváno řady metod zděděných od třídy Algorithm (např. nextStep(), repaint()).
•
Metoda getExpandSelectedIndex() na řádku 65 je tu přítomna opět hlavně kvůli tomu, aby šlo provádět méně změn ve třídách zděděných. Kdyby číslo dalšího kroku bylo v metodě code() daného kroku napevno, v potomkovi by se musel přepsat celý krok, pokud bychom se chtěli po provedení přemísťovat na jiný index.
4.18.10 Další příklady Nyní ještě ve zkratce k několika dalším třídám, které mají sloužit k simulování: •
DFS – Určeno pro simulování prohledávání do hloubky. Je zde překryta metoda step6() zděděná od třídy BFS a metoda closeNode(Color closeColor) zděděná od třídy Algorithm. Celá simulace tak vezme jen několik málo řádků kódu.
•
BFSWithDestination – Pro simulování prohledávání do šířky, kde máme zadaný cílový uzel. Třída dědí od třídy BFS a slouží k demonstrování, jak si vynutit výběr cílového uzlu. Třída si realizuje frontu otevřených uzlů sama.
•
BFSLocalized – Třída, na které je ukázáno, jak lze algoritmy případně lokalizovat. U většiny algoritmů lokalizace není řešena, protože typický uživatel si spíš napíše simulaci sám pro sebe nebo pro kolegy, a nějaká lokalizace ho nebude zajímat, protože je pracnější. Základem lokalizace je překrytí metody loadLanguage, která má defaultně prázdné tělo.
•
DFSWithOrder – Jde o variantu procházení do hloubky, kde je u uzlů vypisováno, v kterém pořadí byly procházeny. Algoritmus ukazuje, jak u uzlů vypisovat hodnoty, které ale po ukončení provádění algoritmu budou nahrazeny původními hodnotami.
73
•
Dijskra – Pomocí této třídy se realizuje simulace Dijskrova algoritmu. Algoritmus si tu prostřednictvím několika. needs metod vynucuje, aby uživatel musel zadat startovní uzel, aby všechny hrany byly ohodnocené číslem, a aby tato čísla byla nezáporná.
•
PigIsomorfism – Jenom demonstruje, že graf může být nakreslený různými způsoby. Ve zdrojovém kódu lze najít, jak si vytvořit nový graf se zadaným zobrazovačem a jak automaticky posunovat s uzly grafu. Na začátku simulace máme graf ve tvaru prasátka, a když spustíme automatické krokování, tak se uzly tohoto grafu pohybují libovolnými směry.
4.18.11 Příznaky a staré hodnoty uzlů a hran Kvůli simulování algoritmů byly rozhraní Edge a Node obohaceny o několik metod, které se hodí pro provádění algoritmů. Za zmínku stojí hlavně následující: •
public void setColorInAlgorithm(Color color) – Příklad metody, pomocí které si uzel / hrana uloží starou hodnotu, kterou obsahoval, a nahradí si jí novou.
•
public void resetToAttributesBeforeAlgorithm() - Souvisí s předešlou metodou. Uzel / hrana si díky ní obnoví svoje původní data.
•
public void setAlgorithmIndication(Object object) – Prostřednictvím této metody si lze u hrany / uzlu uložit referenci na libovolný objekt. Hodí se k indikaci. Např. že je uzel ve stavu UZAVŘEN nebo z jaké hrany se do uzlu v rámci prohledávání přišlo.
•
public void setAlgorithmIndicationField(Object[] object) – Obdoba předešlého. Indikačních příznaků lze takto uložit více.
•
public Object getAlgorithmIndication() - Pro získání indikace.
•
public void clearAllAlgorithmIndications() - Slouží k tomu, aby se všechny indikace nastavily na null. V programu je dostupná i možnost vytvoření si kopie celého grafu.
4.19 Generátor grafů 4.19.1 Základní principy V případě generátoru se opět nesetkáváme s o mnoho odlišnou funkčností, než u jiných částí programu. Opět tu tedy využíváme možnosti třídy SystemInfoFunctions z balíčku functions. Pro generátory platí:
74
•
Generovat grafy lze prostřednictvím instancí tříd, které implementují rozhraní Generator.
•
Při otevření okna pro nabídku generátorů dochází k vyhledání dostupných generátorů. Od těch se vytvoří instance. Jde o jednoduché objekty, které při volání metody displayMe() vytváří panel, ve kterém lze různě nastavovat možnosti pro generování.
•
Kliknutím na tlačítko Generovat, které je v okně dostupné, dochází k volání metody generate() vybraného generátoru. Ta povětšinou jen volá stejnojmennou metodu u svého panelu.
•
Toto řešení se např. od panelu s nabídkou algoritmů pro simulování liší v tom, že v roletce s generátory není název třídy, ale už lokalizovaný název generátoru. Generátor si svůj obsah (panel) vytváří až v době, kdy je vybrán v roletce. Důvod je ten, aby se zbytečně nevytvářelo něco, co třeba uživatel vůbec nepoužije.
Obrázek 4.29: Třídy, které se podílí na realizaci generátoru 75
4.19.2 Dostupné generátory V tuto chvíli jsou v rámci nastínění možností v programu dostupné celkem 4 generátory. Jde konkrétně o tyto (názvy odpovídají názvům tříd, které je realizují): •
BasicGenerator -Jde o převzatý generátor z BP.
•
BinaryTreeGenerator – Tento generátor umí generovat binární stromy. V panelu lze nastavit, jestli chceme mít ohodnocené hrany či uzly, jestli mají být orientované hrany a jestli má být strom úplný. Pokud ne, tak může nastat situace, že graf bude mít např. jen jeden uzel.
•
PigGenerator – Příklad generátoru, kde nelze nastavovat vůbec nic. Generátor jen vytvoří vytvoření pevně definovaného grafu vzhledově připomínajícího prasátko.
•
ShapeGenerator – Součástí tohoto generátoru jsou obrázky, na kterých je znázorněna přesná podoba grafu, který bude při stisku tlačítka Generovat vygenerován.
76
5
Testování
Tato nedlouhá kapitola je určena testování. Pojem je to relativně obecný, proto bude zmíněno hned několik skutečností, které lze pod pojem testování zahrnout.
5.1
HW nároky
Na HW nebyly kladeny žádné speciální nároky, ale samozřejmě je krásné, když výsledný SW chodí i na strojích méně výkonných. V tomto směru jsem provedl testy jen takovým stylem, že jsem program zkusil spustit na několika běžných přístrojích. Úspěšně jsem tak s programem pracoval mimo jiné i na následujících počítačích: •
notebook Acer TravelMate 2492NWLMi, Intel Celeron M, 1.6 Ghz, 1MB L2 Cache, 1GB RAM, MS Windows 7
•
notebook HP Compac X6000, Pentium 4 CPU 3,20 GHZ, 1 GB RAM, MS Windows XP
•
netbook Asus Eee PC 900A, Intel Atom, 1.60 Ghz, 0.99GB RAM, MS Windows XP
5.2
Spouštění v různých OS a JDK
Program jsem zkoušel spustit v následujících operačních systémech (na všech funkčně): •
Windows XP
•
Windows Vista
•
Windows 7 (64 bit)
•
Ubuntu 10.04 (Linux) V uvedeném OS Windows 7 bylo zkoušeno program spouštět jak s pomocí JDK 5.0, tak
s pomocí JDK 6. Práce s programem proběhla úspěšně. Na ostatních OS bylo použito jen JDK 6.
5.3
Testování funkčnosti SystemInfoFunctions
Důležitou součástí programu je třída SystemInfoFunctions. Prostřednictvím jejích metod mimo jiné dochází k vyhledání dostupných tříd, které implementují zadaný interface (dále označované jen jako třídy). Z funkčního hlediska je důležité, aby takové chování bylo možné, i když program nebude spouštěn z NetBeans jako původní NetBeans projekt. Konkrétně jsem zkoušel následující scénáře:
77
5.3.1 Vytváření nových projektů 1. Dostupnost tříd, když v NetBeans spustíme původní projekt. Funkční. 2. Dostupnost tříd, když původní vygenerovaný JAR balík (GraphEditor2) přesuneme na disku někam jinam a tam spustíme. Funkční. 3. Vytvoření nového projektu B, kde si do knihoven přidáme i balík GraphEditor2.jar představující náš programu. Když zavoláme main metodu původního projektu, program funguje stejně. 4. Když si přesuneme JAR balík projektu B na jiné místo na disku (musíme i s vygenerovanou složkou lib), tak stále vše funguje. 5. Když si do projektu B přidáme novou třídu NewGenerator, která bude implementovat např. rozhraní Generator, a opakujeme body 3 a 4, tak v nabídce generátorů se navíc objevuje námi nově vytvořený generátor. 6. Když si vytvoříme projekt C, kterému do knihoven přidáme balík B.jar, tak spuštění původního programu neuspěje. Důvod je ten, že balík B.jar má u sebe složku lib se souborem GraphEditor2.jar. Tento soubor je nutné do knihoven přidat také. Teprve pak spuštění programu uspěje. 7. Obsahem lib adresáře vytvořeného při souboru C.jar jsou i soubory B.jar a GraphEditor2.jar. Úspěšně lze tedy pro C.jar provést obdobu kroku 4.
5.3.2 Spouštění z konzole / příkazové řádky Spouštěním z disku v kapitole 5.2.1 se myslelo takové chování, kdy člověk proklikal myší na soubor GraphEditor2.jar a takto ho spustil. Ve Windows je takové chování možné. Co když ale někdo bude chtít spustit soubor z příkazové řádky (případně z konzole v Linuxu)? I toto jsem radši vyzkoušel, a výsledky byly zajímavé. Zdát se dokonce mohlo, že je ve třídě SystemInfoFunctions něco špatně, ale nebylo tomu tak. Zde bych odkázal na [16] a kapitolu 3.6. Problémem byla nevhodná cesta v CLASSPATH. Pokud program spouštíme z příkazové řádky pomocí programu java.exe s přepínačem -jar, tak musíme jako argument zadat absolutní cestu k JAR souboru našeho programu. Příklad nevhodného spouštění v příkazové řádce ve Windows: C:\java\jdk6\bin\java.exe -jar GraphEditor2.jar
Příklad správného spouštění v příkazové řádce ve Windows: 78
C:\java\jdk6\bin\java.exe -jar C:\GraphEditor2\dist\GraphEditor2.jar
Při správném spuštění programu z příkazové řádky, během kterého se do CLASSPATH uložila absolutní cesta ke složce, ve které byl umístěn balík GraphEditor2.jar, proběhl test všech funkcionalit úspěšně.
5.4
Testování funkčnosti ostatních částí programu
V této kapitole se podíváme na testování funkčnosti obecně. To probíhalo převážně hned při vyvíjení programu. Každá funkcionalita, kterou člověk napsala, byla hned otestována. Sledovalo se, jestli dělá, co by dělat měla, a co by se stalo, kdyby na vstupu takové funkcionality byly nesprávné hodnoty. Pro kritická místa, kde třeba bylo nutné, aby v nějaké proměnné nebylo null, byly vytvořeny JUnit testy, které toto testovaly. Tyto testy byly pravidelně spouštěny. Občas člověk díky nim odhalil chybu,kterou udělal z nepozornosti. O všech objevených chybách byl okamžitě udělán záznam, aby nebylo opomenuto je opravit. K opravě chyb docházelo většinou hned. Dále bych tu po vzoru BP zmínil několik ilustračních situací ke větším problémům, které člověk testoval. Mohlo by na nich být vidět, na jaké věci si člověk musel dávat pozor. Tyto situace budou probrány zběžně, protože částečně kryjí s obsahem BP. 1. Testování, jestli došlo k nabídnutí všech dostupných tříd, které implementují zadané rozhraní A. Člověk si vždycky zkusil vytvořit několik tříd, které implementovaly zadané rozhraní A, rozmístil je po různých balíčcích, různě jim nastavoval, jestli jsou abstraktní nebo ne, a sledoval, jestli roletka, která je má nabízet, obsahuje správné hodnoty. Tyto hodnoty byly vybírány, čímž byla testována správná reakce na výběr konkrétní možnosti. 2. Testování jazykových výpisů. Při vytváření objektů, které obsahovaly text, který měl být lokalizovaný, došlo vždycky k vytvoření kódu, který by uměl načíst lokalizované zprávy z properties souboru, ale takový soubor ještě neexistoval. Člověk si tak ověřil, jestli se ve všech výpisech objeví výchozí hodnoty. Když bylo vše v pořádku, tak teprve pak byl postupně vytvářen obsah potřebného souboru. Během toho bylo testováno, jestli jsou výchozí texty postupně zaměňovány texty lokalizovanými. 3. Testování změny Locale (jazyku) K několika třídám nevznikly texty lokalizované jen pro Češtinu, ale i pro jiný jazyk. Na těchto textech pak bylo vyzkoušeno, jestli funguje změna Locale, což odpovídá přepnutí výpisů do jiného jazyka. Změnu Locale lze provést v souboru settings/GlobalData.xml. 79
4. Testování souborů s nastavením U každé objektu, který umožňoval ukládání a načítání uživatelského nastavení, bylo okamžitě vyzkoušeno, jestli toto funguje. Program byl pravidelně spouštěn bez uživatelského nastavení (stačilo vymazat složku settings). Docházelo díky tomu i ke sledování, jestli se všechny soubory s uživatelským nastavením opět vygenerují. Před dopsáním této práce byl program funkčně spuštěn i z CD. Do souborů s nastavením byly dokonce ručně vkládány nesmyslné hodnoty a došlo i k odstranění několika řádků, jestli s tím program nebude mít nějaké problémy. 5. Sledování výpisů logu Log v průběhu vývoje realizoval objekt, který informace o všech vzniklých výjimkách vypisoval na standardní výstup. Člověk si tak testoval log a měl zároveň na očích všechny výpisy o všech výjimkách, které během práce s programem vznikly. Na případné chyby bylo možné hned reagovat. 6. Vytváření příkladů Ke všem funkcionalitám, které program nabízí, jsem se snažil vytvořit několik příkladů, aby spíš byly odchyceny některé sporné či chybné bloky kódu. Sledoval jsem přitom, jak moc příjemně se mi s nabízeným API pracuje a snažil jsem se uvažovat, jestli by nebyl možný nějaký refactoring, díky kterému by se práce s vytvořeným kódem usnadnila. 7. Zaměňování objektů Vytvářením možností pro Manage okno jsem si zároveň testoval, jestli je možné zaměnit na konkrétních místech instanci jedné třídy instancí jiné třídy (implementující stejné rozhraní). K odchycení několika problémů zde došlo. 8. Další testování Dál by se v těchto místech dalo pokračovat popisováním dílčích testů souvisejících již s jednotlivostmi. Vše by odpovídalo obecnému principu funkčního testování popsanému v úvodu. Testování bylo postaveno především na zkoušení různým možností, které by mohly nastat. K tomu navíc bylo využíváno nástroje zvaného Profiler. Jde o plug-in pro NetBeans IDE, ve kterém bylo vidět, které metody vyžadují nejvíce paměti a času. Díky tomu došlo k náhradě několika sporných kódů výkonnějšími alternativami.
80
5.5
Testy použitelnosti
Testování použitelnosti je posledním problémem, kterého bych se chtěl v rámci testování dotknout. Pojato nebylo nijak velkolepě, ale proběhlo. Nebyly vytvořeny žádné dotazníky, nebyla k dispozici žádná laboratoř, jen jsem se postupně sešel se 3 lidmi, kteří vědí, co je to teorie grafů a ty jsem poprosil, aby si v programu zkusili nakreslit graf ve tvaru prasátka a pak si na něm nechali automaticky simulovat procházení grafu do šířky. Všichni dotyční bez jakéhokoli radění různě rychle uspěli. Já během toho vizuálně posbíral několik podnětů, které se projevily převážně na vzhledu hlavního okna programu – změněny např. byly obrázky dvou ikonek v nástrojové liště. Co se týče pokročilejších funkcionalit, tak o těch proběhla jen diskuse. Jak bylo uváděno, program je spíš taková spustitelná knihovna pro práci s grafy, a aby bylo např. možné si vytvářet vlastní algoritmy, tak se člověk musí s celým prostředím seznámit blíže.
81
6
Podobné programy
Nyní několika odstavci k programům, které by mohlo jít označit za podobné. Učiněných požadavků na tuto práci, jak je vidno v kapitole 2.2, bylo dost. Proto bude asi slušné zmínit, že hlavními takovými porovnávacími faktory jsou možnost vytváření grafů a možnost simulování grafových algoritmů. Další informací, jejíž uvedení pokládám za vhodné, je skutečnost, že na podobné programy nejsem žádný odborník. S několika jsem se sice seznámil v době psaní BP, ale na všechny jsem už úspěšně zapomněl. Nejde totiž o SW díla, která by člověk běžně používal. K jejich využití může docházet např. jen v rámci semestrálního kurzu na nějaké VŠ. O následujících programech, které budou uvedeny, proto platí, že jde jen o ukázky jiných dostupných řešení. K těm jsem se dostal prostřednictvím vyhledávání ve vyhledávači Google. Vycházel jsem přitom z názoru, že řada z lidí, kteří by podobný program sháněli, by postupovala stejným způsobem. To je i účel vyhledávačů, aby vyhledávaly potřebné. V souvislosti s tímto procesem, prostřednictvím kterého jsem se dostal k jiným řešením, pro korektnost uveďme: Škála programů, které jsem vyzkoušel, byla ovlivněna tím, jaké odkazy vyhledávač nabídl na předních pozicích. • U populárnějších / kvalitnějších programů je pravděpodobné, že o nich internetoví uživatelé více píší a že na ně více odkazují. Tím se zvyšuje šance, že vyhledávač nabídne na předních pozicích stránky, které s těmito programy souvisí. • Běžný uživatel, který použije vyhledávač, prochází nabídnuté výsledky od první pozice a příliš daleko v rámci vyhledaných výsledků se nedostane. • Případné kvalitní programy, ke kterým existuje přístup přes špatně vyhledatelné stránky, mé pozornosti nejspíše unikly. A nyní už k několika programům, které lze považovat za typické zástupce. •
6.1
Pokročilý editor grafů Jde o editor, který jsem implementoval v rámci BP. Výstup programu a práce s ním se příliš
neliší. Inspiroval jsem se chováním běžných kreslících editorů. Vnitřní struktura je ale úplně jiná. Více o BP v [2]. Kromě tohoto programu, který na ČVUT v rámci bakalářských a diplomových prací vznikl, lze na https://dip.felk.cvut.cz/ najít i jiné práce. Ty se funkčně, co jsem si zběžně některé pročítal, s programem vzniklým při této DP kryjí jen částečně (více či méně).
82
6.2
uDraw(Graph) 3.1
uDraw(Graph) [20] je zástupce programů, které umožňují kreslení grafů a editaci vytvořeného obsahu, ale nelze zde simulovat grafové algoritmy. Moje uživatelské zkušenosti konkrétně s uDraw(Graph) 3.1 jsou takové, že se mi neovládal dobře. Vykreslovaný obsah různě skákal po obrazovce, občas se něco nevykreslilo, hrany se samy od sebe různě zalamovaly a zase narovnávaly atd. Z mého pohledu zkrátka neintuitivní chování. Jako plus bych zmínil přítomnost ukládání a načítání obsahu, funkcionality umožňující činnosti jako přeorientování hran, a širokou paleta možností, jak obsah vizualizovat – např. uzel může mít 8 různých tvarů, a ty lze ještě různě formátovat.
6.3
yEd Graph Editor 3.5
Program stejného ražení jako uDraw(Graph), ale od prvního pohledu mnohem propracovanější. S programem se mi pracovalo příjemně a graf šlo vizualizovat mnoha způsoby. Podporovány jsou všechny známé grafové formáty. Více v [21].
6.4
Applety
Na Internetu lze najít řadu stránek, kde jsou dostupné různé programy (povětšinou applety), prostřednictvím kterých autoři nastiňují, jak který algoritmus funguje. Taková řešení jsou typická tím, že je v nich graf zadán napevno a uživatel jeho strukturu nemůže nijak ovlivnit. Příklady: • •
6.5
http://www-b2.is.tokushima-u.ac.jp/~ikeda/suuri/main/index.shtml http://cam.zcu.cz/~rkuzel/aplety/
Graph Magics 2.1
Program, kde lze grafy nejenom vytvářet, ale je tu dostupných i několik algoritmů, které lze nad takovými grafy provést. Přidat si vlastní algoritmus nelze. Co se týká ovládání, tak to z mého pohledu bylo rovněž jako v případě programu uDraw(Graph) neintuitivní. Člověk se musel s programem poměrně dlouho seznamovat, aby se dostal k činnostem, na které ho program na Internetu nalákal. Zdarma ke stažení je pouze 30 denní trial verze. Více viz. v [22].
6.6
Platforma NetBeans
Není to tak neznámý fakt, že NetBeans není jen vývojové prostředí (NetBeans IDE). NetBeans je i vývojová platforma. Ta má dostupných již několik verzí. Cílem je usnadnit vývojářům tvorbu okenních aplikací, kdy nemusí znovu programovat něco, co už je hotové – například splash screen. V podstatě můžeme vzít NetBeans IDE a přidat do něj nějaký doplněk (IDE je postaveno na této 83
platformě). A jsou i jiné možnosti, jak platformu použít. Doplňkem by v takovém případě byl simulátor grafových algoritmů. Nad touto možností jsem velmi vážně uvažoval, zkoušel jsem si různé věci, které by šlo nad platformou udělat, ale nakonec jsem se rozhodl pro vlastní řešení. Co se týče platformy NetBeans, tak práce s ní byla z mého pohledu poměrně obtížná, člověk si četl manuály, jak co udělat, když si takto přidal např. položku do menu, došlo k vytvoření několika souborů, a pohled do obsáhlé dokumentace k platformě, která poskytuje veliké množství tříd, které by člověk k ničemu nevyužil, byl až děsivý. Ve výsledku jsem si nedokázal představit, že mi použití platformy ke zvládnutí všech 31 požadavků, které byly v kapitole 2 vyjmenovány, práci nějak zjednoduší. Závěrem bych ještě uvedl, že kromě NetBeans existují i jiné platformy. Např. Eclipse Platform.
84
7
Závěr
A dostáváme se k závěru této práce. Je čas být o něco více subjektivním. A v prvé řadě musím přiznat, že jsem měl poněkud velké oči, když jsem začal na tomto projektu dělat. Člověk si vymýšlel různé roztodivnosti, které ve výsledku nejsou až tak důležité, co by mohl program umět. Člověk si kreslil různé diagramy, z jakých částí by se mohl program skládat a jak by tyto části mezi sebou spolupracovaly. Následně třeba tyto diagramy byly v některých případech nahrazovány diagramy novými, kde byl celý problém řešen o něco více obecně, a ve výsledku stres, že tohle všechno nelze ani zdaleka stíhat. V mých představách byl v počátcích vývoje program, který bude zvládat kde co. V tomto programu mělo být možné grafy zobrazovat nepřebernou sadou způsobů, mělo být možné různě podrobnými způsoby simulovat chování všech aspoň trochu známých grafových algoritmů, měla být dostupná spousta generátorů, pro generování speciálních typů grafů, a takto bych mohl pokračovat dál. Závěr? Zadání práce splněno, požadavky vyjmenované v části s analýzou také, ale osobní představy nikoli. Program představil možnosti, jak všechny učiněné požadavky řešit a vytvořil pro všechno několik ukázkových příkladů. Těch by ale dle mého názoru měla být spousta, aby se využilo možností, které program nabízí. Z toho důvodu se domnívám, že by budoucnost programu mohla být i taková, že by ho někdo jiný např. v rámci své BP dále rozšířil.
7.1
Náměty na vylepšení
V případě dalšího vylepšování doporučuji se zaměřit hlavně na následující: •
Přidání dalších tříd pro simulaci grafových algoritmů. Simulovat lze cokoli, co půjde krokovat a vizualizovat – např. i činnost automatů.
•
Přidání dalších specializovaných generátorů. Pěkné by mohlo být vytvořit podporu předmětům vyučovaným na škole. V této souvislosti mě napadají generátory grafů diskutovaných v předmětu Paralelní systémy a algoritmy, a vytištěné ve skriptech na Teoretickou informatiku.
•
Nabídnutí všech možných informací k právě vybranému grafu. Např. že graf je orientovaný, acyklický, jeho průměr je 4 atd.
85
Možné jsou i další z mého pohledu celkem zajímavá vylepšení: •
Nabízené algoritmy k simulaci by mohly býti tématizovatelné, aby se z nich lépe vybíralo. Např. již nyní je v programu několik ukázkových algoritmů, které se různými způsoby zabývají procházením do šířky. Ty by mohly být shluknuty pod jednu položku Procházení do šířky.
•
V současné chvíli se výpisy toho, co simulace zrovna provádí, zapisují slovně. Co krok, to jeden řádek v listu s výpisy. Zde by mohla vzniknout druhá možnost, která by se hodila pro pseudokódy. Výpis toho, co se právě provádí, by v takovém řešení mohl být i na 2 řádcích, mezi které by byly vloženy výpisy pro jiné kroky.
•
Při simulaci by se mohly používat zobrazovače obsahů využívaných front apod. Tedy nabídka funkčností, pomocí kterých by se přehledně zobrazil obsah zadaného pole / seznamu / proměnné.
•
Dostupná by mohla být historie změn, která by uvažovala skutečnost, že graf může být zobrazován různými způsoby.
•
Neznám možnosti Javy 3D, ale stálo by za prozkoumání, jak moc složité by bylo s její pomocí vytvořit 3D zobrazovač grafu. Hodilo by se např. pro mřížky.
•
V panelu, ve kterém se simulují algoritmy, by mohlo existovat tlačítko pro zobrazení detailů o simulovaném v grafu. V BP již bylo. Do DP jsem nezařazoval z časových důvodů, protože jde jen víceméně o záležitost psaní textů, které o algoritmech pojednávají. Uživatel samozřejmě musí mít možnost vytvářet simulaci i bez takového textu.
•
Mohla by být prozkoumána možnost, že když v programu budeme mít simulace algoritmů a k nim třeba i informační text popisovaný o bod výše, tak jestli by z toho nemohl být generován nějaký výstup zobrazitelný i mimo okno programu. Např. HTML stránka s appletem, který by pro simulaci algoritmu používal graf, který uživatel v době generování výstupu používal.
•
Kdyby se to dalo využít, tak by mohla být prozkoumána i možnost, jestli by nemohl program obsahovat nějaký objekt s funkcí serveru, který by poslouchal na zadaném portu a přijímal případné požadavky od jiných programů, které by byly spuštěné jako klienti. Mohlo by takto být komunikováno s programem na počítači připojeném k diaprojektoru.
86
7.2
Zhodnocení
Uvedl jsem své pocity a názory z vytvořeného programu, uvedl jsem i poměrně nemálo možností, jak z programu učinit ještě komplexnější pomůcku, a teď bych uvedl už jen opravdu poslední odstavec. V něm chci vyjádřit skutečnosti, že zadání pokládám za splněné a že práci na programu pokládám za přínosnou, protože jsem se díky ní seznámil s řadou věcí a v určité míře se v nich i procvičil. Doufám, že program bude mít svůj přínos i pro jiné a že ho třeba ještě časem někdo vylepší.
87
8
Seznam použité literatury
[1] KOLÁŘ, Josef. Teoretická informatika. 2. vyd.. Praha : Česká informatická společnost, 2004 [2] ŠACH, Martin. Pokročilý editor grafů : bakalářská práce. Praha : ČVUT Fakulta elektrotechnická, 2007. [online] [přístup 3. květen 2010] Dostupné na : http://dip.felk.cvut.cz/browse/pdfcache/sachm2_2007bach.pdf [3] Java (programovací jazyk) na Wikipedii [online] [přístup 9. květen 2010] Dostupné na: http://cs.wikipedia.org/wiki/Java_(programovací_jazyk) [4] Java 6 System Requirements [online] [přístup 11. květen 2010]] Dostupné na: http://www.java.com/en/download/help/6000011000.xml [5] Java Reflection Performance [online] [přístup 11. květen 2010] ] Dostupné na: http://stackoverflow.com/questions/435553/java-reflection-performance [6] Objektově orientované programování na Wikipedii [online] [přístup 9. květen 2010] Dostupné na: http://cs.wikipedia.org/wiki/Objektově_orientované_programování [7] SEMECKÝ, Jiří: Naučte se Javu – výjimky [online] [přístup 11. květen 2010] Dostupné na: http://interval.cz/clanky/naucte-se-javu-vyjimky/ [8] Principy javovské grafiky a GUI [online] [přístup 11. květen 2010] Dostupné na: http://www.kai.tul.cz/~kopetschke/java/PG2-JavaGUI1.pdf [9] The JavaTM Tutorials: 2D Graphics [online] [přístup 9. květen 2010] Dostupné na: http://java.sun.com/docs/books/tutorial/2d/index.html [10] Graf (teorie grafů) na Wikipedii [online] [přístup 9. květen 2010] Dostupné na: http://cs.wikipedia.org/wiki/Graf_(teorie_grafů) [11] Brandes, U. - Eiglsperger, M. - Lerner, J. GraphML Primer [online] [přístup 9. květen 2010] Dostupné na: http://graphml.graphdrawing.org/primer/graphml-primer.html
88
[12] Winter, A. - KullBach, B. - Riediger V. An Overview of the GXL Graph Exchange Language. [online] [přístup 9. květen 2010] Dostupné na: http://www.gupro.de/GXL [13] XGMML Format [online] [přístup 9. květen 2010] Dostupné na: http://gephi.org/users/supported-graph-formats/xgmml-format/ [14] Himsolt, Michael, GML: A portable Graph File Format [online] [přístup 9. květen 2010] Dostupné na: http://www.infosun.fim.uni-passau.de/Graphlet/GML/ [15] The JavaTM Tutorials: The Reflection API [online] [přístup 9. květen 2010] Dostupné na: http://java.sun.com/docs/books/tutorial/reflect/index.html [16] Classpath (Java) [online] [přístup 11. květen 2010] Dostupné na: http://en.wikipedia.org/wiki/Classpath_(Java) [17] Document Object Model [online] [přístup 11. květen 2010] Dostupné na: http://cs.wikipedia.org/wiki/Document_Object_Model [18] Simple API for XML [online] [přístup 11. květen 2010] Dostupné na: http://cs.wikipedia.org/wiki/Simple_API_for_XML [19] The JavaTM Tutorials: Internationalization [online] [přístup 9. květen 2010] Dostupné na: http://java.sun.com/docs/books/tutorial/i18n/index.html [20] Universität Bremen: uDraw(Graph) – The powerful solution for graph visualization [online] [přístup 11. květen 2010] Dostupné na: http://www.informatik.uni-bremen.de/uDrawGraph/en/uDrawGraph/uDrawGraph.html [21] yEd Graph Editor [online] [přístup 11. květen 2010] Dostupné na: http://www.yworks.com/en/products_yed_about.html [22] GraphMagic [online] [přístup 11. květen 2010] Dostupné na: http://www.graph-magics.com/
89
A
Seznam použitých zkratek
API – Applicaion Programming Interface BP - Bakalářská Práce DP – Diplomová Práce DOM – Document Object Model GML – Graph Modelling Language GUI – Graphical User Interface GXL – Graph Exchange Language HTML – HyperText Markup Language HW - Hardware i18n - Internacionalization JAR – Java Archive JDK - Java Development Kit JFC – Java Foundation Classes JVM – Java Virtual Machine OOP – Objektově Orientované Programování OS – Operační Systém SAX - Simple API for XML SW - Software XGMML – Extensible Graph Markup and Modeling Language XML - Extensible Markup Language
90
B
Uživatelská příručka
Součástí programu je Nápověda, kde je k některým úkonům poskytnut pomocný text. V rámci tohoto dodatku bude jen popsáno, jak program spustit:
Spouštění programu Existuje několik možností, jak program spustit. Výpis následuje: 1. Přiložené CD obsahuje NetBeans projekt programu (z OS Windows 7). Ten si lze otevřít v NetBeans IDE (http://netbeans.org). 2. Přiložené CD obsahuje JAR balík s názvem GraphEditor2.jar. Ten lze spouštěn několika způsoby: ◦ Na některých OS postačí na tento soubor poklikat myší a zachová se jako běžný spustitelný soubor. ◦ Obecně je nutné použít interpret jazyka Java – program java.exe. A to s parametrem -jar. Jako další parametr se uvede absolutní cesta k souboru GraphEditor2. Vhodné je uvést i absolutní cestu k programu java.exe, abychom měli jistotu, že ne nespouští např. jeho starší verze z jiného JRE. Pro spuštění je potřebné aspoň JRE 5.0. příklad z příkazové řádky ve Windows: C:\java\jdk6\bin\java.exe -jar C:\GraphEditor2\dist\GraphEditor2.jar
(Nutno samozřejmě nahradit vlastní cestou k souborům. V Linuxu je spouštění obdobné.)
91
C
Obsah CD
obsahCD.txt – dokument s tímto obsahem readme.txt – dokument s pomocnými radami dp.pdf – celý tento dokumentace GraphEditor2.jar – JAR balí programu /GraphEditor2 – Projekt z NetBeans /B – Jiný projekt z NetBeans, který jako knihovnu využívá soubor GraphEditor2.jar. Byl diskutován v kapitole 5.3.1. /JRE -Instalační soubory k prostředí, přes které lze program spustit.
92