В начало!  
Сделай закладку этой страницы в Digg Сделай закладку этой страницы в Del.icoi.us Сделай закладку этой страницы в Slashdot Сделай закладку этой страницы в Technorati




Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285

Warning: Division by zero in /home/sunadmin/developers.sun.ru/docs/mambots/content/geshibot/geshi.class.php on line 2285
Возможности Java: API для работы с исходными кодами
Автор Seema Richard, Deepa Sobhana   
18.01.2009 г.
Вы знаете как произвести статический анализ кода, наподобие того, что делают Checkstyle или FindBugs? А как в интегрированных средах, таких как NetBeans и Eclipse, реализована поддержка рефакторинга и средства быстрого анализа кода?

Во-многих случаях интегрированные среды и инструменты используют свой внутренний API для синтаксического разбора исходного кода и представления его в стандартной древовидной структуре, абстрактном синтаксическом дереве (Abstract Syntax Tree), которое в дальнейшем используется для анализа элементов исзодного кода.

У нас есть хорошая новость! Начиная с Java SE 6, появился стандартный API, который позволяет выполнить не только этот, но и многие другие задачи, связанные с работой с исходным кодом, а именно:

В этой статье мы расскажем об этих возможностях и продемонстрируем их на примере небольшого приложения, позволяющего проверить набор правил по тому как надо писать Java код в заданном наборе входных файлов. Наше приложение не просто печатает сообщения о нарушении правил, но также и указывает местонахождение некорректного фрагмента кода. В качестве правила для проверки мы используем следующее:

  • класс, переопределяющий equals(), должен также переопределять hashcode() (с правильной сигнатурой метода)
Класс TestClass, код которого приведен ниже, не удовлетворяет этому правилу и не переопределяет hashcode(). Мы будем использовать его далее для демонстрации возможностей нового API.
  1. public class TestClass implements Serializable {
  2. int num;
  3.  
  4. @Override
  5. public boolean equals(Object obj) {
  6. if (this == obj)
  7. return true;
  8. if ((obj == null) || (obj.getClass() != this.getClass()))
  9. return false;
  10. TestClass test = (TestClass) obj;
  11. return num == test.num;
  12. }
  13. }
 

Вызов компилятора из кода программы: Java Compiler API

Все знают как использовать javac для компиляции исходного кода на Java в набор .class файлов. Зачем нужен API для компилятора? Ответ прост. Возможность программного вызова компилятора позволяет использовать компилятор в Ваших собственных программах и библиотеках на Java, то есть компиляция может быть одним из сервисов уровня приложения. Для примера, вот пара типовых сценариев применения:


  • Программный интерфейс к компилятору позволяет серверам приложений (GlassFish и т.п.) минимизировать время развертывания приложений, избегая, например, необходимости использовать внешний компилятор для компиляции исходного кода сервлета, который был сгененриров из JSP страницы.

  • Инструменты разработки, такие как интегрированные среды (Netbeans и т.п.), анализаторы кода (FindBugs и т.п.), системы управления сборкой проекта (ant, и другие) могут вызывать компилятор напрямую и значительно снизить временные затраты на компиляцию.

Классы, предоставляющие доступ к компилятору, находятся в пакете javax.tools. Класс ToolProvider позволяет получить экземпляр объекта реализующего интерфейс JavaCompiler с помощью метода getSystemJavaCompiler().  Экземпляр компилятора позволяет создавать задания на компиляцию, которые, собственно, и выполняют компиляцию. Набор файлов для компиляции передается через параметры задачи на компиляцию. Для этого используется абстракция файлового менеджера, JavaFileManager, позволяющая получать исходный код из различных источников - файловой системы, базы данных, оперативной памяти и т.д. В нашем примере мы воспользуемся StandardFileManager, реализацию файлового менеджера основанную на java.io.File. Получить экземпляр стандартного файлового менеджера можно с помощью метода getStandardFileManager() у JavaCompiler


