400 lines
16 KiB
Python
400 lines
16 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
Created on Thu Jan 19 16:29:03 2012
|
|
|
|
@author: Fesh0r
|
|
@version: v0.1
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
import fnmatch
|
|
import shutil
|
|
import re
|
|
from optparse import OptionParser
|
|
|
|
|
|
_MODIFIERS = r'public|protected|private|static|abstract|final|native|synchronized|transient|volatile|strictfp'
|
|
|
|
_REGEXP = {
|
|
# convert OSX double 1.23456789E-12D to Windows double 1.23456789E-012D
|
|
'fltexp': re.compile(r'(?<=[Ee][+-])([0-9]{2})(?=[DdFf])'),
|
|
|
|
# Remove trailing whitespace
|
|
'trailing': re.compile(r'[ \t]+$', re.MULTILINE),
|
|
|
|
# close up extends, and implements that JAD puts on a seperate line
|
|
'extends': re.compile(r'$\n {4}(?=extends|implements)', re.MULTILINE),
|
|
|
|
# close up throws that JAD puts on a seperate line
|
|
'throws': re.compile(r'$\n {8}(?=throws)', re.MULTILINE),
|
|
|
|
# close up method parameters that have wrapped
|
|
'params': re.compile(r'^ {4}(?P<main>\S.*?)(?P<wrapped>(?:\n {12}\S.*?)+)(?P<suffix>(?:\n {4}\{$)|;)', re.MULTILINE),
|
|
'params_sub': re.compile(r'\n {12}', re.MULTILINE),
|
|
|
|
# Remove repeated blank lines
|
|
'newlines': re.compile(r'^\n{2,}', re.MULTILINE),
|
|
|
|
# Rename variables called enum
|
|
'rename_enum': re.compile(r'(?<=\W)(enum)(?=\W)'),
|
|
|
|
'modifiers': re.compile(r'(' + _MODIFIERS + ') '),
|
|
'list': re.compile(r', '),
|
|
'deindent': re.compile(r'^ {4}(.*$)', re.MULTILINE),
|
|
|
|
# Class
|
|
'class': re.compile(r'^(?P<modifiers>(?:(?:' + _MODIFIERS + r') )*)(?P<type>class|interface|enum) (?P<name>[\w$]+)(?: extends (?P<extends>[\w$.]+(?:, [\w$.]+)*))?(?: implements (?P<implements>[\w$.]+(?:, [\w$.]+)*))?\n\{\n(?P<body>(?:.*?\n)*?)(?P<end>\}\n+)', re.MULTILINE),
|
|
|
|
# method regex
|
|
'method': re.compile(r'^ {4}(?P<modifiers>(?:(?:' + _MODIFIERS + r') )*)(?P<type>(?!' + _MODIFIERS + r')[\w$.[\]]+) (?P<name>[\w$]+)\((?P<parameters>.*?)\)(?: throws (?P<throws>[\w$.]+(?:, [\w$.]+)*))?\n {4}\{\n(?P<body>(?:.*?\n)*?)(?P<end> {4}\}\n+)', re.MULTILINE),
|
|
|
|
# abstract method regex
|
|
'method_abstract': re.compile(r'^ {4}(?P<modifiers>(?:(?:' + _MODIFIERS + r') )*)(?P<type>(?!' + _MODIFIERS + r')[\w$.[\]]+) (?P<name>[\w$]+)\((?P<parameters>.*?)\)(?: throws (?P<throws>[\w$.]+(?:, [\w$.]+)*))?(?P<end>;\n+)', re.MULTILINE),
|
|
|
|
# move super call to start of method
|
|
'fix_super': re.compile(r'(?P<before>(?:.*\n)*)(?P<super> {8}super\((?P<parameters>.*?)\);\n)(?P<after>(?:.*\n)*)', re.MULTILINE),
|
|
|
|
# remove super call in enum
|
|
'enum_super': re.compile(r' {8}super\(\w+, \w+\);\n', re.MULTILINE),
|
|
|
|
# static block
|
|
'static': re.compile(r'^ {4}static\n {4}\{\n(?P<body>(?:.*?\n)*?)(?P<end> {4}\}\n+)', re.MULTILINE),
|
|
|
|
# field_1234.field_5678 += abc + def;
|
|
'str1': re.compile(r'(?P<indent>^ +)new StringBuilder\(\);\n +(?P<dest1>.*?);\n +JVM INSTR dup_x1 ;\n +(?P<dest2>.*?);\n +append\(\);\n +(?P<src1>.*?);\n(:? +append\(\);\n +(?P<src2>.*?);\n)? +append\(\);\n +toString\(\);\n +(?P=dest2);$', re.MULTILINE),
|
|
|
|
# field_1234[field_5678] += abc + def;
|
|
'str2': re.compile(r'(?P<indent>^ +)new StringBuilder\(\);\n +(?P<dest1>.*?);\n +(?P<dest2>.*?);\n +JVM INSTR dup2_x1 ;\n +JVM INSTR aaload ;\n +append\(\);\n +(?P<src1>.*?);\n +append\(\);\n(:? +(?P<src2>.*?);\n +append\(\);\n)? +toString\(\);\n +JVM INSTR aastore ;$', re.MULTILINE),
|
|
|
|
# if(test) goto _L1; else goto _L2
|
|
'if_goto': re.compile(r'(?P<indent>^ +)if(?P<test>\(.*\)) goto (?P<label1>_L[0-9]+); else goto (?P<label2>_L[0-9]+)\n(?P<label3>_L[0-9]+):$', re.MULTILINE),
|
|
}
|
|
|
|
_REGEXP_STR = {
|
|
'constructor': r'^ {4}(?P<modifiers>(?:(?:' + _MODIFIERS + r') )*)%s\((?P<parameters>.*?)\)(?: throws (?P<throws>[\w$.]+(?:, [\w$.]+)*))?\n {4}\{\n(?P<body>(?:.*?\n)*?)(?P<end> {4}\}\n+)',
|
|
|
|
'enum_methods': r'^ {4}public static %s(?:\[\])? value(?:s|Of)\(.*?\)\n {4}\{\n(?:.*?\n)*? {4}\}\n+',
|
|
|
|
'enum_fields': r'^ {4}(?:public|private) static final %s [\w$.[\]]+;.*\n',
|
|
|
|
'enum_entries': r'^ {8}(?P<name>[\w$]+) = new %s\("(?P=name)", [0-9]+(?:, (?P<body>.*?))?\);\n+',
|
|
|
|
'enum_values': r'^ {8}(?P<name>[\w$]+)(?P<body> = \(new %s\[\] \{\n(?:.*\n)*? {8}\}\));\n+',
|
|
}
|
|
|
|
|
|
class Error(Exception):
|
|
pass
|
|
|
|
|
|
class ParseError(Error):
|
|
pass
|
|
|
|
|
|
def jadfix(srcdir):
|
|
for path, _, filelist in os.walk(srcdir, followlinks=True):
|
|
for cur_file in fnmatch.filter(filelist, '*.java'):
|
|
src_file = os.path.normpath(os.path.join(path, cur_file))
|
|
_process_file(src_file)
|
|
|
|
|
|
def _process_class(class_name, class_type, modifiers, extends, implements, body, end):
|
|
if class_type == 'class':
|
|
# if we have an enum class then fix the class declaration
|
|
if 'final' in modifiers and 'Enum' in extends:
|
|
modifiers.remove('final')
|
|
extends.remove('Enum')
|
|
class_type = 'enum'
|
|
if class_type == 'interface':
|
|
# is this an annotation type? still missing too much info for it to actually work
|
|
if 'Annotation' in extends:
|
|
extends.remove('Annotation')
|
|
class_type = '@interface'
|
|
if class_type == 'enum':
|
|
body = _process_enum(class_name, body)
|
|
|
|
# process normal methods
|
|
def method_match(match):
|
|
modifiers = _REGEXP['modifiers'].findall(match.group('modifiers'))
|
|
if match.group('modifiers') and not modifiers:
|
|
raise ParseError('no modifiers match in %s \'%s\'' % (match.group('name'), match.group('modifiers')))
|
|
parameters = []
|
|
if match.group('parameters'):
|
|
parameters = _REGEXP['list'].split(match.group('parameters'))
|
|
throws = []
|
|
if match.group('throws'):
|
|
throws = _REGEXP['list'].split(match.group('throws'))
|
|
return _process_method(class_name, match.group('name'), modifiers, match.group('type'), parameters, throws,
|
|
match.group('body'), match.group('end'))
|
|
body = _REGEXP['method'].sub(method_match, body)
|
|
|
|
# process abstract methods
|
|
def method_abstract_match(match):
|
|
modifiers = _REGEXP['modifiers'].findall(match.group('modifiers'))
|
|
if match.group('modifiers') and not modifiers:
|
|
raise ParseError('no modifiers match in %s \'%s\'' % (match.group('name'), match.group('modifiers')))
|
|
parameters = []
|
|
if match.group('parameters'):
|
|
parameters = _REGEXP['list'].split(match.group('parameters'))
|
|
throws = []
|
|
if match.group('throws'):
|
|
throws = _REGEXP['list'].split(match.group('throws'))
|
|
return _process_method_abstract(class_name, match.group('name'), modifiers, match.group('type'), parameters,
|
|
throws, match.group('end'))
|
|
body = _REGEXP['method_abstract'].sub(method_abstract_match, body)
|
|
|
|
# process constructors
|
|
def constructor_match(match):
|
|
modifiers = _REGEXP['modifiers'].findall(match.group('modifiers'))
|
|
if match.group('modifiers') and not modifiers:
|
|
raise ParseError('no modifiers match in %s \'%s\'' % (match.group('name'), match.group('modifiers')))
|
|
parameters = []
|
|
if match.group('parameters'):
|
|
parameters = _REGEXP['list'].split(match.group('parameters'))
|
|
throws = []
|
|
if match.group('throws'):
|
|
throws = _REGEXP['list'].split(match.group('throws'))
|
|
return _process_constructor(class_type, class_name, modifiers, parameters, throws, match.group('body'),
|
|
match.group('end'))
|
|
constructor_regex = re.compile(_REGEXP_STR['constructor'] % re.escape(class_name), re.MULTILINE)
|
|
body = constructor_regex.sub(constructor_match, body)
|
|
|
|
# rebuild class
|
|
out = ''
|
|
if modifiers:
|
|
out += ' '.join(modifiers) + ' '
|
|
out += class_type + ' ' + class_name
|
|
if extends:
|
|
out += ' extends ' + ', '.join(extends)
|
|
if implements:
|
|
out += ' implements ' + ', '.join(implements)
|
|
out += '\n{\n' + body + end
|
|
return out
|
|
|
|
|
|
def _process_enum(class_name, body):
|
|
# remove super call in constructor
|
|
body = _REGEXP['enum_super'].sub(r'', body)
|
|
|
|
# remove values and valueOf methods
|
|
methods_regex = re.compile(_REGEXP_STR['enum_methods'] % re.escape(class_name), re.MULTILINE)
|
|
body = methods_regex.sub(r'', body)
|
|
|
|
# remove enum fields and $VALUES
|
|
fields_regex = re.compile(_REGEXP_STR['enum_fields'] % re.escape(class_name), re.MULTILINE)
|
|
body = fields_regex.sub(r'', body)
|
|
|
|
# rebuild enum entries from static block
|
|
body = _process_enum_static(class_name, body)
|
|
|
|
return body
|
|
|
|
|
|
def _process_enum_static(class_name, enum_body):
|
|
# do we have a static block?
|
|
static_match = _REGEXP['static'].search(enum_body)
|
|
if not static_match:
|
|
return enum_body
|
|
|
|
body = static_match.group('body')
|
|
|
|
# for each enum field in the static build up a enum entry
|
|
enum_entries = ''
|
|
entries = []
|
|
|
|
def _enum_entries_match(match):
|
|
entry_body = ''
|
|
if match.group('body'):
|
|
entry_body = '(' + match.group('body') + ')'
|
|
new_entry = ' ' + match.group('name') + entry_body
|
|
new_entry = _REGEXP['deindent'].sub(r'\1', new_entry)
|
|
entries.append(new_entry)
|
|
return ''
|
|
entries_regex = re.compile(_REGEXP_STR['enum_entries'] % re.escape(class_name), re.MULTILINE | re.DOTALL)
|
|
body = entries_regex.sub(_enum_entries_match, body)
|
|
if entries:
|
|
enum_entries = '\n' + ',\n'.join(entries) + ';\n\n'
|
|
|
|
# remove the $VALUES array from the static block
|
|
values_regex = re.compile(_REGEXP_STR['enum_values'] % re.escape(class_name), re.MULTILINE)
|
|
body = values_regex.sub('', body)
|
|
|
|
# add the entries and $VALUES to start of body
|
|
enum_body = enum_entries + enum_body
|
|
|
|
# remove the entries and values from the static block
|
|
# and remove the block entirely if now empty
|
|
full_static = ''
|
|
if body:
|
|
full_static = ' static\n {\n' + body + static_match.group('end')
|
|
enum_body = _REGEXP['static'].sub(full_static, enum_body)
|
|
return enum_body
|
|
|
|
|
|
def _process_method(_class_name, method_name, modifiers, method_type, parameters, throws, body, end):
|
|
# kill off the wierd _mthclass$ methods that JAD sticks in for some reason
|
|
if method_name == '_mthclass$' and 'static' in modifiers:
|
|
return ''
|
|
|
|
body = _process_string(body)
|
|
body = _process_if_goto(body)
|
|
|
|
# rebuild method
|
|
out = ' '
|
|
if modifiers:
|
|
out += ' '.join(modifiers) + ' '
|
|
out += method_type + ' ' + method_name + '(' + ', '.join(parameters) + ')'
|
|
if throws:
|
|
out += ' throws ' + ', '.join(throws)
|
|
out += '\n {\n' + body + end
|
|
return out
|
|
|
|
|
|
def _process_method_abstract(_class_name, method_name, modifiers, method_type, parameters, throws, end):
|
|
# rebuild method
|
|
out = ' '
|
|
if modifiers:
|
|
out += ' '.join(modifiers) + ' '
|
|
out += method_type + ' ' + method_name + '(' + ', '.join(parameters) + ')'
|
|
if throws:
|
|
out += ' throws ' + ', '.join(throws)
|
|
out += end
|
|
return out
|
|
|
|
|
|
def _process_constructor(class_type, class_name, modifiers, parameters, throws, body, end):
|
|
if class_type == 'enum':
|
|
if len(parameters) >= 2:
|
|
if parameters[0].startswith('String ') and parameters[1].startswith('int '):
|
|
parameters = parameters[2:]
|
|
# empty constructor
|
|
if body == '' and len(parameters) == 0:
|
|
return ''
|
|
else:
|
|
raise ParseError('invalid initial parameters in enum %s: %s' % (class_name, str(parameters)))
|
|
else:
|
|
raise ParseError('not enough parameters in enum %s: %s' % (class_name, str(parameters)))
|
|
|
|
body = _process_string(body)
|
|
body = _process_if_goto(body)
|
|
|
|
# move super calls to start of constructor and remove empty super calls
|
|
def super_match(match):
|
|
if match.group('parameters'):
|
|
return match.group('super') + match.group('before') + match.group('after')
|
|
else:
|
|
return match.group('before') + match.group('after')
|
|
body = _REGEXP['fix_super'].sub(super_match, body)
|
|
|
|
# rebuild constructor
|
|
out = ' '
|
|
if modifiers:
|
|
out += ' '.join(modifiers) + ' '
|
|
out += class_name + '(' + ', '.join(parameters) + ')'
|
|
if throws:
|
|
out += ' throws ' + ', '.join(throws)
|
|
out += '\n {\n' + body + end
|
|
return out
|
|
|
|
|
|
def _process_string(body):
|
|
# fix up plain string appends
|
|
def string1_match(match):
|
|
indent = match.group('indent')
|
|
src = match.group('src1')
|
|
if match.group('src2'):
|
|
src = '%s + %s' % (src, match.group('src2'))
|
|
dest = match.group('dest2')
|
|
if match.group('dest1') != 'this':
|
|
dest = '%s.%s' % (match.group('dest1'), dest)
|
|
return '%s%s += %s;' % (indent, dest, src)
|
|
body = _REGEXP['str1'].sub(string1_match, body)
|
|
|
|
# fix up string appends to an array
|
|
def string2_match(match):
|
|
indent = match.group('indent')
|
|
src = match.group('src1')
|
|
if match.group('src2'):
|
|
src = '%s + %s' % (src, match.group('src2'))
|
|
dest = '%s[%s]' % (match.group('dest1'), match.group('dest2'))
|
|
return '%s%s += %s;' % (indent, dest, src)
|
|
body = _REGEXP['str2'].sub(string2_match, body)
|
|
|
|
return body
|
|
|
|
|
|
def _process_if_goto(body):
|
|
def if_goto_match(match):
|
|
indent = match.group('indent')
|
|
# depending on the following label negate the if test
|
|
if match.group('label3') == match.group('label2'):
|
|
test = 'if(!%s)' % match.group('test')
|
|
comment = '## JADFIX %s %s' % (match.group('label2'), match.group('label1'))
|
|
else:
|
|
test = 'if%s' % match.group('test')
|
|
comment = '## JADFIX %s %s' % (match.group('label1'), match.group('label2'))
|
|
label = '%s:' % match.group('label3')
|
|
return '%s%s\n%s\n%s' % (indent, test, comment, label)
|
|
body = _REGEXP['if_goto'].sub(if_goto_match, body)
|
|
return body
|
|
|
|
|
|
def _process_file(src_file):
|
|
class_name = os.path.splitext(os.path.basename(src_file))[0]
|
|
tmp_file = src_file + '.tmp'
|
|
with open(src_file, 'r') as fh:
|
|
buf = fh.read()
|
|
|
|
buf = _REGEXP['fltexp'].sub(r'0\1', buf)
|
|
buf = _REGEXP['trailing'].sub(r'', buf)
|
|
|
|
buf = _REGEXP['extends'].sub(r' ', buf)
|
|
buf = _REGEXP['throws'].sub(r' ', buf)
|
|
|
|
def params_match(match):
|
|
body = re.sub(_REGEXP['params_sub'], r' ', match.group('wrapped'))
|
|
return ' %s%s%s' % (match.group('main'), body, match.group('suffix'))
|
|
buf = _REGEXP['params'].sub(params_match, buf)
|
|
|
|
buf = _REGEXP['rename_enum'].sub(r'\1_', buf)
|
|
|
|
def class_match(match):
|
|
if class_name != match.group('name'):
|
|
raise ParseError("file name and class name differ: '%s' '%s" % (class_name, match.group('name')))
|
|
modifiers = _REGEXP['modifiers'].findall(match.group('modifiers'))
|
|
if match.group('modifiers') and not modifiers:
|
|
raise ParseError("no modifiers match in %s '%s'" % (match.group('name'), match.group('modifiers')))
|
|
extends = []
|
|
if match.group('extends'):
|
|
extends = _REGEXP['list'].split(match.group('extends'))
|
|
implements = []
|
|
if match.group('implements'):
|
|
implements = _REGEXP['list'].split(match.group('implements'))
|
|
return _process_class(match.group('name'), match.group('type'), modifiers, extends, implements,
|
|
match.group('body'), match.group('end'))
|
|
(buf, match_count) = _REGEXP['class'].subn(class_match, buf)
|
|
if not match_count:
|
|
raise ParseError('no class in %s' % class_name)
|
|
|
|
buf = _REGEXP['newlines'].sub(r'\n', buf)
|
|
|
|
with open(tmp_file, 'w') as fh:
|
|
fh.write(buf)
|
|
shutil.move(tmp_file, src_file)
|
|
|
|
|
|
def main():
|
|
usage = 'usage: %prog [options] src_dir'
|
|
version = '%prog 6.0'
|
|
parser = OptionParser(version=version, usage=usage)
|
|
options, args = parser.parse_args()
|
|
if len(args) != 1:
|
|
print >> sys.stderr, 'src_dir required'
|
|
sys.exit(1)
|
|
jadfix(args[0])
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|