czwartek, 30 września 2010

Mechanizm refleksji - dostęp do prywatnych atrybutów.

Oczywiście mechanizm refleksji, umożliwia dostęp nie tylko do metod prywatnych, ale również prywatnych atrybutów klasy. Krótki kodzik poniżej, gdyby ktoś potrzebował dostać się do takiego prywatnego pola.

private Integer getPrivateField(SomeClass someClass) 
    throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {

    Field field = SomeClass.class.getDeclaredField("privateInteger");
    field.setAccessible(true); 
 
    return (Integer) field.get(someClass); 
}

public class SomeClass{
    
    private Integer privateInteger;
    ...
}

piątek, 17 września 2010

Testowanie metod prywatnych w javie

Pomijając dysputy o tym, czy w ogóle można, czy też nie można testować metod prywatnych. Zamieszczam przykład jak to zrobić jeśli, ktoś będzie w sytuacji, że po prostu musi to zrobić.

Mamy przykładowo klasę do stestowania:
public class SomeClass{
    ...
    private Boolean someMethod(String s, Integer i){
        ...
    }    
} 

Podczas testów używam mechanizmu refleksji i schematu given/when/then, o którym więcej można posłuchać tutaj. Przykładowa klasa testująca metodę prywatną z klasy SomeClass może być następująca:
import static org.fest.assertions.Assertions.assertThat;
import static org.fest.assertions.Fail.fail;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.junit.Before;
import org.junit.Test;

public class SomeClassTest {
    
    SomeClass someClass;
    
    @Before
    public void init() {
        someClass = new SomeClass();    
    }
    
    @Test
    public void shouldDoSomethingGood() {        
        //given
        //sekcja przygotowująca
        
        //when
        Boolean result=null;
        try {
            result = invokePrivateMethodSomeMethod("test",10);
        } 
        catch (Exception e) {
            e.printStackTrace();
            fail("Nie oczekujemy wyjątku");
        }        
        //then
        assertThat(result).isTrue();    
    }
    
    private Boolean invokePrivateMethodSomeMethod(String s, Integer i) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException{
    
        Class[] args =  {String.class, Integer.class}; 
        //niestety musimy wpisać nazwę metody jako string - niezbyt dobre rozwiązanie w przypadku późniejszego refaktoringu
        Method method = SystemUtils.class.getDeclaredMethod("someMethod", args);
        method.setAccessible(true);
        Object[] argObjects = {s,i};
        return (Boolean) method.invoke(someClass, argObjects);
    }
}

piątek, 10 września 2010

Spring form:checkboxes a JSF h:selectManyCheckbox

Krótka notka dla wszystkich, którzy przesiedli się z JSF'a na Spring'a (wersja 2.5.6) i przyszło im użyć tagu form:checkboxes. Idea obu tagów jest taka sama, czyli jak "przyjemnie" obsłużyć na formularzu wiele checkboxów.

Załóżmy, że mamy encje Employee i Language, które są w relacji wiele-do-wielu.
Aby wyświetlić na formularzu wszystkie checkboxy (języki) z zaznaczonymi tymi, które dany pracownik zna. W JSF używamy:


  


To samo w Spring'u wygląda następująco


Pominę kwestie skąd i jak wstrzykiwane są allLanguages i languages. 

Wszystko teoretycznie powinno działać, ale niestety nie jest tak różowo. Pojawia się następujący błąd:
Failed to convert property value of type [java.lang.String] to required type [java.util.Set] for property languages; nested exception is java.lang.IllegalArgumentException: Cannot convert value of type [java.lang.String] to required type [emp.entity.Language] for property languages[0]: no matching editors or conversion strategy found
Przyczyną błędu jest brak odpowiedniego PropertyEditor'a dla Language. Przykładowy editor dla klasy Language:
public class LanguageEditor extends PropertyEditorSupport {    
    private LanguageManager languageManager;   
    public LanguageEditor(LanguageManager languageManager) {
        super();
        this.languageManager = languageManager;
    }
    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        setValue(languageManager.getLanguageById(Long.parseLong(text)));
    }    
    @Override
    public String getAsText() {        
        Language language = (Language) getValue();
        return language == null ? null :  Long.toString(language.getId());
    }    
}

W moim przypadku metoda setAsText używa managera, który pobiera encje Language o danym id z bazy. Trzeba jeszcze tylko dodać tego editora w metodzie initBuilder w kontrolerze który odpowiada za obsługę formularzu:

public class EmployeeEditController extends SimpleFormController{
    ...
    @Override
    protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
    super.initBinder(request, binder);
    binder.registerCustomEditor(Language.class, new LanguageEditor(languageManager));
    
    .... 

} 

I coś takiego rozwiązuje zgłaszany błąd.