Следующий фрагмент кода демонстрирует эти шаги:

  1. //Получаем экземпляр JavaCompiler
  2. JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
  3.  
  4. //Получаем стандартный файловый менеджер
  5. StandardJavaFileManager fileManager = compiler.
  6. getStandardFileManager(null, null, null);
  7. //Получаем список файлов для компиляции
  8. //(нам нужен единственный файл, TestClass.java)
  9. Iterable<? extends JavaFileObject> compilationUnits1 =
  10. fileManager.getJavaFileObjectsFromFiles("TestClass.java");
 

Методу getStandardFileManager() можно передать объект для получения диагностических сообщений о возникших "не фатальных" ошибках. В нашем случае мы игнорируем диагностические сообщения и поэтому передаем null. За подробной информацией о других параметрах рекомендуес заглянуть в Java 6 API. 


В результате работы метода getJavaFileObjectsfromFiles() мы получаем набор объектов типа JavaFileObject, которые соответствуют входным файлам с исходным кодом.


Следующий шаг - создать задание на компиляцию. Для этого воспользуемся методом getTask() у JavaCompiler. Это еще не запуск самой компиляции, для запуска надо вызвать метод call() у созданного объекта CompilationTask. Следующий фрагмент кода демонстрирует создание задания и запуск компиляции:

  1. //Создаем задание
  2. CompilationTask task = compiler.getTask(null, fileManager, null,
  3. null, null, compilationUnits1);
  4. //Запускаем процесс компиляции
  5. task.call();
 

Если компиляция завершится без ошибок, то в директории появится файл TestClass.class.

Встраиваемые обработчики аннотаций: Pluggable Annotation Processing API

В Java SE 5.0 появилась поддержка для добавления и обработки метаинформации в ввиде аннотаций для элементов языка, таких как классы, поля, методы, и т.д. Аннотации обычно используются внешними инструментами или средой исполнения для разных полезных задач - контроль над поведением приложения, генерация кода, и т.п. Обработка аннотаций возможно как на этапе компиляции, так и на этапе исполнения аннотированного кода. 


Обработчики аннотаций - это вспомогательные модули, которые динамически встраиваются в компилятор для анализа исходного кода и обработки аннотаций в этом коде. Обработчики аннотаций могут использовать метаинформацию для выполнения множества разных задач, например:


  • Аннотации можно использовать для генерации файлов дескрипторов для описания схемы развертывания приложения, таких как persistence.xml или ejb-jar.xml, используемых в технологии EJB.

  • Обработчики аннотаций могут использовать метаинформацию для генерации кода. Например, обработчик может сгенерировать интерфейсы Home и Remote для аннотированной компоненты EJB.

  • Аннотации можно использовать для проверки корректности кода или развертываемых объектов.

В Java 5.0 для обработки аннотаций можно воспользоваться специальной утилитой apt (Annotation Processing Tool) и связанным с ним программным интерфейсом на основе reflection (com.sun.mirror.*). Утилита APT выполняет подходящие обработчики аннотаций на заданном наборе фвйлов с кодом на Java. Программный интерфейс предоставляет доступ к информации об аннотациях на этапе компиляции в режиме только для чтения. Самый главный минус apt - это не стандартная утилита, то есть это решение специфичное для
JDK от Sun Microsystems.


В Java SE 6 появилась новая возможность - встраиваемые обработчики аннотаций (Pluggable Annotation Processing), позволяющая стандартным способом создавать свои обработчики аннотаций. Обработчики могут быть добавлены в javac динамически и могут работать с о всем множеством аннотаций, которые используются во входных файлах.


Программный интерфейс разделен на две части: определение и взаимодействие с обработчиками событий (пакет javax.annotation.processing) и моделирование языка Java (пакет javax.lang.model).

Пишем свой обработчик аннотаций

В этом разделе мы объясним как написать свой обработчик аннотаций и как добавить его в задание на компиляцию. Наш обработчик аннотаций расширяет AbstractProcessor (реализующий интерфейс Processor) и предоставляет свою реализацию метода process().


Класс обработчика содержит две аннотации на уровне класа - @SupportedAnnotationTypes и @SupportedSourceVersion. Аннотация SupportedSourceVersion используется для указания версии исходного кода, которую поддерживает обработчик аннотаций. Аннотация SupportedAnnotationTypes используется для задания списка аннотаций, в которых заинтересован этот обработчик. Например, @SupportedAnnotationTypes ("javax.persistence.*") означает, что обработчик заинтересован в обработке аннотаций связанных с Java Persistence API.


