Skip to content

Instantly share code, notes, and snippets.

@tshirtman
Last active April 30, 2020 22:01
Show Gist options
  • Select an option

  • Save tshirtman/8907228 to your computer and use it in GitHub Desktop.

Select an option

Save tshirtman/8907228 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python
'''
Usage:
parse.py <path> <exportpath> <suffix> [<packagename>]
[--blacklist=<blacklist>] [--debug] [--cleanup]
parse.py --jar=<jarfile> <exportpath> [<packagename>]
--suffix=<suffix> [--blacklist=<blacklist>] [--debug] [--cleanup]
Options:
path is the path to look java classes into
exportpath is the destination dir to produce classes into
packagename is an optional package name to put in
the generated classe files
-j --jar=<jarfile> used to look for classes in a jar
instead of a path
-b --blacklist=<blacklist> indicate a file to filter packages/imports from
-d --debug display more info about what happens
-c --cleanup remove old content of exportpath before
starting
'''
# TODO
# handling jars
blacklist = [
"com.android.internal",
"android.os",
"android.util.LongSparseArray",
"android.widget.RemoteViews.OnClickHandler",
"com.android.tools.layoutlib.create",
"org.objectweb",
"com.android.test",
"com.google.android.util",
"com.google.mockwebserver",
"libcore.javax.net.ssl",
"android.view.accessibility",
"dalvik.system",
"com.android.ide.common",
"com.android.layoutlib.bridge",
]
try:
import plyj.parser as plyj
import jinja2
from docopt import docopt
except ImportError:
print "please install plyj, jinja2 and docopt"
exit()
import os
import shutil
parser = plyj.Parser()
env = jinja2.Environment()
def get_type(ptype):
"""Get the string representing a type
"""
if isinstance(ptype, str):
return ptype
elif isinstance(ptype.name, str):
return ptype.name
else:
return ptype.name.value
def render_params(params):
"""A filter to display the parameters of a function call
"""
return ', '.join(
'{type} {name}'.format(
type=get_type(param.type),
name=param.variable.name)
for param in params)
def render_params_values(params):
return ', '.join(
'{name}'.format(
name=param.variable.name)
for param in params)
def import_types(module, blacklist):
"""This gets all the imports done in the original module, not all of
them are necessary, but it's easier this way
"""
for i in module.import_declarations:
if not in_blacklist(i.name.value, blacklist):
yield 'import %s;\n' % i.name.value
elif debug:
print "import %s skipped because it's blacklisted" % i.name.value
env.filters['render_params'] = render_params
env.filters['render_params_values'] = render_params_values
env.filters['get_type'] = get_type
class_template = env.from_string('''\
{% if package %}package {{ package }};{% endif %}
import {{ module.package_declaration.name.value }}.*;
{{ imports }}\
public class {{ cls.name }}{{ suffix }} extends {{ cls.name }} {
public interface I{{ cls.name }} {\
{% for method in methods %}
{{ method.return_type | get_type }} {{ method.name }}({{ method.parameters | render_params }});\
{% endfor %}
}
private I{{ cls.name }} implem = null;
public void setImplem(I{{ cls.name }} implem) {
this.implem = implem;
}
{% for method in methods %}
{{ method.return_type | get_type }} {{ method.name }}({{ method.parameters | render_params }}) {
if (this.implem)
return this.implem.{{ method.name }}({{ method.parameters | render_params_values }});
}{% endfor %}
}
''')
def in_blacklist(name, blacklist):
return any(x in name for x in blacklist)
def get_abstract_classes(path, blacklist):
"""Being given a path, this function will return all the abstract
classes defined in it
"""
for path, _, files in os.walk(path):
for f in files:
if not f.endswith('.java'):
continue
j = parser.parse_file(file(os.path.join(path, f)))
if debug:
print "parsing %s" % os.path.join(path, f)
if not j:
continue
if j.package_declaration:
if debug:
print "package %s" % j.package_declaration.name.value
if in_blacklist(j.package_declaration.name.value, blacklist):
if debug:
print "skipping package %s because it's blacklisted" % (
j.package_declaration.name.value)
continue
for tp in j.type_declarations:
if (
tp and 'public' in tp.modifiers and
'abstract' in tp.modifiers
):
if debug:
print "found abstract class %s in %s" % (
tp.name, j.package_declaration)
yield j, tp
def get_abstract_methods(tp):
"""Given an abstract class, this will return all its abstract
methods
"""
for member in tp.body:
if (
isinstance(member, plyj.MethodDeclaration) and
'abstract' in member.modifiers
):
yield member
if __name__ == '__main__':
arguments = docopt(__doc__)
debug = arguments.get('--debug')
if debug:
print "passed arguments: %s" % arguments
outpath = os.path.join(
arguments['<exportpath>'],
*arguments.get('<packagename>', '').split('.'))
if arguments.get('--cleanup'):
shutil.rmtree(outpath)
suffix = arguments.get('<suffix>', '')
if not os.path.exists(outpath):
os.makedirs(outpath)
blacklistfile = arguments.get('--blacklist')
if blacklistfile:
with open(blacklistfile) as f:
blacklist.extend(list(l[:-1] for l in f.readlines()))
print "blacklist: %s" % ';'.join(blacklist)
for module, cls in get_abstract_classes(arguments['<path>'], blacklist):
methods = list(get_abstract_methods(cls))
package = arguments.get('<packagename>')
javaclass = class_template.render(
package=package,
imports=''.join(import_types(module, blacklist)),
module=module,
suffix=suffix,
cls=cls,
methods=methods)
with open('%s.java' % (os.path.join(
outpath, cls.name + suffix)), 'w'
) as f:
f.write(javaclass)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment