3

Там вдали, за рекой

Это река Казанка, на которой стоит Казань:)






1

HI-TECH

Скоро в продажу поступят новые беспроводные компьютерные мыши, выполненные в стиле ретро. Специально для ностальгируещих по временам I, II и III Pentium.








0

Клиент для Blogger своими руками-4

Привет всем читателям нашего блога! Приношу извинения за мое долгое отсутствие -
как, наверное многие успели заметить, за меня отдувалась моя ненаглядная жена :) Счас я сижу на больничном и имею возможность продолжить мои эксперименты с клиентом для Blogger-а :). Обычно я сначала писал программку, а потом, уже имея какой-то готовый результат, садился писать статью. Сейчас будет все по-другому, поскольку все это время я занимался... Ну скажем так, РАБОТОЙ (на самом деле - пытался совместить работу и "учебу" в автошколе:)))) Поэтому никакого готового кода пока нет, будем его сочинять по ходу дела. Итак, в предыдущем посте я добавил в класс BlogEditor возможность получать содержимое всех постов выбранного блога и выяснил, как получить их html-представление. Но у мене такое чувство, что чего-то не хватает. Пожалуй, было бы неплохо указывать, какие посты мы хотим получить, например - последние 15 штук, или за последний месяц, или за какой-то интервал времени (например, в интервале между пасхой и курбан-байрамом :))).

Пишем сначала тест, то есть программку, в которой будет использоваться наш код (пусть даже еще не написанный :)))

package test.blogeditutil;
import com.google.gdata.client.*;
import com.google.gdata.data.*;
import com.google.gdata.util.*;
import java.io.*;
import javax.swing.*;
import java.util.*;
import blogeditutil.*;
//Распечатка избранных постов
class TestBlogEditorSelective
{
public static void main(String args[]) throws IOException, ServiceException
{
BlogEditor bedit = new BlogEditor();
String blogname = QueryBlogName.getBlogName();
List<Entry> postlist = bedit.getPostEntryList(blogname, 1, 15);
assert(postlist.size() <= 15);
System.out.println("Blog posts of " + blogname + ":");
for(Entry p: postlist)
{
System.out.println("Post caption: " + p.getTitle().getPlainText());
System.out.println("Date and time: " + p.getPublished());
}
}
}
Класс BlogEditor описан мной в предыдущих постах. Класс QueryBlogName содержит всего один метод, который возвращает строку, введенную пользователем. Как видно, нам хочется получить 15 последних постов. Компилируем, и... конечно, ничего не компилируется, поскольку метод getPostEntryList(String, int, int) в классе BlogEditor мной пока не реализован. Сделать его из существующего варианта класса - дело нескольких минут:

public class BlogEditor
{
...

...
public List<Entry> getPostEntryList(String blogname, int start, int nres)
throws IOException, ServiceException
{
String blog_id = getBloggerID("http://"+blogname+BLOGGER_SERVER);
if(blog_id == null)
throw new IOException("Can't get blogID for " + blogname+BLOGGER_SERVER);
//Запрос на получение постов:
Query query = new Query(new URL (BLOGGER_FEED + blog_id + POST_LOCATION));
query.setStartIndex(start);
query.setMaxResults(nres);
return bservice.getFeed(query, Feed.class).getEntries();
}
}

Когда интерфейс класса меняется, это есть весьма нехорошо. Поэтому старый метод getPostEntryList с одним параметром типа String я тоже реализую, но уже иначе:

public class BlogEditor
{
...

...
private final int MAX_RESULTS = 10000000;
public List<Entry> getPostEntryList(String blogname)
throws IOException, ServiceException
{
return getPostEntryList(blogname, 1, MAX_RESULTS);
}
}
После этого все нормально компилится и тест TestBlogEditorSelective тоже запускается без проблем. Еще одна полезная возможность - получить список постов, опубликованных в определенный интервал времени. Заново переписывать тест мне лень, и я просто вставляю еще один вызов в конец метода main класса TestBlogEditorSelective:

