in src/org/jetbrains/java/decompiler/main/ClassWriter.java [737:1006]
private boolean methodToJava(ClassNode node, StructMethod mt, TextBuffer buffer, int indent, BytecodeMappingTracer tracer) {
ClassWrapper wrapper = node.getWrapper();
StructClass cl = wrapper.getClassStruct();
MethodWrapper methodWrapper = wrapper.getMethodWrapper(mt.getName(), mt.getDescriptor());
boolean hideMethod = false;
int start_index_method = buffer.length();
MethodWrapper outerWrapper = (MethodWrapper)DecompilerContext.getProperty(DecompilerContext.CURRENT_METHOD_WRAPPER);
DecompilerContext.setProperty(DecompilerContext.CURRENT_METHOD_WRAPPER, methodWrapper);
try {
boolean isInterface = cl.hasModifier(CodeConstants.ACC_INTERFACE);
boolean isAnnotation = cl.hasModifier(CodeConstants.ACC_ANNOTATION);
boolean isDeprecated = mt.hasAttribute(StructGeneralAttribute.ATTRIBUTE_DEPRECATED);
boolean clInit = false, dInit = false, compact = false;
MethodDescriptor md = MethodDescriptor.parseDescriptor(mt, node);
int flags = mt.getAccessFlags();
if ((flags & CodeConstants.ACC_NATIVE) != 0) {
flags &= ~CodeConstants.ACC_STRICT; // compiler bug: a strictfp class sets all methods to strictfp
}
if (CodeConstants.CLINIT_NAME.equals(mt.getName())) {
flags &= CodeConstants.ACC_STATIC; // ignore all modifiers except 'static' in a static initializer
}
if (isDeprecated) {
appendDeprecation(buffer, indent);
}
if (interceptor != null) {
String oldName = interceptor.getOldName(cl.qualifiedName + " " + mt.getName() + " " + mt.getDescriptor());
appendRenameComment(buffer, oldName, MType.METHOD, indent);
}
boolean isSynthetic = (flags & CodeConstants.ACC_SYNTHETIC) != 0 || mt.hasAttribute(StructGeneralAttribute.ATTRIBUTE_SYNTHETIC);
boolean isBridge = (flags & CodeConstants.ACC_BRIDGE) != 0;
if (isSynthetic) {
appendComment(buffer, "synthetic method", indent);
}
if (isBridge) {
appendComment(buffer, "bridge method", indent);
}
GenericMethodDescriptor descriptor = mt.getSignature();
appendAnnotations(buffer, indent, mt);
buffer.appendIndent(indent);
appendModifiers(buffer, flags, METHOD_ALLOWED, isInterface, METHOD_EXCLUDED);
if (isInterface && !mt.hasModifier(CodeConstants.ACC_STATIC) && !mt.hasModifier(CodeConstants.ACC_PRIVATE) && mt.containsCode()) {
// 'default' modifier (Java 8)
buffer.append("default ");
}
String name = mt.getName();
boolean init = false;
if (CodeConstants.INIT_NAME.equals(name)) {
if (node.type == ClassNode.CLASS_ANONYMOUS) {
name = "";
dInit = true;
}
else {
name = node.simpleName;
init = true;
}
if (cl.getRecordComponents() != null) {
RecordConstructorContext recordConstructorContext = tryToDeleteRecordConstructor(cl, mt, methodWrapper, md);
compact = recordConstructorContext.compact;
hideMethod = recordConstructorContext.hideConstructor;
}
}
else if (CodeConstants.CLINIT_NAME.equals(name)) {
name = "";
clInit = true;
}
boolean throwsExceptions = false;
int paramCount = 0;
final List<TypeAnnotation> typeAnnotations = TypeAnnotation.listFrom(mt);
if (!clInit && !dInit) {
if (descriptor != null && !descriptor.typeParameters.isEmpty()) {
appendTypeParameters(buffer, descriptor.typeParameters, descriptor.typeParameterBounds, typeAnnotations);
buffer.append(' ');
}
final List<TypeAnnotation> emptyTypeAnnotations = TargetInfo.EmptyTarget.extract(typeAnnotations);
if (init) {
emptyTypeAnnotations.forEach(typeAnnotation -> typeAnnotation.writeTo(buffer));
} else {
buffer.append(ExprProcessor.getCastTypeName(descriptor == null ? md.ret : descriptor.returnType, TypeAnnotationWriteHelper.create(emptyTypeAnnotations)));
buffer.append(' ');
}
buffer.append(toValidJavaIdentifier(name));
if (!compact) {
buffer.append('(');
List<VarVersion> mask = methodWrapper.synthParameters;
int lastVisibleParameterIndex = -1;
for (int i = 0; i < md.params.length; i++) {
if (mask == null || mask.get(i) == null) {
lastVisibleParameterIndex = i;
}
}
int index = methodWrapper.varproc.getFirstParameterVarIndex();
boolean hasDescriptor = descriptor != null;
//mask should now have the Outer.this in it... so this *shouldn't* be nessasary.
//if (init && !isEnum && ((node.access & CodeConstants.ACC_STATIC) == 0) && node.type == ClassNode.CLASS_MEMBER)
// index++;
for (int i = methodWrapper.varproc.getFirstParameterPosition(); i < md.params.length; i++) {
if (mask == null || mask.get(i) == null) {
VarType parameterType = hasDescriptor && !descriptor.parameterTypes.isEmpty() ? descriptor.parameterTypes.get(paramCount) : md.params[i];
if (paramCount > 0) {
buffer.append(", ");
}
Type paramType;
if (descriptor != null && descriptor.parameterTypes.size() > paramCount) {
paramType = descriptor.parameterTypes.get(paramCount);
}
else {
paramType = md.params[i];
}
appendParameterAnnotations(buffer, mt, paramType, paramCount);
VarVersion pair = new VarVersion(index, 0);
if (methodWrapper.varproc.isParameterFinal(pair) ||
methodWrapper.varproc.getVarFinal(pair) == VarProcessor.VAR_EXPLICIT_FINAL) {
buffer.append("final ");
}
String typeName;
List<TypeAnnotation> typeParamAnnotations = TargetInfo.FormalParameterTarget.extract(typeAnnotations, i);
boolean isVarArg = i == lastVisibleParameterIndex && mt.hasModifier(CodeConstants.ACC_VARARGS) && parameterType.getArrayDim() > 0;
if (isVarArg) {
parameterType = parameterType.decreaseArrayDim();
}
typeName = ExprProcessor.getCastTypeName(parameterType, TypeAnnotationWriteHelper.create(typeParamAnnotations));
if (ExprProcessor.UNDEFINED_TYPE_STRING.equals(typeName) &&
DecompilerContext.getOption(IFernflowerPreferences.UNDEFINED_PARAM_TYPE_OBJECT)) {
typeName = ExprProcessor.getCastTypeName(VarType.VARTYPE_OBJECT, TypeAnnotationWriteHelper.create(typeParamAnnotations));
}
//workaround to send to usages
VarType type = methodWrapper.varproc.getVarType(pair);
if (parameterType instanceof GenericType && (type == null || type.equals(VarType.VARTYPE_OBJECT))) {
methodWrapper.varproc.setVarType(pair, parameterType);
}
buffer.append(typeName);
if (isVarArg) {
buffer.append("...");
}
buffer.append(' ');
String parameterName = methodWrapper.varproc.getVarName(pair);
if (parameterName == null) {
parameterName = "param" + index; // null iff decompiled with errors
}
parameterName = methodWrapper.methodStruct.getVariableNamer().renameParameter(flags, typeName, parameterName, index);
buffer.append(parameterName);
paramCount++;
}
index += md.params[i].getStackSize();
}
buffer.append(')');
StructExceptionsAttribute attr = mt.getAttribute(StructGeneralAttribute.ATTRIBUTE_EXCEPTIONS);
if ((descriptor != null && !descriptor.exceptionTypes.isEmpty()) || attr != null) {
throwsExceptions = true;
buffer.append(" throws ");
boolean useDescriptor = hasDescriptor && descriptor != null && !descriptor.exceptionTypes.isEmpty();
for (int i = 0; i < attr.getThrowsExceptions().size(); i++) {
if (i > 0) {
buffer.append(", ");
}
TargetInfo.ThrowsTarget.extract(typeAnnotations, i).forEach(typeAnnotation -> typeAnnotation.writeTo(buffer));
VarType type = useDescriptor ? descriptor.exceptionTypes.get(i) : new VarType(attr.getExcClassname(i, cl.getPool()), true);
buffer.append(ExprProcessor.getCastTypeName(type, Collections.emptyList()));
}
}
}
}
tracer.incrementCurrentSourceLine(buffer.countLines(start_index_method));
if ((flags & (CodeConstants.ACC_ABSTRACT | CodeConstants.ACC_NATIVE)) != 0) { // native or abstract method (explicit or interface)
if (isAnnotation) {
StructAnnDefaultAttribute attr = mt.getAttribute(StructGeneralAttribute.ATTRIBUTE_ANNOTATION_DEFAULT);
if (attr != null) {
buffer.append(" default ");
buffer.append(attr.getDefaultValue().toJava(0, BytecodeMappingTracer.DUMMY));
}
}
buffer.append(';');
buffer.appendLineSeparator();
}
else {
if (!clInit && !dInit) {
buffer.append(' ');
}
// We do not have line information for method start, lets have it here for now
buffer.append('{').appendLineSeparator();
tracer.incrementCurrentSourceLine();
RootStatement root = wrapper.getMethodWrapper(mt.getName(), mt.getDescriptor()).root;
if (root != null && !methodWrapper.decompiledWithErrors) { // check for existence
try {
// to restore in case of an exception
BytecodeMappingTracer codeTracer = new BytecodeMappingTracer(tracer.getCurrentSourceLine());
TextBuffer code = root.toJava(indent + 1, codeTracer);
hideMethod |= code.length() == 0 &&
(clInit || dInit || hideConstructor(node, !typeAnnotations.isEmpty(), init, throwsExceptions, paramCount, flags)) ||
isSyntheticRecordMethod(cl, mt, code);
buffer.append(code);
tracer.setCurrentSourceLine(codeTracer.getCurrentSourceLine());
tracer.addTracer(codeTracer);
}
catch (Throwable t) {
String message = "Method " + mt.getName() + " " + mt.getDescriptor() + " in class " + node.classStruct.qualifiedName + " couldn't be written.";
DecompilerContext.getLogger().writeMessage(message, IFernflowerLogger.Severity.WARN, t);
methodWrapper.decompiledWithErrors = true;
}
}
if (methodWrapper.decompiledWithErrors) {
buffer.appendIndent(indent + 1);
if (methodWrapper.decompiledWithErrorsMessage != null) {
buffer.append("// $FF: " + methodWrapper.decompiledWithErrorsMessage);
}
else {
buffer.append("// $FF: Couldn't be decompiled");
}
buffer.appendLineSeparator();
tracer.incrementCurrentSourceLine();
}
else if (root != null) {
tracer.addMapping(root.getDummyExit().bytecode);
}
buffer.appendIndent(indent).append('}').appendLineSeparator();
}
tracer.incrementCurrentSourceLine();
}
finally {
DecompilerContext.setProperty(DecompilerContext.CURRENT_METHOD_WRAPPER, outerWrapper);
}
// save total lines
// TODO: optimize
//tracer.setCurrentSourceLine(buffer.countLines(start_index_method));
return !hideMethod;
}