További pdfTeX gyöngyszemek

A múltkor beszéltem arról, hogyan érdemes szerintem [intlink id=”604″ type=”post”]LaTeX szerkesztést végezni OSX-en[/intlink] Eclipse kiterjesztés segítségével. Azóta némi további érdekességet sikerült összeszedni a témáról – és az ötletek szerintem a TeX környezetek többségére alkalmazhatóak.

Egyik alapvető érdekesség, hogy a pdfTeX motor csak a jpg, png és pdf fájlokat támogatja képfájlként. Ez egyfelől jó (lehet), mert bizonyos képek könnyebben elérhetőek ilyen formátumban, másrészt viszont rossz, mert ezek nem szerkeszthetők vektorosan olyan egyszerűen, ha úgy hozná a sors. Ezért lenne jó vagy svg vagy pedig eps támogatás hozzá.

EPS támogatás beizzítása pdfTeX motorhoz

Abban az esetben, ha az ember gépére telepítve van az imageMagick program, annak a convert parancsát ki lehet hívogatni, és ezzel szinte bármilyen formátumból bármilyen formátumot elő lehet állítani. A megoldás nyilvánvaló előnye, hogy hihetetlenül rugalmas, hátránya, hogy igényli a convert parancs jelenlétét a rendszerben – azaz a hordozhatóság csökken. A következő parancs megmutatja, hogy milyen jellegű utasítást kell kiadni a LaTeX preambulumban:

[cc]\DeclareGraphicsRule{.tif}{png}{.png}{`convert #1 `dirname #1`/`basename #1 .tif`.png}[/cc]

A DeclareGraphicsRule parancs során meg kell adni fájlkiterjesztést, amire figyelni kell, egy típust, egy beolvasási fájlkiterjesztést és egy parancsot. Részletek a következő pdf fájlban: http://www.ctan.org/tex-archive/macros/latex/required/graphics/grfguide.pdf

De tegyük fel, hogy eggyel hordozhatóbb megoldást szeretnénk. Itt jöhet szóba az az opció, hogy a legtöbb TeX disztribúció tartalmaz egy epstolatex parancssori alkalmazást – amint a neve is mutatja, ez egyszerűen eps fájlokat konvertál pdf-re. Külön előnye a programnak, hogy van egy package, ami ennek az egyszerűbb felparaméterezését teszi lehetővé. A package a könnyen megjegyezhető epstopdf névre hallgat.

Az alapértelmezett könyvtárstruktúra és az epstopdf parancs elérhetősége esetén mindössze a package-et kell importálni, és működik, némileg komplexebb felállás esetén a megoldás tovább hegeszthető. Én például azt valósítottam meg, hogy vegye figyelembe az img, src és temp mappákat, amikbe dolgozom, és miközben a LaTeX könyvtára az src, az eps-t az img-ből vegye, míg a pdf-et a temp-be dobja. Ezt a következőképpen sikerült elérni:

  1. Először is megadtam a preambulumban a [cci_latex]\epstopdfsetup{outdir=../temp/,verbose}[/cci_latex] parancsot – ez a temp mappába teszi a pdf-et, és a verbose segítségével kicsit részletesebben kifejti a hasfájásait…
  2. És ezután a képek beszúrásánál az [cci_latex]\includegraphics[width=10cm]{../img/csp.eps}[/cci_latex] módon adom meg a képfájlokat.

Ezután már a rendszer megfelelő hegesztés után elkezd működni. Az epstopdf package dokumentációja alapján a package-et rá lehet venni a DeclareGraphicsRule-lal, hogy mégse az eps2pdf-et használja, vagy ne az alapértelmezett paraméterezéssel.

PDF-specifikus beállítások a rendszerben

Ha csak simán pdfTeX-hel lefordítjuk a LaTeX forrásunkat, akkor már egy használható pdf fájlt kapunk – lehet böngészni meg továbbítani. De mi van, ha mi szeretnénk néhány fancy pdf feature-t kihasználni (na, ez szép hunglish lett 🙂 )? Tipikusan olyasmikre gondoltam most, mint az, hogy a pdf metaadatokat töltsük ki, illetve tegyük klikkelhetővé a hivatkozásokat, valamint a pdf megjelenítő számára is tegyük elérhetővé a dokumentum szerkezetét. Nem hangzik túl extrának, de nagyban növelheti az online olvashatóságot (aki kinyomtatja, annak nyilvánvalóan nem számít…)

Na, csapjunk is bele a lecsóba: a hyperref package jelenti a megoldást a problémára. A bekapcsolt állapot mellett automatikusan klikkelhetővé válnak a hivatkozások, és értelmezni fogja a dokumentum szerkezetét. A metaadatokat nem tölti ki azért automatikusan (noha nem lenne rossz 🙂 ). Mindenesetre ezeket a hyperref csomag opcionális paraméterei között lehet megadni, vagy pedig a hypersetup parancs segítségével. Részletek itt: http://andy-roberts.net/misc/latex/pdftutorial.html

Két fontos megjegyzés a hyperref használatával kapcsolatban: egyrészt opcionális paraméterről van ugyan szó, mégis érdemes megadni a fordítót (esetemben pdftex), ugyanis többféleképpen is keletkezhet pdf a kimeneten, és nem lehet ránézésre eldönteni, hogy melyik fog következni. Az előző oldalon szerepelnek a használható fordítók.

A másik: a hyperref csomagot olyan későn importáljuk, amennyire csak tudjuk (gyk. csak azt tegyük utána, amit feltétlenül szükséges), mert ellenkező esetben sok-sok warningot kapunk, hogy bizonyos hivatkozások megkettőződtek, stb. ami nem könnyíti meg a tényleges hibák felderítését. Ha a végére kerül, akkor ez a probléma nem lép fel (de legalábbis kevésbé). Igen, szép dolog a makrónyelv, ott ilyen szép kis függőségek is kialakulhatnak.

LaTeX szerkesztés OSX-en

Ugyancsak a múltkor ígértem meg [[Dokumentumszerkesztés OSX-en Stampie módra|dokumentumszerkesztés kapcsán]], hogy írok részleteket a [[http://texlipse.sourceforge.net/|TeXlipse]] beállításáról úgy, hogy minden jól menjen a gépen. Az akkori ígéretben még az is benne volt, hogy akkor nem találkoztam még a koncepció gyermekbetegségeivel. De ennek ellenére érdemes lehet leírni, hátha más is fel tudja használni az itt leírtakat.

Talán éppen emiatt merem azt mondani, hogy érdemes lehet megvizsgálni akár más platformokra is, amiket itt leírtam, mert a TeXlipse szinte mindenhol működik, és a többi platformra is van pdfsync-képes program.

Fontos megjegyzés: a TeXlipse a verziószáma ellenére helyenként még kicsit nehézkesen használható, főleg, ha bizonyos új komponensekkel kerül együttes felhasználásra, de ennek ellenére jól használható TeX környezet kialakítására alkalmas.

Ugyancsak a múltkor ígértem meg [[Dokumentumszerkesztés OSX-en Stampie módra|dokumentumszerkesztés kapcsán]], hogy írok részleteket a [[http://texlipse.sourceforge.net/|TeXlipse]] beállításáról úgy, hogy minden jól menjen a gépen. Az akkori ígéretben még az is benne volt, hogy akkor nem találkoztam még a koncepció gyermekbetegségeivel. De ennek ellenére érdemes lehet leírni, hátha más is fel tudja használni az itt leírtakat.

Talán éppen emiatt merem azt mondani, hogy érdemes lehet megvizsgálni akár más platformokra is, amiket itt leírtam, mert a TeXlipse szinte mindenhol működik, és a többi platformra is van pdfsync-képes program.

Fontos megjegyzés: a TeXlipse a verziószáma ellenére helyenként még kicsit nehézkesen használható, főleg, ha bizonyos új komponensekkel kerül együttes felhasználásra, de ennek ellenére jól használható TeX környezet kialakítására alkalmas.

De mielőtt belecsapnék a lecsóba, még egy ügyes OSX-es programra szeretném felhívni a figyelmet: a [[http://texlipse.sourceforge.net/|Skim]] nevű PDF nézegető program sokkal alkalmasabb a LaTeX kimenetének nézegetéséhez, mint a beépített Preview nevű program (ami azért a rossztól még mindig távol áll, de hát az Apple programjait is felül lehet múlni 🙂 ). Miért is? Az egyik hasznos dolog, hogy bizonyos esetekben be lehet állítani, hogy automatikusan töltse be a megváltozott pdf fájlt, és tudja a pdfsync nevű dolgot.

Ezek mik is? Hogyan is lehet őket használni? Haladjunk sorban. A módosított fájlok újratöltése egy hasznos lehetőség, hiszen így lehetővé válik, hogy a Skimre átváltva mindig a legfrissebb változatot lássam. Hogyan kell igénybe venni? Először is a Preferences ablak Sync fülén engedélyezni kell a Check for file changes kapcsolót. Ezután, ha észleli, hogy a megnyitott fájl frissült, megkérdezi, hogy be akarjuk-e tölteni. Azért nem tölti be automatikusan, mert ha a fájlhoz tartoztak nem mentett megjegyzések, kijelölések, akkor azok elvesznek. De a kérdésnél rögtön megjelenik az Auto kapcsoló, amivel a folyamat automatizálható – egészen a Skim leállásáig. Zsír.

Amivel még sok jót lehet kezdeni, az a pdfsync. Ez egy viszonylag modern megközelítés, alapvetően az a célja, hogy bizonyos metaadatokat állítson elő, amivel meg lehet állapítani, hogy a pdf fájl melyik sorához a tex forrás melyik sora tartozik (tudomásom szerint alapvetően pdf fájlokhoz képes szinkronizálni, aminek az egyik oka lehet, hogy az [[http://itexmac.sourceforge.net/pdfsync.html|OSX-specifikus LaTeX-szerkesztő]] számára készült el az elején, OSX-en pedig a ps, illetve dvi fájlokkal nehéz mit kezdeni, a pdf-et viszont gyárilag hihetetlenül kényelmesen kezeli. És ez az oka annak is, hogy a LaTeX-et a pdflatex fordítóval érdemes fordítani… Na mindegy, visszatérve a pdfsynchez, ez a metaadat csak akkor használható, ha mind a szerkesztő, mind a nézegető képes értelmezni az adatokat. Az én esetemben ez fennállt, csak megfelelően be kellett állítani a rendszert.

A Skim beállítása viszonylag egyszerű: az előbb emlegetett Sync lapon vannak a kapcsolódó beállítások. Miután a TeXlipse-et nem ismeri, kénytelen voltam egyedi sablont beállítani. A TeXlipse dokumentáció [[http://texlipse.sourceforge.net/manual/build.html|vonatkozó részei]] alapján létrehoztam egy egyszerű script fájlt (be lehetne írni az egyedi paraméterek közé is, de külön fájlban jobban kezelhető), és a fájl tartalma a következő lett:

java -classpath /Applications/eclipse/plugins/net.sourceforge.texlipse_1.2.2/texlipse.jar net.sourceforge.texlipse.viewer.util.FileLocationClient -p 55000 -f $2 -l $1

Itt a classpath-nál meg kellett adni a telepített texlipse jarját (sok sikert a kikereséséhez a plugin mappából 🙂 ), a FileLocationClient egy hozzáadott érték, a -p után pedig egy portszámot kell megadni, amit a TeXlipse beállítások között is szerepeltetni kell (nyilván ugyanazt a számot 🙂 ).

A Skim pdfsync paraméterei között egyrészt ezt a fájlt kellett megadni, másrészt paraméternek a %line “%file” paramétereket kell megadni – ezek segítségével lehet visszahívni a szerkesztőt. Ha más szerkesztőt akarunk használni, akkor nyilván a szerkesztő dokumentációjából kell kiszedni a dolgokat. De például az Aquamacs különösebb beállítás nélkül is viszi a szolgáltatást.

Mindenesetre nézzük akkor, hogy mit kell beállítani az Eclipse-ben, hogy működjön. Először is a TeXlipse számára kell a Skimet felvenni megjelenítőként – itt viszont nem a szokásos Skim.app/Contents/MacOS/Skim hívást kell megtenni, hanem egy külön script segítségével kell értesíteni, azaz a Skim.app/Contents/SharedSupport/displayline elemet kell megadni futtathatóként a TeXlipse számára, míg paraméterekként a %line %file %texfile értékeket (a leírások ajánlják a %file és a %texfile értékek idézőjelek közé helyezését, mert ekkor szóközt tartalmazó fájlok esetén is működik, de nálam úgy nem működött a rendszer – a Skim nem találta meg a hivatkozott fájlt).

Ja, igen, a beállítás helye: Eclipse Preferences, TeXlipse/Viewer Settings, és itt lehet felvenni egy új nézőprogramot. A Viewer input format értéke pdf, az Inverse search support értéknek a “Viewer runs external command” értéket kell megadni, és be kell jelölni a “Viewer supports forward search” jelölőnégyzetet. És ha ez megvan, akkor a nézőprogramok listája alatt a megfelelő portszámot be kell állítani.

Na, ha ez megvan, akkor lehet tesztelni. A tex fájl elejére még hozzá kell adni a pdfsync package-et, és jöhet a menet.

Sajnos nálam nem sikerült minden esetben futtatni a megoldást: abban az esetben, ha a forrásfájl, a létrehozott pdfsync fájl és a pdf fájl egy mappában volt, nem több fájlból állt a tex projekt, és nem használtam bibtex-et. Szóval sajnos nem csak a gyakorlatban lényegtelen esetekben nem működött, hanem sajnos elég sokszor… De amikor működött, akkor a Command + 4 (win-esek, linuxosok: ctrl+4) billentyűkombinációval az Eclipse-ből a pdf fájl megfelelő részére ugrott, a pdf fájlból pedig a Command+Shift lenyomása mellett kattintva lehetett visszaugrani az Eclipse-be. Sajnálom, hogy ez most nem működik.

Ha valakinek ezt sikerül jobban működésre bírni, esetleg képes megoldani a rendes futtatást, érdekelnek a részletek.

Ma sikerült egy másik problémakörbe belefutnom a LaTeX szerkesztés kapcsán. A gond akkor lép fel, ha megpróbáljuk SVN-en osztani a LaTeX projekt mappát, és különböző mappában vannak a forrásfájlok és az ideiglenes fájlok. Hihetetlenül idegesítő tud lenni, hogy minden egyes fordításkor (amit gyakorlatilag minden mentés triggerel), és hibaüzenetek nagy tömegét kapom, hogy nem tudta átmozgatni az elkészült ideiglenes fájlokat, mert nem sikerült felülírnia a fájlokat – hála annak, hogy az nem került feltöltésre az SVN szerverre.

Az egyedüli lehetséges megoldás az volt, hogy az ideiglenes fájlokat hozzá kellett adni az svn:ignore változóhoz, hogy ne akarja őket egyáltalán feltölteni az SVN szerverre. Ehhez a projekt jobb gombos menüjéből a Team/Add Property… pontja alatt felbukkanó ablakban kellett adatokat megadni: megadtam a paraméter nevét (svn:ignore), beállítottam, hogy a szabály rekurzívan teljesüljön minden erőforrásra, és a következő értékeket adtam meg a beviteli mezőbe:
*.aux
*.bbl
*.blg
*.bst
*.dvi
*.idx
*.lof
*.log
*.toc
temp
tmp
main.synctex.gz

Ez a megoldás megszüntette a problémát, ezzel lehetővé téve, hogy tovább működtessem a rendszert.

Kottaszerkesztés LilyPonddal OSX-en

A LilyPond ugyanaz a kottaszedésben, mint a LaTeX dokumentumszedésben, minden szempontból: tipográfiai szabványoknak és konvencióknak eleget tevő, gyönyörű kimenetet generál egy szöveges formában megadott forrásfájlból. Azonban nincs könnyű dolguk azoknak, akik össze szeretnék házasítani kedvenc Leopardjukkal. Sem a LilyPond grafikus felülete, sem a magyar fejlesztésű, nagyon sokat tudó LilyPondTool nevű jEdit plugin nem működik az OSX legújabb verziója alatt.

Kompromisszumos megoldásként nekem a TeXShop használata jött be. Amit így kapunk, az a PDF-előnézet, az ismerős munkafolyamat és a szintaxiskiemelés. Sajnos a hangról forrásfájlra ugrás nem működik, valamint a generált MIDI-t is kézzel kell megnyitnunk.

  1. Telepítés: Töltsük le innen a 2.12 verziót MacOS X alá, méghozzá a G3, G4, G5 Macs (sic!) változatot, akkor is, ha Intel alapú Mac-ünk van! Szokásos módon tegyük az /Applications könyvtárba a LilyPond.app-ot.
  2. Parancssori támogatás: A PDF-generálás sajnos ékezetes fájlneveknél nem működik, így írtam egy cseles shell scriptet, ami ezt megoldja, és az ideiglenes PostScript fájlt is eltakarítja:

    #! /bin/sh
    dirname=$(dirname "$1")
    basename=$(basename "$1" .ly)
    filename="${dirname}/${basename}"
    tempname="${dirname}/.tmp"
    /Applications/LilyPond.app/Contents/Resources/bin/lilypond -o "${tempname}" "$1"
    if [ -f "${tempname}.pdf" ]; then
    mv "${tempname}.pdf" "${filename}.pdf"
    fi
    if [ -f "${tempname}.midi" ]; then
    mv "${tempname}.midi" "${filename}.midi"
    fi
    rm -f "${tempname}.ps"

    Mentsük el /usr/bin/lilypond néven, és varázsoljuk futtathatóvá:

    sudo chmod +x /usr/bin/lilypond
  3. TexShop támogatás: Mivel a piszkos munkát elvégzi a fenti script, már csak az alábbi hihetetlenül bonyolult TeXShop engine-nel kell bővíteni a repertoárt ([cci]~/Library/TeXShop/Engines/LilyPond.engine[/cci]):

    #! /bin/sh
    echo "Processing..."
    lilypond "$1"
    echo "Done."

    Itt van még egy dummy LilyPond template ([cci]~/Library/TeXShop/Templates/LilyPond.tex[/cci]):
    [cc]
    \version “2.12.2”

    \header {
    title = “”
    composer = “”
    tagline = “”
    }

    \score {
    <>

    \midi {
    }

    \layout {
    }
    }

    \paper {
    }[/cc]

    Ezután nincs más dolgunk, mint hozzátársítani a [cci].ly[/cci] kiterjesztést a TeXShop.app-hoz, és készen is vagyunk.

Még egy dolog. Ha Finale vagy Sibelius kottáinkat át szeretnénk konvertálni LilyPondba, a migrációs folyamat köztes lépéseként a MusicXML formátumot vehetjük igénybe. A Finale alapból tartalmaz MusicXML plugint, a File/MusicXML/Export… paranccsal végezhető el az exportálás. Sibeliushoz külön le kell tölteni a megfelelő plugint, ehhez ez az oldal nyújt segítséget. (A plugin batch mode-ot is támogat, ezt érdemes kihasználni.)

A MusicXML fájlokat a [cci]/Applications/LilyPond.app/Contents/Resources/bin/musicxml2ly[/cci] programmal konvertálhatjuk LilyPondba. Ez nem tökéletes, sajnos még picit bugzik a segédprogram, én főleg a dalszövegek melizmáinál tapasztaltam problémát, úgyhogy ne felejtsük el utólag átnézni az eredményt, és szükség esetén kézzel finomhangolni.

Források

Java kimenet UTF-8 kódolásban – második felvonás

Ezt nem lehet megunni, újra és újra hasonló problémákba ütközök. Most kivételesen nem parancssorban kellett UTF-8 kimenetet generálnom, hanem a Java 6 beépített HTTP szerveréről a kliens felé adott generált válaszban merült fel ugyanez a probléma. A különös az volt, hogy működött, mindenféle konverzió nélkül, tehát teljesen nyugodt voltam, hogy ez a kódrészletet jól írtam meg. Órákba tellet rájönnöm, hogy ez a feltételezés hibás volt.

Ezt nem lehet megunni, újra és újra hasonló problémákba ütközök. Most kivételesen nem parancssorban kellett UTF-8 kimenetet generálnom, hanem a Java 6 beépített HTTP szerveréről a kliens felé adott generált válaszban merült fel ugyanez a probléma. A különös az volt, hogy működött, mindenféle konverzió nélkül, tehát teljesen nyugodt voltam, hogy ez a kódrészletet jól írtam meg. Órákba tellet rájönnöm, hogy ez a feltételezés hibás volt.

A problémakör egyszerű, bár érdekes. Widget-alapú AJAX-os webalkalmazást fejlesztek az önálló laboromhoz, de erről később. A lényeg az, hogy amikor a felhasználó csatlakozik a szerverhez, az egy generált HTML oldallal válaszol, és minden további feladatra JavaScript kódot küld, amit a kliens lefuttat. A javascript kódban a szövegeket előrelátóan Base64 kódolásban vittem át, így ezekkel probléma nem volt. Nem ez volt a helyzet azonban a generált HTML kóddal. Arra lettem figyelmes, hogy a webalkalmazást megváltoztatva a böngészőben nem jelenik meg semmi..

A helyzet felettébb furcsa volt, a generálás során nem merült fel hiba, sőt, a generált kimenet a szerver oldalon jó volt és az átvitel sem dobott kivételt. A kliens oldalon megérkeztek a fejléc sorai, de nem kapott kimenetet. Nem értettem a problémát, hiszen addig jó volt, és azon a részen nem változtattam. Hosszas próbálgatás és debugolás után rájöttem, hogy a hiba megjelenése erősen korrelál azzal a ténnyel, hogy a generált kimenetben szerepel-e ékezetes karakter. Ekkor merült fel bennem, hogy a probléma a kódolással van.

Tehát, a megoldás az, hogy előkeresem a [intlink id=”556″ type=”post”]korábbi cikkemet[/intlink], és az ott szereplő megoldást extrapolálom az én esetemre. Azaz, amikor a HttpExchange kimeneti adatfolyamát lekérem, egy egyszerű PrintStream helyett a következő módon wrappolom, és a böngészőt is értesítem róla, hogy milyen kódolásban kap adatot:


PrintStream out = new PrintStream(arg0.getResponseBody(),true,"UTF-8");
arg0.getResponseHeaders().add("Content-Type","text/html; charset=UTF-8");

A nagyobbik fejtörést az okozta, hogy még így se működött, a hiba ugyanaz maradt, csak annyi változott, hogy a Web dev toolbar már kijelezte az újonann beadott header sort is. Ekkor megakadt a szemem a következő soron:


arg0.sendResponseHeaders(200, text.toCharArray().length);
out.print(text);

Ezzel ugye elküldöm a kliensnek hibakódot (200 = OK), illetve a küldött tartalom hosszát. Ez automatikusan hozzáad egy “Content-Length” sort a header-ekhez. Amikor ezt írtam, jó ötletnek tünt. De! A java belső UCS kódolásában a karakterek száma nem egyezik meg a byte-ok számával a karakterlánc UTF-8 -beli kódolásában. A megoldás: a sendResponseHeaders() metódus második argumentuma legyen 0. Ez azt jelenti, hogy tetszőleges hosszúságú tartalmat küldhetünk el, a bytesorozat végét az jelzi, hogy bezárjuk a kapcsolatot.

Így tényleg működik. csak az érdekesség kedvéért egy rövid idézet a HttpExchange dokumentációjából:

"If the call to sendResponseHeaders() specified a fixed response body length, then the exact number of bytes specified in that call must be written to this stream. If too many bytes are written, then write() will throw an IOException. If too few bytes are written then the stream close() will throw an IOException. In both cases, the exchange is aborted and the underlying TCP connection closed."

Hogy mi ezzel a probléma? Csak az, hogy a dokumentáció szerint kivételt kellett volna kapnom, amikor úgy zárom le, hogy a küldött byte-ok száma nem egyezik az előre megadottal. Ez viszont nem történt meg, hanem egy furcsa, és visszakövethetetlen hibajelenségbe ütköztem. Talán gyorsabban megtalálom a hibát, ha kapok egy értelmes hibaüzenetet.

2D adatszerkezet modellezése Javában

…avagy hogyan lehet pozitív tulajdonság a gyengeség és a lustaság. 🙂

Arról a gyakran előforduló modellről van szó, amikor sorok és oszlopok metszeteiben rácselemek csücsülnek, melyek automatikusan létrejönnek a nekik megfelelő sor-oszlop párra való hivatkozáskor, valamint automatikusan megszűnnek soruk vagy oszlopuk törlése esetén.
A megvalósítás kézenfekvőnek tűnik: a sorokban/oszlopokban egy map-ben tároljuk az oszlopokkal/sorokkal indexelve a rácselemeket.

Valahogy így:
[cc_java]
public class Node {
}

public class Column {
}

public class Row {

protected final Map nodes = new HashMap();

public Map getNodes() {
return nodes;
}

}
[/cc_java]

Mi a gond ezzel?

  • Ha törlünk egy oszlopot, az összes sor map-jében még ott marad a rá mutató referencia, mint kulcs. Ez memory leakhez vezet: a szemétgyűjtő nem tudja eltakarítani a “zombi” oszlopokat és a hozzájuk tartozó, ugyancsak zombi rácselemeket. Ez ráadásul az adatstruktúra perzisztálásánál is problémát jelent.
  • Hogyan s mikor hozzuk létre a rácselemeket? Mindig, amikor létrehozunk egy oszlopot? Ezt jobban lehetne automatizálni, ráadásul ha nem minden rácspontba akarunk elemet tenni, fölöslegesen hozzuk létre ezeket.

Több megoldás is lehetséges, gondolhatnánk elsőre:

  • Gondolkodhatnánk egy címkézett gráfban, és a beépített kollekciók helyett alkalmazhatnánk valamiféle gráfkezelő könyvtárat, pl. a JGraphT-t, mely gondoskodik csúcs törlésekor a hozzá tartozó élekről.
  • Kibővíthetnénk a lista adatszerkezetet eseménykezelőkkel, melyek elem hozzáadásakor és törlésekor végrehajtanak egy megadott kódot.

Azonban a probléma megoldható a Java Collections Framework keretein belül, karöltve természetes kiegészítőjével, a Collections Generic API-val (a Commons Collections generikus változata). Nehogy már egy ilyen általános jelenséget ne tudjunk a megannyi hasznos absztrakciót tartalmazó Java nyelv elemeivel modellezni! A kulcsfogalom a gyenge referencia, mellyel befolyásolhatjuk a garbage collector működését, ezzel rábízva az elárvult elemek törlését.

Javában az alapértelmezett referencia erős (strong), de ezenkívül még létezik a gyenge (weak), lágy (soft) és fantom (phantom) referencia. A weak reference lényege, hogy amennyiben egy objektumra csak ilyenek mutatnak, a szemétgyűjtő teljes lelki nyugalommal eltakarítja. Nekünk pont ez a viselkedés jön kapóra: a map-ben gyenge referenciákat fogunk csak tárolni az oszlopokra/sorokra. (A másik két típus most nem érdekes számunkra, pedig azok is hasznosnak bizonyulhatnak más esetekben.)

A Collections API tartalmazza a WeakHashMap osztályt, mely a HashMap-hez hasonló, azonban kulcsai gyenge referenciák. A WeakHashMap ezeket átlátszóan kezeli, önmagától létrehozva és dereferálva nekünk őket. Így programunkban az automatikus törléshez csak két dolgot kell módosítanunk: HashMap helyett WeakHashMap-et használunk, és a kívánt pillanatban meghívjuk a garbage collectort.

A létrehozás pedig lusta map segítségével történik: a rácselemek példányosítása akkor és csak akkor történik, amikor először hivatkozunk az őket azonosító sor-oszlop párosra. Ehhez is kész eszközt kapunk a kezükbe a Collections Generic Libraryben: a LazyMap-et és az InstantiateFactory-t, melyek a Decorator és a Factory pattern alkalmazásával érik el céljukat.

Imhol a forráskód módosított része. Figyelemreméltó, hogy mennyi minden változott…
[cc_java]
public class Row {

protected final Map nodes = LazyMap.decorate(new WeakHashMap(), new InstantiateFactory(Node.class)); // !

}
[/cc_java]

És egy kis tesztosztály, a kiértékelés a kedves olvasó feladata:
[cc_java]
public class GridTest {

public static void main(String[] args) {
Grid grid = new Grid();
for (int i = 0; i < 2; i++) {
grid.getRows().add(new Row());
grid.getColumns().add(new Column());
}
for (Row row : grid.getRows()) {
for (Column column : grid.getColumns()) {
System.out.println(row.getNodes().get(column));
}
}
grid.getColumns().remove(1);
System.gc(); // !
for (Row row : grid.getRows()) {
for (Column column : row.getNodes().keySet()) {
System.out.println(row.getNodes().get(column));
}
}

}

}
[/cc_java]

Hát ilyen power a Collections Framework, amint már Stampie is [intlink id=”587″ type=”post”]rámutatott[/intlink], ami pedig esetleg hiányzik belőle, azt a Collections Generic pótolja. Mi a tanulság? Használjuk ki a nyelvi lehetőségeket, amelyek rendelkezésünkre állnak!