Отметим, что если список аннотаций для обработки задан как @SupportedAnnotationTypes("*"), то обработчик аннотаций будет вызван даже если в исходном коде аннотации не используются. Такой подход позволяет возспользоваться интерфейсами для моделирования и работы с абстрактным синтаксическим деревом для обработки неразмеченного исходного кода. Используя эти программные интерфесы можно получить много полезной информации о модификаторах, полях, методах и т.д.


Вот пример кода обработчика аннотаций:

  1. @SupportedSourceVersion(SourceVersion.RELEASE_6)
  2. @SupportedAnnotationTypes("*")
  3. public class CodeAnalyzerProcessor extends AbstractProcessor {
  4. @Override
  5. public boolean process(Set<? extends TypeElement> annotations,
  6. RoundEnvironment roundEnvironment) {
  7. for (Element e : roundEnvironment.getRootElements()) {
  8. System.out.println("Element is "+ e.getSimpleName());
  9. // Вставьте сюда код для обработки коневых элементов дерева
  10. }
  11. return true;
  12. }
  13. }
  14.  
 

Отбор обработчиков аннотаций для вызова происходит на основе того, какие аннотации есть в коде, какие обработчики доступны и какие типы аннотаций они поддерживают. Обработка аннотаций может быть многопроходной. Например, на первом проходе обрабатывается исходный код, во время второго прохода происходит обработка результата полученного после первого прохода, и т.д.


Реализация обработчика аннотаций должна перегружать метод process() у AbstractProcessor, который получает два аргумента:

  1. Множество аннотаций (TypeElements) найденных в исходном коде.
  2. Информацию о текущем проходе по обработке аннотаций, представленную объектом RoundEnvironment.

Если обработчик выполнил обработку аннотаций данного типа, то его метод  process() должен вернуть true - это предотвратит вызов других обработчиков для этого множества аннотаций. Иначе, если process() вернет false, будет вызван следующий подходящий обработчик (если он есть). 

Добавляем обработчик аннотаций

Реализовав свой обработчик аннотаций, его необходимо встроить в процесс компиляции. Обработчик может вызываться утилитой командной строки javac или через программный интерфейс к компилятору.


Утилита javac позволяет указать дополнительные обработчики, используя опцию --processor, которой нужно передать полное имя обработчика. Вот пример вызова javac c указанием обработчика аннотаций:

  1. javac -processor demo.codeanalyzer.CodeAnalyzerProcessor TestClass.java
 


Здесь CodeAnalyzerProcessor - это обработчик аннотаций и TestClass - это входной файл. javac будет искать класс CodeAnalyzerProcessor в текущем пути поиска классов (classpath).


Ниже приведен модифицированный фрагмент кода, вызывающий компилятор с помощью API, который использует наш обработчик аннотаций. Для добавления одного или нескольких обработчиков аннотаций в задание на компиляцию можно воспользоваться методом  setProcessors() у класса CompilationTask. Добавлять обработчики необходимо до того как вы вызвали метод call(). Обратите внимание, что обработка аннотаций происходит до фактической компиляции. Но если код компилируется с ошибками, то и обработка аннотаций не будет произведена.

  1. CompilationTask task = compiler.getTask(null, fileManager, null,
  2. null, null, compilationUnits1);
  3. //Создаем список для хранения обработчиков аннотаций
  4. LinkedList<AbstractProcessor> processors = new LinkedList<AbstractProcessor>();
  5.  
  6. //Запослняем список нашими обработчиками
  7. processors.add(new CodeAnalyzerProcessor());
  8.  
  9. //Добавляем обработчики в задание на компиляцию
  10. task.setProcessors(processors);
  11.  
  12. //Запускаем компиляцию
  13. task.call();
 

Если Вы запустите этот фрагмент кода, то при обработке файла TestClass.java сработает обработчик аннотаций и напечатает "TestClass."

Работаем с абстрактным синтаксическим деревом: Compiler Tree API

