/*
* @(#)JavaMemberDepend.java 1.12 06/10/10
*
* Copyright 1990-2008 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 only, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License version 2 for more details (a copy is
* included at /legal/license.txt).
*
* You should have received a copy of the GNU General Public License
* version 2 along with this work; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 or visit www.sun.com if you need additional
* information or have any questions.
*
*/
import util.*;
import util.Set;
import java.util.Enumeration;
import java.util.Vector;
import dependenceAnalyzer.*;
import java.io.PrintStream;
import java.io.FileOutputStream;
import components.*;
class JavaMemberDepend extends DepgenUtil{
boolean hadError = false;
boolean details = false;
boolean classlistonly = false;
int verbosity= 0;
/*
* Java linking.
* The member linker determines which class members
* are required to satisfy the references of the
* members named, taking into account the problems
* introduced by method overriding and references from
* unanalysed sources, as directed by command line options.
*
* The member linker works off a list of things that
* must be included, iteratively analysing the members
* on this list, and building a new list of required
* members.
*
* This operation is controlled by a number of command-line
* options:
* -classpath and -exclude, as decribed below.
*
* -load full-member-name
* to put the named member on the work list.
* -loadClass full-classname
* to put all members of the named class on the
* work list. This is especially useful for classes
* that will be used by dynamically-loaded code
* in ways that cannot be analysed at this time.
* -interface full-classname
* to indicate that the named class, whether a
* Java class or a Java interface, defines an
* interface contract which must be met by its
* subclasses, even if the named class, itself
* is excluded from the load. This is especially
* useful when packaging up something, such as
* a subclass of java.lang.Applet, for dynamic
* loading, and it must meet the named interface
* contract.
*/
/*
* These are the flags that control member loading:
* REQ -- the marked element is required to run. Either
* it was specified with -load or -loadClass, or
* was determined to be necessary for something
* that was.
* INITREQ -- constructor required. Marks a class if
* at least one of its constructors is marked REQ.
* INTF -- interface. Marks a member of a class specified
* with -interface.
* LISTED -- just a tag for members listed by the user
* as needing loading. Either as individuals or
* as a member of a listed class.
*/
static final int REQ =(1<<29);
static final int INITREQ=(1<<28);
static final int INTF =(1<<27);
static final int ANY =0xffffffff;
static final int LISTED =(1<<26);
Vector initialList = new Vector();
Vector loaded;
PrintStream nativelist = null;
private void loadNode( DependenceNode node ){
node.flags |= LISTED;
initialList.addElement( node );
}
/*
* loadMember -- triggered by the command-line option
* -load full-member-name
*/
private void loadMember( String membername ){
MemberDependenceNode node = stringToNode( membername );
if ( node == null ){
System.out.println(
Localizer.getString("javafilter.cannot_find_member",
membername));
hadError = true;
return;
}
loadNode( node );
loadNode( ((MemberName)(node.name())).classEntry );
}
/*
* loadConstructor -- triggered by the command-line option
* -new full-class-name or
* -new full-class-name(signature)V
*/
private void loadConstructor( String constructorname ){
MemberDependenceNode node = stringToConstructorNode( constructorname );
if ( node == null ){
System.out.println(
Localizer.getString("javafilter.cannot_find_constructor",
constructorname)
);
hadError = true;
return;
}
loadNode( node );
loadNode( ((MemberName)(node.name())).classEntry );
}
/*
* loadClass -- triggered by the command-line option
* -loadClass full-classname
*/
private void loadClass( String classname ){
ClassEntry c = stringToClass( classname );
if ( c == null ){
System.out.println(
Localizer.getString("javafilter.cannot_find_class",
classname)
);
hadError = true;
return;
}
loadNode( c );
allClassMembers( classname );
}
/*
* interfaceClass -- triggered by the command-line option
* -interface full-classname
*/
private boolean interfaceClass( String classname ){
if ( ! this.filter.accept( null, sanitizeClassname( classname ) ) ){
// oops. Not excluded.
System.err.println(
Localizer.getString("javafilter.class_interface_but_not_exclude"
,classname)
);
loadClass( classname );
return true;
}
ClassEntry c = stringToClass( classname );
if ( c == null ){
System.out.println(
Localizer.getString("javafilter.cannot_find_class",
classname)
);
hadError = true;
return false;
}
if ( ! analyzer.makeInterface( c ) ){ // big magic here!
System.err.println(
Localizer.getString("javafilter.could_not_make_interface",
classname));
hadError = true;
return false;// something bad happened here.
}
Enumeration memberlist = c.members();
if ( memberlist == null ) return true; // no work.
while ( memberlist.hasMoreElements() ){
DependenceNode node = (DependenceNode)( memberlist.nextElement() );
node.flags |= INTF;
}
return true;
}
/*
* -wholeClass classname
* -wholeClass packagename.*
* also implicit in -loadClass
*/
private void allClassMembers( String name ){
if ( wholeClass == null )
wholeClass = new ClassnameFilter();
wholeClass.includeName( sanitizeClassname( name ) );
}
/*
* -wholeData classname
* -wholeData packagename.*
*/
private void allClassData( String name ){
if ( wholeData == null )
wholeData = new ClassnameFilter();
wholeData.includeName( sanitizeClassname( name ) );
}
class DependenceRelation{
String fromName;
String verb;
String toName;
DependenceRelation( String f, String v, String t ){
fromName = f;
verb = v;
toName = t;
}
}
/*
* addedDepencence -- called in response to a command-line
* option such as
* -dependence full-member-name verb full-member-name
*/
private void addDependence( DependenceRelation r ){
DependenceNode fromNode = stringToNode( r.fromName );
MemberDependenceNode destMember = null;
DependenceNode destClass = null;
boolean toClass = false;
int arcType;
if ( r.verb.equalsIgnoreCase("reads") ){
toClass = false;
arcType = ARC_READS;
} else if ( r.verb.equalsIgnoreCase("writes") ){
toClass = false;
arcType = ARC_WRITES;
} else if ( r.verb.equalsIgnoreCase("calls") ){
toClass = false;
arcType = ARC_CALLS;
} else if ( r.verb.equalsIgnoreCase("classref") ){
toClass = true;
arcType = ARC_CLASS;
} else {
System.out.println(
Localizer.getString("javafilter.unrecognized_dependence_verb",
r.verb));
hadError = true;
return;
}
try {
if ( toClass ){
destClass = stringToClass( r.toName );
} else {
destMember = stringToNode( r.toName );
destClass = ((MemberName)(destMember.name())).classEntry;
analyzer.addDependenceArc( fromNode, destMember, arcType );
}
analyzer.addDependenceArc( fromNode, destClass, ARC_CLASS );
} catch ( NullPointerException e ){
/*
* This is extra-cautious. I don't believe any of these
* situations can occur using the current DependenceAnalyzers.
* I believe that, barring catastrophies such as running out of memory,
* data structures will be created on-the-fly by the stringToXXX
* methods.
*/
if ( fromNode == null ){
System.out.println(
Localizer.getString("javafilter.cannot_find",
r.fromName));
hadError = true;
return;
}
if ( !toClass ){
if ( destMember == null ){
System.out.println(
Localizer.getString("javafilter.cannot_find",
r.toName));
hadError = true;
return;
}
}
if ( destClass == null ){
System.out.println(
Localizer.getString("javafilter.cannot_find",
r.toName));
hadError = true;
return;
}
// else, none of the above
// propigate the error, as it is not one we want to deal with.
throw e;
}
}
/*
* doLoad(). Called at the end of argument processing.
*/
private void doLoad(){
// first, work off the lists we collected
// during argument processing.
// all the -classpath and -exclude stuff
// has been taken care of.
// add interfaces and dependences first.
// then things to load
Enumeration work = interfaceClasses.elements();
while ( work.hasMoreElements() ){
interfaceClass( (String) ( work.nextElement() ) );
}
work = dependences.elements();
while ( work.hasMoreElements() ){
addDependence( (DependenceRelation)( work.nextElement() ) );
}
work = membersToLoad.elements();
while ( work.hasMoreElements() ){
loadMember( (String)( work.nextElement() ) );
}
work = constructorsToLoad.elements();
while ( work.hasMoreElements() ){
loadConstructor( (String)( work.nextElement() ) );
}
work = classesToLoad.elements();
while ( work.hasMoreElements() ){
loadClass( (String)( work.nextElement() ) );
}
work = paths.elements();
while ( work.hasMoreElements() ){
PathPair pp = (PathPair)(work.nextElement());
loadMember( (String) pp.src );
loadMember( (String) pp.dest );
}
work = showClasses.elements();
while ( work.hasMoreElements() ){
loadClass( (String)( work.nextElement() ) );
}
if ( hadError ) return; // oops.
if ( initialList.size() == 0) return; // vacuous!
if ( verbosity > 0 ){
System.err.println(
Localizer.getString("javafilter.loading_based_on_these_requirements"));
printList( System.err, initialList );
}
if ( wholeClass == null )
wholeClass = new NeverAccept();
if ( wholeData == null )
wholeData = new NeverAccept();
loaded = processList( initialList, REQ, INITREQ, REQ|INTF, verbosity );
if ( loaded == null ) {
System.err.println(Localizer.getString("javafilter.nothing_to_load"));
} else if (verbosity > 0 ){
System.out.println("These members loaded:");
printList( System.out, loaded );
}
}
static void printDependences( DependenceNode node ){
if ( node.state() != DependenceNode.ANALYZED ){
if (node.state() == DependenceNode.ERROR)
System.out.println( node.name()+" unanalysed");
return;
}
System.out.println( node.name()+" depends on:");
Enumeration e = node.dependsOn();
while ( e.hasMoreElements() ){
DependenceArc a = (DependenceArc)(e.nextElement());
Object mn = a.to().name();
String dep = (String) mn.toString();
/*
if (dep.startsWith("java/io") ||
dep.startsWith("java/lang") ||
dep.startsWith("java/net") ||
dep.startsWith("java/util") )
continue;
*/
System.out.println(" "+dep );
}
}
static void printClassList( DependenceNode node ){
if ( node.state() != DependenceNode.ANALYZED ){
// if (node.state() == DependenceNode.ERROR)
// System.out.println( node.name()+" unanalysed");
return;
}
String str = node.name().toString();
/* Ignore array */
if (str.startsWith("["))
return;
int index = str.indexOf('.');
if (index > 0)
System.out.println(str.substring(0, index) + ".class");
else
System.out.println(str + ".class");
Enumeration e = node.dependsOn();
while ( e.hasMoreElements() ){
DependenceArc a = (DependenceArc)(e.nextElement());
Object mn = a.to().name();
str = (String) mn.toString();
if (str.startsWith("["))
continue;
index = str.indexOf('.');
if (index > 0)
System.out.println(str.substring(0, index) + ".class");
else
System.out.println(str + ".class");
}
}
private void showOne( String classname ){
MemberDependenceNode mdn;
ClassEntry c = stringToClass( classname.replace('.', '/') );
mdn = c.lookupMember( staticInitializerName+staticInitializerSig );
if (mdn != null)
printDependences(mdn);
Enumeration members = c.members();
if ( members != null ){
while ( members.hasMoreElements() ){
mdn = (MemberDependenceNode)(members.nextElement() );
printDependences(mdn);
}
}
}
private void showAll() {
analyzer.analyzeAllDependences();
Enumeration e = analyzer.allNodes();
int nClasses = 0;
while ( e.hasMoreElements()){
DependenceNode node = (DependenceNode)(e.nextElement());
printDependences( node );
nClasses++;
}
System.out.println("TOTAL OF "+nClasses+" CLASSES");
}
private void showClassList() {
analyzer.analyzeAllDependences();
Enumeration e = analyzer.allNodes();
while ( e.hasMoreElements()){
DependenceNode node = (DependenceNode)(e.nextElement());
printClassList( node );
}
}
private void showPath( String srcmembername, String destmembername ){
MemberDependenceNode src = stringToNode( srcmembername );
MemberDependenceNode dest = stringToNode( destmembername );
if ( src.state() != DependenceNode.ANALYZED ){
System.err.println( srcmembername+" not part of graph");
return;
}
if ( dest.state() != DependenceNode.ANALYZED ){
System.err.println( destmembername+" not part of graph");
return;
}
clearUserFlags( PathComponent.INSET );
Set tipset = new Set();
PathComponent r = new PathComponent( null, src );
tipset.addElement( r );
for ( int i = 0; i < 50; i++ ){
Set newtips = new Set();
Enumeration newGrowth = tipset.elements();
while ( newGrowth.hasMoreElements() ){
PathComponent t = (PathComponent)(newGrowth.nextElement() );
if ( t.link.state() == DependenceNode.UNANALYZED ){
System.err.println("Potential path element unanalyzed: "+t.link.name() );
} else {
t.grow( newtips );
}
}
if ( newtips.size() == 0 ){
// exhaused the space without finding it.
System.out.println("No path from "+srcmembername+" to "+destmembername );
return;
}
// now see if we got to our destination.
newGrowth = newtips.elements();
boolean foundDest = false;
while ( newGrowth.hasMoreElements() ){
PathComponent t = (PathComponent)(newGrowth.nextElement() );
if ( t.link == dest ){
t.print( System.out );
System.out.println();
foundDest = true;
}
t.link.flags |= PathComponent.INSET;
}
if ( foundDest ){
return;
}
// round and round.
tipset = newtips;
}
System.out.println("Path from "+srcmembername+" to "+destmembername+" is too long" );
}
private void printList( java.io.PrintStream o, Vector elementList ){
printFlaggedList( o, elementList, ANY );
}
private void printFlaggedList( java.io.PrintStream o, Vector elementList, int flagmask ){
if ( elementList == null ) {
o.println(" <nothing>");
return;
}
Enumeration loadlist = elementList.elements();
while ( loadlist.hasMoreElements() ){
DependenceNode thing = (DependenceNode)(loadlist.nextElement());
if ( (thing.flags&flagmask) != 0 ){
o.println( thing.name() );
}
}
o.flush();
}
private void processArgs( String args[] ){
for ( int i = 0; i < args.length; i++ ){
String a = args[i];
/*
* -classpath is common to all uses of
* dependence analysis. The argument is a
* :-separated list of places to look for
* class files. This can include jar/zip files
* and directories. This can be given multiple
* times, and is cumulative.
*/
if ( a.equals("-classpath") ){
finder.addToSearchPath( args[++i] );
continue;
}
/*
* -exclude is common to all uses of dependence
* analysis. The argument is a full class name,
* for a class which is not to be analysed
* (except for the names it exports and
* superclass relationships). If the last
* component of the name is '*', then this
* represents a whole package to be excluded.
*/
if ( a.equals("-exclude") ){
excludeClass( args[++i] );
continue;
}
/*
* -f filename
* to name a file containing more commands.
* It is used mostly for including canned
* sequences of commands, such as those naming
* all the classes required by the implementation.
* They are processed in order, immediately.
*/
if ( a.equals("-f" ) ){
String filename = args[++i];
try {
String newargs[] = parseOptionFile( filename );
processArgs( newargs ); // just call us recursively.
} catch ( java.io.IOException ioe ){
System.out.println(
Localizer.getString("javafilter.could_not_process",
filename)
);
ioe.printStackTrace();
hadError = true;
}
continue;
}
/*
* -v increases verbosity.
*/
if ( a.equals("-v" ) ){
verbosity += 1;
continue;
}
/*
* -nativelist filename
* to have the names of all native methods included
* written on the named file.
*/
if ( a.equals("-nativelist") ){
try {
nativelist = new BufferedPrintStream( new FileOutputStream( args[++i] ) );
} catch ( Exception e ){
System.err.println(
Localizer.getString("javafilter.cannot_open_nativelist", args[i])
);
hadError = true;
}
continue;
}
/*
* Dependence loading.
* -load full-member-name to specify a member.
*/
if ( a.equals("-load") ){
membersToLoad.addElement( args[++i] );
continue;
}
/*
* -new full-class-name
* -new full-class-name(sig)V
* A shorthand for naming constructors
* -load full-class-name.<init>()V or
* -load full-class-name.<init>(sig)V
*/
if ( a.equals("-new") ){
constructorsToLoad.addElement( args[++i] );
continue;
}
/*
* -main full-class-name is shorthand for
* -load full-class-name.main(Ljava/lang/String;)V
*/
if ( a.equals("-main") ){
membersToLoad.addElement( args[++i] + "." + mainName + mainSig );
continue;
}
/*
* -loadClass full-class-name to specify all
* members of a class.
*/
if ( a.equals("-loadClass") ){
classesToLoad.addElement( args[++i] );
continue;
}
/*
* -interface full-class-name to specify a
* contract that must be honored by
* subclasses of the named class or
* implementors of an interface.
*/
if ( a.equals("-interface") ){
interfaceClasses.addElement( args[++i] );
continue;
}
/*
* -wholeClass full-class-name or
* -wholeClass packagename.*
* load the whole class.
*/
if ( a.equals("-wholeClass") ){
allClassMembers( args[++i] );
continue;
}
/*
* -wholeData full-class-name or
* -wholeData packagename.*
* load all the data.
*/
if ( a.equals("-wholeData") ){
allClassData( args[++i] );
continue;
}
/*
* -dependence full-member-name verb full-member-name
* To describe to the program dependences that it
* cannot be understood. Chiefly to describe
* dependences from native-code methods, which cannot
* otherwise be analysed. "verb" is one of:
* reads
* writes
* calls
* classRef, in which case the 3rd operand is actually
* a full-class-name
*/
if ( a.equals("-dependence") ){
dependences.addElement(
new DependenceRelation( args[++i], args[++i], args[++i] )
);
continue;
}
if ( a.equals("-help")) {
printUsage();
break;
}
if ( a.equals("-listSignatureDepend")) {
analyzer.useSignatureDependence(true);
continue;
}
/*
* -path a z
* To show the how a dependes on z. Prints all dependence chains of the
* shortest length. Probably does not deal in overloading issues, yet.
*/
if ( a.equals("-path") ){
paths.addElement( new PathPair( args[++i], args[++i] ) );
continue;
}
if ( a.equals("-showAll")) {
details = true;
continue;
}
if ( a.equals("-showClassList")) {
classlistonly = true;
continue;
}
if ( a.equals("-showDirect") ) {
showClasses.addElement( args[++i] );
break;
}
System.out.println(Localizer.getString("javafilter.command_not_recognized", args[i]));
hadError = true;
}
}
private void printUsage() {
System.out.println("printUsage");
}
private Vector interfaceClasses;
private Vector dependences;
private Vector membersToLoad;
private Vector constructorsToLoad;
private Vector classesToLoad;
private Vector paths;
private Vector showClasses;
private boolean process( String args[] ){
interfaceClasses = new Vector();
dependences = new Vector();
membersToLoad = new Vector();
constructorsToLoad = new Vector();
classesToLoad = new Vector();
paths = new Vector();
showClasses = new Vector();
processArgs( args );
if ( ! hadError )
doLoad();
/* showPath must do after the nodes are analyzed. */
Enumeration p = paths.elements();
if ( (! hadError) && ( p!=null) ){
while (p.hasMoreElements() ){
PathPair pp = (PathPair)(p.nextElement());
showPath( pp.src, pp.dest );
}
}
p = showClasses.elements();
if ( (! hadError) && ( p!=null) ){
while (p.hasMoreElements() ){
showOne((String)p.nextElement());
}
}
if (details)
showAll();
if (classlistonly)
showClassList();
return hadError;
}
public static void main( String args[] ){
boolean fail = (new JavaMemberDepend().process( args ));
if ( fail ){
System.exit( 1 );
}
}
class NeverAccept extends util.ClassnameFilter{
public boolean accept( java.io.File dir, String className ){
return false;
}
}
class PathPair {
String src;
String dest;
PathPair( String s, String d ){
src = s;
dest = d;
}
}
}