Export all classes in call graph?

R

Robert Mischke

I've come across this problem several times: What tools can I use to
extract all class files that are in the call graph of my main method,
or of a given set of files?

Example 1:

Imagine an application with all its classes in com.foo.app.* . Some of
these classes import classes from com.foo.lib.* . Now I want to export
all classes from com.foo.app.* and com.foo.lib.* that are imported
directly or indirectly by my (specified) main class (say
com.foo.app.Main).

Somehow I expect this to be a standard task, but haven't found support
for it yet. On the other hand, I don't see any theoretical problems
with this, so I wonder how this can be done.

Example 2:

I want to build a standalone JFace application. Unfortunately, since
Eclipse 3.0, there are some dependencies to consider: runtime.jar,
osgi.jar and core.jar. I now want to compress these into a jar that
contains all classes of JFace (easy) and all indirectly referenced
classes from the other jars (the tricky part).

I already tried to achieve this with ProGuard obfuscator (with
-dontobfuscate, -keep *, etc), but didn't get a result that I could
compile an application against; there were always some methods and
classes missing that some class needed. I might finally get this to
work, but somehow it feels like a clumsy way to go. Isn't there a
better way to do this?

Thanks,
Robert
 
L

Lucy

Robert Mischke said:
I've come across this problem several times: What tools can I use to
extract all class files that are in the call graph of my main method,
or of a given set of files?

What's a call graph? Is it the same as a set of files?
 
R

Robert Mischke

Lucy said:
What's a call graph? Is it the same as a set of files?

With "call graph" I mean the set of all classes that may be directly
or indirectly used from classes you specify.

In the example below, B and C are in the call graph of A, while D is
not. In Java, the call graph can be easily found by following the
"import" statements, as long as they are accurate and don't use "*"s
(because this also includes classs that are not neccesarily in the
call graph).

It's called a "graph" because you can envision the relations as a
graph, with classes as nodes and the "import" relations as edges.

Robert



-------

import B;
import C;

public class A
{
B b1 = new B();
B b2 = new C();

public void run()
{
b1.doSomething();
b2.doSomething();
}
}

------

public class B
{
public void doSomething() {};
}

------

public class C extends B
{
}

------

public class D
{
}

------
 
R

Robert Mischke

Filip Larsen said:
Robert Mischke wrote


Maybe JDepend (http://www.clarkware.com/software/JDepend.html) can serve
your needs.

Hmm, I came across this one when I did a web search, but I haven't
found an option to "export all used classes" or something, which would
at least give a base to automate the export with other tools. JDepend
seems to focus on producing human-readable reports instead.

So in case I haven't missed anything, JDepend (despite what its name
suggests) does not do the job.

Robert
 
R

Robert Mischke

I've come across this problem several times: What tools can I use to
extract all class files that are in the call graph of my main method,
or of a given set of files?

I forgot to point out that a solution would need to work with compiled
..class files, too, to allow compressing/including of libraries that
have no source code available.

Robert
 
I

iamfractal

Hi, Robert!

This won't "export" your classes (I must admit, I'm not entirely sure
what that means), and will, like JDepend, just show you the dependency
results, but it will show you the individual classes, and not just the
packages (as is JDepend's strength).

<Prepares trumpet for a really good blow.>

1) Go to www.EdmundKirwan.com -> Fractal -> Fractality.
2) Download version 2.0, and start the jar file.
3) Click File -> Options -> Advanced -> Java Class files -> OK
4) Click File -> Load mast name space, and enter the root directory of
your class structure.
5) When the system is loaded, right click on any package, and click
"Dependencies from," to see all the class referenced from that package.
6) And while you're at it, click Analyis view -> Circular dependencies,
to if they've been naughty little programmers (or nice).

Some caveats:
1) Jars within jars are not analysed (in the Personal Edition).
2) Default-package classes are not analysed (in the Personal Edition).
3) Class files are not guaranteed to hold references to all the classes
referenced in the sources files. For example, if you import a public
static variable from another class, then the compiler puts that value
into your class file without storing a reference to the class from
which it was taken.
4) Yes, yes, the documentation is out-of-date, but will be synchronised
with Version 2.1, expected any moment ...

..ed

www.EdmundKirwan.com - Home of The Fractal Class Composition.
 
T

Thomas Weidenfeller

Robert said:
With "call graph" I mean the set of all classes that may be directly
or indirectly used from classes you specify.

A call graph works on method-level, not class level (often, for the
purpose of a call graph, constructors are also treated as methods). A
call graph indicates which methods (of which classes) are used (are
"called") by other methods, not just which classes somewhat dependent on
each other.

You are probably after compile-time dependency or class dependency, not
a call graph.
In the example below, B and C are in the call graph of A, while D is
not.

"in the call graph of A" has no meaning. You probably mean "children of A".
In Java, the call graph can be easily found by following the
"import" statements, as long as they are accurate and don't use "*"s
(because this also includes classs that are not neccesarily in the
call graph).

This is absolutely wrong. Import statements introduce a compile-time
dependency, but not necessarily a class dependency. And, if no method is
used from the imported class, the imported class would also have no
business in showing up in a call-graph.

import A;

/** never uses A **/
class B {
int ten() { return 10; }
}

You can only compile this, when A is in the CLASSPATH (compile-time
dependency), but you can change A as much as you like without having an
influence on B (no class dependency). And since B uses nothing of A, no
method of A would ever appear as a children of a method of B in a
call-graph.

/Thomas
 
B

bugbear

Robert said:
I've come across this problem several times: What tools can I use to
extract all class files that are in the call graph of my main method,
or of a given set of files?

