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 в браузере и любоваться пейзажем :)))


0

Геометрические параметры винного аромата

Сегодня после тренажерного зала, зашли в магазин купить вина. У нас начался период дегустации перед Новым годом:)
Купили JP Chenet "Medium sweet 2007" красное полусладкое. Давно собирались продегустировать что-нибудь из JP Chenet, точнее я все уговаривала мужа, поскольку те вина этого производителя, что я уже пила, оставили весьма приятно впечатление, или правильнее будет сказать послевкусие. Ну жно заметить, муж не сопротивлялся:)
Читаю этикетку: "вино крсное полуслдакое, бла-бла-бла... произведено из винограда сортов бла-бла-бла... выращеного во Франции в местечке бла-бла-бла... имеет богатый бла-бла-ба ОКРУГЛЫЙ аромат!!!
Долго по этому поводу прикалывались. Муж все пытался себе приставить геометрическую форму вкуса:)

В общем, пришли домой, открыли, налили, выпили.

И скажу я вам, аромат действительно, кроме всего прочего, ОКРУГЛЫЙ. Теперь я знаю? что это такое, но объяснить не смогу, не пытайте:)
Как говорится, лучше один раз увидеть, в смысле испробовать, чем сто раз услышать, в смысле прочитать:)

0

И снова повод есть у программистов...

Сегодня еще одна из версий дня программиста. Во всяком случае, есть повод отметить. 10-го декабря 1815-го года у поэта Джорджа Байрона и его супруги - Анны Изабеллы, которую муж за довольно сильное пристрастие к математике, ласково назвал «королева параллелограммов» появилась дочка.

Дочь сердца моего, малютка Ада!

Похожа ль ты на мать?

В последний раз, когда была мне суждена отрада,

Улыбку видеть детских синих глаз,

Я отплывал…


Байрон последний раз посмотрел на дочку, когда ей не исполнилось еще и месяца и… укатил в романтическое путешествие по Италии. «Королева параллелограммов», окончательно уставшая от мужниных причуд, сбежала к родителям и оба они представить себе не могли, что малышка Ада станет легендой кибернетики. Впрочем, мама как раз стремилась сделать все, чтобы насолить бывшему мужу, а насолить ему можно было только с математической точностью. В тринадцать лет малышка Ада уже рисовала модели летательных аппаратов, в восемнадцать встретила мужчину своей судьбы – мистера Бэббиджа. Он занимал кафедру профессора математики Кэмбриджского университета. То самое место, на котором полтора века назад работал сэр Исаак Ньютон. Вместе они придумали первый в мире компьютер и программы к нему. Деревянный «дисковод», картонные перфокарты, «процессор» на шестеренках и рычагах.

Текст самым наглым образом стащила отсюда

2

Букинистический weekend или библиотека программиста

Наконец-то мы с мужем заказали новый книжный шкаф!!! Я тут от восторга уже целую неделю прыгаю!:) и буду еще недели две прыгать, пока его не привезут:)
А пока у нас идет подготовка, которая заключется в том, чтобы разобрать старый шкаф. А для этого необходимо рассортировать книги в нем: какие-то нужно оставить дома, а какие-то можно отправить на дачу следом за шкафом:)
Нужно сказать, что старый шкаф дюже вместительный. Казалось бы не особо высокий, не глубокий - в два ряда в нем книги не встают, но вытаскивать из него тома и перевязывать их бичовкой устали жутко, заняло это у нас несколько часов. Возможно, у меня возникло такое впечатление от того, что литература все техническая и большей частью устаревшая, живой интерес во мне пробудился лишь к изданиям по фотоделу. Но химия и физика фотопечати сейчас также не актуальны, а, значит, им дорога на дачу. Буду летними вечерами, лежа в гамаке, настольгировать, вспоминая свои эксперементы в темной комнате с красной лампой при фотоувелеичителе, проявителе, закрепителе и глянцевателе:)
Но вернемся к нашим баранам (это я в накануне Курбан-байрама про них вспомнила:))А потом началось самое интересное! Разборка самого шкафа!!! В общем, когда мы отодвинули шкаф от стены, нам открылась тайна его возраста:) На его задней стенке оказался шикарнейший советский штамп, на котором среди прочих параметрв (артикул, серия и т.д.) стояла и дата изготовления данного изделия! Блин, этот монстр старше меня почти на 20 лет! Изготовлен он был аж в 1963 году!
Сделан добротно, хотя и из такого, извините, дерьма. Но вот шурупов, в отличии от прочих материалов, для него не пожалели, натыкано их было более чем достаточно(умаялись, выворачиать. Их электрический шуроповерт не брал!!!), причем до сих пор гадаем, для чего надо было заднюю фанерную стенку крепить шурапами длиной 4,5 - 5 см...
Но, слава Богу, мытарства наши закончились, сегодня и сам шкаф, и часть его содержимого перевезено на дачу!
Ждем новый! Проблем с его наполнением не возникнет. Вот и сегодня Lotrex приобрел еще одну книгу по Java "Java без сбоев" Стивена Стелтинга.

2

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

Недавно я писал о том, как я начал эксперимент по созданию моего blogger-клиента. На сегодняшний день имеются некоторые крошечные результаты, о которых я собираюсь рассказать.
Не ставя себе сразу умопомрачительную цель, я решил для начала написать маленькую программку, реализующую простую задачу - вывести список постов (заголовки, время публикации и контент) данного пользователя.

После прочтения доки на GData API выяснилось, что список постов будет иметь тип List<Entry>, где Entry - специальный класс, описывающий 1 элемент фида.
Сказано-сделано, скоро был готов примерно такой код:

package blogeditutil;

