Jarek Przygódzki. Blog programisty

Ogólne przemyślenia na temat rzemiosła / sztuki / nauki programowania i tematów pokrewnych.

Sortowanie kolekcji z wykorzystaniem wyrażeń lambda w Java 8

leave a comment »

Sortowanie kolekcji obiektów po wybranym atrybucie

Jedną z rzeczy które zawsze irytowały mnie w Javie była ilość kodu która trzeba było napisać żeby posortować kolekcję zawierającą typu zdefiniowane przez użytkownika po kluczu który implementuje już interfejs Comparable. Weźmy prosty obiekt POJO

class Person {

    public Integer   id;
    public String    firstName;
    public String    lastName;
    public LocalDate birthDate;

    public Person(Integer id, String firstName, String lastName, LocalDate birthDate) {
        this.id        = id;
        this.firstName = firstName;
        this.lastName  = lastName;
        this.birthDate = birthDate;
    }

    @Override
    public String toString() {
        return String.format(
                "Person {id=%d, firstName=%s, lastName=%s, birthDate=%s}",
                id, firstName, lastName, birthDate);
    }
}

Jak posortować listę takich obiektów po dacie urodzin? Jednym z popularnych sposobów jest zdefiniowanie komparatora jako klasy anonimowej

	Collections.sort(personList, new Comparator<Person>() {
		@Override
		public int compare(Person p1, Person p2) {
			return p1.birthDate.compareTo(p2.birthDate);
		}
	   });
   }

Kod nie wygląda zbyt elegancko a co więcej nie uwzględnia sytuacji, gdy data zatrudnienia będzie równa null
Można też oczywiście zdefiniować komparator w osobnej klasie, ma to sens gdy takie sortowanie powtarza się w wielu miejscach gdyż umożliwia to ponowne wykorzystanie kodu


class PersonByBirthDateComparator implements Comparator<Person>() {
	@Override
	public int compare(Person p1, Person p2) {
		return p1.birthDate.compareTo(p2.birthDate);
	}
   });
}
Collections.sort(personList, new PersonByBirthDateComparator());

Biblioteka na ratunek?

Skoro sortowanie kolekcji obiektów po wybranym atrybucie jest tak uciążliwe, to na pewno musi istnieć jakaś biblioteka która to usprawnia – lenistwo jest przecież jedną z cnót programistów, prawda?
I rzeczywiście, częściowo tak jest

Google Guava

Korzystając z klasy Ordering możemy problem sortowanie rozwiązać tak

