NetBeans 6.5 - źródła zhackowane

Siedzę sobie właśnie w mieszkaniu, po obfitym obiedzie u mojej cioci (polędwiczki były wyśmienite :-) i raczę się swojskim winem. Będąc w tym błogostanie postanowiłem zobaczyć, czy dzisiaj faktycznie mam dobry dzień i wszystkie problemy rozwiązuję w mgnienie oka.

I cóż innego miałbym robić, jak nie zająć się rozwiązaniem problemu ze środowiskiem NetBeans - Issue 148131: IllegalArgumentException: Called DataObject.find on null, z którym to już walczę od jakiegoś czasu. Z pozytywnym nastawieniem z dzisiejszego przedpołudnia NetBeans 6.5 - Graal zdobyty! przystąpiłem do tropienia błędu we wtyczce Axis2.

Błąd wydaje się trywialny, mamy podaną klasę oraz linię w której otrzymujemy NullPointerException (NPE)

java.lang.IllegalArgumentException: Called DataObject.find on null
        at org.openide.loaders.DataObject.find(DataObject.java:474)
        at org.netbeans.modules.websvc.axis2.wizards.WsFromJavaWizardIterator.instantiate(WsFromJavaWizardIterator.java:100)
        at org.openide.loaders.TemplateWizard.handleInstantiate(TemplateWizard.java:595)
        at org.openide.loaders.TemplateWizard.instantiateNewObjects(TemplateWizard.java:416)
        at org.openide.loaders.TemplateWizardIterImpl.instantiate(TemplateWizardIterImpl.java:248)
        at org.openide.loaders.TemplateWizardIteratorWrapper.instantiate(TemplateWizardIteratorWrapper.java:161)
        at org.openide.WizardDescriptor.callInstantiateOpen(WizardDescriptor.java:1387)
        at org.openide.WizardDescriptor.callInstantiate(WizardDescriptor.java:1341)
        at org.openide.WizardDescriptor.access$1600(WizardDescriptor.java:119)
        at org.openide.WizardDescriptor$Listener$2$1.run(WizardDescriptor.java:1908)

W przypadku NPE nie patrzymy gdzie faktycznie pojawił się błąd, tylko na wcześniejsze wywołanie, bo to ta metoda wywołała inną przekazując null zamiast referencji, co w tym przypadku oznacza, że problemu należy szukać w WsFromJavaWizardIterator w linii 100. Mając zbudowane środowisko testowe NetBeans ze źródeł oraz zainstalowany ostatni Nigthly Build nie pozostaje nic tylko otworzyć projekt websvc.axis2 i rozpocząć polowanie na robala (ang. bug hunting)

Projekt websvc.axis2

Po otwarciu projektu otwieramy klasę WsFromJavaWizardIterator, pomocny okaże się skrót klawiszowy SHIFT + ALT + O, i następnie za pomocą CTRL + G skaczemy do linii 100.

Źródła WsFromJavaWizardIterator.java

Niby wszystko wygląda w porządku

    FileObject template = Templates.getTemplate( wiz );
    if (!((Boolean)wiz.getProperty(WizardProperties.PROP_GENERATE_SAMPLE_METHOD)).booleanValue()) {
        template = template.getParent().getFileObject("EmptyWebService","java"); //NOI18N
    }
    DataObject dTemplate = DataObject.find( template );

Błąd pojawia się w linii

DataObject dTemplate = DataObject.find( template );

a to oznacza, że przekazywany obiekt template jest null, a jako że cały problem występuje tylko przy odznaczonej fladze Generate Sample Method to problem zawęża się do klauzuli if() {...}, czyli wywołanie

getFileObject("EmptyWebService","java")

zwraca nieszczęsnego nulla.

Sprawdzenie JavaDoca dla metody getFileObject() daje mi informację, że zwraca ona referencję do pliku lub folderu, a że pewnie jest używana w milionie innych miejsc w środowisku, zakładam, że jej implementacja jest prawidłowa (to tak jak gra w brydża, gra się na największe możliwe prawdopodobieństwo ;-). W takim przypadku problemem są parametry przekazane do tej metody, same w sobie nie są puste ani null więc może plik o podanej nazwie nie istnieje. I znów za pomocą SHIFT + ALT + O wyszukuję EmptyWebService

Wyszukiwanie EmptyWebService

Plik istnieje

Niestety, ślepy trop, plik istnieje, jednak co można zauważyć nie zgadza się rozszerzenie pliku, metoda szuka EmptyWebService z rozszerzeniem java a plik ma rozszerzenie template, to może być to, zmieniam rozszrzenie i uruchamiam projekt.

    FileObject template = Templates.getTemplate( wiz );
    if (!((Boolean)wiz.getProperty(WizardProperties.PROP_GENERATE_SAMPLE_METHOD)).booleanValue()) {
        template = template.getParent().getFileObject("EmptyWebService","template"); //NOI18N
    }
    DataObject dTemplate = DataObject.find( template );

Jest to rewelacyjna cecha projektu NetBeans, za pomocą środowiska NetBeans możemy debugować środowisko NetBeans :D
Dobra, projekt uruchomiony, przechodzę te same kroki jak w Netbeans - chyba znów go polubiłem..., czyli tworze bibliotekę, dodaje do niej WebService na bazie Axis2 i odznaczam flagę Generate Empty Method

Test wizarda

i naciskam Finish mając nadzieję, że to jest to

A jednak błąd...

