Pokazywanie postów oznaczonych etykietą java. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą java. Pokaż wszystkie posty

31 grudnia 2010

Custom PagingNavigator with changing items per page in Wicket

As I am migrating my blog to my own domain, a few posts in the future will be published in both places to allow You, reader, to change address and rss smoothly :)

New blog address: http://tomaszdziurko.pl
New RSS link: http://www.tomaszdziurko.pl/feed

This post can be found at
http://tomaszdziurko.pl/2010/12/custom-pagingnavigator-with-changing-items-per-page-in-wicket/



In one of my recent projects I had to create Wicket pagination component with one additional functionality allowing user to dynamically change maximum number of items presented on each page.

Finished component will look like below:



Of course I was not going to implement this from the scratch, because most of work had been already done by Wicket authors and commiters in component PagingNavigator. Source of this component as a reference to changes I made can be found here.

The first steps in creating our component:
- add List itemsPerPageValues holding numbers for 'items per page' which will be shown to the user allowing him to click and change this value.
- provide default List DEFAULT_ITEMS_PER_PAGE_VALUES, which will be used (what a surprise!) as a default :)
- give our component reference to the DataView object on which we will execute change items per page method.

Below we can see changes in class fields and constructors:

public class CustomPagingNavigator extends Panel {

public static final String NAVIGATION_ID = "navigation";
public static final List<Integer> DEFAULT_ITEMS_PER_PAGE_VALUES = Arrays.asList(5, 10, 50);

private PagingNavigation pagingNavigation;
private final DataView<?> dataView;
private final IPagingLabelProvider labelProvider;
private final List<Integer> itemsPerPageValues;
private WebMarkupContainer pagingLinksContainer;

public CustomPagingNavigator(final String id, final DataView<?> dataView) {
this(id, dataView, null, DEFAULT_ITEMS_PER_PAGE_VALUES);
}

public CustomPagingNavigator(final String id, final DataView<?> dataView, List<Integer> itemsPerPageValues) {
this(id, dataView, null, itemsPerPageValues);
}

public CustomPagingNavigator(final String id, final DataView<?> dataView, final IPagingLabelProvider labelProvider) {
this(id, dataView, labelProvider, DEFAULT_ITEMS_PER_PAGE_VALUES);
}

public CustomPagingNavigator(final String id, final DataView<?> dataView, final IPagingLabelProvider labelProvider,
List<Integer> itemsPerPageValues) {
super(id);
this.dataView = dataView;
this.labelProvider = labelProvider;
this.itemsPerPageValues = itemsPerPageValues;

// these methods will be described later in this post
addContainerWithPagingLinks();
addLinksChangingItemsPerPageNumber();
}
(...)
}


First new method is addContainerWithPagingLinks based mainly of onBeforeRender from PagingNavigator. This method adds paging links to the component to allow user to change number of viewed page. One small addition is an overriden isVisible which will hide paging fragment when there is only one page to show.

private void addContainerWithPagingLinks() {

pagingLinksContainer = new WebMarkupContainer("pagingLinksContainer") {
@Override
public boolean isVisible() {
return dataView.getPageCount() > 1;
}
}

pagingNavigation = newNavigation(dataView, labelProvider);
pagingLinksContainer.add(pagingNavigation);

// Add additional page links
pagingLinksContainer.add(newPagingNavigationLink("first", dataView, 0).add(
new TitleAppender("PagingNavigator.first")));
pagingLinksContainer.add(newPagingNavigationIncrementLink("prev", dataView, -1).add(
new TitleAppender("PagingNavigator.previous")));
pagingLinksContainer.add(newPagingNavigationIncrementLink("next", dataView, 1).add(
new TitleAppender("PagingNavigator.next")));
pagingLinksContainer.add(newPagingNavigationLink("last", dataView, -1).add(
new TitleAppender("PagingNavigator.last")));

add(pagingLinksContainer);
}

Next step is to override isVisible method in our component which will hide it completely when DataView has no items to render:

@Override
public boolean isVisible() {
return dataView.getItemCount() > 0;
}

And now it's time to create the core of our component: a place where items per page can be changed. This is done in method:

private void addLinksChangingItemsPerPageNumber() {
ListView<Integer> itemsPerPageList = new ListView<Integer>("itemsPerPage", itemsPerPageValues) {
@Override
protected void populateItem(ListItem<Integer> item) {
Link<Void> itemPerPageLink = new ItemPerPageLink<Void>("itemPerPageLink", dataView,
pagingLinksContainer, item.getModelObject());
itemPerPageLink.add(new Label("itemsValue", item.getModel()));
item.add(itemPerPageLink);
}
};

add(itemsPerPageList);
}

In the above method we create ListView for Integers from itemsPerPageValues and for each number we add link (new class ItemPerPageLink explained below) which will change DataView itemsPerPage property. Except reference to DataView we also give to our link reference to pagingLinksContainer to hide it after user changes itemsPerPage and there will be only one page to show.

Complete class ItemPerPageLink source code:

public class ItemPerPageLink<T> extends Link<T> {

private final int itemsPerPage;
private final DataView<?> dataView;
private final WebMarkupContainer pagingLinksContainer;

public ItemPerPageLink(final String id, final DataView<?> dataView, WebMarkupContainer pagingLinksContainer, int itemsPerPageValue) {
super(id);
this.dataView = dataView;
this.pagingLinksContainer = pagingLinksContainer;
this.itemsPerPage = itemsPerPageValue;
setEnabled(itemsPerPageValue != dataView.getItemsPerPage());

}

@Override
public void onClick() {
dataView.setItemsPerPage(itemsPerPage);
pagingLinksContainer.setVisible(dataView.getPageCount() > 1);
}

@Override
protected void onComponentTag(ComponentTag tag) {
super.onComponentTag(tag);
tag.put("title", itemsPerPage);
}

}

