Wiemy już, że moduły dodatkowe mogą definiować specjalne rozszerzenia plików i dodawać edytory udostępniające dla tych typów plików specjalne funkcje edycji. W trakcie edytowania (lub budowania) zasobu moduł dodatkowy może musieć oznaczyć zasoby w celu przekazania użytkownikowi informacji o napotkanych problemach lub innych komunikatów. Mechanizm znaczników zasobów jest używany do zarządzania informacjami tego rodzaju.
Znacznik jest jak mała żółta karteczka przyczepiona do zasobu. W znaczniku można zapisać informacje dotyczące problemu (na przykład położenie, istotność) lub czynność, która musi zostać wykonana. Położenie znacznika można również zapisać po prostu jako zakładkę.
Użytkownicy mogą szybko przechodzić do oznaczonego położenia w obrębie zasobu. Interfejs użytkownika środowiska roboczego obsługuje prezentację zakładek, punktów zatrzymania, czynności oraz problemów wzdłuż krawędzi edytora. Te znaczniki mogą być również pokazywane jako elementy widoków, takich jak widok czynności czy widok zakładek.
Interfejs API zasobów platformy definiuje metody służące do tworzenia znaczników, ustawiania ich wartości oraz rozszerzania platformy o nowe typy znaczników. Zadaniem platformy jest zarządzanie znacznikami, natomiast zadaniem modułów dodatkowych jest kontrolowanie ich tworzenia i usuwania oraz ustawianie wartości atrybutów.
Znaczniki są z założenia niewielkimi obiektami. W pojedynczym projekcie mogą znajdować się setki, a nawet tysiące znaczników. Na przykład kompilator Java używa znacznika do oznakowania każdego problemu napotkanego w kodzie źródłowym.
Znaczniki dotyczące usuwanych zasobów są eliminowane przez platformę, natomiast za usunięcie nieaktualnych znaczników dotyczących istniejących zasobów odpowiadają moduły dodatkowe.
Manipulowanie znacznikami przypomina manipulowanie zasobami. Znaczniki są obiektami uchwytów. Uchwyt znacznika można otrzymać z zasobu, jednak nie wiadomo, czy on naprawdę istnieje, dopóki nie zostanie użyta metoda exists() albo nie zostanie wykonana inna operacja manipulująca znacznikiem. Po ustaleniu, że znacznik istnieje, można rozpocząć wyszukiwanie nazwanych atrybutów, które mogą być z nim powiązane.
Platforma, która jest właścicielem znaczników i nimi zarządza, zajmuje się ich utrwalaniem i powiadamianiem obiektów nasłuchiwania o dodaniu, usunięciu lub zmianie znacznika. Moduły dodatkowe odpowiadają za tworzenie niezbędnych znaczników, zmienianie ich atrybutów oraz usuwanie ich, gdy już nie są potrzebne.
Znaczniki nie są tworzone bezpośrednio przy użyciu konstruktora. Są tworzone przez zastosowanie metody fabryki (IResource.createMarker()) dla powiązanego zasobu.
IMarker marker = file.createMarker(IMarker.TASK);
Aby utworzyć znacznik o zasięgu globalnym (niepowiązany z konkretnym zasobem), można użyć katalogu głównego obszaru roboczego (IWorkspace.getRoot()) jako zasobu.
Kod usuwający znaczniki jest bardzo prosty.
try { marker.delete(); } catch (CoreException e) { // przypadek niepowodzenia }
Gdy znacznik jest usuwany, jego obiekt znacznika (uchwyt) staje się "nieaktualny". Do sprawdzenia, czy obiekt znacznika jest wciąż ważny moduły dodatkowe powinny używać protokołu IMarker.exists().
Znaczniki mogą zostać usunięte grupowo przez wysłanie do zasobu żądania usunięcia jego znaczników. Ta metoda jest pomocna przy usuwaniu wielu znaczników za jednym razem lub w przypadku, gdy odwołania lub identyfikatory znaczników są niedostępne.
int depth = IResource.DEPTH_INFINITE; try { resource.deleteMarkers(IMarker.PROBLEM, true, depth); } catch (CoreException e) { // przypadek niepowodzenia }
W trakcie usuwania grupy znaczników należy określić typ znaczników, które mają zostać usunięte, na przykład IMarker.PROBLEM lub null, jeśli mają zostać usunięte wszystkie znaczniki. Drugi argument określa, czy znaczniki określonego podtypu mają zostać usunięte. (Podtypom przyjrzymy się później, gdy będziemy definiowali nowe typy znaczników). Argument depth kontroluje głębokość usuwania.
Znaczniki można także usunąć przy użyciu metody IWorkspace.deleteMarkers(IMarker []).
Mając dany znacznik, można wysłać zapytanie o jego powiązany zasób, jego identyfikator (unikalny w stosunku do tego zasobu) oraz jego typ. Można również uzyskać dostęp do informacji dodatkowych za pośrednictwem atrybutów ogólnych.
Każdy typ znacznika ma specjalny zestaw atrybutów zdefiniowany przez obiekt tworzący dany typ znacznika przy użyciu konwencji nazewnictwa. Interfejs IMarker definiuje dla typów znaczników platformy zestaw stałych zawierających standardowe nazwy atrybutów (oraz niektóre wartości oczekiwane). Poniższa metoda manipuluje atrybutami przy użyciu stałych platformy.
IMarker marker = file.createMarker(IMarker.TASK); if (marker.exists()) { try { marker.setAttribute(IMarker.MESSAGE, "A sample marker message"); marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_HIGH); } catch (CoreException e) { // Należy określić obsługę przypadku, gdy znacznik już nie istnieje. } }
Atrybuty są zazwyczaj obsługiwane jako pary nazw i wartości, gdzie nazwy są łańcuchami, a wartości mogą przyjmować jeden z obsługiwanych typów (boolean, liczba całkowita, łańcuch). Ograniczenie typów wartości umożliwia platformie szybko i łatwo utrwalać znaczniki.
Do zasobów można wysyłać zapytania dotyczące ich znaczników i znaczników ich elementów potomnych. Na przykład wysłanie zapytania do elementu głównego obszaru roboczego bez ograniczenia głębokości dotyczy wszystkich znaczników tego obszaru roboczego.
IMarker[] problems = null; int depth = IResource.DEPTH_INFINITE; try { problems = resource.findMarkers(IMarker.PROBLEM, true, depth); } catch (CoreException e) { // przypadek niepowodzenia }
Wynik zwrócony przez metodę findMarkers zależy od przekazanych argumentów. W powyższym fragmencie kodu wyszukiwane były wszystkie znaczniki typu PROBLEM występujące w danym zasobie oraz wszystkie jego bezpośrednie i pośrednie elementy potomne.
Jeśli jako typ znacznika przekazana zostanie wartość null, zwrócone zostaną wszystkie typy znaczników powiązane z danym zasobem. Drugi argument określa, czy mają być zwracane elementy potomne zasobu. Argument depth kontroluje głębokość wyszukiwania elementów potomnych zasobu. Argument depth może przyjmować wartość DEPTH_ZERO (tylko dany zasób), DEPTH_ONE (dany zasób i wszystkie jego bezpośrednie elementy potomne) lub DEPTH_INFINITE (dany zasób oraz wszystkie jego bezpośrednie i pośrednie elementy potomne).
Standardowe znaczniki platformy (czynność, problem i zakładka) są trwałe. Oznacza to, że ich stan zostanie zapisany na czas między zamknięciem i uruchomieniem środowiska roboczego. Jednak charakter znaczników trwałych można selektywnie zmienić na przejściowy, ustawiając wartość atrybutu zastrzeżonego transient na true.
Nowe typy znaczników deklarowane przez moduły dodatkowe nie są trwałe, o ile nie zostaną zadeklarowane jako takie.
Moduły dodatkowe mogą deklarować własne typy znaczników przy użyciu punktu rozszerzenia org.eclipse.core.resources.markers. Standardowe typy znaczników dla problemów, czynności i zakładek są zadeklarowane przez platformę w kodzie modułu dodatkowego zasobów.
<extension id="problemmarker" point="org.eclipse.core.resources.markers" name="%problemName"> <super type="org.eclipse.core.resources.marker"/> <persistent value="true"/> <attribute name="severity"/> <attribute name="message"/> <attribute name="location"/> </extension> <extension id="taskmarker" point="org.eclipse.core.resources.markers" name="%taskName"> <super type="org.eclipse.core.resources.marker"/> <persistent value="true"/> <attribute name="priority"/> <attribute name="message"/> <attribute name="done"/> <attribute name="userEditable"/> </extension> <extension id="bookmark" point="org.eclipse.core.resources.markers" name="%bookmarkName"> <super type="org.eclipse.core.resources.marker"/> <persistent value="true"/> <attribute name="message"/> <attribute name="location"/> </extension>
Nowe typy znaczników powstają z już istniejących dzięki dziedziczeniu wielobazowemu. Nowe typy znaczników dziedziczą wszystkie atrybuty ich nadtypów i dodają nowe atrybuty zdefiniowane jako część deklaracji. Przechodnio dziedziczą również atrybuty nadtypów ich nadtypów. Poniższy fragment kodu definiuje nowy rodzaj znacznika w hipotetycznym module dodatkowym com.example.markers.
<extension id="mymarker" point="org.eclipse.core.resources.markers" /> <extension id="myproblem" point="org.eclipse.core.resources.markers"> <super type="org.eclipse.core.resources.problemmarker"/> <super type="com.example.markers.mymarker"/> <attribute name="myAttribute" /> <persistent value="true" /> </extension>
Należy zwrócić uwagę, że typ org.eclipse.core.resources.problemmarker jest właściwie jednym z predefiniowanych typów (znanym jako IMarker.PROBLEM).
Jedynym atrybutem nadtypu, który nie został odziedziczony jest flaga persistence. Domyślną wartością atrybutu persistence jest false, więc każdy typ znacznika, który powinien być trwały, musi zawierać zapis <persistent value="true"/>.
Po zadeklarowaniu nowego typu znacznika w pliku manifestu modułu dodatkowego, można tworzyć instancje znacznika typu com.example.markers.myproblem oraz dowolnie ustawiać lub pobierać wartość atrybutu myAttribute.
Deklarowanie nowych atrybutów umożliwia przypisanie danych do znaczników, które mają zostać użyte w innym miejscu (w widokach lub edytorach). Znaczniki danego typu nie muszą mieć przypisanych wartości dla wszystkich zadeklarowanych atrybutów. Deklaracje atrybutów służą raczej rozwiązaniu problemów konwencji nazewnictwa (dlatego przy omawianiu opisu znacznika wszyscy używają określenia "komunikat") niż ograniczaniu treści.
public IMarker createMyMarker(IResource resource) { try { IMarker marker = resource.createMarker("com.example.markers.myproblem"); marker.setAttribute("myAttribute", "MYVALUE"); return marker; } catch (CoreException e) { // Należy określić obsługę przypadku, gdy wartość atrybutu zostaje odrzucona. } }
Zapytania do znaczników o typie niestandardowym można wysyłać w ten sam sposób, co do znaczników o typach zdefiniowanych w ramach platformy. Poniższa metoda wyszukuje wszystkie znaczniki typu mymarker powiązane z danym zasobem docelowym i wszystkimi jego elementami potomnymi. Należy zauważyć, że wyszukane zostaną również wszystkie znaczniki typu myproblem, ponieważ wartość true zostanie przekazana do argumentu includeSubtypes.
public IMarker[] findMyMarkers(IResource target) { String type = "com.example.markers.mymarker"; IMarker[] markers = target.findMarkers(type, true, IResource.DEPTH_INFINITE); }