class TestBlogEditorSelective
{
public static void main(String args[]) throws IOException, ServiceException
{
...
.
.
//Посты, опубликованные между 1 декабря 2008 и 25 января 2009 года:
postlist = bedit.getPostEntryList(blogname, new GregorianCalendar(2008, 11, 1),
new GregorianCalendar(2009, 0, 25) );
assert(postlist.size() == 8);
}
Сейчас тест естественно, даже не компилируется. Я пока добавлю в класс BlogEditor самую простую реализацию этого метода, дабы убедится в том, что тест будет обнаруживать ошибку:

public class BlogEditor
{
...
.
public List<Entry> getPostEntryList(String blogname, Calendar date1, Calendar date2)
{ return new ArrayList<Entry>(); }
}
После запуска теста, он вылетел на assert, как и ожидалось. Хорошо, теперь можно сварганить нормальную реализацию. Заглянув в документацию по GData API, можно увидеть, что там используются собственная структура данных, описывающая дату - DateTime. К сожалению, объект класса DateTime нельзя напрямую создать из java.util.Calendar, а можно лишь из java.util.Date. И это при том, что большинство удобных конструкторов и методов в классе java.util.Date объявлены как deprecated (нежелательными в изпользовании, устаревшими); Ну, ничего страшного, всего лишь код будет чуть длиннее. Итак, новая реализация, которая уже делает то, что нужно:

public class BlogEditor
{
private final String BLOGGER_FEED = "http://www.blogger.com/feeds/",
POST_LOCATION = "/posts/default",
BLOGGER_SERVER = ".blogspot.com";
private GoogleService bservice;
public BlogEditor()
{ bservice = new GoogleService("blogger", "lotrex-BlogEditor-0.0");}
...
.
.
public List<Entry> getPostEntryList(String blogname, Calendar date1,
Calendar date2)
throws IOException, ServiceException
{
String blog_id = getBloggerID("http://"+blogname+BLOGGER_SERVER);
if(blog_id == null)
throw new IOException("Can't get blogID for " + blogname+BLOGGER_SERVER);
//Запрос на получение постов:
Query query = new Query(new URL (BLOGGER_FEED + blog_id + POST_LOCATION));
query.setPublishedMin(new DateTime(date1.getTime()));
query.setPublishedMax(new DateTime(date2.getTime()));
return bservice.getFeed(query, Feed.class).getEntries();
}
}

Все вроде хорошо, и тест проходит... Теперь надо бы устранить дублирование в двух версиях метода getPostEntryList, которое получилось из-за использования супертехнологии "copy-paste". Понятно, что дублирующийся код надо выделить в отдельный private-метод и как-то этот метод обозвать. В таких ситуациях я частенько трачу больше всего времени именно на то, чтобы придумать методу название. В этом случае я решил его обозвать getPostQuery, что должно (на мой взгляд:)) означать - "получить запрос на получение фида постов":

public class BlogEditor
{
private final String BLOGGER_FEED = "http://www.blogger.com/feeds/",
POST_LOCATION = "/posts/default",
BLOGGER_SERVER = ".blogspot.com";
...
.
private Query getPostQuery(String blogname) throws IOException,
ServiceException
{
String blog_id = getBloggerID("http://"+blogname+BLOGGER_SERVER);
if(blog_id == null)
throw new IOException("Can't get blogID for " + blogname + BLOGGER_SERVER);
//Запрос на получение постов:
return new Query(new URL (BLOGGER_FEED + blog_id + POST_LOCATION));
}
}

Остается заменить дублирующийся код в каждой из версий метода getPostEntryList на вызов getPostQuery(blogname) и проверить срабатывание теста, что я и сделал. Уфф, надеюсь с получением постов я на этом закончу. Потому что пора уж заняться чем-нить посерьезнее и интереснее - например, добавлением постов, комментов и картинок :)). Насчет последнего я пока не уверен, что получится...

2

Дела-заботы и фото

Что-то мы с мужем закрутились, все дела, все заботы...

Мы скоро вернемся.:)
Обязуюсь с опозданием, но проддержать идею BeBeKK@ и рассказать про нашего королевского двортерьера Джима:)))

А пока просто зимние, еще декабрьские фото










2

Ёлки-палки

Поздравляем всех наших читателей с наступившим Новым годом и Рождеством Христовым!!!

А поскольку одним из символов Нового года и Рождества является ёлка, то о ней. точнее о них сегодня и поговорим!:)

А теперь пойдут картинки:)

Вот так Казань готовилась к Новому году:








А вот так мы готовились к Новому году!


Хвастаюсь:))))












С Рождеством!!!





P.S. А как вы готовились к праздникам? Хвалитесь ёлками и украшениями!;)



