[Java] Analiza i wizualizacja zależności pomiędzy plikami JAR

Jednym z wyzwań zarządzania duża bazą kodu, szczególnie w sytuacji gdy jest to tzw. Dojrzały/Odziedziczony Kod (ang. Legacy Code) jest zarządzanie zależnościami. Czy jesteście w stanie odpowiedzieć, dla dowolnego modułu, od jakich modułów on zależy i jakie zależą od niego? Czy jesteście w stanie określić pełny zbiór zależności, także tych przechodnich?

Moim ulubionym służącym do tego narzędziem (obok JBoss Tattletale – ale nie wiem jak zmusić go do generowania raportów w XML) jest JarAnalyzer. Rozwiązanie, które zaprezentuję na przykładzie dystrybucji Hibernate 4.1.9.Final jest nieco partyzanckie, ale działa.

Pierwszym krokiem jest wygenerowanie raportu w łatwym do maszynowego przetwarzania formacie XML. Program zakłada, że wszystkie pliki JAR są bezpośrednio we wskazanym katalogu, musimy więc skopiować wszystkie pliki JAR to wspólnego katalogu

find /tmp/hibernate-release-4.1.9.Final/lib -name "*.jar" -exec cp {} /tmp/hibernate-dist-all-jars \;

Okazuje się, że biblioteka JBoss Logging występuje w dwóch wersjach – 3.1.0.GA i 3.1.1.GA – więc usuwamy starszą z nich by uniknąć nieporozumień.
Następnie generujemy raport w formacie XML korzystając z klasy com.kirkk.analyzer.textui.XMLUISummary. W przypadku Windows możemy skorzystać z dostarczonego wraz programem skryptu runxmlsummary.bat, w przypadku innych systemów musimy radzić sobie sami, np.tak

