/* * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jetbrains.java.decompiler.main; 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; import java.io.BufferedWriter; import java.io.IOException; import java.io.StringWriter; import java.util.*; import java.util.Map.Entry; 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 < inner.getClassentries().size(); i++) { int[] entry = inner.getClassentries().get(i); String[] strentry = inner.getStringentries().get(i); Object[] arr = new Object[4]; // arr[0] not used String innername = strentry[0]; // nested class type arr[2] = entry[1] == 0 ? (entry[2] == 0 ? ClassNode.CLASS_ANONYMOUS : ClassNode.CLASS_LOCAL) : ClassNode.CLASS_MEMBER; // original simple name String simpleName = strentry[2]; String savedName = mapNewSimpleNames.get(innername); if (savedName != null) { simpleName = savedName; } else if (simpleName != null && DecompilerContext.getOption(IFernflowerPreferences.RENAME_ENTITIES)) { IIdentifierRenamer renamer = DecompilerContext.getPoolInterceptor().getHelper(); if (renamer.toBeRenamed(IIdentifierRenamer.ELEMENT_CLASS, simpleName, null, null)) { simpleName = renamer.getNextClassname(innername, simpleName); mapNewSimpleNames.put(innername, simpleName); } } arr[1] = simpleName; // original access flags arr[3] = entry[3]; // enclosing class String enclClassName = null; if (entry[1] != 0) { enclClassName = strentry[1]; } else { enclClassName = cl.qualifiedName; } if (!innername.equals(enclClassName)) { // self reference StructClass enclosing_class = context.getClasses().get(enclClassName); if (enclosing_class != null && enclosing_class.isOwn()) { // own classes only Object[] arrold = mapInnerClasses.get(innername); if (arrold == null) { mapInnerClasses.put(innername, arr); } else { if (!InterpreterUtil.equalObjectArrays(arrold, arr)) { DecompilerContext.getLogger() .writeMessage("Inconsistent inner class entries for " + innername + "!", IFernflowerLogger.WARNING); } } // reference to the nested class HashSet 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.getAccessFlags(); 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(StructClass cl, BufferedWriter writer) throws IOException { ClassNode root = mapRootClasses.get(cl.qualifiedName); if (root.type != ClassNode.CLASS_ROOT) { return; } try { DecompilerContext.setImportCollector(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.getImportCollector()); // 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); int index = cl.qualifiedName.lastIndexOf("/"); if (index >= 0) { String packageName = cl.qualifiedName.substring(0, index).replace('/', '.'); writer.write("package "); writer.write(packageName); writer.write(";"); writer.write(DecompilerContext.getNewLineSeparator()); writer.write(DecompilerContext.getNewLineSeparator()); } DecompilerContext.setProperty(DecompilerContext.CURRENT_CLASS_NODE, root); DecompilerContext.getImportCollector().writeImports(writer); writer.write(DecompilerContext.getNewLineSeparator()); writer.write(strwriter.toString()); writer.flush(); } finally { destroyWrappers(root); } } private static 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 static 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 static void destroyWrappers(ClassNode node) { node.wrapper = null; node.classStruct.releaseResources(); for (ClassNode nd : node.nested) { destroyWrappers(nd); } } public HashMap getMapRootClasses() { return mapRootClasses; } public static 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); if (!is_method_reference) { // content method in the same class, check synthetic flag StructMethod mt = classStruct.getMethod(content_method_name, content_method_descriptor); is_method_reference = !mt.isSynthetic(); // 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 static 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; } } }