<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Ondra[sej] Blog &#187; Java</title>
	<atom:link href="http://www.ondrejsykora.com/blog/category/java/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.ondrejsykora.com/blog</link>
	<description></description>
	<lastBuildDate>Sun, 21 Mar 2010 13:17:49 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2</generator>
		<item>
		<title>Rychlost alokací v Javě</title>
		<link>http://www.ondrejsykora.com/blog/2008/01/05/rychlost-alokaci-v-jave/</link>
		<comments>http://www.ondrejsykora.com/blog/2008/01/05/rychlost-alokaci-v-jave/#comments</comments>
		<pubDate>Sat, 05 Jan 2008 00:41:59 +0000</pubDate>
		<dc:creator>ondrasej</dc:creator>
				<category><![CDATA[Java]]></category>

		<guid isPermaLink="false">http://ondra.sykorky.cz/blog/2008/01/05/rychlost-alokaci-v-jave/</guid>
		<description><![CDATA[Když jsem před časem programoval Sudoku solver v Javě, použil jsem jednoduchý, dalo by se říci až naivní, přístup k vytvoření zásobníku pro backtracking. Při řešení jednoduchých sudoku naivní implementace nevadí, protože k backtrackování dochází jen minimálně. Jak to je s (ne)efektivností tohoto řešení doopravdy se ukázalo až na opravdu těžkých zadáních z Minimum Sudoku. [...]]]></description>
			<content:encoded><![CDATA[<p>Když jsem před časem programoval <a href="http://ondra.sykorky.cz/projects/sudoku-solver">Sudoku solver</a> v Javě, použil jsem jednoduchý, dalo by se říci až naivní, přístup k vytvoření zásobníku pro backtracking. Při řešení jednoduchých sudoku naivní implementace nevadí, protože k backtrackování dochází jen minimálně. Jak to je s (ne)efektivností tohoto řešení doopravdy se ukázalo až na opravdu těžkých zadáních z <a href="http://people.csse.uwa.edu.au/gordon/sudokumin.php">Minimum Sudoku</a>.<br />
<span id="more-157"></span><br />
Při tvorbě jsem efektivitu moc neřešil &#8211; testoval jsem hlavně na jednodušších zadáních, kde řešení nacházel poměrně rychle. Když jsem pak chtěl vyzkoušet &#8220;nejtěžší&#8221; variaty z Minimum Sudoku, tak mě doba výpočtu v řádu vteřin trochu překvapila a začal jsem do programu mírně vrtat. Vyvrtané informace shrnují následující odstavce.</p>
<p>Na začátek pár slov o reprezentaci dat a uložení hrací plochy. Ke každému políčku (<code>Field</code>) si algoritmus udržuje tři základní údaje: hodnotu políčka (0-9; 0 znamená nevyplněné políčko) a množinu hodnot, které políčku ještě jde přiřadit (jeden bit pro každou hodnotu). Stav hrací plochy je pak uložen jako dvourozměrné pole políček.</p>
<p>Základní myšlenka řešícího algoritmu spočívá v propagaci možných hodnot a backtrackingu, podobně jako v programování s omezujícími podmínkami. Po přiřazení hodnoty jednomu políčku je tato hodnota zakázána všem ostatním políčkům ve stejné skupině (řádku, sloupci, čtverci). Když v některém políčku zbyde jediná hodnota, pak ji tomuto políčku přiřadí (a provede další propagaci). Pokud jsou v některém poli zakázány všechny možné hodnoty, prohledávání v této větvi končí neúspěchem.<br />
Pokud po skončení propagace hodot jsou některá políčka stále nepřiřazená, algoritmus zkusí do takového pole dosadit jednu z povolených hodnot (to, že políčko zůstalo po propagaci volné znamená, že na základě dostupných informací je do něj možné dosadit více než jednu hodnotu) a provádí backtracking.</p>
<h3>Naivní řešení</h3>
<p>Naivní řešení používá pro uložení zásobníku <code>java.util.Stack&lt;T&gt;</code>. Při každém sestupu při backtrackování se alokuje nová reprezentace hrací plochy a zařadí na zásobník. O staré stavy ze zásobníku, pro které prohledávání selhalo se stará garbage collector. Jednoduché, ale funkční.</p>
<p><code><b>private</b> Stack<field[][]> gameStateStack;</p>
<p><b>private static</b> Field[][] cloneGameState(Field[][] _source) {<br />
&nbsp;&nbsp;Field[][] clone = <b>new</b> Field[GAME_SIZE][];<br />
&nbsp;&nbsp;<b>for</b>(int i=0; i < GAME_SIZE; i++)<br />
&nbsp;&nbsp;&nbsp;&nbsp;clone[i] = <b>new</b> Field[GAME_SIZE];<br />
&nbsp;&nbsp;<b>for</b>(int x=0; x < GAME_SIZE; x++)<br />
&nbsp;&nbsp;&nbsp;&nbsp;<b>for</b>(int y=0; y < GAME_SIZE; y++)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;clone[x][y] = <b>new</b> Field(_source[x][y]);<br />
&nbsp;&nbsp;<b>return</b> clone;<br />
}</p>
<p><b>private void</b> popGameState() {<br />
&nbsp;&nbsp;currentGameState = gameStateStack.pop();<br />
}</p>
<p><b>private void</b> pushGameState() {<br />
&nbsp;&nbsp;Field[][] new_state = cloneGameState(currentGameState);<br />
&nbsp;&nbsp;gameStateStack.push(currentGameState);<br />
&nbsp;&nbsp;currentGameState = new_state;<br />
}<br />
</code></p>
<h3>Zlepšení s předalokováním</h3>
<p>Teď se nad problémem zamyslíme trochu víc. Je snadné si uvědomit, že při řešení Sudoku 9&#215;9 nikdy nebude hloubka zanoření backtrackingu vyšší než 81 &#8211; podle počtu polí na hrací ploše. Ve skutečnosti bude ještě nižší, protože některá pole jsou obsazená již v zadání. 81 je poměrně málo. Dost málo na to, aby šlo všech 81 stavů hrací plochy na zásobníku předalokovat a při backtrackingu do nich jen zkopírovat aktuální stav hry.</p>
<p><code><i>// alokováno při inicializaci</i><br />
<b>private</b> Field[][][] gameStateStack = <b>null</b>;<br />
<b>private int</b> stackPosition = 0;</p>
<p><b>private void</b> cloneGameState() {<br />
&nbsp;&nbsp;Field[][] source = gameStateStack[stackPosition - 1];<br />
&nbsp;&nbsp;Field[][] clone = gameStateStack[stackPosition];<br />
&nbsp;&nbsp;<b>for</b>(int x=0; x < GAME_SIZE; x++)<br />
&nbsp;&nbsp;&nbsp;&nbsp;<b>for</b>(int y=0; y < GAME_SIZE; y++)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;clone[x][y].assign(source[x][y]);<br />
}</p>
<p><b>private void</b> popGameState() {<br />
&nbsp;&nbsp;stackPosition&minus;&minus;;<br />
&nbsp;&nbsp;currentGameState = gameStateStack[stackPosition];<br />
}</p>
<p><b>private void</b> pushGameState() {<br />
&nbsp;&nbsp;stackPosition++;<br />
&nbsp;&nbsp;cloneGameState();<br />
&nbsp;&nbsp;currentGameState = gameStateStack[stackPosition];<br />
}</code></p>
<h3>Výsledky</h3>
<p>Efektivitu obou verzí kódu jsem testoval na následujícím zadání (těžké sudoku se sedmnácti podporami):<br />
<code><br />
-&nbsp;-&nbsp;-|-&nbsp;-&nbsp;-|-&nbsp;1&nbsp;3<br />
-&nbsp;-&nbsp;-|-&nbsp;3&nbsp;-|-&nbsp;8&nbsp;-<br />
-&nbsp;7&nbsp;-|-&nbsp;-&nbsp;-|-&nbsp;-&nbsp;-<br />
-----+-----+-----<br />
-&nbsp;-&nbsp;-|2&nbsp;-&nbsp;6|-&nbsp;-&nbsp;-<br />
-&nbsp;3&nbsp;-|-&nbsp;-&nbsp;-|9&nbsp;-&nbsp;-<br />
-&nbsp;-&nbsp;-|-&nbsp;1&nbsp;-|-&nbsp;-&nbsp;-<br />
-----+-----+-----<br />
6&nbsp;-&nbsp;-|5&nbsp;-&nbsp;-|2&nbsp;-&nbsp;4<br />
-&nbsp;-&nbsp;-|4&nbsp;-&nbsp;-|7&nbsp;-&nbsp;-<br />
1&nbsp;-&nbsp;-|-&nbsp;-&nbsp;-|-&nbsp;-&nbsp;-</code></p>
<p>Naměřené časy shrnuje tabulka níže. Jde o průměrné časy ze 100 pokusů, aby měl JVM dost času se zahřát. Při řešení jednoho zadání solver provedl vždy 174844 rekurzivních sestupů a tedy i &#8220;alokací&#8221; nového pole.</p>
<table>
<tr>
<td><b>Naivní verze:</b></td>
<td>3140,3 ms (&plusmn; 30 ms)</td>
</tr>
<tr>
<td><b>Verze bez alokací:</b></td>
<td>2940,3 ms (&plusmn; 35 ms)</td>
</tr>
</table>
<p>Časy jsem měřil na počítači s procesorem Intel Core2Duo 1800MHz, spoustou paměti, Gentoo Linuxem a Javou 1.6 (nicméně výsledky na Javě 1.5 jsou obdobné). Rozdíl je sice viditelný (cca 7%), přesto je o dost menší, než jsem čekal. Je vidět, že se v Sunu opravdu snaží o co nejefektivnější JVM a na jejich tvrzení, že nemá smysl optimalizovat na počet alokací opravdu něco bude <img src='http://www.ondrejsykora.com/blog/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://www.ondrejsykora.com/blog/2008/01/05/rychlost-alokaci-v-jave/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Validace hodnot v JTable</title>
		<link>http://www.ondrejsykora.com/blog/2006/06/06/validace-hodnot-v-jtable/</link>
		<comments>http://www.ondrejsykora.com/blog/2006/06/06/validace-hodnot-v-jtable/#comments</comments>
		<pubDate>Tue, 06 Jun 2006 21:05:23 +0000</pubDate>
		<dc:creator>ondrasej</dc:creator>
				<category><![CDATA[Java]]></category>

		<guid isPermaLink="false">http://ondra.sykorky.cz/blog/2006/06/06/validace-hodnot-v-jtable/</guid>
		<description><![CDATA[Při práci s daty pomocí Swingovské komponenty JTable je občas potřeba kontrolovat data, která jsou pomocí ní do tabulky ukládána a pokud vkládaná hodnota není v pořádku, uživatele včas upozornit. Pokud jste již s JTable někdy pracovali, určitě víte, že to není nic snadného. Pro snadnou práci s tabulkami rozděluje Swing podle principů MVC práci [...]]]></description>
			<content:encoded><![CDATA[<p>Při práci s daty pomocí Swingovské komponenty JTable je občas potřeba kontrolovat data, která jsou pomocí ní do tabulky ukládána a pokud vkládaná hodnota není v pořádku, uživatele včas upozornit. Pokud  jste již  s  JTable někdy pracovali, určitě víte, že to není nic snadného.<span id="more-151"></span></p>
<p>Pro snadnou práci s tabulkami rozděluje Swing podle principů MVC práci s tabulkou na komponentu, která ji zobrazuje (<a title="javax.swing.JTable" href="http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/JTable.html">JTable</a>) a na její model (<a title="javax.swing.table.TableModel" href="http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/table/TableModel.html">TableModel</a>). Ten se stará o správu dat a jejich zpřístupnění komponentě <a title="javax.swing.JTable" href="http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/JTable.html">JTable</a> a také má na starosti změnu dat v případě, že uživatel provede editaci pomocí <a title="javax.swing.JTable" href="http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/JTable.html">JTable</a>. Toto předávání dat od uživatele se děje pomocí metody <a title="javax.swing.table.setValueAt(Object,int,int)" href="http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/table/TableModel.html#setValueAt(java.lang.Object,%20int,%20int)">setValueAt</a>.</p>
<p>Tento způsob práce s tabulkami v každém případě práci zjednodušuje, zdá se ale, že na některé možné problémy autoři pozapomněli, nebo se rozhodli je neřešit. Tím hlavním, který nás při práci na Guidovi v poslední době trápil byla kontrola dat zadávaných do tabulky. Pokud je správáná libovolná hodnota, kterou je možné v editoru zadat, je vše v pořádku a není nutné nic řešit. Jak ale zařídit, aby do tabulky šlo zadat (téměř) libovolný řetězec, který ale musí být různý od všech ostatních hodnot ve stejném sloupci?</p>
<p>Standardní &#8211; a podle mě i správné z hlediska návrhu aplikace &#8211; by bylo provádět takovéto kontroly v modelu tabulky. Tím by byla zajištěna funkčnost kontrol automaticky ve všech komponentách, které s modelem pracují bez dalších starostí programátora. Možností jak takové chování zajistit je několik:</p>
<ul>
<li>setValueAt bude vracet boolean podle toho, jestli hodnotu přijala, nebo ne. Pokud hodnotu odmítne, <a title="javax.swing.JTable" href="http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/JTable.html">JTable</a> snadno pozná, že zadaná hodnota nebyla správná a nechá uživatele ji opravit.</li>
<li>setValueAt bude v případě nesprávné hodnoty vyhazovat výjimku speciálního typu, kterou <a title="javax.swing.JTable" href="http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/JTable.html">JTable</a> odchytí a zareaguje na ni. Toto řešení bych považoval za nejvhodnější, protože nejlépe odpovídá principům programování v jazyce Java.</li>
<li><a title="javax.swing.table.TableModel" href="http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/table/TableModel.html">TableModel</a> bude obsahovat metodu typu
<pre><strong>boolean</strong> isValid(Object value, <strong>int</strong> row, <strong>int</strong> column)</pre>
<p>která bude kontrolovat, zda hodnota, kterou uživatel zadává do tabulky je platná. V takovém případě ale stejně bude nutné provádět dodatečné kontroly i v <a title="javax.swing.table.setValueAt(Object,int,int)" href="http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/table/TableModel.html#setValueAt(java.lang.Object,%20int,%20int)">setValueAt</a> a tím je toto řešení nejméně vhodné.</li>
</ul>
<p>Ve skutečnosti ale není použito ani jedno z nich, &#8220;návratová hodnota&#8221; <a title="javax.swing.table.setValueAt(Object,int,int)" href="http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/table/TableModel.html#setValueAt(java.lang.Object,%20int,%20int)">setValueAt</a> je typu void a podle definice interface <a title="javax.swing.table.TableModel" href="http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/table/TableModel.html">TableModel</a> nesmí vyhazovat výjimky (mimo <a title="java.lang.RuntimeException" href="http://java.sun.com/j2se/1.5.0/docs/api/java/lang/RuntimeException.html">RuntimeException</a> a odvozených, které není nutné deklarovat v hlavičce metody) &#8211; a ani jejich výskyt <a title="javax.swing.JTable" href="http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/JTable.html">JTable</a> neošetřuje.</p>
<h3>Kontroly v modelu</h3>
<p>Pokud chce programátor z modelu tabulky zabránit ukončení editace a nastavení hodnoty, stačí uvnitř <a title="javax.swing.table.setValueAt(Object,int,int)" href="http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/table/TableModel.html#setValueAt(java.lang.Object,%20int,%20int)">setValueAt</a> vyhodit výjimku odvozenou od <a title="java.lang.RuntimeException" href="http://java.sun.com/j2se/1.5.0/docs/api/java/lang/RuntimeException.html">RuntimeException</a>. Ta projde kódem <a title="javax.swing.JTable" href="http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/JTable.html">JTable</a> bez  zachycení  a defacto ukončí zpracování události, při které byla  <a title="javax.swing.table.setValueAt(Object,int,int)" href="http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/table/TableModel.html#setValueAt(java.lang.Object,%20int,%20int)">setValueAt</a> volána. Díky tomu se hodnota do tabulky nenastaví a editor zůstane aktivní. Zatím jsme si nevšimli, že by tímto způsobem docházelo k nějakým defektům v modelu nebo v <a title="javax.swing.JTable" href="http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/JTable.html">JTable</a> &#8211; ale co není, může být.</p>
<p>Určitou vadou na kráse je <em>stacktrace výjimky vypsaný na konzoli</em>, o který se postará EventDispatchThread a který jednak může zbytečně děsit neinformvané uživatele a navíc působí dost neprofesionálně. Druhou nepříjemnou vlastností je to, že <em>na editoru není žádným způsobem poznat, že došlo k chybě</em> a hodnota nebyla tabulkou přijata. Všimněte si, že pokud používáte standardní editor pro editaci sloupčeku s číselnými hodnotami a zadáte nesmyslnou hodnotu, tak se kolem editoru objeví červený rámeček signalizující, že něco není dobře. A všimněte si také, že tímto způsobem žádný rámeček nevznikne.</p>
<h3>Kontroly v editoru</h3>
<p>Pátrání po původu červeného rámčeku mě dovedlo až do zdrojáku samotné <a title="javax.swing.JTable" href="http://java.sun.com/j2se/1.5.0/docs/api/">JTable</a>. Autoři od <a title="javax.swing.DefaultCellEditor" href="http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/DefaultCellEditor.html">DefaultCellEditoru</a> odvodili speciální editory postavené na textovém editačním poli, které umožńují editovat libovolnou hodnotu, pokud její třída obsahuje konstruktor vytvářející nové hodnoty z řetězců. Pokud novou hodnotu vytvořit nelze (při vytváření dojde k výjimce), nastaví si editor červený rámeček a pokračuje dál, jako kdyby se nic nestalo.</p>
<p>Ukazuje se, že jediný způsob, jak dosáhnout podobného efektu je vytvoření vlastního <a title="javax.swing.table.TableCellEditor" href="http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/table/TableCellEditor.html">TableCellEditoru</a>, který bude provádět kontroly před ukončením editace podobně, jako to dělá generický editor od autorů <a title="javax.swing.JTable" href="http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/JTable.html">JTable</a> při konverzi z řetězce. Tento generický editor bohužel je v privátní vnořená třída v <em>JTable.java</em> a tak ho není možné recyklovat a vylepšit o další kontroly. Naštěstí množství změn není tak velké, aby podle nich nešlo napsat vlastní editor fungující stejným způsobem.</p>
<p>Protože se jedná o obecný princip, je samotná kontrola z editoru vydělená do objektu implementujícího následující interfce. Metoda <em>isTableCellValueValid</em> vrací true, pokud je hodnota v zadaném sloupci a řádku přijatelná, jinak vrátí false.</p>
<pre>
<strong>public interface</strong> TableCellValidator {
    <strong>boolean</strong> isTableCellValueValid(
                        Object value, <strong>int</strong> row, <strong>int</strong> column);
}
</pre>
<p>Editor je upravený tak, aby při ukončení editace zkonvertovanou hodnotu předložil validátoru ke kontrole a dále jí předával jen po odslouhlasení.</p>
<pre>
<strong>public class</strong> ValidatingTableCellEditor
                            <strong>extends</strong> DefaultCellEditor {
    <strong>private</strong> Object value;
    <strong>private int</strong> editedColumn;
    <strong>private int</strong> editedRow;
    <strong>private</strong> TableCellValidator validator;

    <em>// ... redakčně kráceno ...</em>

    <strong>public</strong> Component getTableCellEditorComponent(
                JTable table, Object value,
                <strong>boolean</strong> isSelected, <strong>int</strong> row, <strong>int</strong> column) {
        <strong>this</strong>.value = value;
        <strong>this</strong>.editedColumn = column;
        <strong>this</strong>.editedRow = row;
        <em>// nastav výchozí (černou) barvu rámečku </em>
        ((JComponent)getComponent()).setBorder(
                    new LineBorder(Color.BLACK));
        <em>// ...</em>
        <strong>return super</strong>.getTableCellEditorComponent(
                            table, value, isSelected,
                            row, column);
    }

    <strong>public boolean</strong> stopCellEditing() {
        String str_val = (String)<strong>super</strong>.getCellEditorValue();
        Object val = <strong>null</strong>;

        <em>// ... zkovertuj str_val na val ...</em>
        <strong>if</strong>(validator != <strong>null</strong>
                &#038;&#038; !validator.isTableCellValueValid(
                            val, editedRow, editedColumn)) {
            validationFailed();
            <strong>return false</strong>;
        }
        <strong>this</strong>.value = val;
        <strong>return super</strong>.stopCellEditing();
    }

    <strong>protected void</strong> validationFailed() {
        ((JComponent)getComponent()).setBorder(
                    <strong>new</strong> LineBorder(Color.RED));
    }
}</pre>
<p>Takové řešení má velkou výhodu v tom, že uživatel díky červenému orámování jasně vidí, že zadaná hodnota nebyla přijata a také nejsou vypisovány žádné chybové hlášky na konzoli. Výrazná nevýhoda tohoto přístupu naopak je v tom, že <b>je nutné se explicitně postarat o to, aby se editor provádějící kontroly používal správným způsobem ve všech tabulkových komponentách, které s modelem pracují</b>, nehledě na to, že hodnoty do tabulky nemusí být vždy zadávané přes JTable a jeji cell editor.</p>
<p>Správné řešení (ve smyslu nejlepší, které znám) je zkombinovat oba dva přístupy a uživatele blokovat jak při editaci pomocí editoru, tak, pokud si nenechá říct, na něj vyhběhnout s <a href="http://java.sun.com/j2se/1.5.0/docs/api/java/lang/RuntimeException.html" title="java.lang.RuntimeException">RuntimeException</a> a on už si to rád rozmyslí. Pokud bude obojí provedeno dost důsledně, bude výsledkem jak konzistentní uživatelské rozhraní, tak bezpečné zázemí objektů pro správu dat.</p>
<p>Poud byste měli zájem o zdrojové kódy editoru, který používáme, napiště mi na <a href="mailto:ondrasejATcentrumDOTcz">ondrasejATcentrumDOTcz</a> a domluvíme se <img src='http://www.ondrejsykora.com/blog/wp-includes/images/smilies/icon_wink.gif' alt=';-)' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://www.ondrejsykora.com/blog/2006/06/06/validace-hodnot-v-jtable/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

