decompiler: fixed incorrect method references from single letter classes
[idea/community.git] / plugins / java-decompiler / engine / src / org / jetbrains / java / decompiler / main / ClassWriter.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package org.jetbrains.java.decompiler.main;
17
18 import org.jetbrains.java.decompiler.code.CodeConstants;
19 import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode;
20 import org.jetbrains.java.decompiler.main.collectors.BytecodeMappingTracer;
21 import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger;
22 import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences;
23 import org.jetbrains.java.decompiler.main.rels.ClassWrapper;
24 import org.jetbrains.java.decompiler.main.rels.MethodWrapper;
25 import org.jetbrains.java.decompiler.modules.decompiler.ExprProcessor;
26 import org.jetbrains.java.decompiler.modules.decompiler.exps.AnnotationExprent;
27 import org.jetbrains.java.decompiler.modules.decompiler.exps.ConstExprent;
28 import org.jetbrains.java.decompiler.modules.decompiler.exps.Exprent;
29 import org.jetbrains.java.decompiler.modules.decompiler.exps.NewExprent;
30 import org.jetbrains.java.decompiler.modules.decompiler.stats.RootStatement;
31 import org.jetbrains.java.decompiler.modules.decompiler.vars.VarTypeProcessor;
32 import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionPair;
33 import org.jetbrains.java.decompiler.modules.renamer.PoolInterceptor;
34 import org.jetbrains.java.decompiler.struct.StructClass;
35 import org.jetbrains.java.decompiler.struct.StructField;
36 import org.jetbrains.java.decompiler.struct.StructMember;
37 import org.jetbrains.java.decompiler.struct.StructMethod;
38 import org.jetbrains.java.decompiler.struct.attr.*;
39 import org.jetbrains.java.decompiler.struct.consts.PrimitiveConstant;
40 import org.jetbrains.java.decompiler.struct.gen.FieldDescriptor;
41 import org.jetbrains.java.decompiler.struct.gen.MethodDescriptor;
42 import org.jetbrains.java.decompiler.struct.gen.VarType;
43 import org.jetbrains.java.decompiler.struct.gen.generics.*;
44 import org.jetbrains.java.decompiler.util.InterpreterUtil;
45
46 import java.util.*;
47
48 public class ClassWriter {
49   private final ClassReference14Processor ref14processor;
50   private final PoolInterceptor interceptor;
51
52   public ClassWriter() {
53     ref14processor = new ClassReference14Processor();
54     interceptor = DecompilerContext.getPoolInterceptor();
55   }
56
57   private void invokeProcessors(ClassNode node) {
58     ClassWrapper wrapper = node.getWrapper();
59     StructClass cl = wrapper.getClassStruct();
60
61     InitializerProcessor.extractInitializers(wrapper);
62
63     if (node.type == ClassNode.CLASS_ROOT && DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_CLASS_1_4)) {
64       ref14processor.processClassReferences(node);
65     }
66
67     if (cl.hasModifier(CodeConstants.ACC_ENUM) && DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM)) {
68       EnumProcessor.clearEnum(wrapper);
69     }
70
71     if (DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ASSERTIONS)) {
72       AssertProcessor.buildAssertions(node);
73     }
74   }
75
76   public void classLambdaToJava(ClassNode node, TextBuffer buffer, Exprent method_object, int indent, BytecodeMappingTracer origTracer) {
77     ClassWrapper wrapper = node.getWrapper();
78     if (wrapper == null) {
79       return;
80     }
81
82     boolean lambdaToAnonymous = DecompilerContext.getOption(IFernflowerPreferences.LAMBDA_TO_ANONYMOUS_CLASS);
83
84     ClassNode outerNode = (ClassNode)DecompilerContext.getProperty(DecompilerContext.CURRENT_CLASS_NODE);
85     DecompilerContext.setProperty(DecompilerContext.CURRENT_CLASS_NODE, node);
86
87     BytecodeMappingTracer tracer = new BytecodeMappingTracer(origTracer.getCurrentSourceLine());
88
89     try {
90       StructClass cl = wrapper.getClassStruct();
91
92       DecompilerContext.getLogger().startWriteClass(node.simpleName);
93
94       if (node.lambdaInformation.is_method_reference) {
95         if (!node.lambdaInformation.is_content_method_static && method_object != null) {
96           // reference to a virtual method
97           buffer.append(method_object.toJava(indent, tracer));
98         }
99         else {
100           // reference to a static method
101           buffer.append(ExprProcessor.getCastTypeName(new VarType(node.lambdaInformation.content_class_name, true)));
102         }
103
104         buffer.append("::");
105         buffer.append(node.lambdaInformation.content_method_name);
106       }
107       else {
108         // lambda method
109         StructMethod mt = cl.getMethod(node.lambdaInformation.content_method_key);
110         MethodWrapper methodWrapper = wrapper.getMethodWrapper(mt.getName(), mt.getDescriptor());
111         MethodDescriptor md_content = MethodDescriptor.parseDescriptor(node.lambdaInformation.content_method_descriptor);
112         MethodDescriptor md_lambda = MethodDescriptor.parseDescriptor(node.lambdaInformation.method_descriptor);
113
114         if (!lambdaToAnonymous) {
115           buffer.append('(');
116
117           boolean firstParameter = true;
118           int index = node.lambdaInformation.is_content_method_static ? 0 : 1;
119           int start_index = md_content.params.length - md_lambda.params.length;
120
121           for (int i = 0; i < md_content.params.length; i++) {
122             if (i >= start_index) {
123               if (!firstParameter) {
124                 buffer.append(", ");
125               }
126
127               String parameterName = methodWrapper.varproc.getVarName(new VarVersionPair(index, 0));
128               buffer.append(parameterName == null ? "param" + index : parameterName); // null iff decompiled with errors
129
130               firstParameter = false;
131             }
132
133             index += md_content.params[i].stackSize;
134           }
135
136           buffer.append(") ->");
137         }
138
139         buffer.append(" {").appendLineSeparator();
140         tracer.incrementCurrentSourceLine();
141
142         methodLambdaToJava(node, wrapper, mt, buffer, indent + 1, !lambdaToAnonymous, tracer);
143
144         buffer.appendIndent(indent).append("}");
145
146         addTracer(cl, mt, tracer);
147       }
148     }
149     finally {
150       DecompilerContext.setProperty(DecompilerContext.CURRENT_CLASS_NODE, outerNode);
151     }
152
153     DecompilerContext.getLogger().endWriteClass();
154   }
155
156   public void classToJava(ClassNode node, TextBuffer buffer, int indent, BytecodeMappingTracer tracer) {
157     ClassNode outerNode = (ClassNode)DecompilerContext.getProperty(DecompilerContext.CURRENT_CLASS_NODE);
158     DecompilerContext.setProperty(DecompilerContext.CURRENT_CLASS_NODE, node);
159
160     int startLine = tracer != null ? tracer.getCurrentSourceLine() : 0;
161     BytecodeMappingTracer dummy_tracer = new BytecodeMappingTracer(startLine);
162
163     try {
164       // last minute processing
165       invokeProcessors(node);
166
167       ClassWrapper wrapper = node.getWrapper();
168       StructClass cl = wrapper.getClassStruct();
169
170       DecompilerContext.getLogger().startWriteClass(cl.qualifiedName);
171
172       // write class definition
173       int start_class_def = buffer.length();
174       writeClassDefinition(node, buffer, indent);
175
176       boolean hasContent = false;
177       boolean enumFields = false;
178
179       dummy_tracer.incrementCurrentSourceLine(buffer.countLines(start_class_def));
180
181       for (StructField fd : cl.getFields()) {
182         boolean hide = fd.isSynthetic() && DecompilerContext.getOption(IFernflowerPreferences.REMOVE_SYNTHETIC) ||
183                        wrapper.getHiddenMembers().contains(InterpreterUtil.makeUniqueKey(fd.getName(), fd.getDescriptor()));
184         if (hide) continue;
185
186         boolean isEnum = fd.hasModifier(CodeConstants.ACC_ENUM) && DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM);
187         if (isEnum) {
188           if (enumFields) {
189             buffer.append(',').appendLineSeparator();
190             dummy_tracer.incrementCurrentSourceLine();
191           }
192           enumFields = true;
193         }
194         else if (enumFields) {
195           buffer.append(';');
196           buffer.appendLineSeparator();
197           buffer.appendLineSeparator();
198           dummy_tracer.incrementCurrentSourceLine(2);
199           enumFields = false;
200         }
201
202         fieldToJava(wrapper, cl, fd, buffer, indent + 1, dummy_tracer); // FIXME: insert real tracer
203
204         hasContent = true;
205       }
206
207       if (enumFields) {
208         buffer.append(';').appendLineSeparator();
209         dummy_tracer.incrementCurrentSourceLine();
210       }
211
212       // FIXME: fields don't matter at the moment
213       startLine += buffer.countLines(start_class_def);
214
215       // methods
216       for (StructMethod mt : cl.getMethods()) {
217         boolean hide = mt.isSynthetic() && DecompilerContext.getOption(IFernflowerPreferences.REMOVE_SYNTHETIC) ||
218                        mt.hasModifier(CodeConstants.ACC_BRIDGE) && DecompilerContext.getOption(IFernflowerPreferences.REMOVE_BRIDGE) ||
219                        wrapper.getHiddenMembers().contains(InterpreterUtil.makeUniqueKey(mt.getName(), mt.getDescriptor()));
220         if (hide) continue;
221
222         int position = buffer.length();
223         int storedLine = startLine;
224         if (hasContent) {
225           buffer.appendLineSeparator();
226           startLine++;
227         }
228         BytecodeMappingTracer method_tracer = new BytecodeMappingTracer(startLine);
229         boolean methodSkipped = !methodToJava(node, mt, buffer, indent + 1, method_tracer);
230         if (!methodSkipped) {
231           hasContent = true;
232           addTracer(cl, mt, method_tracer);
233           startLine = method_tracer.getCurrentSourceLine();
234         }
235         else {
236           buffer.setLength(position);
237           startLine = storedLine;
238         }
239       }
240
241       // member classes
242       for (ClassNode inner : node.nested) {
243         if (inner.type == ClassNode.CLASS_MEMBER) {
244           StructClass innerCl = inner.classStruct;
245           boolean isSynthetic = (inner.access & CodeConstants.ACC_SYNTHETIC) != 0 || innerCl.isSynthetic() || inner.namelessConstructorStub;
246           boolean hide = isSynthetic && DecompilerContext.getOption(IFernflowerPreferences.REMOVE_SYNTHETIC) ||
247                          wrapper.getHiddenMembers().contains(innerCl.qualifiedName);
248           if (hide) continue;
249
250           if (hasContent) {
251             buffer.appendLineSeparator();
252             startLine++;
253           }
254           BytecodeMappingTracer class_tracer = new BytecodeMappingTracer(startLine);
255           classToJava(inner, buffer, indent + 1, class_tracer);
256           startLine = buffer.countLines();
257
258           hasContent = true;
259         }
260       }
261
262       buffer.appendIndent(indent).append('}');
263
264       if (node.type != ClassNode.CLASS_ANONYMOUS) {
265         buffer.appendLineSeparator();
266       }
267     }
268     finally {
269       DecompilerContext.setProperty(DecompilerContext.CURRENT_CLASS_NODE, outerNode);
270     }
271
272     DecompilerContext.getLogger().endWriteClass();
273   }
274
275   private static void addTracer(StructClass cls, StructMethod method, BytecodeMappingTracer tracer) {
276     StructLineNumberTableAttribute table = (StructLineNumberTableAttribute)method.getAttributes().getWithKey(StructGeneralAttribute.ATTRIBUTE_LINE_NUMBER_TABLE);
277     tracer.setLineNumberTable(table);
278     String key = InterpreterUtil.makeUniqueKey(method.getName(), method.getDescriptor());
279     DecompilerContext.getBytecodeSourceMapper().addTracer(cls.qualifiedName, key, tracer);
280   }
281
282   private void writeClassDefinition(ClassNode node, TextBuffer buffer, int indent) {
283     if (node.type == ClassNode.CLASS_ANONYMOUS) {
284       buffer.append(" {").appendLineSeparator();
285       return;
286     }
287
288     ClassWrapper wrapper = node.getWrapper();
289     StructClass cl = wrapper.getClassStruct();
290
291     int flags = node.type == ClassNode.CLASS_ROOT ? cl.getAccessFlags() : node.access;
292     boolean isDeprecated = cl.getAttributes().containsKey("Deprecated");
293     boolean isSynthetic = (flags & CodeConstants.ACC_SYNTHETIC) != 0 || cl.getAttributes().containsKey("Synthetic");
294     boolean isEnum = DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM) && (flags & CodeConstants.ACC_ENUM) != 0;
295     boolean isInterface = (flags & CodeConstants.ACC_INTERFACE) != 0;
296     boolean isAnnotation = (flags & CodeConstants.ACC_ANNOTATION) != 0;
297
298     if (isDeprecated) {
299       appendDeprecation(buffer, indent);
300     }
301
302     if (interceptor != null) {
303       String oldName = interceptor.getOldName(cl.qualifiedName);
304       appendRenameComment(buffer, oldName, MType.CLASS, indent);
305     }
306
307     if (isSynthetic) {
308       appendComment(buffer, "synthetic class", indent);
309     }
310
311     appendAnnotations(buffer, cl, indent);
312
313     buffer.appendIndent(indent);
314
315     if (isEnum) {
316       // remove abstract and final flags (JLS 8.9 Enums)
317       flags &= ~CodeConstants.ACC_ABSTRACT;
318       flags &= ~CodeConstants.ACC_FINAL;
319     }
320
321     appendModifiers(buffer, flags, CLASS_ALLOWED, isInterface, CLASS_EXCLUDED);
322
323     if (isEnum) {
324       buffer.append("enum ");
325     }
326     else if (isInterface) {
327       if (isAnnotation) {
328         buffer.append('@');
329       }
330       buffer.append("interface ");
331     }
332     else {
333       buffer.append("class ");
334     }
335
336     GenericClassDescriptor descriptor = null;
337     if (DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_GENERIC_SIGNATURES)) {
338       StructGenericSignatureAttribute attr = (StructGenericSignatureAttribute)cl.getAttributes().getWithKey("Signature");
339       if (attr != null) {
340         descriptor = GenericMain.parseClassSignature(attr.getSignature());
341       }
342     }
343
344     buffer.append(node.simpleName);
345
346     if (descriptor != null && !descriptor.fparameters.isEmpty()) {
347       appendTypeParameters(buffer, descriptor.fparameters, descriptor.fbounds);
348     }
349
350     buffer.append(' ');
351
352     if (!isEnum && !isInterface && cl.superClass != null) {
353       VarType supertype = new VarType(cl.superClass.getString(), true);
354       if (!VarType.VARTYPE_OBJECT.equals(supertype)) {
355         buffer.append("extends ");
356         if (descriptor != null) {
357           buffer.append(GenericMain.getGenericCastTypeName(descriptor.superclass));
358         }
359         else {
360           buffer.append(ExprProcessor.getCastTypeName(supertype));
361         }
362         buffer.append(' ');
363       }
364     }
365
366     if (!isAnnotation) {
367       int[] interfaces = cl.getInterfaces();
368       if (interfaces.length > 0) {
369         buffer.append(isInterface ? "extends " : "implements ");
370         for (int i = 0; i < interfaces.length; i++) {
371           if (i > 0) {
372             buffer.append(", ");
373           }
374           if (descriptor != null) {
375             buffer.append(GenericMain.getGenericCastTypeName(descriptor.superinterfaces.get(i)));
376           }
377           else {
378             buffer.append(ExprProcessor.getCastTypeName(new VarType(cl.getInterface(i), true)));
379           }
380         }
381         buffer.append(' ');
382       }
383     }
384
385     buffer.append('{').appendLineSeparator();
386   }
387
388   private void fieldToJava(ClassWrapper wrapper, StructClass cl, StructField fd, TextBuffer buffer, int indent, BytecodeMappingTracer tracer) {
389     int start = buffer.length();
390     boolean isInterface = cl.hasModifier(CodeConstants.ACC_INTERFACE);
391     boolean isDeprecated = fd.getAttributes().containsKey("Deprecated");
392     boolean isEnum = fd.hasModifier(CodeConstants.ACC_ENUM) && DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM);
393
394     if (isDeprecated) {
395       appendDeprecation(buffer, indent);
396     }
397
398     if (interceptor != null) {
399       String oldName = interceptor.getOldName(cl.qualifiedName + " " + fd.getName() + " " + fd.getDescriptor());
400       appendRenameComment(buffer, oldName, MType.FIELD, indent);
401     }
402
403     if (fd.isSynthetic()) {
404       appendComment(buffer, "synthetic field", indent);
405     }
406
407     appendAnnotations(buffer, fd, indent);
408
409     buffer.appendIndent(indent);
410
411     if (!isEnum) {
412       appendModifiers(buffer, fd.getAccessFlags(), FIELD_ALLOWED, isInterface, FIELD_EXCLUDED);
413     }
414
415     VarType fieldType = new VarType(fd.getDescriptor(), false);
416
417     GenericFieldDescriptor descriptor = null;
418     if (DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_GENERIC_SIGNATURES)) {
419       StructGenericSignatureAttribute attr = (StructGenericSignatureAttribute)fd.getAttributes().getWithKey("Signature");
420       if (attr != null) {
421         descriptor = GenericMain.parseFieldSignature(attr.getSignature());
422       }
423     }
424
425     if (!isEnum) {
426       if (descriptor != null) {
427         buffer.append(GenericMain.getGenericCastTypeName(descriptor.type));
428       }
429       else {
430         buffer.append(ExprProcessor.getCastTypeName(fieldType));
431       }
432       buffer.append(' ');
433     }
434
435     buffer.append(fd.getName());
436
437     tracer.incrementCurrentSourceLine(buffer.countLines(start));
438
439     Exprent initializer;
440     if (fd.hasModifier(CodeConstants.ACC_STATIC)) {
441       initializer = wrapper.getStaticFieldInitializers().getWithKey(InterpreterUtil.makeUniqueKey(fd.getName(), fd.getDescriptor()));
442     }
443     else {
444       initializer = wrapper.getDynamicFieldInitializers().getWithKey(InterpreterUtil.makeUniqueKey(fd.getName(), fd.getDescriptor()));
445     }
446     if (initializer != null) {
447       if (isEnum && initializer.type == Exprent.EXPRENT_NEW) {
448         NewExprent nexpr = (NewExprent)initializer;
449         nexpr.setEnumConst(true);
450         buffer.append(nexpr.toJava(indent, tracer));
451       }
452       else {
453         buffer.append(" = ");
454         // FIXME: special case field initializer. Can map to more than one method (constructor) and bytecode intruction.
455         buffer.append(initializer.toJava(indent, tracer));
456       }
457     }
458     else if (fd.hasModifier(CodeConstants.ACC_FINAL) && fd.hasModifier(CodeConstants.ACC_STATIC)) {
459       StructConstantValueAttribute attr =
460         (StructConstantValueAttribute)fd.getAttributes().getWithKey(StructGeneralAttribute.ATTRIBUTE_CONSTANT_VALUE);
461       if (attr != null) {
462         PrimitiveConstant constant = cl.getPool().getPrimitiveConstant(attr.getIndex());
463         buffer.append(" = ");
464         buffer.append(new ConstExprent(fieldType, constant.value, null).toJava(indent, tracer));
465       }
466     }
467
468     if (!isEnum) {
469       buffer.append(";").appendLineSeparator();
470       tracer.incrementCurrentSourceLine();
471     }
472   }
473
474   private static void methodLambdaToJava(ClassNode lambdaNode,
475                                          ClassWrapper classWrapper,
476                                          StructMethod mt,
477                                          TextBuffer buffer,
478                                          int indent,
479                                          boolean codeOnly, BytecodeMappingTracer tracer) {
480     MethodWrapper methodWrapper = classWrapper.getMethodWrapper(mt.getName(), mt.getDescriptor());
481
482     MethodWrapper outerWrapper = (MethodWrapper)DecompilerContext.getProperty(DecompilerContext.CURRENT_METHOD_WRAPPER);
483     DecompilerContext.setProperty(DecompilerContext.CURRENT_METHOD_WRAPPER, methodWrapper);
484
485     try {
486       String method_name = lambdaNode.lambdaInformation.method_name;
487       MethodDescriptor md_content = MethodDescriptor.parseDescriptor(lambdaNode.lambdaInformation.content_method_descriptor);
488       MethodDescriptor md_lambda = MethodDescriptor.parseDescriptor(lambdaNode.lambdaInformation.method_descriptor);
489
490       if (!codeOnly) {
491         buffer.appendIndent(indent);
492         buffer.append("public ");
493         buffer.append(method_name);
494         buffer.append("(");
495
496         boolean firstParameter = true;
497         int index = lambdaNode.lambdaInformation.is_content_method_static ? 0 : 1;
498         int start_index = md_content.params.length - md_lambda.params.length;
499
500         for (int i = 0; i < md_content.params.length; i++) {
501           if (i >= start_index) {
502             if (!firstParameter) {
503               buffer.append(", ");
504             }
505
506             String typeName = ExprProcessor.getCastTypeName(md_content.params[i].copy());
507             if (ExprProcessor.UNDEFINED_TYPE_STRING.equals(typeName) &&
508                 DecompilerContext.getOption(IFernflowerPreferences.UNDEFINED_PARAM_TYPE_OBJECT)) {
509               typeName = ExprProcessor.getCastTypeName(VarType.VARTYPE_OBJECT);
510             }
511
512             buffer.append(typeName);
513             buffer.append(" ");
514
515             String parameterName = methodWrapper.varproc.getVarName(new VarVersionPair(index, 0));
516             buffer.append(parameterName == null ? "param" + index : parameterName); // null iff decompiled with errors
517
518             firstParameter = false;
519           }
520
521           index += md_content.params[i].stackSize;
522         }
523
524         buffer.append(") {").appendLineSeparator();
525
526         indent += 1;
527       }
528
529       RootStatement root = classWrapper.getMethodWrapper(mt.getName(), mt.getDescriptor()).root;
530       if (!methodWrapper.decompiledWithErrors) {
531         if (root != null) { // check for existence
532           try {
533             buffer.append(root.toJava(indent, tracer));
534           }
535           catch (Throwable ex) {
536             DecompilerContext.getLogger().writeMessage("Method " + mt.getName() + " " + mt.getDescriptor() + " couldn't be written.", ex);
537             methodWrapper.decompiledWithErrors = true;
538           }
539         }
540       }
541
542       if (methodWrapper.decompiledWithErrors) {
543         buffer.appendIndent(indent);
544         buffer.append("// $FF: Couldn't be decompiled");
545         buffer.appendLineSeparator();
546       }
547
548       if (root != null) {
549         tracer.addMapping(root.getDummyExit().bytecode);
550       }
551
552       if (!codeOnly) {
553         indent -= 1;
554         buffer.appendIndent(indent).append('}').appendLineSeparator();
555       }
556     }
557     finally {
558       DecompilerContext.setProperty(DecompilerContext.CURRENT_METHOD_WRAPPER, outerWrapper);
559     }
560   }
561
562   private static String toValidJavaIdentifier(String name) {
563     if (name == null || name.isEmpty()) return name;
564
565     boolean changed = false;
566     StringBuilder res = new StringBuilder(name.length());
567     for (int i = 0; i < name.length(); i++) {
568       char c = name.charAt(i);
569       if ((i == 0 && !Character.isJavaIdentifierStart(c))
570           || (i > 0 && !Character.isJavaIdentifierPart(c))) {
571         changed = true;
572         res.append("_");
573       }
574       else res.append(c);
575     }
576     if (!changed) {
577       return name;
578     }
579     return res.append("/* $FF was: ").append(name).append("*/").toString();
580   }
581
582   private boolean methodToJava(ClassNode node, StructMethod mt, TextBuffer buffer, int indent, BytecodeMappingTracer tracer) {
583     ClassWrapper wrapper = node.getWrapper();
584     StructClass cl = wrapper.getClassStruct();
585     MethodWrapper methodWrapper = wrapper.getMethodWrapper(mt.getName(), mt.getDescriptor());
586
587     boolean hideMethod = false;
588     int start_index_method = buffer.length();
589
590     MethodWrapper outerWrapper = (MethodWrapper)DecompilerContext.getProperty(DecompilerContext.CURRENT_METHOD_WRAPPER);
591     DecompilerContext.setProperty(DecompilerContext.CURRENT_METHOD_WRAPPER, methodWrapper);
592
593     try {
594       boolean isInterface = cl.hasModifier(CodeConstants.ACC_INTERFACE);
595       boolean isAnnotation = cl.hasModifier(CodeConstants.ACC_ANNOTATION);
596       boolean isEnum = cl.hasModifier(CodeConstants.ACC_ENUM) && DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM);
597       boolean isDeprecated = mt.getAttributes().containsKey("Deprecated");
598       boolean clinit = false, init = false, dinit = false;
599
600       MethodDescriptor md = MethodDescriptor.parseDescriptor(mt.getDescriptor());
601
602       int flags = mt.getAccessFlags();
603       if ((flags & CodeConstants.ACC_NATIVE) != 0) {
604         flags &= ~CodeConstants.ACC_STRICT; // compiler bug: a strictfp class sets all methods to strictfp
605       }
606       if (CodeConstants.CLINIT_NAME.equals(mt.getName())) {
607         flags &= CodeConstants.ACC_STATIC; // ignore all modifiers except 'static' in a static initializer
608       }
609
610       if (isDeprecated) {
611         appendDeprecation(buffer, indent);
612       }
613
614       if (interceptor != null) {
615         String oldName = interceptor.getOldName(cl.qualifiedName + " " + mt.getName() + " " + mt.getDescriptor());
616         appendRenameComment(buffer, oldName, MType.METHOD, indent);
617       }
618
619       boolean isSynthetic = (flags & CodeConstants.ACC_SYNTHETIC) != 0 || mt.getAttributes().containsKey("Synthetic");
620       boolean isBridge = (flags & CodeConstants.ACC_BRIDGE) != 0;
621       if (isSynthetic) {
622         appendComment(buffer, "synthetic method", indent);
623       }
624       if (isBridge) {
625         appendComment(buffer, "bridge method", indent);
626       }
627
628       appendAnnotations(buffer, mt, indent);
629
630       buffer.appendIndent(indent);
631
632       appendModifiers(buffer, flags, METHOD_ALLOWED, isInterface, METHOD_EXCLUDED);
633
634       if (isInterface && mt.containsCode()) {
635         // 'default' modifier (Java 8)
636         buffer.append("default ");
637       }
638
639       String name = mt.getName();
640       if (CodeConstants.INIT_NAME.equals(name)) {
641         if (node.type == ClassNode.CLASS_ANONYMOUS) {
642           name = "";
643           dinit = true;
644         }
645         else {
646           name = node.simpleName;
647           init = true;
648         }
649       }
650       else if (CodeConstants.CLINIT_NAME.equals(name)) {
651         name = "";
652         clinit = true;
653       }
654
655       GenericMethodDescriptor descriptor = null;
656       if (DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_GENERIC_SIGNATURES)) {
657         StructGenericSignatureAttribute attr = (StructGenericSignatureAttribute)mt.getAttributes().getWithKey("Signature");
658         if (attr != null) {
659           descriptor = GenericMain.parseMethodSignature(attr.getSignature());
660           if (descriptor != null) {
661             int actualParams = md.params.length;
662             List<VarVersionPair> sigFields = methodWrapper.signatureFields;
663             if (sigFields != null) {
664                actualParams = 0;
665               for (VarVersionPair field : methodWrapper.signatureFields) {
666                 if (field == null) {
667                   actualParams++;
668                 }
669               }
670             }
671             else if (isEnum && init) actualParams -= 2;
672             if (actualParams != descriptor.params.size()) {
673               String message = "Inconsistent generic signature in method " + mt.getName() + " " + mt.getDescriptor() + " in " + cl.qualifiedName;
674               DecompilerContext.getLogger().writeMessage(message, IFernflowerLogger.Severity.WARN);
675               descriptor = null;
676             }
677           }
678         }
679       }
680
681       boolean throwsExceptions = false;
682       int paramCount = 0;
683
684       if (!clinit && !dinit) {
685         boolean thisVar = !mt.hasModifier(CodeConstants.ACC_STATIC);
686
687         if (descriptor != null && !descriptor.fparameters.isEmpty()) {
688           appendTypeParameters(buffer, descriptor.fparameters, descriptor.fbounds);
689           buffer.append(' ');
690         }
691
692         if (!init) {
693           if (descriptor != null) {
694             buffer.append(GenericMain.getGenericCastTypeName(descriptor.ret));
695           }
696           else {
697             buffer.append(ExprProcessor.getCastTypeName(md.ret));
698           }
699           buffer.append(' ');
700         }
701
702         buffer.append(toValidJavaIdentifier(name));
703         buffer.append('(');
704
705         // parameters
706         List<VarVersionPair> signFields = methodWrapper.signatureFields;
707
708         int lastVisibleParameterIndex = -1;
709         for (int i = 0; i < md.params.length; i++) {
710           if (signFields == null || signFields.get(i) == null) {
711             lastVisibleParameterIndex = i;
712           }
713         }
714
715         boolean firstParameter = true;
716         int index = isEnum && init ? 3 : thisVar ? 1 : 0;
717         boolean hasDescriptor = descriptor != null;
718         int start = isEnum && init && !hasDescriptor ? 2 : 0;
719         int params = hasDescriptor ? descriptor.params.size() : md.params.length;
720         for (int i = start; i < params; i++) {
721           if (hasDescriptor || (signFields == null || signFields.get(i) == null)) {
722             if (!firstParameter) {
723               buffer.append(", ");
724             }
725
726             appendParameterAnnotations(buffer, mt, paramCount);
727
728             if (methodWrapper.varproc.getVarFinal(new VarVersionPair(index, 0)) == VarTypeProcessor.VAR_EXPLICIT_FINAL) {
729               buffer.append("final ");
730             }
731
732             if (descriptor != null) {
733               GenericType parameterType = descriptor.params.get(i);
734
735               boolean isVarArg = (i == lastVisibleParameterIndex && mt.hasModifier(CodeConstants.ACC_VARARGS) && parameterType.arrayDim > 0);
736               if (isVarArg) {
737                 parameterType = parameterType.decreaseArrayDim();
738               }
739
740               String typeName = GenericMain.getGenericCastTypeName(parameterType);
741               if (ExprProcessor.UNDEFINED_TYPE_STRING.equals(typeName) &&
742                   DecompilerContext.getOption(IFernflowerPreferences.UNDEFINED_PARAM_TYPE_OBJECT)) {
743                 typeName = ExprProcessor.getCastTypeName(VarType.VARTYPE_OBJECT);
744               }
745
746               buffer.append(typeName);
747
748               if (isVarArg) {
749                 buffer.append("...");
750               }
751             }
752             else {
753               VarType parameterType = md.params[i];
754
755               boolean isVarArg = (i == lastVisibleParameterIndex && mt.hasModifier(CodeConstants.ACC_VARARGS) && parameterType.arrayDim > 0);
756               if (isVarArg) {
757                 parameterType = parameterType.decreaseArrayDim();
758               }
759
760               String typeName = ExprProcessor.getCastTypeName(parameterType);
761               if (ExprProcessor.UNDEFINED_TYPE_STRING.equals(typeName) &&
762                   DecompilerContext.getOption(IFernflowerPreferences.UNDEFINED_PARAM_TYPE_OBJECT)) {
763                 typeName = ExprProcessor.getCastTypeName(VarType.VARTYPE_OBJECT);
764               }
765
766               buffer.append(typeName);
767
768               if (isVarArg) {
769                 buffer.append("...");
770               }
771             }
772
773             buffer.append(' ');
774             String parameterName = methodWrapper.varproc.getVarName(new VarVersionPair(index, 0));
775             buffer.append(parameterName == null ? "param" + index : parameterName); // null iff decompiled with errors
776
777             firstParameter = false;
778             paramCount++;
779           }
780
781           index += md.params[i].stackSize;
782         }
783
784         buffer.append(')');
785
786         StructExceptionsAttribute attr = (StructExceptionsAttribute)mt.getAttributes().getWithKey("Exceptions");
787         if ((descriptor != null && !descriptor.exceptions.isEmpty()) || attr != null) {
788           throwsExceptions = true;
789           buffer.append(" throws ");
790
791           for (int i = 0; i < attr.getThrowsExceptions().size(); i++) {
792             if (i > 0) {
793               buffer.append(", ");
794             }
795             if (descriptor != null && !descriptor.exceptions.isEmpty()) {
796               GenericType type = descriptor.exceptions.get(i);
797               buffer.append(GenericMain.getGenericCastTypeName(type));
798             }
799             else {
800               VarType type = new VarType(attr.getExcClassname(i, cl.getPool()), true);
801               buffer.append(ExprProcessor.getCastTypeName(type));
802             }
803           }
804         }
805       }
806
807       tracer.incrementCurrentSourceLine(buffer.countLines(start_index_method));
808
809       if ((flags & (CodeConstants.ACC_ABSTRACT | CodeConstants.ACC_NATIVE)) != 0) { // native or abstract method (explicit or interface)
810         if (isAnnotation) {
811           StructAnnDefaultAttribute attr = (StructAnnDefaultAttribute)mt.getAttributes().getWithKey("AnnotationDefault");
812           if (attr != null) {
813             buffer.append(" default ");
814             buffer.append(attr.getDefaultValue().toJava(indent + 1, new BytecodeMappingTracer())); // dummy tracer
815           }
816         }
817
818         buffer.append(';');
819         buffer.appendLineSeparator();
820         tracer.incrementCurrentSourceLine();
821       }
822       else {
823         if (!clinit && !dinit) {
824           buffer.append(' ');
825         }
826
827         // We do not have line information for method start, lets have it here for now
828         buffer.append('{').appendLineSeparator();
829         tracer.incrementCurrentSourceLine();
830
831         RootStatement root = wrapper.getMethodWrapper(mt.getName(), mt.getDescriptor()).root;
832
833         if (root != null && !methodWrapper.decompiledWithErrors) { // check for existence
834           try {
835             TextBuffer code = root.toJava(indent + 1, tracer);
836
837             hideMethod = (clinit || dinit || hideConstructor(wrapper, init, throwsExceptions, paramCount)) && code.length() == 0;
838
839             buffer.append(code);
840           }
841           catch (Throwable ex) {
842             DecompilerContext.getLogger().writeMessage("Method " + mt.getName() + " " + mt.getDescriptor() + " couldn't be written.", ex);
843             methodWrapper.decompiledWithErrors = true;
844           }
845         }
846
847         if (methodWrapper.decompiledWithErrors) {
848           buffer.appendIndent(indent + 1);
849           buffer.append("// $FF: Couldn't be decompiled");
850           buffer.appendLineSeparator();
851           tracer.incrementCurrentSourceLine();
852         }
853
854         if (root != null) {
855           tracer.addMapping(root.getDummyExit().bytecode);
856         }
857         buffer.appendIndent(indent).append('}').appendLineSeparator();
858         tracer.incrementCurrentSourceLine();
859       }
860     }
861     finally {
862       DecompilerContext.setProperty(DecompilerContext.CURRENT_METHOD_WRAPPER, outerWrapper);
863     }
864
865     // save total lines
866     // TODO: optimize
867     //tracer.setCurrentSourceLine(buffer.countLines(start_index_method));
868
869     return !hideMethod;
870   }
871
872   private static boolean hideConstructor(ClassWrapper wrapper, boolean init, boolean throwsExceptions, int paramCount) {
873     if (!init || throwsExceptions || paramCount > 0 || !DecompilerContext.getOption(IFernflowerPreferences.HIDE_DEFAULT_CONSTRUCTOR)) {
874       return false;
875     }
876
877     int count = 0;
878     for (StructMethod mt : wrapper.getClassStruct().getMethods()) {
879       if (CodeConstants.INIT_NAME.equals(mt.getName())) {
880         if (++count > 1) {
881           return false;
882         }
883       }
884     }
885
886     return true;
887   }
888
889   private static void appendDeprecation(TextBuffer buffer, int indent) {
890     buffer.appendIndent(indent).append("/** @deprecated */").appendLineSeparator();
891   }
892
893   private enum MType {CLASS, FIELD, METHOD}
894
895   private static void appendRenameComment(TextBuffer buffer, String oldName, MType type, int indent) {
896     if (oldName == null) return;
897
898     buffer.appendIndent(indent);
899     buffer.append("// $FF: renamed from: ");
900
901     switch (type) {
902       case CLASS:
903         buffer.append(ExprProcessor.buildJavaClassName(oldName));
904         break;
905
906       case FIELD:
907         String[] fParts = oldName.split(" ");
908         FieldDescriptor fd = FieldDescriptor.parseDescriptor(fParts[2]);
909         buffer.append(fParts[1]);
910         buffer.append(' ');
911         buffer.append(getTypePrintOut(fd.type));
912         break;
913
914       default:
915         String[] mParts = oldName.split(" ");
916         MethodDescriptor md = MethodDescriptor.parseDescriptor(mParts[2]);
917         buffer.append(mParts[1]);
918         buffer.append(" (");
919         boolean first = true;
920         for (VarType paramType : md.params) {
921           if (!first) {
922             buffer.append(", ");
923           }
924           first = false;
925           buffer.append(getTypePrintOut(paramType));
926         }
927         buffer.append(") ");
928         buffer.append(getTypePrintOut(md.ret));
929     }
930
931     buffer.appendLineSeparator();
932   }
933
934   private static String getTypePrintOut(VarType type) {
935     String typeText = ExprProcessor.getCastTypeName(type, false);
936     if (ExprProcessor.UNDEFINED_TYPE_STRING.equals(typeText) &&
937         DecompilerContext.getOption(IFernflowerPreferences.UNDEFINED_PARAM_TYPE_OBJECT)) {
938       typeText = ExprProcessor.getCastTypeName(VarType.VARTYPE_OBJECT, false);
939     }
940     return typeText;
941   }
942
943   private static void appendComment(TextBuffer buffer, String comment, int indent) {
944     buffer.appendIndent(indent).append("// $FF: ").append(comment).appendLineSeparator();
945   }
946
947   private static final String[] ANNOTATION_ATTRIBUTES = {
948     StructGeneralAttribute.ATTRIBUTE_RUNTIME_VISIBLE_ANNOTATIONS, StructGeneralAttribute.ATTRIBUTE_RUNTIME_INVISIBLE_ANNOTATIONS};
949
950   private static void appendAnnotations(TextBuffer buffer, StructMember mb, int indent) {
951     BytecodeMappingTracer tracer_dummy = new BytecodeMappingTracer(); // FIXME: replace with a real one
952
953     for (String name : ANNOTATION_ATTRIBUTES) {
954       StructAnnotationAttribute attribute = (StructAnnotationAttribute)mb.getAttributes().getWithKey(name);
955       if (attribute != null) {
956         for (AnnotationExprent annotation : attribute.getAnnotations()) {
957           buffer.append(annotation.toJava(indent, tracer_dummy)).appendLineSeparator();
958         }
959       }
960     }
961   }
962
963   private static final String[] PARAMETER_ANNOTATION_ATTRIBUTES = {
964     StructGeneralAttribute.ATTRIBUTE_RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS, StructGeneralAttribute.ATTRIBUTE_RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS};
965
966   private static void appendParameterAnnotations(TextBuffer buffer, StructMethod mt, int param) {
967
968     BytecodeMappingTracer tracer_dummy = new BytecodeMappingTracer(); // FIXME: replace with a real one
969
970     for (String name : PARAMETER_ANNOTATION_ATTRIBUTES) {
971       StructAnnotationParameterAttribute attribute = (StructAnnotationParameterAttribute)mt.getAttributes().getWithKey(name);
972       if (attribute != null) {
973         List<List<AnnotationExprent>> annotations = attribute.getParamAnnotations();
974         if (param < annotations.size()) {
975           for (AnnotationExprent annotation : annotations.get(param)) {
976             buffer.append(annotation.toJava(0, tracer_dummy)).append(' ');
977           }
978         }
979       }
980     }
981   }
982
983   private static final Map<Integer, String> MODIFIERS = new LinkedHashMap<Integer, String>() {{
984     put(CodeConstants.ACC_PUBLIC, "public");
985     put(CodeConstants.ACC_PROTECTED, "protected");
986     put(CodeConstants.ACC_PRIVATE, "private");
987     put(CodeConstants.ACC_ABSTRACT, "abstract");
988     put(CodeConstants.ACC_STATIC, "static");
989     put(CodeConstants.ACC_FINAL, "final");
990     put(CodeConstants.ACC_STRICT, "strictfp");
991     put(CodeConstants.ACC_TRANSIENT, "transient");
992     put(CodeConstants.ACC_VOLATILE, "volatile");
993     put(CodeConstants.ACC_SYNCHRONIZED, "synchronized");
994     put(CodeConstants.ACC_NATIVE, "native");
995   }};
996
997   private static final int CLASS_ALLOWED =
998     CodeConstants.ACC_PUBLIC | CodeConstants.ACC_PROTECTED | CodeConstants.ACC_PRIVATE | CodeConstants.ACC_ABSTRACT |
999     CodeConstants.ACC_STATIC | CodeConstants.ACC_FINAL | CodeConstants.ACC_STRICT;
1000   private static final int FIELD_ALLOWED =
1001     CodeConstants.ACC_PUBLIC | CodeConstants.ACC_PROTECTED | CodeConstants.ACC_PRIVATE | CodeConstants.ACC_STATIC |
1002     CodeConstants.ACC_FINAL | CodeConstants.ACC_TRANSIENT | CodeConstants.ACC_VOLATILE;
1003   private static final int METHOD_ALLOWED =
1004     CodeConstants.ACC_PUBLIC | CodeConstants.ACC_PROTECTED | CodeConstants.ACC_PRIVATE | CodeConstants.ACC_ABSTRACT |
1005     CodeConstants.ACC_STATIC | CodeConstants.ACC_FINAL | CodeConstants.ACC_SYNCHRONIZED | CodeConstants.ACC_NATIVE | CodeConstants.ACC_STRICT;
1006
1007   private static final int CLASS_EXCLUDED = CodeConstants.ACC_ABSTRACT | CodeConstants.ACC_STATIC;
1008   private static final int FIELD_EXCLUDED = CodeConstants.ACC_PUBLIC | CodeConstants.ACC_STATIC | CodeConstants.ACC_FINAL;
1009   private static final int METHOD_EXCLUDED = CodeConstants.ACC_PUBLIC | CodeConstants.ACC_ABSTRACT;
1010
1011   private static void appendModifiers(TextBuffer buffer, int flags, int allowed, boolean isInterface, int excluded) {
1012     flags &= allowed;
1013     if (!isInterface) excluded = 0;
1014     for (int modifier : MODIFIERS.keySet()) {
1015       if ((flags & modifier) == modifier && (modifier & excluded) == 0) {
1016         buffer.append(MODIFIERS.get(modifier)).append(' ');
1017       }
1018     }
1019   }
1020
1021   private static void appendTypeParameters(TextBuffer buffer, List<String> parameters, List<List<GenericType>> bounds) {
1022     buffer.append('<');
1023
1024     for (int i = 0; i < parameters.size(); i++) {
1025       if (i > 0) {
1026         buffer.append(", ");
1027       }
1028
1029       buffer.append(parameters.get(i));
1030
1031       List<GenericType> parameterBounds = bounds.get(i);
1032       if (parameterBounds.size() > 1 || !"java/lang/Object".equals(parameterBounds.get(0).value)) {
1033         buffer.append(" extends ");
1034         buffer.append(GenericMain.getGenericCastTypeName(parameterBounds.get(0)));
1035         for (int j = 1; j < parameterBounds.size(); j++) {
1036           buffer.append(" & ");
1037           buffer.append(GenericMain.getGenericCastTypeName(parameterBounds.get(j)));
1038         }
1039       }
1040     }
1041
1042     buffer.append('>');
1043   }
1044 }