Powrót do konwerterów, ale tym razem w kontekście tabeli.

Problem

Mamy sobie tabelkę wyświetlającą jakieś tam dane. W jednej z kolumn chcemy wyświetlić informację o polu obiektu, które to pole nie jest prymitywne/boxowane/stringiem (jeżeli jest to vaadinowy komponent GUI to zostanie osadzony w komórce, ale to jest wyjątek). Przykładowo:

Listing 1. definicja tabeli

Table table = new Table("Informacje o oddziałach");
table.addContainerProperty("id", Long.class, null);
table.addContainerProperty("name", String.class, "");
table.addContainerProperty("address", Address.class, null);
table.setColumnHeaders("id", "Nazwa", "Adres");

Jak widać w ostatniej kolumnie chcemy umieścić adres oddziału. Niby nic, ale jeżeli przekażemy obiekt bezpośrednio to zostanie na nim wywołana metoda toString i dostaniemy jakieś syfne dane. Oczywiście można pokusić się o napisanie tej metody tak by zwracała dane sformatowane w jakiś domyślny sposób. Można też użyć konwertera.

Konwerter i tabela

Do każdej kolumny w tabeli można podpiąć konwerter w następujący sposób:

Listing 2. dodawanie konwertera

table.setConverter("address", new AddressConverter());

Sam konwerter jest już prosty do napisania. Wystarczy, że zaimplementujemy interfejs Converter<CEL, ŹRÓDŁO>:

Listing 3. implementacja konwertera

private class AddressConverter implements Converter<String, Address> {
	@Override
	public Address convertToModel(String value, 
		Class<? extends Address> targetType,
		Locale locale) throws ConversionException {
		return addressDao.fromString(value);
	}

	@Override
	public String convertToPresentation(Address value, 
		Class<? extends String> targetType, 
		Locale locale) throws ConversionException {
		return value == null ? "" : value.street()+"/"+value.city();
	}

	@Override
	public Class<Address> getModelType() {
		return Address.class;
	}

	@Override
	public Class<String> getPresentationType() {
		return String.class;
	}
}

I w sumie było by to na tyle… interfejs jest paskudny i trochę dużo zbędnego kodu tu, ale inaczej się nie da…

Ale jak masz Javę 8

Można się pokusić o napisanie tego wszystkiego lambdami, co trochę ułatwi życie i może wprowadzić Sajgon w kodzie jak radośnie nazwiecie zmienne, co też zademonstruję.

Na początek potrzebujemy funkcję przyjmująca trzy argumenty:

Listing 4. funkcja o trzech argumentach

@FunctionalInterface
public interface TriFunction<T, U, V, R> {

	R apply(T t, U u, V v);
}

Następnie potrzebujemy fabrykę, która będzie nam produkować nasze konwertery na podstawie lambd:

Listing 5. fabryka konwerterów

public class ConverterFactory {

	public static <T, S> Converter<T, S> get(
		TriFunction<T, Class<? extends S>, Locale, S> toModel,
	        TriFunction<S, Class<? extends T>, Locale, T> toPresentation,
	        Class<S> modelType, 
		Class<T> presentationType) {
		
		return new Converter<T, S>() {
			@Override
			public S convertToModel(T value, 
			                        Class<? extends S> targetType, 
			                        Locale locale) throws ConversionException {
				return toModel.apply(value, targetType, locale);
			}

			@Override
			public T convertToPresentation(S value, 
			                               Class<? extends T> targetType, 
			                               Locale locale) throws ConversionException {
				return toPresentation.apply(value, targetType, locale);
			}

			@Override
			public Class<S> getModelType() {
				return modelType;
			}

			@Override
			public Class<T> getPresentationType() {
				return presentationType;
			}
		};
	}
}

niezłe prawda? To tutaj to są funkcje wyższego rzędu w praktyce. Bardzo przydatne zastosowanie teorii (w językach bardziej funkcyjnych można to zrobić ładniej).

I teraz nasz konwerter można zapisać tak:

Listing 6. konwerter z lambd

table.setConverter("address", 
	new ConverterFactory().get((a, b, c) -> addressDao.fromString(a),
			(a, b, c) -> a == null ? "" : a.street()+"/"+a.city(),
			Role.class,
			String.class));

Elegancko. No prawie 😉 Popatrzcie na nazwy zmiennych w lambdach i zapamiętajcie, że nie należy takich rzeczy robić w kodzie produkcyjnym. Niestety Java nie udostępnia mechanizmu parametru domyślnego jak Scala reprezentowanego znakiem \_, czy też parametru ignorowanego jak Erlang (ten sam znaczek). Ja wypracowałem sobie konwencję, że jeżeli jakiś parametr jest nieużywany to nazywam go $ jeżeli mam kilka takich parametrów, to liczba znaków $ rośnie. Jak będzie ich więcej niż dwa to oznacza, że trzeba refaktorować.

Dziękuję za to, ze dotrwałeś do końca. Jeżeli spodobał ci się ten wpis to podziel się nim ze znajomymi korzystając z przycisków poniżej. Do tego one służą.