Mirror of the JODE repository
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
jode/jode/src/net/sf/jode/decompiler/ClassAnalyzer.java

782 lines
22 KiB

/* ClassAnalyzer Copyright (C) 1998-2002 Jochen Hoenicke.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* 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 for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; see the file COPYING.LESSER. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
package net.sf.jode.decompiler;
import net.sf.jode.GlobalOptions;
import net.sf.jode.type.MethodType;
import net.sf.jode.type.Type;
import net.sf.jode.bytecode.ClassFormatException;
import net.sf.jode.bytecode.ClassInfo;
import net.sf.jode.bytecode.ClassPath;
import net.sf.jode.bytecode.FieldInfo;
import net.sf.jode.bytecode.MethodInfo;
import net.sf.jode.expr.Expression;
import net.sf.jode.expr.ThisOperator;
import net.sf.jode.flow.TransformConstructors;
import net.sf.jode.flow.StructuredBlock;
import net.sf.jode.util.SimpleSet;
import java.lang.reflect.Modifier;
import java.util.Vector;
import java.io.IOException;
///#def COLLECTIONS java.util
import java.util.Collection;
import java.util.Set;
///#enddef
public class ClassAnalyzer
implements Scope, Declarable, ClassDeclarer
{
ImportHandler imports;
ClassInfo clazz;
ClassDeclarer parent;
ProgressListener progressListener;
/**
* The complexity for initi#alizing a class.
*/
private static double INITIALIZE_COMPLEXITY = 0.03;
/**
* The minimal visible complexity.
*/
private static double STEP_COMPLEXITY = 0.03;
/**
* The value of the strictfp modifier.
* JDK1.1 doesn't define it.
*/
private static int STRICTFP = 0x800;
double methodComplexity = 0.0;
double innerComplexity = 0.0;
String name;
StructuredBlock[] blockInitializers;
FieldAnalyzer[] fields;
MethodAnalyzer[] methods;
ClassAnalyzer[] inners;
int modifiers;
TransformConstructors constrAna;
MethodAnalyzer staticConstructor;
MethodAnalyzer[] constructors;
/**
* The outer values for method scoped classes.
*/
OuterValues outerValues;
/**
* The outer instance for non-static class scope classes.
*/
Expression outerInstance;
public ClassAnalyzer(ClassDeclarer parent,
ClassInfo clazz, ImportHandler imports,
Expression[] outerValues)
throws ClassFormatException, IOException
{
clazz.load(ClassInfo.ALL);
ClassInfo superClass = clazz.getSuperclass();
String myPackage = clazz.getName().substring
(clazz.getName().lastIndexOf('.') + 1);
while (superClass != null) {
int howMuch = (superClass.getName().startsWith(myPackage)
&& (superClass.getName().lastIndexOf('.')
< myPackage.length()))
? ClassInfo.DECLARATIONS : ClassInfo.PUBLICDECLARATIONS;
try {
superClass.load(howMuch);
} catch (IOException ex) {
GlobalOptions.err.println
("Warning: Can't get "
+ (howMuch == ClassInfo.PUBLICDECLARATIONS
? "public" : "all")
+ " information of " + superClass
+" to detect name conflicts.");
GlobalOptions.err.println(ex.toString());
superClass.guess(howMuch);
}
superClass = superClass.getSuperclass();
}
this.parent = parent;
this.clazz = clazz;
this.imports = imports;
modifiers = clazz.getModifiers();
name = clazz.getClassName();
/* Check if this is a normal non-static inner class and set
* outerInstance.
*/
if ((Options.options & Options.OPTION_INNER) != 0
&& parent instanceof ClassAnalyzer && !isStatic())
outerInstance = new ThisOperator(((ClassAnalyzer) parent).clazz);
if (outerValues != null)
this.outerValues = new OuterValues(this, outerValues);
}
public ClassAnalyzer(ClassDeclarer parent,
ClassInfo clazz, ImportHandler imports)
throws ClassFormatException, IOException
{
this(parent, clazz, imports, null);
}
public ClassAnalyzer(ClassInfo clazz, ImportHandler imports)
throws ClassFormatException, IOException
{
this(null, clazz, imports);
}
public ClassPath getClassPath() {
return clazz.getClassPath();
}
public final boolean isStatic() {
return Modifier.isStatic(modifiers);
}
public final boolean isStrictFP() {
return (modifiers & STRICTFP) != 0;
}
public FieldAnalyzer getField(int index) {
return fields[index];
}
public int getFieldIndex(String fieldName, Type fieldType) {
for (int i=0; i< fields.length; i++) {
if (fields[i].getName().equals(fieldName)
&& fields[i].getType().equals(fieldType))
return i;
}
return -1;
}
public MethodAnalyzer getMethod(String methodName, MethodType methodType) {
for (int i=0; i< methods.length; i++) {
if (methods[i].getName().equals(methodName)
&& methods[i].getType().equals(methodType))
return methods[i];
}
return null;
}
public int getModifiers() {
return modifiers;
}
public ClassDeclarer getParent() {
return parent;
}
public void setParent(ClassDeclarer newParent) {
this.parent = newParent;
}
public ClassInfo getClazz() {
return clazz;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public OuterValues getOuterValues() {
return outerValues;
}
public Expression getOuterInstance() {
return outerInstance;
}
public void addBlockInitializer(int index, StructuredBlock initializer) {
if (blockInitializers[index] == null)
blockInitializers[index] = initializer;
else
blockInitializers[index].appendBlock(initializer);
}
public void initialize() {
FieldInfo[] finfos = clazz.getFields();
MethodInfo[] minfos = clazz.getMethods();
ClassInfo[] innerInfos = clazz.getClasses();
if (finfos == null) {
/* This means that the class could not be loaded.
* give up.
*/
return;
}
if ((Options.options & Options.OPTION_INNER) != 0
&& innerInfos != null) {
/* Create inner classes */
int innerCount = innerInfos.length;
inners = new ClassAnalyzer[innerCount];
for (int i=0; i < innerCount; i++) {
try {
inners[i] = new ClassAnalyzer
(this, innerInfos[i], imports, null);
} catch (ClassFormatException ex) {
GlobalOptions.err.println("Inner class "+innerInfos[i]
+" malformed!");
ex.printStackTrace(GlobalOptions.err);
} catch (IOException ex) {
GlobalOptions.err.println("Can't read inner class "
+innerInfos[i]+".");
ex.printStackTrace(GlobalOptions.err);
}
}
} else
inners = new ClassAnalyzer[0];
fields = new FieldAnalyzer[finfos.length];
methods = new MethodAnalyzer[minfos.length];
blockInitializers = new StructuredBlock[finfos.length+1];
for (int j=0; j < finfos.length; j++)
fields[j] = new FieldAnalyzer(this, finfos[j], imports);
staticConstructor = null;
Vector constrVector = new Vector();
for (int j=0; j < methods.length; j++) {
methods[j] = new MethodAnalyzer(this, minfos[j], imports);
if (methods[j].isConstructor()) {
if (methods[j].isStatic())
staticConstructor = methods[j];
else
constrVector.addElement(methods[j]);
/* Java bytecode can't have strictfp modifier for
* classes, while java can't have strictfp modifier
* for constructors. We handle the difference here.
*
* If only a few constructors are strictfp and the
* methods aren't this would add too much strictfp,
* but that isn't really dangerous.
*/
if (methods[j].isStrictFP())
modifiers |= STRICTFP;
}
methodComplexity += methods[j].getComplexity();
}
constructors = new MethodAnalyzer[constrVector.size()];
constrVector.copyInto(constructors);
// initialize the inner classes.
for (int j=0; j < inners.length; j++) {
if (inners[j] == null)
continue;
inners[j].initialize();
innerComplexity += inners[j].getComplexity();
}
}
/**
* Gets the complexity of this class. Must be called after it has
* been initialized. This is used for a nice progress bar.
*/
public double getComplexity() {
return (methodComplexity + innerComplexity);
}
public void analyze(ProgressListener pl, double done, double scale) {
if (GlobalOptions.verboseLevel > 0)
GlobalOptions.err.println("Class " + name);
double subScale = scale / methodComplexity;
if (pl != null)
pl.updateProgress(done, name);
imports.useClass(clazz);
if (clazz.getSuperclass() != null)
imports.useClass(clazz.getSuperclass());
ClassInfo[] interfaces = clazz.getInterfaces();
for (int j=0; j< interfaces.length; j++)
imports.useClass(interfaces[j]);
if (fields == null) {
/* This means that the class could not be loaded.
* give up.
*/
return;
}
// First analyze constructors and synthetic fields:
constrAna = null;
if (constructors.length > 0) {
for (int j=0; j< constructors.length; j++) {
if (pl != null) {
double constrCompl = constructors[j].getComplexity()
* subScale;
if (constrCompl > STEP_COMPLEXITY)
constructors[j].analyze(pl, done, constrCompl);
else {
pl.updateProgress(done, name);
constructors[j].analyze(null, 0.0, 0.0);
}
done += constrCompl;
} else
constructors[j].analyze(null, 0.0, 0.0);
}
constrAna = new TransformConstructors(this, false, constructors);
constrAna.removeSynthInitializers();
}
if (staticConstructor != null) {
if (pl != null) {
double constrCompl
= staticConstructor.getComplexity() * subScale;
if (constrCompl > STEP_COMPLEXITY)
staticConstructor.analyze(pl, done, constrCompl);
else {
pl.updateProgress(done, name);
staticConstructor.analyze(null, 0.0, 0.0);
}
done += constrCompl;
} else
staticConstructor.analyze(null, 0.0, 0.0);
}
// If output should be immediate, we delay analyzation to output.
// Note that this may break anonymous classes, but the user
// has been warned.
if ((Options.options & Options.OPTION_IMMEDIATE) != 0)
return;
// Analyze fields
for (int j=0; j < fields.length; j++)
fields[j].analyze();
// Now analyze remaining methods.
for (int j=0; j < methods.length; j++) {
if (!methods[j].isConstructor())
if (pl != null) {
double methodCompl = methods[j].getComplexity()
* subScale;
if (methodCompl > STEP_COMPLEXITY)
methods[j].analyze(pl, done, methodCompl);
else {
pl.updateProgress(done, methods[j].getName());
methods[j].analyze(null, 0.0, 0.0);
}
done += methodCompl;
} else
methods[j].analyze(null, 0.0, 0.0);
}
}
public void analyzeInnerClasses(ProgressListener pl,
double done, double scale) {
double subScale = scale / innerComplexity;
// If output should be immediate, we delay analyzation to output.
// Note that this may break anonymous classes, but the user
// has been warned.
if ((Options.options & Options.OPTION_IMMEDIATE) != 0)
return;
// Now analyze the inner classes.
for (int j=0; j < inners.length; j++) {
if (inners[j] == null)
continue;
if (pl != null) {
double innerCompl = inners[j].getComplexity() * subScale;
if (innerCompl > STEP_COMPLEXITY) {
double innerscale = subScale * inners[j].methodComplexity;
inners[j].analyze(pl, done, innerscale);
inners[j].analyzeInnerClasses(null, done + innerscale,
innerCompl - innerscale);
} else {
pl.updateProgress(done, inners[j].name);
inners[j].analyze(null, 0.0, 0.0);
inners[j].analyzeInnerClasses(null, 0.0, 0.0);
}
done += innerCompl;
} else {
inners[j].analyze(null, 0.0, 0.0);
inners[j].analyzeInnerClasses(null, 0.0, 0.0);
}
}
// Now analyze the method scoped classes.
for (int j=0; j < methods.length; j++)
methods[j].analyzeInnerClasses();
}
public void makeDeclaration(Set done) {
// First prepare constructors:
if (constrAna != null)
constrAna.transform();
if (staticConstructor != null) {
new TransformConstructors
(this, true, new MethodAnalyzer[] { staticConstructor })
.transform();
}
// If output should be immediate, we delay analyzation to output.
// Note that this may break anonymous classes, but the user
// has been warned.
if ((Options.options & Options.OPTION_IMMEDIATE) != 0)
return;
for (int j=0; j < fields.length; j++)
fields[j].makeDeclaration(done);
for (int j=0; j < inners.length; j++)
if (inners[j] != null)
inners[j].makeDeclaration(done);
for (int j=0; j < methods.length; j++)
methods[j].makeDeclaration(done);
}
public void dumpDeclaration(TabbedPrintWriter writer) throws IOException
{
dumpDeclaration(writer, null, 0.0, 0.0);
}
public void dumpDeclaration(TabbedPrintWriter writer,
ProgressListener pl, double done, double scale)
throws IOException
{
if (fields == null) {
/* This means that the class could not be loaded.
* give up.
*/
return;
}
writer.startOp(TabbedPrintWriter.NO_PAREN, 0);
/* Clear the SUPER bit, which is also used as SYNCHRONIZED bit. */
int modifiedModifiers = modifiers & ~(Modifier.SYNCHRONIZED
| STRICTFP);
if (clazz.isInterface())
/* interfaces are implicitily abstract */
modifiedModifiers &= ~Modifier.ABSTRACT;
if (parent instanceof MethodAnalyzer) {
/* method scope classes are implicitly private */
modifiedModifiers &= ~Modifier.PRIVATE;
/* anonymous classes are implicitly final */
if (name == null)
modifiedModifiers &= ~Modifier.FINAL;
}
String modif = Modifier.toString(modifiedModifiers);
if (modif.length() > 0)
writer.print(modif + " ");
if (isStrictFP()) {
/* The STRICTFP modifier is set.
* We handle it, since java.lang.reflect.Modifier is too dumb.
*/
writer.print("strictfp ");
}
/* interface is in modif */
if (!clazz.isInterface())
writer.print("class ");
writer.print(name);
ClassInfo superClazz = clazz.getSuperclass();
if (superClazz != null &&
superClazz.getName() != "java.lang.Object") {
writer.breakOp();
writer.print(" extends " + (writer.getClassString
(superClazz, Scope.CLASSNAME)));
}
ClassInfo[] interfaces = clazz.getInterfaces();
if (interfaces.length > 0) {
writer.breakOp();
writer.print(clazz.isInterface() ? " extends " : " implements ");
writer.startOp(TabbedPrintWriter.EXPL_PAREN, 1);
for (int i=0; i < interfaces.length; i++) {
if (i > 0) {
writer.print(", ");
writer.breakOp();
}
writer.print(writer.getClassString
(interfaces[i], Scope.CLASSNAME));
}
writer.endOp();
}
writer.println();
writer.openBraceClass();
writer.tab();
dumpBlock(writer, pl, done, scale);
writer.untab();
writer.closeBraceClass();
}
public void dumpBlock(TabbedPrintWriter writer)
throws IOException
{
dumpBlock(writer, null, 0.0, 0.0);
}
public void dumpBlock(TabbedPrintWriter writer,
ProgressListener pl, double done, double scale)
throws IOException
{
double subScale = scale / getComplexity();
writer.pushScope(this);
boolean needFieldNewLine = false;
boolean needNewLine = false;
Set declared = null;
if ((Options.options & Options.OPTION_IMMEDIATE) != 0)
declared = new SimpleSet();
for (int i=0; i< fields.length; i++) {
if (blockInitializers[i] != null) {
if (needNewLine)
writer.println();
writer.openBrace();
writer.tab();
blockInitializers[i].dumpSource(writer);
writer.untab();
writer.closeBrace();
needFieldNewLine = needNewLine = true;
}
if ((Options.options & Options.OPTION_IMMEDIATE) != 0) {
// We now do the analyzation we skipped before.
fields[i].analyze();
fields[i].makeDeclaration(declared);
}
if (fields[i].skipWriting())
continue;
if (needFieldNewLine)
writer.println();
fields[i].dumpSource(writer);
needNewLine = true;
}
if (blockInitializers[fields.length] != null) {
if (needNewLine)
writer.println();
writer.openBrace();
writer.tab();
blockInitializers[fields.length].dumpSource(writer);
writer.untab();
writer.closeBrace();
needNewLine = true;
}
for (int i=0; i< inners.length; i++) {
if (needNewLine)
writer.println();
if (inners[i] == null) {
writer.println("COULDN'T READ INNER CLASS!");
continue;
}
if ((Options.options & Options.OPTION_IMMEDIATE) != 0) {
// We now do the analyzation we skipped before.
inners[i].analyze(null, 0.0, 0.0);
inners[i].analyzeInnerClasses(null, 0.0, 0.0);
inners[i].makeDeclaration(declared);
}
if (pl != null) {
double innerCompl = inners[i].getComplexity() * subScale;
if (innerCompl > STEP_COMPLEXITY)
inners[i].dumpSource(writer, pl, done, innerCompl);
else {
pl.updateProgress(done, name);
inners[i].dumpSource(writer);
}
done += innerCompl;
} else
inners[i].dumpSource(writer);
needNewLine = true;
}
for (int i=0; i< methods.length; i++) {
if ((Options.options & Options.OPTION_IMMEDIATE) != 0) {
// We now do the analyzation we skipped before.
if (!methods[i].isConstructor())
methods[i].analyze(null, 0.0, 0.0);
methods[i].analyzeInnerClasses();
methods[i].makeDeclaration(declared);
}
if (methods[i].skipWriting())
continue;
if (needNewLine)
writer.println();
if (pl != null) {
double methodCompl = methods[i].getComplexity() * subScale;
pl.updateProgress(done, methods[i].getName());
methods[i].dumpSource(writer);
done += methodCompl;
} else
methods[i].dumpSource(writer);
needNewLine = true;
}
writer.popScope();
clazz.drop(ClassInfo.DECLARATIONS);
}
public void dumpSource(TabbedPrintWriter writer)
throws IOException
{
dumpSource(writer, null, 0.0, 0.0);
}
public void dumpSource(TabbedPrintWriter writer,
ProgressListener pl, double done, double scale)
throws IOException
{
dumpDeclaration(writer, pl, done, scale);
writer.println();
}
public void dumpJavaFile(TabbedPrintWriter writer)
throws IOException {
dumpJavaFile(writer, null);
}
public void dumpJavaFile(TabbedPrintWriter writer, ProgressListener pl)
throws IOException {
imports.init(clazz.getName());
LocalInfo.init();
initialize();
double done = 0.05;
double scale = (0.75) * methodComplexity
/ (methodComplexity + innerComplexity);
analyze(pl, INITIALIZE_COMPLEXITY, scale);
done += scale;
analyzeInnerClasses(pl, done, 0.8 - done);
makeDeclaration(new SimpleSet());
imports.dumpHeader(writer);
dumpSource(writer, pl, 0.8, 0.2);
if (pl != null)
pl.updateProgress(1.0, name);
writer.flush();
}
public boolean isScopeOf(Object obj, int scopeType) {
if (clazz.equals(obj) && scopeType == CLASSSCOPE)
return true;
return false;
}
static int serialnr = 0;
public void makeNameUnique() {
name = name + "_" + serialnr++ + "_";
}
public boolean conflicts(String name, int usageType) {
return conflicts(clazz, name, usageType);
}
private static boolean conflicts(ClassInfo info,
String name, int usageType) {
while (info != null) {
if (usageType == NOSUPERMETHODNAME || usageType == METHODNAME) {
MethodInfo[] minfos = info.getMethods();
for (int i = 0; i< minfos.length; i++)
if (minfos[i].getName().equals(name))
return true;
}
if (usageType == NOSUPERFIELDNAME || usageType == FIELDNAME
|| usageType == AMBIGUOUSNAME) {
FieldInfo[] finfos = info.getFields();
for (int i=0; i < finfos.length; i++) {
if (finfos[i].getName().equals(name))
return true;
}
}
if (usageType == CLASSNAME || usageType == AMBIGUOUSNAME) {
try {
info.load(ClassInfo.DECLARATIONS);
} catch (IOException ex) {
info.guess(ClassInfo.DECLARATIONS);
}
ClassInfo[] iinfos = info.getClasses();
if (iinfos != null) {
for (int i=0; i < iinfos.length; i++) {
if (iinfos[i].getClassName().equals(name))
return true;
}
}
}
if (usageType == NOSUPERFIELDNAME
|| usageType == NOSUPERMETHODNAME)
return false;
ClassInfo[] ifaces = info.getInterfaces();
for (int i = 0; i < ifaces.length; i++)
if (conflicts(ifaces[i], name, usageType))
return true;
info = info.getSuperclass();
}
return false;
}
/**
* Get the class analyzer for the given class info. This searches
* the method scoped/anonymous classes in this method and all
* outer methods and the outer classes for the class analyzer.
* @param cinfo the classinfo for which the analyzer is searched.
* @return the class analyzer, or null if there is not an outer
* class that equals cinfo, and not a method scope/inner class in
* an outer method.
*/
public ClassAnalyzer getClassAnalyzer(ClassInfo cinfo) {
if (cinfo == getClazz())
return this;
if (parent == null)
return null;
return getParent().getClassAnalyzer(cinfo);
}
/**
* Get the class analyzer for the given inner class.
* @param name the short name of the inner class
* @return the class analyzer, or null if there is no inner
* class with the given name.
*/
public ClassAnalyzer getInnerClassAnalyzer(String name) {
/** require name != null; **/
int innerCount = inners.length;
for (int i=0; i < innerCount; i++) {
if (inners[i] != null && inners[i].name.equals(name))
return inners[i];
}
return null;
}
/**
* We add the named method scoped classes to the declarables.
*/
public void fillDeclarables(Collection used) {
for (int j=0; j < methods.length; j++)
methods[j].fillDeclarables(used);
}
public void addClassAnalyzer(ClassAnalyzer clazzAna) {
if (parent != null)
parent.addClassAnalyzer(clazzAna);
}
public String toString() {
return getClass().getName()+"["+getClazz()+"]";
}
}