Абстрактное синтаксическое дерево (AST) - это представление кода на Java в виде древовидной структуры, узлами которой являются конструкции языка программирования Java или поддеревья (Tree) из них состоящие. Например, классу Java соответствует ClassTree, декларация метода представляется с помощью MethodTree, декларация переменной - VariableTree, аннотации - AnnotationTree, и т.д. Доступ к этой структуре возможен только в режиме чтения.


Программный интерфейс для работы с абстрактным синтаксическим деревом (AST) предоставляет ряд вспомогательных классов для выполнения операций над деревом - такие как, например, TreeVisitor и TreeScanner. TreeVisitor удобен для проведения более глубокого анализа исходного кода - он позволяет обойти все узлы дерева и собрать необходимую информацию о полях, методах, аннотациях и других эллементах классов. Реализации TreeVisitor основаны на едином шаблоне - при посещении узла дерева вызывается метод типа visitX(), наиболее подходящий типу посещенного узла (например, visitClass() или visitMethod()). 


По умолчанию доступны 3 реализации TreeVisitor - SimpleTreeVisitor, TreePathScanner, и TreeScanner. Наше демонстрационное приложение использует TreePathScanner для извлечения необходимой информации из файла с кодом. TreePathScanner - это TreeVisitor который позволяет в любой момент получить путь от текущего узла к корню дерева. Для запуска процесса обхода дерева необходимо вызвать метод scan() у класса TreePathScanner. Для того чтобы добавить правила по работе с узлами заданного типа необходимо реализовать соответствуюий метод visitX(). Не забудьте вызвать super.visitX() из переопределяемого метода! Это позволит продолжить обход потомков текущего узла.

Вот пример, иллюстрирующий использование класса TreePathScanner:
  1. public class CodeAnalyzerTreeVisitor extends TreePathScanner<Object, Trees> {
  2. @Override
  3. public Object visitClass(ClassTree classTree, Trees trees) {
  4. ---- ваш код ----
  5. return super.visitClass(classTree, trees);
  6. }
  7. @Override
  8. public Object visitMethod(MethodTree methodTree, Trees trees) {
  9. ---- ваш код ----
  10. return super.visitMethod(methodTree, trees);
  11. }
  12. }
 

Методы visitX() получают два аргумента - дерево, соответствующее текущему узлу (с типом ClassTree для узла типа класс, MethodTree для узла типа метод и т.п.), и объекта типа Trees. Класс Trees предоставляет набор вспомогательных методов для получения информации о путях в дереве и выступает в качестве посредника между JSR 269 и Compiler Tree API. 


В нашем примере есть только один корневой элемент, который соответствует самом классу TreeClass:

  1. CodeAnalyzerTreeVisitor visitor = new CodeAnalyzerTreeVisitor();
  2.  
  3. @Override
  4. public void init(ProcessingEnvironment pe) {
  5. super.init(pe);
  6. trees = Trees.instance(pe);
  7. }
  8. for (Element e : roundEnvironment.getRootElements()) {
  9. TreePath tp = trees.getPath(e);
  10. //запускаем обход
  11. visitor.scan(tp, trees);
  12. }
 

Для того чтобы продемонстрировать как можно собирать информацию при таком обходе, рассмотрим реализацию метода visitClass().
Этот метод вызывается всякий раз, когда в анализируемом дереве (AST) встречается узел соответствующий классу, интерфейсу или enum.
Если бы мы хотели собирать информацию о переменных, то нам следовало бы переопределить метод visitVariable(), если о методах - то visitMethod(), и т.п.
  1. @Override
  2. public Object visitClass(ClassTree classTree, Trees trees) {
  3. //Сохраняем информацию о посещенном классе в модель
  4. JavaClassInfo clazzInfo = new JavaClassInfo();
  5.  
  6. //Получаем путь к текущему узлу
  7. TreePath path = getCurrentPath();
  8.  
  9. //Получем описание типа элемента, который соответствует этому классу
  10. TypeElement e = (TypeElement) trees.getElement(path);
  11.  
  12. //Запоминаем полное имя класса в модели
  13. clazzInfo.setName(e.getQualifiedName().toString());
  14.  
  15. //Запоминаем информацию о предках
  16. clazzInfo.setNameOfSuperClass(e.getSuperclass().toString());
  17.  
  18. //Запоминаем информацию о реализованных интерфейсах
  19. for (TypeMirror mirror : e.getInterfaces()) {
  20. clazzInfo.addNameOfInterface(mirror.toString());
  21. }
  22. return super.visitClass(classTree, trees);
  23. }
 