8

Когда программиста отрывают от компа...



0

Клиент для Blogger своими руками-3

В предыдущем посте я начал описывать создание программки, выдающей список постов данного блога. В прошлый раз задача оказалась решенной не до конца: написанная программка распечатывала неполный список. Прежде чем приступить к внесению изменений в смысловую часть программы (то есть перед тем, как добиваться от нее того, чего я хочу), я внесу некоторые изменения в процесс тестирования. В предыдущем варианте тест класса BlogEditor (функция main(String [])) находился в самом классе. Сейчас я решил, что мне будет удобнее разместить тесты в отдельном пакете, с названием test.
Имена тестовых пакетов будут повторять имена тестируемых, за исключением того, что тестовые пакеты будут начинаться со слова test, а тестирующие классы - со слова Test. Так, для пакета blogeditutil пакет тестов будет называться test.blogeditutil, а для класса BlogEditor тестирующий класс будет называться TestBlogEditor. Класс TestBlogEditor у меня будет содержать всего один метод - main(), тело которого я просто вырежу из исходного класса (из BlogEditor). Еще добавляю всякие внешние классы через директиву import, и конечно, добавляю тестируемый класс. В результате получается следующее:

package test.blogeditutil;
import com.google.gdata.client.*;
import com.google.gdata.data.*;
import com.google.gdata.util.*;
import java.io.*;
import javax.swing.*;
import java.util.*;
import blogeditutil.*;

class TestBlogEditor
{
public static void main(String args[]) throws IOException, ServiceException
{
BlogEditor bedit = new BlogEditor();
String blogname = JOptionPane.showInputDialog(null,
"For example, if blog addresss is lotrex.blogspot.com, input \"lotrex\"",
"Input name of blog");
System.out.println("Blog posts of " + blogname + ":");
List<Entry> postlist = bedit.getPostEntryList(blogname);
for(Entry p: postlist)
{
System.out.println("Post caption: " + p.getTitle().getPlainText());
System.out.println("Date and time: " + p.getPublished());
}
}
}

Этот текст сохраняю в файле TestBlogEditor.java. Было бы неплохо запускать тесты кучкой, и при этом видеть, какой тест не прошел. Я решил использовать для этого утилиту make. Создал в корневой директории моего проекта файл makefile со следующим содержимым:
JVM = java
JFLAGS = -cp .;.\gdata-core-1.0.jar -ea -esa
test:
$(JVM) $(JFLAGS) test.blogeditutil.TestBlogEditor >res.txt

Набираю в командной строке make, жму Enter, усе работает, как и раньше :)
Теперь можно возвращаться к самой программулине :)
Получить полный список постов довольно просто, для этого в тело метода getPostEntryList() достаточно вставить одну строку. Итак, изначально реализация метода getPostEntryList() выглядела следующим образом:

public List<Entry> getPostEntryList(String blogname)
throws IOException, ServiceException
{
String blog_id = getBloggerID("http://"+blogname+BLOGGER_SERVER);
if(blog_id == null)
throw new IOException("Can't get blogID for " + blogname+BLOGGER_SERVER);
//Запрос на получение постов:
Query query = new Query(new URL (BLOGGER_FEED + blog_id + POST_LOCATION));
return bservice.getFeed(query, Feed.class).getEntries();
}

Сначала создается объект класса запроса (класса Query), которому в качестве параметра передавался URL фида. После чего мы сразу обращались к серверу за данными. Но объекту класса Query можно задать кучу параметров, один из которых - максимальное количество результатов (считай-постов) в фиде. Если мы хотим сразу скачать всю ленту целиком, но не знаем, сколько в ней постов, то просто задаем в качестве максимального значения какое-нить число побольше. Новая реализация метода getPostEntryList() немногим отличается от старой:

private final int MAX_RESULTS = 10000000;
public List<Entry> getPostEntryList(String blogname)
throws IOException, ServiceException
{
String blog_id = getBloggerID("http://"+blogname+BLOGGER_SERVER);
if(blog_id == null)
throw new IOException("Can't get blogID for "+ blogname+BLOGGER_SERVER);
//Запрос на получение постов:
Query query = new Query(new URL (BLOGGER_FEED + blog_id + POST_LOCATION));
query.setMaxResults(MAX_RESULTS);
return bservice.getFeed(query, Feed.class).getEntries();
}

