From 663631f0456fcc245dd835889f86541d75161c53 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Thu, 28 Aug 2014 20:52:43 +0400 Subject: java-decompiler: post-import cleanup (classes moved) --- .../java/decompiler/main/ClassesProcessor.java | 449 +++++++++++++++++++++ 1 file changed, 449 insertions(+) create mode 100644 src/org/jetbrains/java/decompiler/main/ClassesProcessor.java (limited to 'src/org/jetbrains/java/decompiler/main/ClassesProcessor.java') diff --git a/src/org/jetbrains/java/decompiler/main/ClassesProcessor.java b/src/org/jetbrains/java/decompiler/main/ClassesProcessor.java new file mode 100644 index 0000000..e0e144f --- /dev/null +++ b/src/org/jetbrains/java/decompiler/main/ClassesProcessor.java @@ -0,0 +1,449 @@ +/* + * Fernflower - The Analytical Java Decompiler + * http://www.reversed-java.com + * + * (C) 2008 - 2010, Stiver + * + * This software is NEITHER public domain NOR free software + * as per GNU License. See license.txt for more details. + * + * This software is distributed WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. + */ + +package org.jetbrains.java.decompiler.main; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.StringWriter; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map.Entry; +import java.util.Set; + +import org.jetbrains.java.decompiler.code.CodeConstants; +import org.jetbrains.java.decompiler.main.collectors.CounterContainer; +import org.jetbrains.java.decompiler.main.collectors.ImportCollector; +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; +import org.jetbrains.java.decompiler.main.extern.IIdentifierRenamer; +import org.jetbrains.java.decompiler.main.rels.ClassWrapper; +import org.jetbrains.java.decompiler.main.rels.LambdaProcessor; +import org.jetbrains.java.decompiler.main.rels.NestedClassProcessor; +import org.jetbrains.java.decompiler.main.rels.NestedMemberAccess; +import org.jetbrains.java.decompiler.modules.decompiler.exps.InvocationExprent; +import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionPaar; +import org.jetbrains.java.decompiler.struct.StructClass; +import org.jetbrains.java.decompiler.struct.StructContext; +import org.jetbrains.java.decompiler.struct.StructMethod; +import org.jetbrains.java.decompiler.struct.attr.StructInnerClassesAttribute; +import org.jetbrains.java.decompiler.struct.gen.VarType; +import org.jetbrains.java.decompiler.util.InterpreterUtil; + +public class ClassesProcessor { + + private HashMap mapRootClasses = new HashMap(); + + public ClassesProcessor(StructContext context) { + + HashMap mapInnerClasses = new HashMap(); + + HashMap> mapNestedClassReferences = new HashMap>(); + HashMap> mapEnclosingClassReferences = new HashMap>(); + + HashMap mapNewSimpleNames = new HashMap(); + + boolean bDecompileInner = DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_INNER); + + // create class nodes + for(StructClass cl: context.getClasses().values()) { + if(cl.isOwn() && !mapRootClasses.containsKey(cl.qualifiedName)) { + + if(bDecompileInner) { + StructInnerClassesAttribute inner = (StructInnerClassesAttribute)cl.getAttributes().getWithKey("InnerClasses"); + if(inner != null) { + + for(int i=0;i set = mapNestedClassReferences.get(enclClassName); + if(set == null) { + mapNestedClassReferences.put(enclClassName, set = new HashSet()); + } + set.add(innername); + + // reference to the enclosing class + set = mapEnclosingClassReferences.get(innername); + if(set == null) { + mapEnclosingClassReferences.put(innername, set = new HashSet()); + } + set.add(enclClassName); + } + } + } + } + } + + ClassNode node = new ClassNode(ClassNode.CLASS_ROOT, cl); + node.access = cl.access_flags; + mapRootClasses.put(cl.qualifiedName, node); + } + } + + if(bDecompileInner) { + + // connect nested classes + for(Entry ent: mapRootClasses.entrySet()) { + // root class? + if(!mapInnerClasses.containsKey(ent.getKey())) { + + HashSet setVisited = new HashSet(); + LinkedList stack = new LinkedList(); + + stack.add(ent.getKey()); + setVisited.add(ent.getKey()); + + while(!stack.isEmpty()) { + + String superClass = stack.removeFirst(); + ClassNode supernode = mapRootClasses.get(superClass); + + HashSet setNestedClasses = mapNestedClassReferences.get(superClass); + if(setNestedClasses != null) { + + StructClass scl = supernode.classStruct; + StructInnerClassesAttribute inner = (StructInnerClassesAttribute) scl.getAttributes().getWithKey("InnerClasses"); + for(int i = 0; i < inner.getStringentries().size(); i++) { + String nestedClass = inner.getStringentries().get(i)[0]; + if (!setNestedClasses.contains(nestedClass)) { + continue; + } + + if(setVisited.contains(nestedClass)) { + continue; + } + setVisited.add(nestedClass); + + ClassNode nestednode = mapRootClasses.get(nestedClass); + if(nestednode == null) { + DecompilerContext.getLogger().writeMessage("Nested class "+nestedClass+" missing!", IFernflowerLogger.WARNING); + continue; + } + + Object[] arr = mapInnerClasses.get(nestedClass); + + if((Integer)arr[2] == ClassNode.CLASS_MEMBER) { + // FIXME: check for consistent naming + } + + nestednode.type = (Integer)arr[2]; + nestednode.simpleName = (String)arr[1]; + nestednode.access = (Integer)arr[3]; + + if(nestednode.type == ClassNode.CLASS_ANONYMOUS) { + StructClass cl = nestednode.classStruct; + + // remove static if anonymous class + // a common compiler bug + nestednode.access &= ~CodeConstants.ACC_STATIC; + + int[] interfaces = cl.getInterfaces(); + + if(interfaces.length > 0) { + if(interfaces.length > 1) { + DecompilerContext.getLogger().writeMessage("Inconsistent anonymous class definition: "+cl.qualifiedName, IFernflowerLogger.WARNING); + } + nestednode.anonimousClassType = new VarType(cl.getInterface(0), true); + } else { + nestednode.anonimousClassType = new VarType(cl.superClass.getString(), true); + } + } else if(nestednode.type == ClassNode.CLASS_LOCAL) { + // only abstract and final are permitted + // a common compiler bug + nestednode.access &= (CodeConstants.ACC_ABSTRACT | CodeConstants.ACC_FINAL); + } + + supernode.nested.add(nestednode); + nestednode.parent = supernode; + + nestednode.enclosingClasses.addAll(mapEnclosingClassReferences.get(nestedClass)); + + stack.add(nestedClass); + } + } + } + } + } + + } + + } + + + public void writeClass(StructContext context, StructClass cl, BufferedWriter outwriter) throws IOException { + + ClassNode root = mapRootClasses.get(cl.qualifiedName); + if(root.type != ClassNode.CLASS_ROOT) { + return; + } + + try { + DecompilerContext.setImpcollector(new ImportCollector(root)); + DecompilerContext.setCountercontainer(new CounterContainer()); + + // lambda processing + LambdaProcessor lambda_proc = new LambdaProcessor(); + lambda_proc.processClass(root); + + // add simple class names to implicit import + addClassnameToImport(root, DecompilerContext.getImpcollector()); + // build wrappers for all nested classes + // that's where the actual processing takes place + initWrappers(root); + + NestedClassProcessor nestedproc = new NestedClassProcessor(); + nestedproc.processClass(root, root); + + NestedMemberAccess nstmember = new NestedMemberAccess(); + nstmember.propagateMemberAccess(root); + + ClassWriter clwriter = new ClassWriter(); + + StringWriter strwriter = new StringWriter(); + clwriter.classToJava(root, new BufferedWriter(strwriter), 0); + + if(DecompilerContext.getOption(IFernflowerPreferences.OUTPUT_COPYRIGHT_COMMENT)) { + outwriter.write("// Decompiled by: Fernflower "+Fernflower.version); + outwriter.write(DecompilerContext.getNewLineSeparator()); + outwriter.write("// Date: "+new SimpleDateFormat("dd.MM.yyyy HH:mm:ss").format(new Date())); + outwriter.write(DecompilerContext.getNewLineSeparator()); + outwriter.write("// Copyright: 2008-2010, Stiver"); + outwriter.write(DecompilerContext.getNewLineSeparator()); + outwriter.write("// Home page: http://www.reversed-java.com"); + outwriter.write(DecompilerContext.getNewLineSeparator()); + outwriter.write(DecompilerContext.getNewLineSeparator()); + } + + int index = cl.qualifiedName.lastIndexOf("/"); + if(index >= 0) { + String packageName = cl.qualifiedName.substring(0, index).replace('/', '.'); + outwriter.write("package "); + outwriter.write(packageName); + outwriter.write(";"); + outwriter.write(DecompilerContext.getNewLineSeparator()); + outwriter.write(DecompilerContext.getNewLineSeparator()); + } + + DecompilerContext.setProperty(DecompilerContext.CURRENT_CLASSNODE, root); + + DecompilerContext.getImpcollector().writeImports(outwriter); + outwriter.write(DecompilerContext.getNewLineSeparator()); + + outwriter.write(strwriter.toString()); + outwriter.flush(); + + } finally { + destroyWrappers(root); + } + } + + private void initWrappers(ClassNode node) throws IOException { + + if(node.type == ClassNode.CLASS_LAMBDA) { + return; + } + + ClassWrapper wrapper = new ClassWrapper(node.classStruct); + wrapper.init(); + + node.wrapper = wrapper; + + for(ClassNode nd: node.nested) { + initWrappers(nd); + } + } + + private void addClassnameToImport(ClassNode node, ImportCollector imp) { + + if(node.simpleName != null && node.simpleName.length() > 0) { + imp.getShortName(node.type == ClassNode.CLASS_ROOT?node.classStruct.qualifiedName:node.simpleName, false); + } + + for(ClassNode nd: node.nested) { + addClassnameToImport(nd, imp); + } + } + + private void destroyWrappers(ClassNode node) { + + node.wrapper = null; + node.classStruct.releaseResources(); + + for(ClassNode nd: node.nested) { + destroyWrappers(nd); + } + } + + public HashMap getMapRootClasses() { + return mapRootClasses; + } + + + public class ClassNode { + + public static final int CLASS_ROOT = 0; + public static final int CLASS_MEMBER = 1; + public static final int CLASS_ANONYMOUS = 2; + public static final int CLASS_LOCAL = 4; + public static final int CLASS_LAMBDA = 8; + + public int type; + + public int access; + + public String simpleName; + + public StructClass classStruct; + + public ClassWrapper wrapper; + + public String enclosingMethod; + + public InvocationExprent superInvocation; + + public HashMap mapFieldsToVars = new HashMap(); + + public VarType anonimousClassType; + + public List nested = new ArrayList(); + + public Set enclosingClasses = new HashSet(); + + public ClassNode parent; + + public LambdaInformation lambda_information; + + public ClassNode(String content_class_name, String content_method_name, String content_method_descriptor, int content_method_invokation_type, + String lambda_class_name, String lambda_method_name, String lambda_method_descriptor, StructClass classStruct) { // lambda class constructor + this.type = CLASS_LAMBDA; + this.classStruct = classStruct; // 'parent' class containing the static function + + lambda_information = new LambdaInformation(); + + lambda_information.class_name = lambda_class_name; + lambda_information.method_name = lambda_method_name; + lambda_information.method_descriptor = lambda_method_descriptor; + + lambda_information.content_class_name = content_class_name; + lambda_information.content_method_name = content_method_name; + lambda_information.content_method_descriptor = content_method_descriptor; + lambda_information.content_method_invokation_type = content_method_invokation_type; + + lambda_information.content_method_key = InterpreterUtil.makeUniqueKey(lambda_information.content_method_name, lambda_information.content_method_descriptor); + + anonimousClassType = new VarType(lambda_class_name, true); + + boolean is_method_reference = (content_class_name != classStruct.qualifiedName); + StructMethod mt = null; + + if(!is_method_reference) { // content method in the same class, check synthetic flag + mt = classStruct.getMethod(content_method_name, content_method_descriptor); + is_method_reference = !((mt.getAccessFlags() & CodeConstants.ACC_SYNTHETIC) != 0 || mt.getAttributes().containsKey("Synthetic")); // if not synthetic -> method reference + } + + lambda_information.is_method_reference = is_method_reference; + lambda_information.is_content_method_static = (lambda_information.content_method_invokation_type == CodeConstants.CONSTANT_MethodHandle_REF_invokeStatic); // FIXME: redundant? + } + + public ClassNode(int type, StructClass classStruct) { + this.type = type; + this.classStruct = classStruct; + + simpleName = classStruct.qualifiedName.substring(classStruct.qualifiedName.lastIndexOf('/')+1); + } + + public ClassNode getClassNode(String qualifiedName) { + for(ClassNode node : nested) { + if(qualifiedName.equals(node.classStruct.qualifiedName)) { + return node; + } + } + return null; + } + + public class LambdaInformation { + + + + public String class_name; + public String method_name; + public String method_descriptor; + + public String content_class_name; + public String content_method_name; + public String content_method_descriptor; + public int content_method_invokation_type; // values from CONSTANT_MethodHandle_REF_* + + public String content_method_key; + + public boolean is_method_reference; + public boolean is_content_method_static; + } + } +} -- cgit v1.2.3