Класс JavaClassInfo, который здесь используется, - это класс модели, где мы храним собранную информацию об анализируемом коде. После выполнения обхода дерева с таким методом visitClass() в модели будет собрана информация о полном имени класса, имени его класса предка, реализованных интерфейсах, и т.д. Эта информация используется далее для проверки анализируемого кода.

Задаем информацию о месте в исходном коде

Про показали как получать информацию об элементах дерава AST и сохранять ее в модели класса, метода или поля. С помощью этой информации можно проверить соблюдаются ли соглашения по написанию кода, соответствует ли код спецификации, и т.п.
Это здорово, но как сообщить о найденных ошибках пользователям, так чтобы они смогли их легко исправить? Необходимо сообщать пользователю о конкретном месте в коде, где обнаружена ошибка.


Информация о позициях всех узлов дерева AST доступна с помощью объекта SourcePositions. Объект предоставляет информацию о начале и конце блока кода в исходном файле, который соответствует данному элементу дерева - ClassTree, MethodTree, FieldTree, и т.п. Позиция определяется как отступ в числе символов от начала файла, первый симвой имеет отступ 0.


Следующий фрагмент кода демострирует применение этих фозможностей, получая информацию о местонахождении заданного объекта Tree в компилируемом файле:

  1. public static LocationInfo getLocationInfo(Trees trees,
  2. TreePath path, Tree tree) {
  3. LocationInfo locationInfo = new LocationInfo();
  4. SourcePositions sourcePosition = trees.getSourcePositions();
  5. long startPosition = sourcePosition.
  6. getStartPosition(path.getCompilationUnit(), tree);
  7. locationInfo.setStartOffset((int) startPosition);
  8. return locationInfo;
  9. }
 

Однако, если мы хотим указать положение более детально - например, указать точно на имя класса или метода, то описанного метода недостаточно. Но можно поискать в анализируемом файле, как в строке. Для доступа к текстовому содержимому анализируемого файла можно воспользоваться JavaFileObject, как в следующем примере:

  1. //Get the compilation unit tree from the tree path
  2. CompilationUnitTree compileTree = treePath.getCompilationUnit();
  3.  
  4. //Get the java source file which is being processed
  5. JavaFileObject file = compileTree.getSourceFile();
  6.  
  7. // Extract the char content of the file into a string
  8. String javaFile = file.getCharContent(true).toString();
  9.  
  10. //Convert the java file content to a character buffer
  11. CharBuffer charBuffer = CharBuffer.wrap (javaFile.toCharArray());
 

Следующий фрагмент кода определяет позиции имен классов в исходном коде с помощью java.util.regex.Pattern и java.util.regex.Matcher. Matcher производит поиск первого вхождения токена совпадающего с именем класса в символьном буффере (построенном с помощью java.nio.CharBuffer), начав с первола символа блока соответствующего узлу класса в дереве AST.

  1. LocationInfo clazzNameLoc = (LocationInfo) clazzInfo.getLocationInfo();
  2. int startIndex = clazzNameLoc.getStartOffset();
  3. int endIndex = -1;
  4. if (startIndex >= 0) {
  5. String strToSearch = buffer.subSequence(startIndex, buffer.length()).toString();
  6. Pattern p = Pattern.compile(clazzName);
  7. Matcher matcher = p.matcher(strToSearch);
  8. matcher.find();
  9. startIndex = matcher.start() + startIndex;
  10. endIndex = startIndex + clazzName.length();
  11. }
  12. clazzNameLoc.setStartOffset(startIndex);
  13. clazzNameLoc.setEndOffset(endIndex);
  14. clazzNameLoc.setLineNumber(compileTree.getLineMap().getLineNumber(startIndex));
 

Еще один полезный объект в API для работы с абстрактным синтаксическим деревом - LineMap. Этот класс предоставляет возможность связывать позиции в виде отступов и номера строк. Для получения номера строки по отступу можно воспользоваться методом getLineMap() у CompilationUnitTree.

