1 Visual Minik - plugin do Eclipse’a, wizualizujący zależności występujące w kodzie Jan Horabik 09. 01. 2006
2 Plan prezentacji Wstęp Visual Minik w działaniu Struktura implementacji Visual Minika Struktura mojej części kodu Możliwości JDT, wykorzystane do wyciągnięcia zależności występujących w kodzie
3 Wstęp Motywacja – w czasie pisania projektu często chcemy szybko zorientować się w zależnościach występujących w kodzie programu (podniesienie jakości tworzonego kodu) Visual Minik – plugin do Eclipse’a, który umożliwia wizualizację tych zależności w postaci grafu (wierzchołki – to np. pakiety, a krawędzie – zależności między nimi). Napisany w wakacje w zespole 4 – osobowym w ramach stażu.
4 Definicje Encja – projekt, pakiet, typ (klasa lub interfejs) lub metoda Zależność - między encjami tego samego rodzaju Metoda A zależy od metody B, jeśli w kodzie metody A jest wywoływana metoda B Typ A zależy od typu B, jeśli - któraś z metod typu A zależy od którejś metody typu B - A rozszerza lub implementuje B - jedna z metod typu A jako parametr lub typ zwracany przyjmuje obiekt typu B Pakiet A zależy od pakietu B, jeśli któryś z typów pakietu A zależy od któregoś z typów pakietu B Projekt A zależy od projektu B, jeśli któryś z pakietów A zależy od któregoś z pakietów B (jary)
5 Definicje Graf zależności – graf, którego wierzchołkami są encje; jeśli encja A zależy od encji B, to w grafie występuje krawędź z A do B Zmiana definicji zależności pociąga za sobą niewielkie zmiany w kodzie plugina
6 Co umożliwia Visual Minik generowanie grafu zależności podział wierzchołków na trzy rodzaje: main, use, return zapis grafu pomiędzy wywołaniami programu (nie trzeba wyliczać informacji za każdym razem) przechodzenie pomiędzy widokami (np. klasy w ramach pakietu) zwijanie i rozwijanie elementów grafu filtrowanie encji automatyczne (intuicyjne) rozmieszczenie wierzchołków grafu i możliwość ich przesuwania skakanie do kodu generowanie statystyk
7 Implementacja Cele: - łatwa zmiana języka, na którym pracujemy (np. C++ zamiast Javy) i definicji zależności (logika musi w jednakowy sposób traktować np. pakiety i klasy) - ograniczenie do minimum kodu korzystającego z zewnętrznych bibliotek i możliwość jego podmiany w razie konieczności (interfejsy)
8 Struktura Visual Minika
9 Entity public class Entity { final private String name; final private ImmObject unit; final private ImmObject data; final private EntityOption comprisedIn; final private EntityOption belongsTo; final private String key;... }
10 DepGetter public interface DepGetter { public Entities getMainEntities(); public Entities getBelongSubEntities(Entity entity);// należenie public Entities getIncludeSubEntities(Entity entity);// zawieranie public Entities getDependentEntities(Entity entity); // encje, które ode mnie zależą public Entities getEntitiesIDependOn(Entity entity); // encje od których ja zależę }
11 Implementacja DepGetter’a Dwa podejścia: 1) Wyliczamy zależności na żądanie Zalety: - mały koszt pamięciowy (proporcjonalny do rozmiaru grafu) – w pamięci trzymamy tylko ten graf, który w danej chwili jest nam potrzebny, - zmiany w kodzie uwzględniane za darmo Wady: - bardzo duży koszt czasowy (w pesymistycznym przypadku za każdym razem analizujemy cały projekt) - wielokrotnie liczymy to samo
12 Implementacja DepGetter’a 2) Wszystkie zależności wyliczamy na początku i trzymamy w pamięci. Zalety: - liniowy (względem rozmiaru projektu) koszt czasowy początkowego wyliczenia wszystkich informacji – dla GenRapa ok. 10min, ale wykonywane tylko raz! (serializacja) - stały koszt czasowy każdej z operacji DepGetter’a (wszystko mamy już policzone) Wady: - bardzo duży koszt pamięciowy, (aby go zredukować wprowadziłem możliwość nie uwzględniania metod) - trudne uwzględnianie zmian w kodzie (nie udało mi się tego zrealizować) - skomplikowany kod ;-)
13 Jak to zrobić (org.eclipse.jdt.core) ?
14
15 Jak to zrobić? //implementacja getMainEntities():... IWorkspace workspace = ResourcesPlugin.getWorkspace(); IJavaModel javaModel = JavaCore.create(workspace.getRoot()); IJavaProject[] projects = javaModel.getJavaProjects(); String projectName = projects[0].getElementName();... //implementacja getBelongSubEntities():... IPackageFragment packageFragment =... ; ICompilationUnit[] compilationUnits = packageFragment.getCompilationUnits(); IType[] types = compilationUnits[0].getTypes(); String typeName = types[0].getElementName();...
16 Java Search Engine Aby zaimplementować getDependentEntities() możemy skorzystać z Java Search Engine. Umożliwia on wyszukiwanie wywołań konkretnych metod, deklaracji metod, implementacji interfejsów itp. Aby z niego skorzystać, należy: - stworzyć wzorzec poszukiwań (SearchPattern) - stworzyć zakres poszukiwań (IJavaSearchScope) - stworzyć obiekt, który będzie otrzymywał wyniki poszukiwań( rozszerzyć SearchRequestor) - stworzyć SearchEngine i wywołać metodę search z powyższymi parametrami
17 SearchPattern IMethod method =...; SearchPattern pattern1 = SearchPattern.createPattern(method, IJavaSearchConstants.REFERENCES); SearchPattern pattern2 = SearchPattern.createPattern("Obj*", IJavaSearchConstants.TYPE, IJavaSearchConstants.DECLARATIONS, SearchPattern.R_CASE_SENSITIVE);
18 IJavaSearchScope IPackageFragment pkg =...; IJavaSearchScope scope1 = SearchEngine.createJavaSearchScope(new IJavaElement[] {pkg}); IJavaSearchScope scope2 = SearchEngine.createWorkspaceScope();
19 SearchRequestor class MySearchRequestor extends SearchRequestor { public void acceptSearchMatch(SearchMatch match) throws CoreException { IJavaElement element = (IJavaElement) match.getElement();... }
20 SearchEngine SearchPattern pattern =...; IJavaSearchScope scope =...; SearchRequestor requestor = new MySearchRequestor(); SearchEngine searchEngine = new SearchEngine(); searchEngine.search(pattern, new SearchParticipant[] {SearchEngine.getDefaultSearchParticipant()}, scope, requestor, null); - umożliwia zaimplementowanie getDependentEntities(), ale nie getEntitiesIDependOn()
21 Parser JDT Aby zaimplementować getEntitiesIDependOn(), musimy sparsować pliki.java AST – Abstract Syntax Tree ASTNode – abstrakcyjna klasa bazowa wszystkich wierzchołków AST, jej podklasy to m.in.: - MethodInvocation - ClassInstanceCreation - ImportDeclaration -...
22 ASTVisitor Odwiedzający AST ASTVisitor ma zdefiniowane metody - public boolean visit (T node) oraz - public void endVisit (T node) dla każdego typu wierzchołka. Należy stworzyć swoją podklasę klasy ASTVisitor i przedefiniować te metody, które nas interesują.
23 Parsowanie plików ICompilationUnit compilationUnit =... ASTParser parser = ASTParser.newParser(AST.JLS2); parser.setSource(compilationUnit); // UWAGA! Jako parametr można podać ClassFile, ale będzie to działać tylko wtedy, gdy jest z nim związany ICompilationUnit ASTNode astNode = parser.createAST(null); ASTVisitor visitor = new MyVisitor(); astNode.accept(visitor);... class MyVisitor extends ASTVisitor{ public boolean visit (MethodInvocation methodInvocation{... }; public boolean visit (TypeDeclaration typeDeclaration){... };
24 Rejestrowanie zmian JDT umożliwia zdobycie informacji o tym, jakie zmiany zachodzą w projekcie, w czasie działania plugina. Należy zaimplementować IResourceChangeListener i zarejestrować obiekt nowo utworzonej klasy w Workspace. Będzie on powiadamiany o zmianach poprzez obiekt klasy IResourceChangeEvent
25 Rejestrowanie zmian IResourceChangeListener listener = new MyResourceChangeReporter(); ResourcesPlugin.getWorkspace().addResourceChangeListener( listener, IResourceChangeEvent.POST_CHANGE); public class MyResourceChangeReporter implements IResourceChangeListener{ public void resourceChanged(IResourceChangeEvent event) { IResource res = event.getResource(); System.out.println("Resources have changed."); event.getDelta().accept(new DeltaPrinter()); }
26 Rejestrowanie zmian class DeltaPrinter implements IResourceDeltaVisitor { public boolean visit(IResourceDelta delta) { IResource res = delta.getResource(); switch (delta.getKind()) { case IResourceDelta.ADDED: System.out.print("Resource "); System.out.print(res.getFullPath()); System.out.println(" was added."); break; case IResourceDelta.REMOVED: System.out.print("Resource "); System.out.print(res.getFullPath()); System.out.println(" was removed."); break; case IResourceDelta.CHANGED: System.out.print("Resource "); System.out.print(res.getFullPath()); System.out.println(" has changed."); break; } return true; // visit the children }