Сейчас результирующий список получился больше, чем в первый раз :) Во всяком случае, первый и последний посты в результирующем списке - те же самые, что и в блоге.
Помимо заголовков постов хотелось бы еще иметь возможность прочитать их содержимое. Содержимое поста (в виде текста) можно получить из того же источника, что его заголовок и дату публикации, ведь объект Entry по сути дела и есть сам пост. Немного модифицируем наш тест (файл TestBlogEditor.java):

class QueryBlogName
{
static String getBlogName()
{
return JOptionPane.showInputDialog(null,
"For example, if blog addresss is lotrex.blogspot.com, input \"lotrex\"",
"Input name of blog");
}
}

class TestBlogEditor
{
public static void main(String args[]) throws IOException, ServiceException
{
BlogEditor bedit = new BlogEditor();
String blogname = QueryBlogName.getBlogName();
List<Entry> postlist = bedit.getPostEntryList(blogname);
System.out.println("Blog posts of " + blogname + ":");
for(Entry p: postlist)
{
System.out.println("Post caption: " + p.getTitle().getPlainText());
System.out.println("Date and time: " + p.getPublished());
}
}
}
class TestBlogEditTextContent
{
public static void main(String args[]) throws IOException, ServiceException
{
BlogEditor bedit = new BlogEditor();
String blogname = QueryBlogName.getBlogName();
List<Entry> postlist = bedit.getPostEntryList(blogname);
System.out.println("Blog posts of " + blogname + ":");
for(Entry p: postlist)
{
System.out.println("-----------------------");
System.out.println("Post caption: " + p.getTitle().getPlainText());
System.out.println("Content:");
System.out.println(p.getPlainTextContent());
}
}
}

Теперь у меня есть 2 теста класса, которые выполняют разные действия. Добавлю в конец makefile еще одну строку, что бы он тоже выполнялся. Теперь содержимое makefile выглядит так:
JVM = java
JFLAGS = -cp .;.\gdata-core-1.0.jar -ea -esa
test:
$(JVM) $(JFLAGS) test.blogeditutil.TestBlogEditor >res.txt
$(JVM) $(JFLAGS) test.blogeditutil.TestBlogEditTextContent >rescontent.txt

Тест не работает! Почему-то матершина (т.е сообщения об ошибках) исходит из того места, где вызывается метод getPlainTextContent(). Как заставить этот метод работать правильно, я так и не понял. Заглянув в документацию мы увидим, что у класса Entry есть метод getTextContent(), который возвращает объект класса TextContent, у которого есть метод getContent(), возвращающий объект класса TextConstruct, у которого есть (наконец-то!!!) метод getPlainText(), который возвращает обычную строку:

String textcontent = p.getTextContent().getContent().getPlainText();

Несколько длинновато, но зато после замены вызова getPlainTextContent() на это длинное выражение все работает. Полученный результат вряд ли можно считать читабельным, он не содержит информации ни о ссылках, ни о разметке. Было бы неплохо получить html-представление постов, дабы потом их спокойно просматривать в браузере. Как оказалось, такая возможность есть, хотя о ней прямо ничего не сказано в документации на GData API. Как оказалось (по крайней мере, для случая, когда мы получаем фид постов), объект класса TextConstruct, который мы получим при вот таком вызове:

TextConstruct textconstr = p.getTextContent().getContent();

можно спокойно приводить к типу HtmlTextConstruct, из которого уже можно получить html-представление поста. Я создал новый класс под названием TestBlogEditHtmlContent, в котором и реализовал вывод содержимого постов в html-представлении. Он отличается от предыдущего тестового класса только телом цокла for, поэтому я приведу тут лишь это различие:

for(Entry p: postlist)
{
System.out.println("<h3>Post caption:<br>" +
p.getTitle().getPlainText() + "<br>");
System.out.println("Content:<br></h3>");
HtmlTextConstruct htmlconstr = (HtmlTextConstruct)(p.getTextContent().getContent());
System.out.println(htmlconstr.getHtml());
System.out.println("<br>----------------------- end of post.<br>");
}

Все это откомпилил, и в конец файла makefile вставил еще одну строчку:

$(JVM) $(JFLAGS) test.blogeditutil.TestBlogEditHtmlContent >res.html
Теперь мона открывать файл res.html в браузере и любоваться пейзажем :)))