In a language with reflection support, I think
(in general) you're toast.

BugBear
 
B

bugbear

Robert said:
Hmm, I came across this one when I did a web search, but I haven't
found an option to "export all used classes" or something, which would
at least give a base to automate the export with other tools. JDepend
seems to focus on producing human-readable reports instead.

Given the list from JDepend, the rest of your problem can be
solved by simple JAR manipulation, either from a Java
proggy of your own devising, or wrapping perl
around pkzip.

BugBear
 
B

Bjorn Borud

[[email protected] (Robert Mischke)]
|
| I forgot to point out that a solution would need to work with compiled
| .class files, too, to allow compressing/including of libraries that
| have no source code available.

as for constructing an application jar file (ie. a jar file which
contains the entire application and its dependencies) I usually have a
staging ant-task which unpacks all the Jar files into a staging dir,
copies in my classes and then I make a jar file of that.

(pruning the staging dir of classes I don't need would be nice.
anyone got something like this set up in Ant?)

-Bjørn
 
R

Robert Mischke

bugbear said:
In a language with reflection support, I think
(in general) you're toast.

Point taken. But let's assume I don't use reflection and hope the
included libraries don't as well :) (Which can, of course, break with
every update.)

Robert
 
R

Robert Mischke

Hi Thomas,

I stand corrected: What I actually meant was a class dependency
graph/set, not a call graph. Although the option to keep only classes
with methods in the actual call graph would be nice, too. (I believe
this is what many obfuscators/shrinkers do; see my second example.)

Thomas Weidenfeller said:
This is absolutely wrong. Import statements introduce a compile-time
dependency, but not necessarily a class dependency. And, if no method is
used from the imported class, the imported class would also have no
business in showing up in a call-graph.

Right, I wasn't thinking when I wrote that quick response. The idea I
wanted to get across was that the import statements give an impression
of the dependencies of a class, but the way I wrote it is inaccurate
at best.
import A;

/** never uses A **/
class B {
int ten() { return 10; }
}

You can only compile this, when A is in the CLASSPATH (compile-time
dependency), but you can change A as much as you like without having an
influence on B (no class dependency). And since B uses nothing of A, no
method of A would ever appear as a children of a method of B in a
call-graph.

Well, this is just what I meant by "accurate": A list of import
statements that is as concise as possible, without asterisks or
superflous classes. (Of course, things become a little circular here.)


Not one of my best usenet postings, for sure... Thanks for pointing
this out.

Robert
 
T

Thomas Weidenfeller

Robert said:
I stand corrected: What I actually meant was a class dependency
graph/set, not a call graph. Although the option to keep only classes
with methods in the actual call graph would be nice, too. (I believe
this is what many obfuscators/shrinkers do; see my second example.)

Each class has the classes which it depends on recorded in
its .class file after compilation. You could use a disassembler or
..class file tools like BCEL to access this information.
Well, this is just what I meant by "accurate": A list of import
statements that is as concise as possible, without asterisks or
superflous classes. (Of course, things become a little circular here.)

I think you should give up on your fixation on import statements. The
java.lang package is usually never mentioned in import statements, but used:

System.out.println("I work without import");

And people can also refer to classes via the full name without any
import statement:

java.util.Arrays.binarySearch(myInts, 42);

Dependency checking in Java source code is notorious, because you really
have to parse the source code and apply the same rules as the Java
compiler to figure out which classes are actually used. Some simple
pattern matching does not work.

You will find that compilers like jikes can generate a dependency list.
There are also a couple of Java source code dependency tools out there
(Google is your friend), non of which seems to be very popular, and
maintenance om many of them has been abandoned.

/Thomas
 
L

Lucy

Robert Mischke said:
Point taken. But let's assume I don't use reflection and hope the
included libraries don't as well :) (Which can, of course, break with
every update.)

Robert

Is there some regular expression or other technique I can use in a program
that will detect the presence of a method definition and a method call?
 
A

Andrew Thompson

Is there some regular expression or other technique I can use in a program
that will detect the presence of a method definition and a method call?

Do you think a RegEx will do the trick after looking at this
(quite obtuse) example?

<sscce>
import java.lang.reflect.*;

public class ReflectionTangle {

public static void main(String[] args) throws Exception {
Object o = new Object();
Object[] p = {o};
System.out.println("Result: " + ((o.
getClass() .
// this is a meaningless comment
// containing the string getMethods
getMethods /* Should we put a 'space' here? */(
))
[5])
/* And a (meaningless)
multi-line comment. */
.invoke(o, p) );
}
}
</sscce>
 
L

Lucy

Andrew Thompson said:
Do you think a RegEx will do the trick after looking at this
(quite obtuse) example?

<sscce>
import java.lang.reflect.*;
What I had in mind was to run grep on the files to create a new file
with data that I could then analyze. Something like

grep "xxxxx" *.java > mydatafile.txt
 
J

John Currier

Robert, do you realize that compile time dependencies can be
significantly different (greater or less) than runtime dependencies?

The analysis of the two are also unrelated. One deal with textual
files (.java) while the other deals with binary files (.class/.jar).
You seem to want to treat them as the same thing.

John
http://schemaspy.sourceforge.net
 
T

Thomas Weidenfeller

Lucy said:
What I had in mind was to run grep on the files to create a new file
with data that I could then analyze. Something like

grep "xxxxx" *.java > mydatafile.txt

Grep works on individual lines. Andrew provided an example where you
have to take multiple lines into account. So how should that work out
with grep?

/Thomas
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,774
Messages
2,569,598
Members
45,158
Latest member
Vinay_Kumar Nevatia
Top