Niestety, błąd ciągle się pojawia, wracam więc do źródeł. Tym razem ustawiam Punkt Przerwania (ang. Break Point) na linii 96 i jeszcze raz uruchamiam projekt w trybie debugowania, tak aby przejść całą ścieżkę wywołań. Klasa FileObject jest abstrakcyjna a nie wiem jak w NetBeans wyszukać wszystkie implementacje danej klasy, ktoś wie?

Intensywne używanie klawisza F8 - Step Over oraz F7 - Step Into doprowadza mnie do klasy AbstractFolder oraz metody getChild(String name, boolean onlyValid)

    private final AbstractFolder getChild(String name, boolean onlyValid) {
        Reference<AbstractFolder> r = map.get(name);
 
        if (r == null) {
            //On OpenVMS, see if the name is stored in a different case
            //to work around a JVM bug.
            //
            if (Utilities.getOperatingSystem() == Utilities.OS_VMS) {
                if (Character.isLowerCase(name.charAt(0))) {
                    r = map.get(name.toUpperCase());
                } else {
                    r = map.get(name.toLowerCase());
                }
 
                if (r == null) {
                    return null;
                }
            } else {
                return null;
            }
        }
	...

Problem leży w brakującym wpisie w obiekcie map dla podanej nazwy, czyli EmptyWebService.template, dziwne, ten plik istnieje, ale nie jest ujęty w mapie dostępnych obiektów (szczegóły obiektu map można uzyskać za pomocą inspekcji wartości). To oznacza, że coś go tam nie ładuje, ale jeśli ładuje inne obiekty (jeśli przejrzy się elementy mapy), to oznacza, że gdzieś jest jakiś błąd konfiguracyjny, który powoduje, że pod szukaną nazwą EmptyWebService.template nie ma nic. Idąc tym tropem znajduję plik mf-layer.xml, który wygląda jak definicja wirtualnego systemu plików

    <folder name="Templates">
        <folder name="WebServices">
            <attr name="SystemFileSystem.localizingBundle" stringvalue="org.netbeans.modules.websvc.axis2.wizards.Bundle"/>
            <!--<attr name="position" intvalue="1500"/>-->
            <file name="AxisFromJava.java" url="nbres:/org/netbeans/modules/websvc/axis2/resources/WebService.template">         
                <attr name="template" boolvalue="true"/>
                <attr name="position" intvalue="3100"/>
                <attr name="javax.script.ScriptEngine" stringvalue="freemarker"/>
                <attr name="templateWizardIterator" newvalue="org.netbeans.modules.websvc.axis2.wizards.WsFromJavaWizardIterator"/>
                <attr name="templateCategory" stringvalue="axis"/>
                <attr name="templateWizardURL" urlvalue="nbresloc:/org/netbeans/modules/websvc/axis2/resources/WebService.html"/>
                <attr name="SystemFileSystem.localizingBundle" stringvalue="org.netbeans.modules.websvc.axis2.wizards.Bundle"/>
                <attr name="SystemFileSystem.icon" urlvalue="nbresloc:/org/netbeans/modules/websvc/axis2/resources/axis_node_16.png"/> 
            </file>
            <file name="AxisFromJavaEmpty.java" url="nbres:/org/netbeans/modules/websvc/axis2/resources/EmptyWebService.template">
                <attr name="javax.script.ScriptEngine" stringvalue="freemarker"/>
                <attr name="position" intvalue="3101"/>
            </file>
            <file name="AxisFromWsdl.java" url="nbres:/org/netbeans/modules/websvc/axis2/resources/EmptyWebService.template">         
                <attr name="template" boolvalue="true"/>
                <attr name="position" intvalue="3200"/>
                <attr name="javax.script.ScriptEngine" stringvalue="freemarker"/>
                <attr name="templateWizardIterator" newvalue="org.netbeans.modules.websvc.axis2.wizards.WsFromWsdlWizardIterator"/>
                <attr name="templateCategory" stringvalue="axis"/>
                <attr name="templateWizardURL" urlvalue="nbresloc:/org/netbeans/modules/websvc/axis2/resources/WebService.html"/>
                <attr name="SystemFileSystem.localizingBundle" stringvalue="org.netbeans.modules.websvc.axis2.wizards.Bundle"/>
                <attr name="SystemFileSystem.icon" urlvalue="nbresloc:/org/netbeans/modules/websvc/axis2/resources/axis_node_16.png"/> 
            </file>
            <file name="AxiomService.java" url="nbres:/org/netbeans/modules/websvc/axis2/resources/EmptyAxiomService.template">
                <attr name="javax.script.ScriptEngine" stringvalue="freemarker"/>
                <attr name="position" intvalue="3201"/>
            </file>
        </folder><!-- Axis2 -->
    </folder>

Jak widać nie ma nigdzie wpisu file name o zawartości EmptyWebService.template, eksperymentując z różnymi konfiguracajami dla elementu <file name="AxisFromJavaEmpty.java"...> dochdzę do wniosku, że konfiguracja jest ok, tylko że wywołanie jest ze złą nazwą. Wracam do klasy WsFromJavaWizardIterator i wywołanie

template = template.getParent().getFileObject("EmptyWebService","java"); //NOI18N

zamieniam na

template = template.getParent().getFileObject("AxisFromJavaEmpty","java"); //NOI18N

czyli na nazwę jaką mam zdefiniowaną w mf-layer.xml i ponownie uruchamiam projekt, przechodząc poprzednie kroki i tym razem pełny sukces, generuje się klasa bez przykładowej metody

Pusta klasa serwisu

Pozostaje tylko przygotować patcha i dodać go do zgłosznia błędu jako rozwiązanie :-)

Pozdrawiam
--
Łukasz
http://www.lenart.org.pl/