In this class we:
1. Disable link for number which is current dataView.itemsPerPage value.
2. Hide pagingLinksContainer when there is only one page.
3. Change itemsPerPage property in onClick method.
4. Set link title to the value of items per page.

And that's all. For those who want complete solution in one place there is complete source of the class CustomPagingNavigator with its markup:


import java.util.Arrays;
import java.util.List;

import org.apache.wicket.Component;
import org.apache.wicket.behavior.AbstractBehavior;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.AbstractLink;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.markup.html.navigation.paging.IPageable;
import org.apache.wicket.markup.html.navigation.paging.IPagingLabelProvider;
import org.apache.wicket.markup.html.navigation.paging.PagingNavigation;
import org.apache.wicket.markup.html.navigation.paging.PagingNavigationIncrementLink;
import org.apache.wicket.markup.html.navigation.paging.PagingNavigationLink;
import org.apache.wicket.markup.html.navigation.paging.PagingNavigator;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.markup.repeater.data.DataView;

public class CustomPagingNavigator extends Panel {

public static final String NAVIGATION_ID = "navigation";
public static final List<Integer> DEFAULT_ITEMS_PER_PAGE_VALUES = Arrays.asList(5, 25, 50);

private PagingNavigation pagingNavigation;
private final DataView<?> dataView;
private final IPagingLabelProvider labelProvider;
private final List<Integer> itemsPerPageValues;
private WebMarkupContainer pagingLinksContainer;

public CustomPagingNavigator(final String id, final DataView<?> dataView) {
this(id, dataView, null, DEFAULT_ITEMS_PER_PAGE_VALUES);
}

public CustomPagingNavigator(final String id, final DataView<?> dataView, List<Integer> itemsPerPageValues) {
this(id, dataView, null, itemsPerPageValues);
}

public CustomPagingNavigator(final String id, final DataView<?> dataView, final IPagingLabelProvider labelProvider) {
this(id, dataView, labelProvider, DEFAULT_ITEMS_PER_PAGE_VALUES);
}

public CustomPagingNavigator(final String id, final DataView<?> dataView, final IPagingLabelProvider labelProvider,
List<Integer> itemsPerPageValues) {
super(id);
this.dataView = dataView;
this.labelProvider = labelProvider;
this.itemsPerPageValues = itemsPerPageValues;

addContainerWithPagingLinks();
addLinksChangingItemsPerPageNumber();
}

@Override
public boolean isVisible() {
return dataView.getItemCount() > 0;
}

private void addContainerWithPagingLinks() {

pagingLinksContainer = new WebMarkupContainer("pagingLinksContainer") {
@Override
public boolean isVisible() {
return dataView.getPageCount() > 1;
}
};

pagingNavigation = newNavigation(dataView, labelProvider);
pagingLinksContainer.add(pagingNavigation);

// Add additional page links
pagingLinksContainer.add(newPagingNavigationLink("first", dataView, 0).add(
new TitleAppender("PagingNavigator.first")));
pagingLinksContainer.add(newPagingNavigationIncrementLink("prev", dataView, -1).add(
new TitleAppender("PagingNavigator.previous")));
pagingLinksContainer.add(newPagingNavigationIncrementLink("next", dataView, 1).add(
new TitleAppender("PagingNavigator.next")));
pagingLinksContainer.add(newPagingNavigationLink("last", dataView, -1).add(
new TitleAppender("PagingNavigator.last")));

add(pagingLinksContainer);
}

protected PagingNavigation newNavigation(final IPageable pageable, final IPagingLabelProvider labelProvider) {
return new PagingNavigation(NAVIGATION_ID, pageable, labelProvider);
}

protected AbstractLink newPagingNavigationIncrementLink(String id, IPageable pageable, int increment) {
return new PagingNavigationIncrementLink<Void>(id, pageable, increment);
}

protected AbstractLink newPagingNavigationLink(String id, IPageable pageable, int pageNumber) {
return new PagingNavigationLink<Void>(id, pageable, pageNumber);
}

private void addLinksChangingItemsPerPageNumber() {
ListView<Integer> itemsPerPageList = new ListView<Integer>("itemsPerPage", itemsPerPageValues) {
@Override
protected void populateItem(ListItem<Integer> item) {
Link<Void> itemPerPageLink = new ItemPerPageLink<Void>("itemPerPageLink", dataView,
pagingLinksContainer, item.getModelObject());
itemPerPageLink.add(new Label("itemsValue", item.getModel()));
item.add(itemPerPageLink);
}
};

add(itemsPerPageList);
}

public final PagingNavigation getPagingNavigation() {
return pagingNavigation;
}

private final class TitleAppender extends AbstractBehavior {
private static final long serialVersionUID = 1L;

private final String resourceKey;

public TitleAppender(String resourceKey) {
this.resourceKey = resourceKey;
}

@Override
public void onComponentTag(Component component, ComponentTag tag) {
tag.put("title", CustomPagingNavigator.this.getString(resourceKey));
}
}
}


And its markup:

<?xml version="1.0" encoding="UTF-8" ?>
<html xmlns:wicket>
<body>
<wicket:panel>

<div>
<div>
Items per page:
<span wicket:id="itemsPerPage">
<a wicket:id="itemPerPageLink"><span wicket:id="itemsValue"></span></a>
</span>
</div>
<div wicket:id="pagingLinksContainer">
<table>

<tbody>
<tr valign="top">
<td>
<a wicket:id="first" href="">
<span>First</span>
</a>
</td>
<td>
<a wicket:id="prev" href="">
<span>Previous</span>
</a>
</td>
<td wicket:id="navigation">
<a wicket:id="pageLink"><span
wicket:id="pageNumber">5</span></a>
</td>

<td>
<a wicket:id="next" href="#">
<span>Next</span></a>
</td>
<td>
<a wicket:id="last" href="#">
<span>Last</span></a>
</td>
</tr>
</tbody>
</table>

</div>
</div>

</wicket:panel>
</body>
</html>


Usage of the component

Our newly created component can be used in a very similar way to the standard Wicket PagingNavigator:


public class DataViewPanel extends Panel {

public DataViewPanel(String id) {
super(id);
DataView dataView = new DataView("dataView", new CustomDataProvider());
dataView.setItemsPerPage(5);
add(dataView);

CustomPagingNavigator customPagingNavigator = new CustomPagingNavigator("paginator", dataView);
add(customPagingNavigator);
}
}

15 czerwca 2010

Szybka informacja o błędzie w aplikacjach napisanych w Wicket

Ten postał został przeniesiony na mojego nowego bloga i można go znaleźć tutaj.

27 kwietnia 2010

Gdzie Java nie tylko w polskiej trawie piszczy?

Ten postał został przeniesiony na mojego nowego bloga i można go znaleźć tutaj.

23 kwietnia 2010

Moje pierwsze TDD, czyli dlaczego NetBeans wymięka

Ten postał został przeniesiony na mojego nowego bloga i można go znaleźć tutaj.

13 grudnia 2009

Java i programowanie - jak zacząć i z czego się uczyć

Ten postał został przeniesiony na mojego nowego bloga i można go znaleźć tutaj.

6 sierpnia 2009

Efektywny programista Java, część 3 i 4 - klasy użytkowe i unikanie zbędnego powielania obiektów

Dzisiaj kolejna porcja porad jak efektywnie programować w Javie. Tym razem w jednym poście dwie porady, bo każda z nich jest zbyt krótka, by umieszczać je oddzielnie.

Klasy użytkowe

Prawie w każdym projekcie pojawia się przynajmniej jedna klasa, która gromadzi metody użytkowe wspomagające działanie aplikacji. Taką klasę nazywamy najczęściej klasą pomocniczą (ang. utility class).
Składa się ona z kilku/kilkunastu metod statycznych używanych w różnych miejscach aplikacji. Taka klasa nie powinna umożliwiać tworzenia swojej instancji, gdyż jest to zbędne.

Bardzo łatwo możemy wymusić takie zachowanie na innych programistach tworząc konstruktor prywatny:


public class Utils {

/*
* This is utility class, you don't have to create instance to use it
*/
private Utils() { }

public static String prepareUserStringForEmail(User user) {
// ...
}

public static String createRandomString(int length) {
// ...
}

public static String formatDateWithTime(Date date) {
// ...
}
}



Dzięki takiemu prostemu zabiegowi, nasz kod zyskał na czytelności, a inni programiście nie będą używać naszej klasy w sposób niepoprawny.

Optymalizowanie tworzenia obiektów, których używamy wielokrotnie

Przyjrzyjmy się poniższemu fragmentowi pewnej klasy:


public class Utils {

/*
* This is utility class, you don't have to create instance to use it
*/
private Utils() { }

public static boolean isOlderThanCodeHardGoProBlogOwner(Date birthDate) {
if(birthDate == null) {
throw new IllegalArgumentException("Date can not be null");
}

Calendar calendar = Calendar.getInstance();
calendar.set(1984, Calendar.JULY, 22);
Date codeHardGoProOwnerDateOfBirth = calendar.getTime();

return birthDate.compareTo(codeHardGoProOwnerDateOfBirth) < 0;
}

}

Sprawdzamy tutaj, czy czyjaś data urodzin jest wcześniejsza niż moja. Tworzymy obiekt klasy Calendar, ustawiamy mu odpowiednie wartości roku, miesiąca i dnia, a następnie porównujemy ze zmienną birthDate.
Gdy wywołamy metodę kolejny raz, obiekt calendar zostanie utworzony ponownie i zostanie mu ustawiona ta sama data co poprzednio. Nie jest to rozwiązanie najbardziej optymalne. Widać tutaj wyraźnie, że tworzenie obiektu calendar i ustawianie mu odpowiedniej wartości powinno zostać wydzielone poza metodę i wykonywane tylko raz. Efekt taki możemy uzyskać przenosząc fragment kodu do bloku statycznego, a zmienną klasy Date uczynić prywatną i statyczną:


public class Utils {

private final static Date CODE_HARD_GO_PRO_BLOG_OWNER_BIRTH_DATE;

static {
Calendar calendar = Calendar.getInstance();
calendar.set(1984, Calendar.JULY, 22);
CODE_HARD_GO_PRO_BLOG_OWNER_BIRTH_DATE = calendar.getTime();
}

private Utils() { }

public static boolean isOlderThanCodeHardGoProBlogOwner(Date birthDate) {
if(birthDate == null) {
throw new IllegalArgumentException("Date can not be null");
}

return birthDate.compareTo(CODE_HARD_GO_PRO_BLOG_OWNER_BIRTH_DATE) < 0;
}

}


Teraz nasza metoda będzie wykonywała się trochę szybciej. Oczywiście zysk będzie znacznie większy, gdy taki wielokrotnie używany obiekt będzie bardziej skomplikowany, a jego tworzenie kosztowniejsze i bardziej czasochłonne.

31 lipca 2009

Wicket - się dzieje

Ostatnio społeczność związana z frameworkiem Wicket ma kilka powodów do zadowolenia. Po pierwsze pojawiła się finalna wersja 1.4. Najważniejsza zmiana w porównaniu z wersją 1.3 to całkowite przejście na Javę 1.5 i wprowadzenie generycznego interfejsu IModel wraz z implementacjami. O wszystkich nowościach i zmianach można poczytać tutaj.
Jednocześnie z pojawieniem się wersji 1.4 został opublikowany ostatni upgrade wersji 1.3 o numerze 1.3.7 poprawiający kilkanaście "bugów". Autorzy zapowiadają, że jeśli nie pojawią się krytyczne błędy to ta gałąź nie będzie dalej rozwijana. W związku z tym należy się powoli przygotowywać do migracji na wersję 1.4 w aplikacjach używających Wicketa 1.3.x. Problem dotyczy również i mnie, bo moja magisterka używa wersji 1.3.6.

Kolejnym wydarzeniem istotnym dla fanów Wicketa jest opublikowanie w serwisie RefCardz ściągawki (ang. cheatsheet) o tym frameworku. Na kilku stronach zebrano najważniejsze informacje, najlepsze praktyki i spor porad bardzo przydatnych dla osób używających Wicketa. Całość można pobrać tutaj.

I na koniec informacja bardziej z naszego, polskiego poletka. Paweł Szulc pracuje nad mavenowym archetypem znacznie przyśpieszającym rozpoczęcie pracy z Wicketem. Projekt nazywa się WicketCool i można się z nim zapoznać pod tym adresem. Bardzo podoba mi się pomysł stworzenia czegoś takiego i mam nadzieję, że pomysł zyska na popularności, a Pawłowi należą się podziękowania za poświęcony czas i po prostu za dobrą robotę.
Dodam tylko, że sam projekt miał premierę na prezentacji Pawła podczas tegorocznej Javarsovii.

30 lipca 2009

Efektywny programista Java, część 2 - łatwiejsze tworzenie parametryzowanych kolekcji i obiektów

Tym razem spróbujemy ułatwić sobie tworzenie parametryzowanych typów kolekcji i obiektów. Pierwsza myśl jaka nam przychodzi do głowy to po prostu użycie konstruktora:

// ...
List<User> usersList = new ArrayList<User>();

Map<IpAddress, User> ipUsersMap = new HashMap<IpAddress, User>();

// ...

To jeszcze nie są zbyt skomplikowane przykłady, ale jeśli okaże się, że musimy stworzyć Mapę złożoną z obiektu i innej kolekcji:

// ...


Map<IpAddress, List<User>> ipUserMap = new HashMap<IpAddress, List<User>>();
// ...

to pojawia się spora ilość zdublowanego kodu z informacją o typach parametrów po lewej i prawej stronie deklaracji.

Okazuje się, że można tego uniknąć wykorzystując tzw. "wnioskowanie typów". Kompilator będzie wiedział jaki tym parametryzowanej instancji ma zwrócić na podstawie tego, do jakiej zmiennej będziemy chcieli zwracany obiekt przypisać. Wygodnym sposobem jest zebranie wszystkich takich metod w jednej klasie użytkowej:


package pl.tdziurko.effectivejava

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;

/**
*
* @author Tomasz Dziurko
*/
public class Helper {

// Do not instantiate this class, this is utility class
private Helper() { }

public static <E> ArrayList<E> newArrayList() {
return new ArrayList<E>();
}

public static <E,K> HashMap <E,K> newHashMap() {
return new HashMap<E,K>();
}

public static <E> HashSet <E> newHashSet() {
return new HashSet<E>();
}

}


Teraz możemy zobaczyć o ile krótszy i wygodniejszy jest kod stosujący wnioskowanie typów:


// ...
List<User> usersList = new ArrayList<User>();
List<User> usersList2 = Helper.newArrayList();

Map<IpAddress, User> ipUsersMap = new HashMap<IpAddress, User>();
Map<IpAddress, User> ipUsersMap2 = Helper.newHashMap();

Map<IpAddress, List<User>> ipUserMap = new HashMap<IpAddress, List<User>>();
Map<IpAddress, List<User>> ipUserMap2 = Helper.newHashMap();

// ...



Możemy skorzystać z wnioskowania typów również przy tworzeniu parametryzowanych obiektów innych niż kolekcje, ale tutaj zaleta jego użycia jest widoczna najbardziej.

Efektywny programista Java, część 1 - singletony

Zgodnie z zapowiedzią w poprzednim poście, dzisiaj początek cyklu postów z najciekawszymi poradami z książki "Java. Efektywne programowanie". W pierwszej kolejności postaram się przedstawić te tematy, z którymi programista Java może zetknąć się najczęściej.

Trzy sposoby na tworzenie singletonów


Najczęściej singleton w Javie wygląda następująco:


package pl.tdziurko.effectivejava;

public class Boniek {

public static final Boniek INSTANCE = new Boniek();

private Boniek() {
// stwórz jedyną instancję Zbigniewa Bońka
}

public void doMagicWithBall() {
// dokonaj czegoś niesamowitego na boisku
}

}


W powyższym przykładzie widać od razu, że jest to singleton: prywatny konstruktor i statyczne pole finalne z jedyną instancją klasy dają o tym wystarczająco jasny sygnał.

Drugi sposób przedstawiony w książce jest bardzo podobny:


public class Boniek {

private static final Boniek INSTANCE = new Boniek();

private Boniek() {
// stwórz jedyną instancję Zbigniewa Bońka
}

public Boniek getInstance() {
return INSTANCE;
}

public void doMagicWithBall() {
// dokonaj czegoś niesamowitego na boisku
}
}


Stosując powyższe podejście mamy większą elastyczność przy późniejszych modyfikacjach naszej klasy. Możemy bez problemu (czyli bez widocznych zmian w zewnętrznym wyglądzie klasy) zmienić implementację metody getInstance(), tak aby zwracała nie jedyną instancję Zbigniewa Bońka, ale na przykład jedną instancję dla danego wątku czy danego klienta.

Oba podejścia nie są niestety odporne na refleksję, nie są również przystosowane do serializacji. Jeśli chcemy, aby nasz singleton po serializacji i deserializacji był ciągle singletonem poza dodaniem implements Serializable musimy przeładować metodę readResolve() tak, aby zwracała obiekt INSTANCE.
Z kolei przed refleksją można się uchronić dodając blok w konstruktorze, który rzuca wyjątek w momencie, gdy istnieje już instancja obiektu tej klasy.

Ostatnim (i najbardziej polecanym przez autora książki) sposobem na tworzenie singletonu jest zastosowanie typu wyliczeniowego:


public enum Boniek {

INSTANCE;

void doMagicWithBall() {
// dokonaj czegoś niesamowitego na boisku
}
}


Dzięki zastosowaniu typu wyliczeniowego mamy za jednym zamachem załatwiony problem singletonu, bezpieczeństwa serializacji i zabezpieczenie przed użyciem refleksji. Wszystko zapewnia sam język i nie musimy się o to martwić.

Osobiście najczęściej stosowałem podejście z polem private final static i metodą getInstance(), ale myślę, że zastosowanie typu wyliczeniowego jest bardzo interesującą alternatywą, którą warto dodać do swojego repertuaru :)

28 lipca 2009

Dobiega końca pierwszy (najtańszy) etap rejestracji na Java Developers' Day 2009

Jeszcze tylko kilka dni mają wszyscy, którzy chcą się załapać w pierwszym (najbardziej promocyjnym) etapie rejestracji na konferencję Java Developers' Day 2009 w Krakowie, która odbędzie się 16 października 2009.
Do 31 lipca studenci mogą zaopatrzyć się w wejściówkę już za 167zł, co za całodniową, anglojęzyczną konferencję o Javie jest kwotą bardzo, ale to bardzo małą. Programiści "starsi wiekiem" ;) zapłacą za bilet 219 zł. Im bliżej października tym cena będzie rosła, więc jeśli ktoś wie, że na pewno chcę się wybrać do grodu Kraka posłuchać o Javie, to warto zapłacić już teraz. Oferta last minute dla spóźnialskich to wydatek prawie 600 złotych (swoją drogą last minute kojarzyło mi się zawsze z okazjami, no ale widocznie coś się ostatnio w tej kwestii pozmieniało :))

Niestety agenda nie jest jeszcze gotowa, ale po przejrzeniu relacji z poprzednich edycji mam nadzieję, że tegoroczne JDD nie będzie gorsze.

PS: Sponsorem tego posta jest Dworzec Zachodni w Warszawie, firma PKP i jej spóźniony o ponad 60 minut pociąg.

26 lipca 2009

Java. Efektywne programowanie - wrażenia

Ostatnio kupiłem książkę, na którą miałem chętkę od dłuższego czasu: polską edycję "Effective Java" autorstwa Joshuy Blocha. Przeczytałem o niej sporo pozytywnych opinii i recenzji (tutaj te z Amazon.com) i sam po jej przeczytaniu mogę powiedzieć jedno:

Jeśli za najlepszą książkę z podstawami Javy uważam serię Core Java, to najlepszą książką dla bardziej doświadczonych programistów jest właśnie Java. Efektywne programowanie.

Po jej przeczytaniu moja świadomość jak powinien wyglądać dobry, wydajny i łatwy w modyfikowaniu kod bardzo wzrosła. Poznałem dużo ciekawych i wartych zastosowania podejść i sposobów tworzenia kodu w różnych sytuacjach. Samą książkę czyta się bardzo przyjemnie, choć kilka tematów jest trochę ciężkich i trzeba się mocniej skoncentrować, żeby w pełni zrozumieć rady w nich zawarte.
Porady, które przedstawia autor przydadzą się każdemu programiście, nawet takiemu, który uważa, że o programowaniu w Javie wie bardzo dużo. Ja jeszcze do takich nie należę, więc nowych sztuczek i trików poznałem naprawdę sporo :) Najbardziej przydatne wydały mi się rozdziały o efektywnym tworzeniu nowych obiektów, stosowaniu typów wyliczeniowych i wprowadzaniu typów ogólnych do metod i klas. Najmniej "porywający" był rozdział o serializacji, ale to głównie dlatego, że nigdy wcześniej nie zagłębiałem bardziej się w ten obszar języka.

Dużą zaletą książki jest modularyzacja treści. Każdy temat można czytać oddzielnie. Jeśli jakieś zagadnienie interesuje nas bardziej, swobodnie można do niego przeskoczyć, jeśli jakieś tematy nie wydają nam się w tej chwili warte uwagi, możemy je ominąć bez uszczerbku dla rozumienia dalszych rozdziałów.

Moim zdaniem "Java. Efektywne programowanie" to pozycja warta uwagi. Porady w niej zawarte przydadzą się każdemu programiście Java, więc książka ta powinna leżeć u każdego z nas na półce i być częstym źródłem inspiracji jak tworzyć dobry i efektywny kod. Może nie każdy temat poruszony w książce przyda nam się już tu i teraz, ale warto mieć tak dobre źródło wiedzy pod ręką i często z niego korzystać.

Najciekawsze i moim zdaniem najbardziej przydatne porady postaram się umieścić na blogu, co może zachęci niektórych do zakupu i zapoznania się z całą książką.

13 lipca 2009

SCJP - preparations for exam and impressions after

Readers from Poland: this is English version of my old post written in Polish which has additional information interesting only for Polish readers.

It's been some time since I passed my Sun Certified Java Programmer 1.5 (SCJP) exam, but I hope this post will help somebody to choose their way of preparations to the exam and consequently achieve better final result.


Teaching aids
- SCJP Sun Certified Programmer for Java 5 Study Guide (Exam 310-055), definitely "must have" book for everyone planning to pass SCJP. This book covers everything you could encounter on exam. I read it whole once but some chapters twice (mainly Generics and Threads which I didn't catch on at first).
- simulator EnthuWare JQ+, it costs only 28$ or 18$ (for students) but wihout it my score would have been lower.

- Java Language Specification and javadocs for further reading about small details
- your favourite IDE to test your own ideas and experiment with questions from example tests. Question 'What would be if I change ..." is one you should ask often while learning :)
- forum SCJP at JavaRanch.com, where you can meet many people who already passed SCJP and also authors of SCJP book.


General tips
1. I think trying to pass the exam without doing some example tests isn't good idea. Questions are rather specific and after few hundreds of example questions your speed and accuracy will increase significantly. Additionally you will be able to see some problems and errors right after you look at the code, almost without deeper analyze.
2. Money spent on SCJP simulator are never wasted money.
3. Play with code. If you are unsure about how something works, check it with your IDE. Try changing some code and see how it works then. My experiments with NetBeans give me a lot of additional knowledge.
4. Don't learn too long. I know it's hard to say with great confidence "I am ready for the exam", but you should pick a date (even in 3-4 months) and do not change it unless you are really, really justified. With such deadline in your head, you will be more motivated to learn regularly with better final effect. A friend of mine is learning for SCJP for 6 months without any plan and his preparations seem to be endless :)

More user stories about how people prepare for SCJP could be found at:
JavaRanch SCJP Wall of Fame.

PS: My result was 80% :)

10 maja 2009

Java Persistence API i prosty audyt

Niedawno po raz pierwszy zetknąłem się z zagadnieniem audytu zmian w bazie danych dokonywanych przez użytkowników aplikacji. Chwila googlania i mamy kilka rozwiązań, jedno z nich to ciekawy artykuł na JavaLobby: "Using a Hibernate Interceptor To Set Audit Trail Properties". Jednak istnieje też prostsze rozwiązanie, które chce tutaj przedstawić.

Jeśli korzystamy z Java Persistence API to możemy zastosować dwie bardzo przydatne adnotacje: @PrePersist i @PreUpdate. Załóżmy, że w każdej klasie chcemy mieć dane o tym kto i kiedy utworzył dany rekord oraz kto i kiedy go ostatnio modyfikował. Ponieważ wszystkie encje mają mieć taką funkcjonalność, logikę i zmienne możemy przenieść do klasy abstrakcyjnej, którą będą rozszerzały wszystkie klasy odwzorowujące:


import java.util.Date;
import javax.persistence.*;

@MappedSuperclass
public abstract class BaseEntity {

private String createdBy;
private Date createdOn;
private String modifiedBy;
private Date modifiedOn;

@Column(name="created_by")
public String getCreatedBy() {
return createdBy;
}

@Column(name="created_on")
@Temporal(TemporalType.TIMESTAMP)
public Date getCreatedOn() {
return createdOn;
}

@Column(name="modified_by")
public String getModifiedBy() {
return modifiedBy;
}

@Column(name="modified_on")
@Temporal(TemporalType.TIMESTAMP)
public Date getModifiedOn() {
return modifiedOn;
}

// tutaj powinny byc settery dla zmiennych

@PrePersist
public void prePersist() {
User user = ApplicationUtils.getLoggedUser();
setCreatedBy(user.getName());
setCreatedOn(new Date());
}

@PreUpdate
public void preUpdate() {
User user = ApplicationUtils.getLoggedUser();
setModifiedBy(user.getName());
setModifiedOn(new Date());
}

}

Metody oznaczone adnotacjami są wywoływane tuż przed zapisem obiektu w bazie danych, pozwalając na dodanie informacji o tym kto i kiedy modyfikował rekord.

Wszystkie adnotacje o zbliżonym działaniu znajdujące się w JPA API to PostLoad, PostPersist, PostRemove, PostUpdate, PrePersist, PreRemove i PreUpdate. Dzięki nim możemy być informowani o wszystkich operacjach zapisu/odczytu/usuwania odbywających się na obiektach z bazy danych. Może to być przydatne, gdy przykładowo takie informacje chcemy zapisywać w pliku logowania.

9 maja 2009

Eclipse i problem z generowanymi equals()

Ostatnio w pracy kolega natrafił na następujący problem:
Tabela JSF <rich:dataTable> do prezentacji korzystała z HashMapy z obiektami pobieranymi z bazy danych. Po zapisaniu nowych obiektów do bazy danych i odświeżeniu tabeli, na stronie pokazywały się dane zupełnie inne niż te, które znajdowały się w bazie. Po kilku godzinach bezskutecznych prób poskromienia JSF-a (bo myśleliśmy, że tam jest problem) okazało się, że przyczyna tkwi w zupełnie innym miejscu.

Otóż Eclipse generuje metodę equals() sprawdzając czy klasy porównywanych obiektów sa takie same:

...
іf (getClass() != obϳ.getClass()) {
return fаlse;
}
...

co w połączeniu z Hibernate może sprawić sporo kłopotów, bo często podczas wczytywania danych z bazy zamiast obiektu naszej klasy tworzony jest obiekt proxy rozszerzający klasę, którą wczytujemy. Przez co metoda powyżej zwróci false i obiekty w naszej HashMapie się nie odnajdą. Rozwiązaniem jest proste: należy stosować instanceof zamiast porównywania samych klas.

Dodatkowo, jak to jest opisane w komentarzu na blogu Proxorkut, jeśli stosujemy leniwe ładowanie, to w metodach equals i hashCode należy odwoływać się do zmiennych przez metody getXXX, a nie bezpośrednio, tak, aby Hibernate miał możliwość dociągnięcia z bazy brakujących danych.

30 kwietnia 2009

Servlety - rodzina klas i interfejsów

Ten postał został przeniesiony na mojego nowego bloga i można go znaleźć tutaj.

25 kwietnia 2009

NNTP, Apache Commons Net i polskie znaki w wiadomościach

Jak co weekend, dzisiaj też usiadłem do magisterki. Spośród kilku rzeczy, które sobie zaplanowałem pojawił się temat wysyłania wiadomości/powiadomień na wewnętrzną grupę dyskusyjną mojego akademika za pomocą protokołu NNTP.

Szybkie rozpoznanie pokazało, że całość jest prosta jak przysłowiowa budowa cepa i nie powinna sprawiać żadnych problemów. Poniżej mała klasa pokazująca sposób wysyłania posta na grupę pw.test przy pomocy biblioteki Apache Commons Net.


package pl.tdziurko.nntptest.main;

import java.io.Writer;
import org.apache.commons.net.nntp.NNTPClient;
import org.apache.commons.net.nntp.SimpleNNTPHeader;

/**
*
* @author Tomasz Dziurko
*/
public class Main {

public static void main(String[] args) throws Exception {

NNTPClient client = new NNTPClient();
client.connect("news.ustronie.pw.edu.pl");

client.selectNewsgroup("pw.test");
Writer postArticle = client.postArticle();

SimpleNNTPHeader headers =
new SimpleNNTPHeader("Tomasz Dziurko <tdziurko@gmail.com>", "Test kodowania polskich znaków");
headers.addNewsgroup("pw.test");
headers.addHeaderField("Mime-Version", "1.0");
headers.addHeaderField("Content-Type","text/plain; charset=UTF-8");
headers.addHeaderField("Content-Transfer-Encoding", "8bit");

postArticle.write(headers.toString());
postArticle.write("ąęóśłżźćń - test polskich znaków\r\n");
postArticle.close();

client.completePendingCommand();

client.disconnect();
}

}


Wszystko działało zgodnie z oczekiwaniami do momentu, gdy zacząłem w treści wiadomości używać polskich znaków. Zamiast "ąęóśłżźćń" pojawił się następujący obrazek:


Kombinacje z ustawianiem innego kodowania w nagłówku wysyłanej wiadomości, zmiana kodowania całego projektu czy nawet zmiana ustawień czytnika (tak tak, zacząłem winić nawet biednego Thunderbirda ;) ) nic nie pomogły. Wtedy postanowiłem zajrzeć do źródeł biblioteki ze stajni Apache'a i po krótkim śledztwie znalazłem winowajcę: klasę org.apache.commons.net.nntp.NNTP.


public class NNTP extends SocketClient
{
/*** The default NNTP port. Its value is 119 according to RFC 977. ***/
public static final int DEFAULT_PORT = 119;

// We have to ensure that the protocol communication is in ASCII
// but we use ISO-8859-1 just in case 8-bit characters cross
// the wire.

private static final String __DEFAULT_ENCODING = "ISO-8859-1";

...

/***
* Initiates control connections and gets initial reply, determining
* if the client is allowed to post to the server. Initializes
* {@link #_reader_} and {@link #_writer_} to wrap
* {@link SocketClient#_input_} and {@link SocketClient#_output_}.
***/
@Override
protected void _connectAction_() throws IOException
{
super._connectAction_();
_reader_ =
new BufferedReader(new InputStreamReader(_input_,
__DEFAULT_ENCODING));
_writer_ =
new BufferedWriter(new OutputStreamWriter(_output_,
__DEFAULT_ENCODING));
__getReply();

_isAllowedToPost = (_replyCode == NNTPReply.SERVER_READY_POSTING_ALLOWED);
}

...

}

Jak widać przy tworzeniu zarówno Writera i Readera zastosowano kodowanie ISO-8859-1, czyli nie posiadające w swoich zasobach polskich ogonków. Gdy zaczynałem się zastanawiać w jaki sposób zbuduję całą bibliotekę po zmianie wartości zmiennej __DEFAULT_ENCODING na "UTF-8", zauważyłem w archiwum ze źródłami plik pom.xml. Alleluja! - pomyślałem - Maven lekiem na moje problemy. Teraz wszystko powinno pójść niczym z płatka :)

NetBeans 6.5 z zainstalowanym pluginem pozwala na otwieranie projektów z pliku pom.xml, więc już w IDE mogłem dokonać modyfikacji biblioteki. Następnie uruchomiłem testy i ku mojemu zdziwieniu okazało się, że jeden z nich nie zakończył się sukcesem. Szybkie pytanie do Wielkiego Googla i już wiedziałem, że pewna metoda testująca działa bez problemów tylko na anglojęzycznych systemach operacyjnych (przenośność Javy ;) ). Żeby już nie przedłużać zabawy ze źródłami po prostu ją wykomentowałem i uruchomiłem testy ponownie.


Ufff, udało się :) Teraz mogłem zbudować zmodyfikowaną bibliotekę i podczepić ją do mojego projektu wysyłającego wiadomość testową.
Poniżej można zobaczyć efekty, wszystkie polskie znaki są prawidłowo wysyłane na grupę dyskusyjną.

21 kwietnia 2009

SCWCD - Cykl życia serwletu

Ten postał został przeniesiony na mojego nowego bloga i można go znaleźć tutaj.

SCWCD - zaczynamy przygotowania

Ten postał został przeniesiony na mojego nowego bloga i można go znaleźć tutaj.

16 kwietnia 2009

Trochę historii - moje wrażenia z nauki do SCJP

Od mojego egzaminu Sun Certified Java Programmer (SCJP) w wersji 1.5 minęło już trochę czasu, ale być może ten post pomoże komuś w wyborze sposobu przygotowań i przyczyni się do uzyskania lepszego wyniku.

Pomoce naukowe
- SCJP Sun Certified Programmer for Java 5 Study Guide (Exam 310-055) , po prostu 'must have' dla każdego planującego zdobycie SCJP. W tej książce jest wszystko co może Cię spotkać na egzaminie. Całość przeczytałem raz, niektóre rozdziały, z których czułem się najmniej pewnie (Generics, Threads) dwukrotnie.
- symulator EnthuWare JQ+, koszt tylko niecałe 28$ lub 18$ (zniżka dla studentów), a bez niego miałbym pewnie kilkanaście procent mniej.
- specyfikacja i api języka do doczytywania niuansów
- IDE to testowania własnych pomysłów na pytania i zabawy kodem z przykładowych pytań
- blog Mariusza Lipińskiego O technologiach dla języka Java, w którym autor opisuje swoje przygotowania do egzaminu (coś w klimatach podsumowań 'Two Minute Drill' z książki Sierry & Bates, tylko przyjemniej się czyta).
- forum SCJP na JavaRanch.com, gdzie udzielają się także autorzy polecanej przeze mnie książki.

Uwagi ogólne
1. Bez przerobienia sporej ilości testów lepiej nie podchodzić do egzaminu. Pytania są specyficzne, a po przerobieniu kilkunastu zestawów szybkość i skuteczność wychwytywania drobnych niuansów znacznie wzrasta.
2. Pieniądze wydane na symulator nigdy nie są pieniędzmi wyrzuconymi w błoto.
3. Eksperymentuj. Nie jesteś czegoś pewny? Zastanawiasz się co będzie jeśli pytanie wyglądałoby trochę inaczej? Odpal środowisko i przetestuj swoje pomysły! Moje zabawy z IDE sporo mi dały.
4. Nie przeciągaj przygotowań. Ustal sobie jakąś datę, nawet za 3-4 miesiące, kiedy chcesz przystąpić do egzaminu i konsekwentnie do niej dąż. Mając w głowie 'deadline' będziesz systematyczniej pracował, aby zdążyć na czas. Mam znajomego, który uczy się do SCJP już ponad pół roku bez żadnych planów kiedy chce podejść do egzaminu i końca jego przygotowań nie widać :)

Formalności
Przelanie odpowiedniej kwoty, przesłanie potwierdzenia przelewu i otrzymanie vouchera zajęło mi 2 dni. Załapałem się na promocję Suna (trzeba było podać odpowiedni kod promocji przy zamawianiu vouchera) pozwalającą na ponowne darmowe podejście do egzaminu w przypadku nie zaliczenia. Na szczęście nie musiałem z niej skorzystać :)
Egzamin zdawałem w BizTech Konsulting w Warszawie. Niewielka sala z klimatyzacją, dwa stanowiska, przyjemna obsługa. Przy planowaniu terminu podejścia do egzaminu warto zadzwonić wcześniej, bo może się okazać, że wolne terminy są za 1-2 tygodnie, więc lepiej nie czekać z tym na ostatnią chwilę.

Więcej o indywidualnych sposobach przygotowań do egzaminu oraz wrażeniach już po można przeczytać:
- na portalu GoldenLine (forum grupy SCJP)
- na SCJP Wall of Fame na JavaRanch - linki do 'story' przy niektórych osobach z listy

PS: A sam zdałem na 80% :)