import com.google.gdata.client.*;
import com.google.gdata.data.*;
import com.google.gdata.util.*;

import java.io.*;
import java.net.*;
import javax.swing.*;
import java.util.*;

public class BlogEditor
{
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.print("Blog posts of " + blogname + ":\n");
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());
}
}
}

Однако, он (конечно) не компилируется, поскольку метод getPostEntryList(String) в классе BlogEditor не реализован. Я написал код, использующий метод getPostEntryList раньше, чем этот метод был реализован. Немного странно, что я использую интерфейс класса (т.е. его методы) раньше, чем я их реализовал или даже объявил. Но как иначе я могу определить, какой интерфейс будет наиболее удобным? Имхо, самый простой способ придумать интерфейс для класса - написать программку, в которой вы заставляете класс делать то, чего хотите от него добиться, с помощью интерфейса, который (пока) не реализован. При этом я придумываю методы, думаю о том, какие параметры эти методы должны принимать и какие действия они должны выполнять. Так, постепенно у меня формируется представление о том, каким должен быть интерфейс класса (конец лирического отступления, прочитанного менторским тоном:)))

Изучая доку на GData API, я пришел к выводу, что реализовать метод getPostEntryList(String blogname), где параметр blogname - имя блога (в моем блоге это будет строка "lotrex"), используя только интерфейс GData API, и без авторизации, невозможно. Не знаю, в чем тут причина - то ли я чего недосмотрел, то ли разработчики GData API, то ли это политика у Google такая... Но хочется все же иметь возможность читать посты блогов, которые вам интересны.

Для того, чтобы получить фид постов, необходимо знать blogID - строку из нескольких десятичных цифр (чем моложе блог, тем она длинее). Ну что ж, предположим что у нас есть метод getBloggerID(String blogaddr), возвращающий blogID, и попробуем этот blogID распечатать, а использование getPostEntryList() пока уберем:

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 ID of " + blogname + ":");
System.out.println(bedit.getBloggerID("http://" + blogname +
".blogspot.com"));
}

Пока это все не компилируется - нужно реализовать метод getBloggerID(). В GData API получение blogID без авторизации не предусмотрено, увы :( Строка blogID имеется в коде главной странице блога (пока что), ее легко обнаружить, поискав выражение вида "blogID=XXXXXXXXX". На мой взгляд, этот способ получения blogID вряд ли можно назвать хорошим из-за того, что он в любой момент может перестать работать. Но иного пути, похоже, нет :(((
Поэтому я с (тяжелым сердцем :))) написал довольно простой метод(а точнее - 2 метода), в котором применил именно этот способ:

private String getBloggerID(String blogaddr) throws IOException,
SocketTimeoutException
{
URL blogurl = new URL(blogaddr);
URLConnection connection = blogurl.openConnection();
boolean isconnect = false;
for(int i=0; i < 10; i++)//10 попыток соединения
{
try{
connection.connect();
}catch(SocketTimeoutException ex){
continue;
}
break;
}
connection.connect();
//Создаем класс BufferedReader для чтения по строкам:
BufferedReader bread = new BufferedReader(
new InputStreamReader(connection.getInputStream()));
String str="";
//Читаем ответ сервера по строкам и производим разбор:
//Искать строку, содержащую blogID=xxxxxxx, где xxx - любая цифра
Pattern pat = Pattern.compile("blogID=([\\d]+)");
while( (str = bread.readLine()) != null)
if( (str = getMatch(pat, str)) != null)
break;
bread.close();
//Искать строку, содержащую только цифры:
pat = Pattern.compile("[\\d]+");
return getMatch(pat, str);
}
private String getMatch(Pattern pat, String str)
{
Matcher match = pat.matcher(str);
if(match.find())
return match.group();
return null;
}

Метод длинноват, требует укорачивания, но пока оставим его таким. Чтобы он работал, нужно подключить пакет поддержки регулярных выражений:
import java.util.regex.*;

Компилируем (файл gdata-core-1.0.jar должен находится в текущей директории):
javac -cp .;.\gdata-core-1.0.jar -encoding Cp1251 .\blogeditutil\BlogEditor.java

Если все ОК, то можно запускать:
java -cp .;.\gdata-core-1.0.jarblogeditutil.BlogEditor

Ура, первый шаг сделан - что-то работает, и даже выдает какие-то цифирки, возможно даже, что эти цифирки - тот самый blogID, ради получения которого было написано так много букфф :))) Что ж, теперь у меня получится написать метод, выдающий список блогов:

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)
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();
}

Теперь можно заставить работать мой первый вариант метода main(), распечатывающий этот список (тот main(), что в начале статьи). Вставляю его целиком, а предыдущий метод main() закрываю комментами. Компиляция проходит нормально. Запускаю (вывод программки перенаправляю в файл, поскольку на консоль выводятся нечитабельные краказяблы):
java -cp .;.\gdata-core-1.0.jarblogeditutil.BlogEditor >res.txt

Ура, список постов получили!!! Поэкспериментировав с этой программкой, можно заметить, что список выводится не полностью. Когда я опробовал эту прогу впервые, для всех блогов выводилась информация о 15 последних постах (или меньше), счас - о 25(или меньше). Интересно, что теперь, когда я запускаю прогу с моего компа, это число совпадает с числом постов в моем блоге, а раньше это было не так. Возможно, тут дело в настройках блога, а может, и нет :) Думаю, выяснить причину будет несложно - исходные коды GData API открыты. Имхо, это не столь уж важно. Важнее то, что в документации не сказано, как узнать общее количество постов в блоге. Но об этом напишу в следующий раз - эта статья и так получилась слишком длинной.