Проверяем соблюдение правил в исходном коде

Теперь, когда мы знаем как получить необходимую информацию из дерева AST, нам осталось реализовать собственно проверку на соблюдение интересующих нас правил в анализаруемом коде. Наша правила хранятся в XML файле, их обработкой занимается специализированный класс RuleEngine. Этот класс читает правила из XML файла и проверяет правила одно за другим. Если входной файл не удовлетворяет правилу, то возвращается список объектов типа ErrorDescription, которые содержат информацию об ошибке и местонахождении ошибки в исходном коде.

  1. ClassFile clazzInfo = ClassModelMap.getInstance().
  2. getClassInfo(className);
  3. for (JavaCodeRule rule : getRules()) {
  4. //проверяем правила по очереди
  5. Collection<ErrorDescription> problems = rule.execute(clazzInfo);
  6. if (problems != null) {
  7. problemsFound.addAll(problems);
  8. }
  9. }
 

Каждое правило реализовано в виде отдельного класса, информация о класса для проверки передается классу-правилу в виде модели. Реализация класса-правила для правила OverrideEqualsHashCode, то есть "класс, переопределяющий equals(), должен также переопределять hashcode() (с правильной сигнатурой метода)",приведена ниже.


Проверка состоит в переборе методов и верификации соблюдения правила для equals() и hashCode(). В нашем тестовом классе TestClass, метод hashcode() отсутствует, хотя есть метод equals(). Поэтому правило вернет ErrorDescription с сообщением об ошибке и информацией о ее местонахождении.

  1. public class OverrideEqualsHashCode extends JavaClassRule {
  2. @Override
  3. protected Collection<ErrorDescription> apply(ClassFile clazzInfo) {
  4. boolean hasEquals = false;
  5. boolean hasHashCode = false;
  6. Location errorLoc = null;
  7. for (Method method : clazzInfo.getMethods()) {
  8. String methodName = method.getName();
  9. ArrayList paramList = (ArrayList) method.getParameters();
  10. if ("equals".equals(methodName) && paramList.size() == 1) {
  11. if ("java.lang.Object".equals(paramList.get(0))) {
  12. hasEquals = true;
  13. errorLoc = method.getLocationInfo();
  14. }
  15. } else if ("hashCode".equals(methodName) &&
  16. method.getParameters().size() == 0) {
  17. hasHashCode = true;
  18. }
  19. }
  20. if (hasEquals) {
  21. if (hasHashCode) {
  22. return null;
  23. } else {
  24. StringBuffer errrMsg = new StringBuffer();
  25. errrMsg.append(CodeAnalyzerUtil.
  26. getSimpleNameFromQualifiedName(clazzInfo.getName()));
  27. errrMsg.append(" : The class that overrides
  28. equals() should ");
  29. errrMsg.append("override hashcode()");
  30. Collection<ErrorDescription> errorList = new
  31. ArrayList<ErrorDescription>();
  32. errorList.add(setErrorDetails(errrMsg.toString(),
  33. errorLoc));
  34. return errorList;
  35. }
  36. }
  37. return null;
  38. }
  39. }
 

Запуск демонстрационного примера

Готовый к запуску вариант демонстрационного примера можно скачать по ссылке из раздела Ресурсы. Сохраните файл в локальную директорию и используйте следующую команду для запуска приложения:

  1. java -classpath <JAVA_HOME>\lib\tools.jar;.; demo.codeanalyzer.main.Main <список входных файлов для проверки разделенных запятой>
 

Заключение

В этой статье мы рассказали о некоторых новых возможностях Java SE 6, позволяющих программно вызывать компилятор, производить синтаксический разбор и анализировать исходный код с помощью вставки аннотаций и анализа синтаксического дерева. Использование стандартного API позволяет писать код, который можно использовать в разных окружениях.

Мы конечно же не смогли рассказать о всех возможностях нового API в этой статье, рекомендуем ознакомиться с документацией, чтобы лучше представлять, что Вы можете с помощью него сделать.

Ресурсы

 

Добавить комментарий


Защитный код
Обновить