void (IIdentifierRenamer renamer) { Map<String, Inner> mapInnerClasses = new HashMap<>(); Map<String, Set<String>> mapNestedClassReferences = new HashMap<>(); Map<String, Set<String>> mapEnclosingClassReferences = new HashMap<>(); Map<String, String> mapNewSimpleNames = new HashMap<>(); boolean bDecompileInner = DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_INNER); boolean verifyAnonymousClasses = DecompilerContext.getOption(IFernflowerPreferences.VERIFY_ANONYMOUS_CLASSES); // create class nodes for (StructClass cl : context.getClasses().values()) { if (cl.isOwn() && !mapRootClasses.containsKey(cl.qualifiedName)) { if (bDecompileInner) { StructInnerClassesAttribute inner = cl.getAttribute(StructGeneralAttribute.ATTRIBUTE_INNER_CLASSES); if (inner != null) { for (StructInnerClassesAttribute.Entry entry : inner.getEntries()) { String innerName = entry.innerName; // original simple name String simpleName = entry.simpleName; String savedName = mapNewSimpleNames.get(innerName); if (savedName != null) { simpleName = savedName; } else if (simpleName != null && renamer != null && renamer.toBeRenamed(IIdentifierRenamer.Type.ELEMENT_CLASS, simpleName, null, null)) { simpleName = renamer.getNextClassName(innerName, simpleName); mapNewSimpleNames.put(innerName, simpleName); } Inner rec = new Inner(); rec.simpleName = simpleName; rec.type = entry.simpleNameIdx == 0 ? ClassNode.CLASS_ANONYMOUS : entry.outerNameIdx == 0 ? ClassNode.CLASS_LOCAL : ClassNode.CLASS_MEMBER; rec.accessFlags = entry.accessFlags; // enclosing class String enclClassName = entry.outerNameIdx != 0 ? entry.enclosingName : cl.qualifiedName; if (enclClassName == null || innerName.equals(enclClassName)) { continue; // invalid name or self reference } if (rec.type == ClassNode.CLASS_MEMBER && !innerName.equals(enclClassName + '$' + entry.simpleName)) { continue; // not a real inner class } StructClass enclosingClass = context.getClasses().get(enclClassName); if (enclosingClass != null && enclosingClass.isOwn()) { // own classes only Inner existingRec = mapInnerClasses.get(innerName); if (existingRec == null) { mapInnerClasses.put(innerName, rec); } else if (!Inner.equal(existingRec, rec)) { String message = "Inconsistent inner class entries for " + innerName + "!"; DecompilerContext.getLogger().writeMessage(message, IFernflowerLogger.Severity.WARN); } // reference to the nested class mapNestedClassReferences.computeIfAbsent(enclClassName, k -> new HashSet<>()).add(innerName); // reference to the enclosing class mapEnclosingClassReferences.computeIfAbsent(innerName, k -> new HashSet<>()).add(enclClassName); } } } } ClassNode node = new ClassNode(ClassNode.CLASS_ROOT, cl); node.access = cl.getAccessFlags(); mapRootClasses.put(cl.qualifiedName, node); } } // set non-sealed if class extends or implements a sealed class and is not final or sealed itself for (ClassNode clazz : mapRootClasses.values()) { if (clazz.classStruct.hasSealedClassesSupport() && (clazz.access & CodeConstants.ACC_FINAL) == 0 && clazz.classStruct.getPermittedSubclasses() == null) { List<String> qualifiedSealedSuperNames = new ArrayList<>(Arrays.asList(clazz.classStruct.getInterfaceNames())); PrimitiveConstant superConst = clazz.classStruct.superClass; if (superConst != null) qualifiedSealedSuperNames.add(superConst.getString()); clazz.setNonSealed( qualifiedSealedSuperNames.stream() .map(mapRootClasses::get) .filter(Objects::nonNull) .map(potentialSuper -> potentialSuper.classStruct.getPermittedSubclasses()) .filter(Objects::nonNull) .anyMatch(permittedList -> permittedList.contains(clazz.classStruct.qualifiedName)) ); } } if (bDecompileInner) { // connect nested classes for (Entry<String, ClassNode> ent : mapRootClasses.entrySet()) { // root class? if (!mapInnerClasses.containsKey(ent.getKey())) { Set<String> setVisited = new HashSet<>(); LinkedList<String> stack = new LinkedList<>(); stack.add(ent.getKey()); setVisited.add(ent.getKey()); while (!stack.isEmpty()) { String superClass = stack.removeFirst(); ClassNode superNode = mapRootClasses.get(superClass); Set<String> setNestedClasses = mapNestedClassReferences.get(superClass); if (setNestedClasses != null) { StructClass scl = superNode.classStruct; StructInnerClassesAttribute inner = scl.getAttribute(StructGeneralAttribute.ATTRIBUTE_INNER_CLASSES); if (inner == null || inner.getEntries().isEmpty()) { DecompilerContext.getLogger().writeMessage(superClass + " does not contain inner classes!", IFernflowerLogger.Severity.WARN); continue; } for (StructInnerClassesAttribute.Entry entry : inner.getEntries()) { String nestedClass = entry.innerName; if (!setNestedClasses.contains(nestedClass)) { continue; } if (!setVisited.add(nestedClass)) { continue; } ClassNode nestedNode = mapRootClasses.get(nestedClass); if (nestedNode == null) { DecompilerContext.getLogger().writeMessage("Nested class " + nestedClass + " missing!", IFernflowerLogger.Severity.WARN); continue; } Inner rec = mapInnerClasses.get(nestedClass); //if ((Integer)arr[2] == ClassNode.CLASS_MEMBER) { // FIXME: check for consistent naming //} nestedNode.simpleName = rec.simpleName; nestedNode.type = rec.type; nestedNode.access = rec.accessFlags; // sanity checks of the class supposed to be anonymous if (verifyAnonymousClasses && nestedNode.type == ClassNode.CLASS_ANONYMOUS && !isAnonymous(nestedNode.classStruct, scl)) { nestedNode.type = ClassNode.CLASS_LOCAL; } 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) { nestedNode.anonymousClassType = new VarType(cl.getInterface(0), true); } else { nestedNode.anonymousClassType = 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); } } } } } } }