export JARANALYZER_HOME=/path/to/JarAnalyzer-1.2
$JAVA_HOME/bin/java -cp $JARANALYZER_HOME/lib/*:$JARANALYZER_HOME/jaranalyzer-1.2.jar com.kirkk.analyzer.textui.XMLUISummary

Program poprosi nas o wskazanie katalogu z plikami JAR i nazwy pliku wynikowego z raportem. Rezultat znajduje się tutaj i składa się z tagów Jar dla każdego z analizowanym plików. Przykładowo wpis dla hibernate-core-4.1.9.Final.jar wygląda następująco

<Jar name="hibernate-core-4.1.9.Final.jar">
	<Summary>
		<Statistics>
			<ClassCount>2903</ClassCount>
			<AbstractClassCount>751</AbstractClassCount>
			<PackageCount>166</PackageCount>
		</Statistics>

		<Metrics>
			<Abstractness>0.26</Abstractness>
			<Efferent>5</Efferent>
			<Afferent>7</Afferent>
			<Instability>0.42</Instability>
			<Distance>0.32</Distance>
		</Metrics>

		<Packages>
		<!-- Usunięte z braku miejsca -->
		</Packages>

		<OutgoingDependencies>
			<Jar>jboss-logging-3.1.1.GA.jar</Jar>
			<Jar>dom4j-1.6.1.jar</Jar>
			<Jar>hibernate-commons-annotations-4.0.1.Final.jar</Jar>
			<Jar>antlr-2.7.7.jar</Jar>
			<Jar>javassist-3.17.1-GA.jar</Jar>
		</OutgoingDependencies>

		<IncomingDependencies>
			<Jar>ehcache-core-2.4.3.jar</Jar>
			<Jar>hibernate-c3p0-4.1.9.Final.jar</Jar>
			<Jar>hibernate-ehcache-4.1.9.Final.jar</Jar>
			<Jar>hibernate-envers-4.1.9.Final.jar</Jar>
			<Jar>hibernate-infinispan-4.1.9.Final-tests.jar</Jar>
			<Jar>hibernate-infinispan-4.1.9.Final.jar</Jar>
			<Jar>hibernate-proxool-4.1.9.Final.jar</Jar>
		</IncomingDependencies>

		<Cycles>
		</Cycles>

		<UnresolvedDependencies>
			<Package>org.apache.tools.ant</Package>
			<Package>org.apache.tools.ant.types</Package>
			<Package>org.apache.tools.ant.taskdefs</Package>
			<Package>org.jboss.jandex</Package>
			<Package>com.fasterxml.classmate</Package>
			<Package>com.fasterxml.classmate.members</Package>
		</UnresolvedDependencies>
	</Summary>

</Jar>

i oprócz informacji o zależnościach zawiera cała masę innych przydatnych informacji. Plik ten można bardzo łatwo przetwarzać i wygenerować np. taki raport

Jar File                                       ClassCount AbstractClassCount PackageCount
---------------------------------------------- ---------- ------------------ --------
hibernate-core-4.1.9.Final.jar                 2903       751                166     
infinispan-core-5.2.0.Beta3.jar                1420       392                90      
jgroups-3.2.0.CR1.jar                          895        99                 24      
ehcache-core-2.4.3.jar                         732        155                57      
javassist-3.17.1-GA.jar                        388        41                 17      
c3p0-0.9.1.jar                                 382        82                 44      
proxool-0.8.3.jar                              300        78                 17      
hibernate-envers-4.1.9.Final.jar               249        60                 35      
antlr-2.7.7.jar                                224        52                 12      
woodstox-core-asl-4.1.1.jar                    203        41                 16      
jboss-marshalling-1.3.15.GA.jar                200        43                 4       
dom4j-1.6.1.jar                                190        39                 14      
hibernate-infinispan-4.1.9.Final-tests.jar     176        14                 14      
hibernate-jpa-2.0-api-1.0.1.Final.jar          176        134                4       
stax2-api-3.1.1.jar                            124        51                 11      
hibernate-commons-annotations-4.0.1.Final.jar  64         17                 7       
hibernate-ehcache-4.1.9.Final.jar              58         12                 6       
jboss-logging-3.1.1.GA.jar                     43         16                 1       
hibernate-infinispan-4.1.9.Final.jar           37         4                  10      
jboss-marshalling-river-1.3.15.GA.jar          35         2                  1       
staxmapper-1.1.0.Final.jar                     31         8                  1       
slf4j-api-1.6.1.jar                            23         10                 3       
jboss-transaction-api_1.1_spec-1.0.0.Final.jar 18         8                  2       
rhq-pluginAnnotations-3.0.4.jar                7          3                  1       
hibernate-c3p0-4.1.9.Final.jar                 3          1                  1       
hibernate-proxool-4.1.9.Final.jar              3          1                  1

 

Następnym krokiem jest analiza i wizualizacja zależności. Najprościej jest je modelować jako graf skierowany w którym wierzchołki są plikami JAR i krawędź A→B oznacza, że A zależy od B. Idealny do wizualizacji tego typu zależności jest Zest, ale nie jest to lekkie rozwiązania więc wystarczyć musi Jung i skrypt Groovy

Efekt końcowy wygląda mnie więcej tak

hibernate-release-4.1.9-deps
Biblioteka JBoss Logging zaciemnia trochę obraz zależności, bo zależy od niej prawie wszystko a ona sama zależy tylko od slf4j. Po jej usunięciu obraz zależności jest bardziej czytelny.

hibernate-release-4.1.9-deps-no-jboss-loggingl

Jeśli uzyskana wizualizacja nas nie satysfakcjonuje, warto spróbować innych algorytmów rozkładu grafu oferowanych przez Jung np. DAGLayout, FRLayout, KKLayout, SpringLayout.

Update

We wcześniejszej części wyraziłem przypuszczenie, że Zest jest idealny do wizualizacji tego typu zależności, które jak się okazało nie do końca odpowiada rzeczywistościjar-deps-vis-with-zest
Kod aplikacji generujący powyższy obrazek wygląda następująco


import org.eclipse.swt.*
import org.eclipse.swt.graphics.*
import org.eclipse.swt.layout.*
import org.eclipse.swt.widgets.*

import org.eclipse.jface.viewers.*

import org.eclipse.zest.core.viewers.*
import org.eclipse.zest.core.widgets.*
import org.eclipse.zest.layouts.*;
import org.eclipse.zest.layouts.algorithms.*

class GraphContentProvider implements IGraphEntityContentProvider {

	def DirectedGraph g;

	GraphContentProvider(DirectedGraph g) {
		this.g = g;
	}
	@Override
	public void dispose() {

	}

	@Override
	public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {

	}

	@Override
	public Object[] getConnectedTo(Object entity) {
		return g.getSuccessors(entity)
	}

	@Override
	public Object[] getElements(Object inputElement) {
		return g.getVertices()
	}

}

class ZestLabelProvider extends LabelProvider {
	@Override
	public String getText(Object element) {
		if (element instanceof String) {
			return element.toString();
		}
		return null;
	}

}
Display d = new Display()
Shell shell = new Shell(d)
shell.with {
	setText("Jar Dependency Visualizer")
	setLayout(new FillLayout(SWT.VERTICAL))
	setSize(1600, 960)
}
viewer = new GraphViewer(shell, SWT.NONE);
viewer.with {
	setContentProvider(new GraphContentProvider(g))
	setConnectionStyle(ZestStyles.CONNECTIONS_DIRECTED)
	setLabelProvider(new ZestLabelProvider())
	setLayoutAlgorithm(
		new CompositeLayoutAlgorithm(LayoutStyles.NO_LAYOUT_NODE_RESIZING,
			[
				new DirectedGraphLayoutAlgorithm(LayoutStyles.NO_LAYOUT_NODE_RESIZING),
				new HorizontalShift(LayoutStyles.NO_LAYOUT_NODE_RESIZING)
			] as LayoutAlgorithm[]))
}

viewer.setInput(null);
viewer.reveal("hibernate-core-4.1.9.Final.jar")
shell.open();
while (!shell.isDisposed()) {
	while (!d.readAndDispatch()) {
		d.sleep();
	}
}

One thought on “[Java] Analiza i wizualizacja zależności pomiędzy plikami JAR

  1. […] W poprzedniej części dowidzieliśmy się, jak utworzyć graf zależności oraz poznaliśmy podstawowe metody wizualizacji. W tym artykule postaram się przedstawić jak różne pojęcia z teorii grafów przekładają się na nasz model (dla przypomnienia – zależności modelujemy jako graf skierowany w którym wierzchołki są plikami JAR i krawędź A→B oznacza, że A zależy od B) […]

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Log Out / Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Log Out / Zmień )

Facebook photo

Komentujesz korzystając z konta Facebook. Log Out / Zmień )

Google+ photo

Komentujesz korzystając z konta Google+. Log Out / Zmień )

Connecting to %s