Collections.sort(personList, Ordering.natural().
	onResultOf(new Function<LocalDate, Person>() {
	  public String call(Person p) {
		 return p.birthDate;
	  }))
  .nullsLast();

Znowu nie wygląda to dobrze, ale wyrażenia lambda ratują sytuację

Collections.sort(personList, Ordering.natural().
	onResultOf(p -> p.birthDate).
	nullsLast();

Apache Commons BeanUtils

Innym rozwiązaniem jest wykorzystanie klasy BeanComparator

Collections.sort(personList, new BeanComparator("birthDate"));

Kod wygląda dobrze, ale w praktyce ma dwie poważne wady. Pierwszą z nich jest duży narzuty wydajnościowy wynikający z dostępu do pól klasy przez refleksję. Drugim problem występuje przy automatycznej refaktoryzacji – jeśli ktoś ma tak jak podświadomy nawyk wciskania Alt+Shift+R gdy widzi nazwę która mu się nie podoba może narobić niezłego bałaganu

Czy może być lepiej

W dotychczasowych dywagacji nasuwają się niezbyt optymistyczne wnioski – a jak dotąd poruszyliśmy tylko prostszy wariant problemu. Co w sytuacji, gdy chcemy posortować obiekty po kluczu złożonym (tj. po kilku polach na raz)? Dlaczego sortownie po kluczu który implementuje już interfejs Comparable jest tak niewygodne?

Z przekonania, że może być lepiej narodził się projekt jarek-przygodzki/more-collections. Nie jest to jeszcze w żadnym razie dzieło skończone, co więcej kod obsługujący sortowanie po kluczach złożonych nie jest zbyt elegancki (ale tutaj chyba lepiej być nie może ze względu na ograniczenia typów uogólnionych) – ale efekty i tak są zachęcające

// Sortowanie imieniu
MoreCollections.sort(personList, p -> p.firstName);
// Sortowanie po  dacie urodzenia
MoreCollections.sort(personList, p -> p.birthDate);
// Sortowanie po kluczu złożonym [lastName, firstName], tj. najpierw po nazwisku
// a potem po imieniu
MoreCollections.sort(personList,
    p -> p.lastName,
    p -> p.firstName);

Jak to działa

Rozwiązanie opiera się o obiekty funkcyjne java.util.function.Function<T, R> które służą to wyłuskania z obiektu typu T klucza sortowania typu R gdzie typ R musi spełniać ograniczenie R extends Comparable<? super R>. Następnie na podstawie takiego operatora wyłuskania (lub kilku) budowany jest specjalny komparator i przekazywany to metody Collections.sort

Comparator<T>; comparator = CombinedComparator.build(propertyAccessor);
Collections.sort(list, comparator);

Na resztę rozwiązania składają się klasy CombinedComparator łącząca wiele komparatorów w jeden i CombinedComparator.ByPropertyComparator która sortuje obiekty typu T względem klucza zwracanego przez wskazaną funkcję. Sam kod biblioteki nie korzysta co prawda z funkcjonalności wersji 8, ale wyrażania lambda pozwalają pisać bardzo idiomatyczny kod z niej korzystający.

Written by Jarek Przygódzki

Wrzesień 18, 2014 at 9:39 pm

Napisane w Java

Tagged with , , ,

Java3D w OSGi

leave a comment »

W repozytorium jarek-przygodzki/Java3D-OSGi znajdują się eksperymenty z integracją biblioteki Java3D z OSGi (szczegóły dotyczące poszczególnych rozwiązań są w komentarzach Git). Szczególnie w przypadku wersji 1.6 mamy dużo ciekawych możliwości. Wszystkie mają swoje wady i zalety, ale w przypadku aplikacji opartej o Eclipse RCP najlepszym jest rozwiązanie z pakunkiem częściowym dla każdej kombinacji os/ws/arch i wykorzystanie dyrektywny Eclipse-PlatformFilter w manifeście. Bardzo ładnie komponuje się zarówno z p2 jak i ze środowiskiem deweloperskim.

Written by Jarek Przygódzki

Wrzesień 16, 2014 at 9:41 pm

Napisane w Java

Tagged with , , ,

Vaadin Eclipse Plugin i projekt oparty o Maven – kompilacja motywu

leave a comment »

Od pewnego czasu mam do czynienia z Vaadin Framework ( w kombinacji Vaadin/WildFly/Maven/Eclipse). Integracja przebiegała doskonale z wyjątkiem pewnego drobiazgu – próba kompilacji motywu kończyła się błędem „Select a theme file (.scss) or a Vaadin project to compile.”. W zasadzie nie przeszkadzałoby mi to specjalnie bo w trybie deweloperskiem Vaadin potrafi kompilować pliki SCSS w locie gdyby nie ogromne opóźnienia (rzędu 10s-100s) przy uruchomieniu serwera w trybie Debug (opisane tutaj). Obejściem jest oczywiście ręczna kompilacja SCSS – tyle że ta kończyła się komunikatem „Select a theme file (.scss) or a Vaadin project to compile.”. W swoim czasie zignorowałem ten problem; dodałem do pliku POM nową zależność

    <dependency>
        <groupId>com.vaadin</groupId>
        <artifactId>vaadin-themes</artifactId>
        <version>${vaadin.version}</version>
    </dependency>

i w razie potrzeby kompilowałem motyw z linii poleceń

mvn vaadin:compile-theme

W końcu ciekawość wzięła górę i postanowiłem udać się do źródeł (przypadkowa gra słów)… i pojawił się problem. Gdzie one są? Większość projektów Vaadin jest na GH oraz na witrynie dev.vaadin.com/git/ – ale pluginu dla Eclipse tam nie ma. W końcu udało mi się je znaleźć na http://dev.vaadin.com/svn/integration/eclipse/ – ale nie było to bynajmniej pierwsze miejsce w którym szukałem. Tutaj mała dygresja – plugin nie działa w pełni w Eclipse 4.4 (Luna) ze względu na wykorzystanie klasy org.eclipse.mylyn.internal.provisional.commons.ui.AbstractNotificationPopup która w nowej wersji Mylyna zmieniła nazwę na org.eclipse.mylyn.commons.ui.dialogs.AbstractNotificationPopup

Kod odpowiadający za kompilację motywu znajduje się w klasie com.vaadin.integration.eclipse.handlers.CompileThemeHandler a problem tkwił w metodzie compileFile gdzie znajduje się sprawdzenie, czy projekt ma naturę Vaadin

    // com.vaadin.integration.eclipse.VaadinFacetUtils.isVaadinProject(IProject)
    /**
     * Check whether a project has the Vaadin project nature.
     * 
     * @param project
     * @return true if the project is an Vaadin project
     */
    public static boolean isVaadinProject(IProject project) {
        if (project == null) {
            return false;
        }

        try {
            IFacetedProject fproj = ProjectFacetsManager.create(project);
            return fproj != null &amp;&amp; fproj.hasProjectFacet(VAADIN_FACET);
        } catch (CoreException e) {
            ErrorUtil.handleBackgroundException(e);
            return false;
        }
    }

Okazało się, że wystarczy dodać facet Vaadin Plug-in for Eclipse 7.0 w konfiguracji projektu i kompilacja zaczęła działać. Jest to oczywiście rozwiązanie chwilowe – katalog .settings jest wyłączony z kontroli SCM – więc przy następnym pobraniu projektu z repozytorium trzeba będzie to potwórzyć :-< .

Written by Jarek Przygódzki

Wrzesień 16, 2014 at 9:19 pm

Napisane w Java

Tagged with , ,

Binarne patche w dystrybucji oprogramowanie i uruchamianie bibliotek Javy w .NET

leave a comment »

Badałem niedawno kwestię wykorzystania binarnych, przyrostowych patchy w dystrybucji oprogramowania. Głownie interesowały mnie dwie kwestie

  • jak to zrobić
  • na jakie oszczędności można liczyć

Podsumowując, bardzo pozytywnie zaskoczył mnie projekt xdelta a negatywnie xdeltaencoder. Na korzyść tego drugiego przemawia tylko fakt, że jest napisany w Javie. Wadą obu projektów jest licencja GPLv2  która uniemożliwia ich wykorzystanie w projektach zamkniętym kodzie. GPLv2  jest  licencją wirusową - każdy program zlinkowany z biblioteką na takiej licencji to tzw.  dzieło pochodne (derived work). 

Pomimo rozczarowania postanowiłem przeprowadzić mały eksperyment i uruchomić tą bibliotekę w środowisku .NET korzystając z IKVM.NET co okazało się… zdumiewająco proste. Wynik jest na GitHubie w repozytorium XDeltaEncoderNet

Co ciekawe, patche wygenerowany przez JVM i .NET zwykle odrobinę się różnią, co może wynikać z różnic w implementacji algorytmów kompresji.

Written by Jarek Przygódzki

Sierpień 19, 2014 at 9:34 pm

Napisane w .NET, Java

Tagged with , ,

[Oracle] Jak stworzyć link bazodanowy ad hoc bez potrzeby edycji pliku tnsnames.ora

leave a comment »

Dzisiaj dowiedziałem się, że można stworzyć link bazodanowy bez potrzeby edycji pliku tnsnames.ora. Zapisuję to tutaj ku pamięci.

CREATE DATABASE LINK testlink
 CONNECT TO $USER_LOGON IDENTIFIED BY by $USER_PASSWORD
 USING
 '(DESCRIPTION=
   (ADDRESS=
    (PROTOCOL=TCP)
    (HOST=$HOST)
    (PORT=$PORT))
   (CONNECT_DATA=
    (SID=$SID)))'
  /
select * from v$version@testlink;
-- Usuwamy link bazodanowy
drop database link testlink;

Written by Jarek Przygódzki

Czerwiec 5, 2014 at 12:43 pm

Napisane w Uncategorized

Tagged with

Lokalizacja (wersje językowe) komunikatów o błędach z wbudowanego w JRE silnika JavaScript-u w OSGi z wykorzystaniem extension:=extclasspath

leave a comment »

Aplikacja nad którą pracuję umożliwia użytkownikom rozszerzenia funkcjonalności przez pisanie skryptów w JS (szczegóły nie są istotne). Oczywiście, gdy pisze się kod pojawiają się komunikaty o błędach a wymaganiem klienta było by były w języku polskim. Ponieważ domyślnie dostępne są dwie wersje językowe – angielska i francuska – stworzony został plik sun/org/mozilla/javascript/internal/resources/Messages_pl.properties z polskimi komunikatami który następnie został opakowany w plik jar sun.org.mozilla.javascript.nl.PL_pl.jar. W końcu plik ten został przekazany jako rozszerzenie bootclasspath aplikacji za pomocą opcji

-Xbootclasspath/a:lib\sun.org.mozilla.javascript.nl.PL_pl.jar

i rozwiązanie takie działo przed dłuższy czas (także w OSGi).

W trakcie migracji na Equinox p2 okazało się jednak, że z wielu względow -Xbootclasspath/a musi odejść – a mnie przypadło wymyślenie jak.

Pierwszym krokiem jest oczywiście ustalenie, dlaczego działało dotychczasowe rozwiązanie. Analiza wykazała, że komunikaty wczytywane są w klasie sun.org.mozilla.javascript.internal.ScriptRuntime wywołaniem

ResourceBundle.getBundle("sun.org.mozilla.javascript.internal.resources.Messages", locale)

a oryginalne lokalizacje (angielska i francuska) umieszczone są w <jre>/lib/resources.jar. Dalsza analiza wykazała, że w tym przypadku do wczytywania lokalizacji wykorzystywany jest tzw. classloader aplikacji. Żeby zrozumieć, dlaczego wyszukiwanie lokalizacji powodzi się trzeba pamiętać o trzech podstawowych faktach dotyczących classloader-ów

  • są zorganizowane są w strukturę drzewiastą i powiązane relacją dziecko-rodzic
  • zwykle przed wczytaniem klasy\zasobu samemu następuje próba delegacji do rodzica (parent-first delegation model – ale model child-first też jest popularny. Ważne jest to, że classloader „widzi” klasy i zasoby swoje i przodków – ale nie potomków
  • główne classloadery (boot, ext, app) zorganizowane są następującojvm-classloader-hierarchy

Podczas wyszukiwania zasobu sun/org/mozilla/javascript/internal/resources/Messages_pl.properties Application Classloader deleguje do Extension ClassLoader który z kolei deleguje do Boostrap ClassLoader który znajduje zasób dzięki -Xbootclasspath – i voilà , mamy polskie komunikaty.

Dla osób dobrze znających OSGi naturalnym sposobem na rozwiązanie przyjazne dla OSGi wydają się tzw. framework extensions czyli pakunki częściowe (fragmenty) których hostem jest pakunek systemowy – system.bundle – i które dodatkowo definiują tzw. punkt rozszerzeń. Zgodnie ze specyfikacją OSGi poprawne są dwie wartości

  • framework
  • bootclasspath

opisane np. tutaj i w specyfikacji OSGi
Pierwszy jest dla nas bezużyteczny (pytanie dla uważnych – dlaczego). Drugi wydaje się idealny jako odpowiednik parametru -Xbootclasspath ale w praktyce okazuje się, że frameworki OSGI nie implementują go z przyczyn technicznych (patrz np. Eclipse/127724). Co nam pozostaje? Okazuje się, że Equinox OSGi obsługuje niestandardowy punkt rozszerzeń który idealnie sprawdza się w tym przypadku. Można bowiem stworzyć fragment który rozszerzy classloader aplikacji korzystając ze składni Fragment-Host: system.bundle; extension:=extclasspath. Wystarczy stworzyć fragment z następującym manifestem

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Mozilla Rhino Localization PL
Bundle-SymbolicName: sun.org.mozilla.javascript.nl.PL_pl
Bundle-Version: 1.0.0.qualifier
Fragment-Host: system.bundle; extension:=extclasspath
Bundle-RequiredExecutionEnvironment: JavaSE-1.6

który zawiera zasób sun/org/mozilla/javascript/internal/resources/Messages_pl.properties

Jak Equinox OSGi obsługuje framework extensions

czyli jak mawia Linus Torvalds Talk is cheap. Show me the code. W tym przypadku kluczowe są dwa miejsca. Pierwszym jest metoda org.eclipse.osgi.internal.baseadaptor.BaseStorageHook.loadManifest gdzie wczytywany jest manifest pakunku

static void loadManifest(BaseData target, Dictionary<String, String> manifest) throws BundleException {
	try {
		target.setVersion(Version.parseVersion(manifest.get(Constants.BUNDLE_VERSION)));
	} catch (IllegalArgumentException e) {
		target.setVersion(new InvalidVersion(manifest.get(Constants.BUNDLE_VERSION)));
	}
	ManifestElement[] bsnHeader = ManifestElement.parseHeader(Constants.BUNDLE_SYMBOLICNAME, manifest.get(Constants.BUNDLE_SYMBOLICNAME));
	int bundleType = 0;
	if (bsnHeader != null) {
		target.setSymbolicName(bsnHeader[0].getValue());
		String singleton = bsnHeader[0].getDirective(Constants.SINGLETON_DIRECTIVE);
		if (singleton == null)
			singleton = bsnHeader[0].getAttribute(Constants.SINGLETON_DIRECTIVE);
		if ("true".equals(singleton)) //$NON-NLS-1$
			bundleType |= BundleData.TYPE_SINGLETON;
	}
	// check that the classpath is valid
	String classpath = manifest.get(Constants.BUNDLE_CLASSPATH);
	ManifestElement.parseHeader(Constants.BUNDLE_CLASSPATH, classpath);
	target.setClassPathString(classpath);
	target.setActivator(manifest.get(Constants.BUNDLE_ACTIVATOR));
	String host = manifest.get(Constants.FRAGMENT_HOST);
	if (host != null) {
		bundleType |= BundleData.TYPE_FRAGMENT;
		ManifestElement[] hostElement = ManifestElement.parseHeader(Constants.FRAGMENT_HOST, host);
		if (Constants.getInternalSymbolicName().equals(hostElement[0].getValue()) || Constants.SYSTEM_BUNDLE_SYMBOLICNAME.equals(hostElement[0].getValue())) {
			String extensionType = hostElement[0].getDirective("extension"); //$NON-NLS-1$
			if (extensionType == null || extensionType.equals("framework")) //$NON-NLS-1$
				bundleType |= BundleData.TYPE_FRAMEWORK_EXTENSION;
			else if (extensionType.equals("bootclasspath")) //$NON-NLS-1$
				bundleType |= BundleData.TYPE_BOOTCLASSPATH_EXTENSION;
			else if (extensionType.equals("extclasspath")) //$NON-NLS-1$
				bundleType |= BundleData.TYPE_EXTCLASSPATH_EXTENSION;
		}
	} else {
		String composite = manifest.get(COMPOSITE_HEADER);
		if (composite != null) {
			if (COMPOSITE_BUNDLE.equals(composite))
				bundleType |= BundleData.TYPE_COMPOSITEBUNDLE;
			else
				bundleType |= BundleData.TYPE_SURROGATEBUNDLE;
		}
	}
	target.setType(bundleType);
	target.setExecutionEnvironment(manifest.get(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT));
	target.setDynamicImports(manifest.get(Constants.DYNAMICIMPORT_PACKAGE));
}

drugim jest metoda org.eclipse.osgi.internal.baseadaptor.BaseStorage.processExtension gdzie dane rozszerzenie jest obsługiwane

protected void processExtension(BaseData bundleData, byte type) throws BundleException {
	if ((bundleData.getType() & BundleData.TYPE_FRAMEWORK_EXTENSION) != 0) {
		validateExtension(bundleData);
		processFrameworkExtension(bundleData, type);
	} else if ((bundleData.getType() & BundleData.TYPE_BOOTCLASSPATH_EXTENSION) != 0) {
		validateExtension(bundleData);
		processBootExtension(bundleData, type);
	} else if ((bundleData.getType() & BundleData.TYPE_EXTCLASSPATH_EXTENSION) != 0) {
		validateExtension(bundleData);
		processExtExtension(bundleData, type);
	}
}

Written by Jarek Przygódzki

Styczeń 15, 2014 at 9:18 pm

Napisane w Java, JVM, OSGi

Tagged with , , ,

[Java] Quartz i pule o zmiennej ilości wątków.

leave a comment »

Quartz to planista zadań powszechnie wykorzystywany w aplikacjach Java i .NET. Jednym z poważnych ograniczeń w wykorzystaniu Quartza na dużą skalę jest domyślna implementacji puli wątków – org.quartz.simpl.SimpleThreadPool która tworzy podczas inicjalizacji pulę wątków o stały rozmiarze i nigdy nie zwalnia nieużywanych wątków. Takie zachowanie ma sens gdy pula jest bardzo mocno obciążona i wątki są cały czas wykorzystywane, ale w praktyce to rozwiązanie się nie sprawdza – dlaczego mamy utrzymywać 1000 wątków tylko dlatego że są potrzebne raz na tydzień? Ponieważ domyślnie Quartz nie ma odpowiedniej implementacji puli wątków zmuszony byłem napisać własną

Pula ta z jednej strony zapewnia ponowne wykorzystanie wątków gdy system jest obciążony (unikając tym samym kosztów wielokrotnego tworzenia wątków – Why is creating a Thread said to be expensive?) a jednocześnie zwalnia wątki które były nieaktywne przez odpowiednio długi czas unikając tym samym niepotrzebnego wykorzystania pamięci natywnej i PermGen-u.

Written by Jarek Przygódzki

Grudzień 16, 2013 at 7:56 pm

Napisane w Java

Tagged with ,

Obserwuj

Otrzymuj każdy nowy wpis na swoją skrzynkę e-mail.