Skip to content

Commit 431b568

Browse files
author
Troy Melhase
committed
Adds real support for synchronized modifier and statement.
1 parent 551f224 commit 431b568

File tree

9 files changed

+167
-2
lines changed

9 files changed

+167
-2
lines changed

doc/features.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,15 @@ Java `switch` and `case` statements are translated to equivalent Python `if` sta
118118

119119
##### synchronized
120120

121-
The compiler currently discards the `synchronized` Java statement. This is incorrect behavior. The correct behavior is (and will be) to apply a synchronized decorator to a function wrapping the construct.
121+
In the case of a `synchronized` method or static method, the compiler will include a decorator, `@synchronized` in
122+
the method or static method preamble. In the case of a `synchronized` block, the compiler will translate to this form:
123+
124+
with lock_for_object(expr):
125+
...
126+
127+
The `lock_for_object` callable is the default and can be controlled via the `methodLockFunctionName` config item.
128+
Also of note, the default `modulePrologueHandlers` uses a generator named `maybeSyncHelpers` to include
129+
Python helper code for synchronization.
122130

123131
##### return
124132
Java `return` statements are translated to equivalent Python `return` statements.

java2python/compiler/visitor.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,19 @@ def acceptSwitch(self, node, memo):
511511
self.factory.expr(left='pass', parent=caseContent)
512512
self.children.remove(parExpr)
513513

514+
def acceptSynchronized(self, node, memo):
515+
""" Accept and process a synchronized statement (not a modifier). """
516+
module = self.parents(lambda x:x.isModule).next()
517+
module.needsSyncHelpers = True
518+
if node.parent.type == tokens.MODIFIER_LIST:
519+
# Skip any synchronized modifier
520+
return
521+
lockName = self.configHandler('LockFunction', 'Name', 'lock_for_object')
522+
withFs = '{left} %s({right}):' % lockName
523+
sync = self.factory.statement('with', fs=withFs, parent=self)
524+
sync.expr.walk(node.children[0], memo)
525+
sync.walk(node.children[1], memo)
526+
514527
def acceptThrow(self, node, memo):
515528
""" Accept and process a throw statement. """
516529
throw = self.factory.statement('raise', fs=FS.lsr, parent=self)

java2python/config/default.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
basic.shebangLine,
2626
basic.simpleDocString,
2727
basic.maybeBsr,
28+
basic.maybeSyncHelpers,
2829
]
2930

3031

@@ -51,6 +52,10 @@
5152
basic.defaultParams,
5253
]
5354

55+
# This is the name of the callable used to construct locks for an object with
56+
# the synchronized keyword.
57+
methodLockFunctionName = 'lock_for_object'
58+
5459
classBaseHandlers = [
5560
basic.defaultBases,
5661
]
@@ -91,6 +96,8 @@
9196
methodPrologueHandlers = [
9297
basic.maybeAbstractMethod,
9398
basic.maybeClassMethod,
99+
# NB: synchronized should come after classmethod
100+
basic.maybeSynchronizedMethod,
94101
basic.overloadedClassMethods,
95102
]
96103

java2python/mod/basic.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,27 +123,47 @@ def maybeClassMethod(method):
123123
if method.isStatic and 'classmethod' not in method.decorators:
124124
yield '@classmethod'
125125

126+
126127
def maybeAbstractMethod(method):
127128
if method.parent and method.parent.isInterface:
128129
yield '@abstractmethod'
129130

130131

132+
def maybeSynchronizedMethod(method):
133+
if 'synchronized' in method.modifiers:
134+
module = method.parents(lambda x:x.isModule).next()
135+
module.needsSyncHelpers = True
136+
yield '@synchronized'
137+
138+
131139
def globalNameCounter(original, counter=count()):
132140
return '__{0}_{1}'.format(original, counter.next())
133141

134142

135143
def getBsrSrc():
136144
from inspect import getsource
137-
from java2python.mod.includes import bsr
145+
from java2python.mod.include.bsr import bsr
138146
return getsource(bsr)
139147

140148

149+
def getSyncHelpersSrc():
150+
from inspect import getsource
151+
from java2python.mod.include import sync
152+
return getsource(sync)
153+
154+
141155
def maybeBsr(module):
142156
if getattr(module, 'needsBsrFunc', False):
143157
for line in getBsrSrc().split('\n'):
144158
yield line
145159

146160

161+
def maybeSyncHelpers(module):
162+
if getattr(module, 'needsSyncHelpers', False):
163+
for line in getSyncHelpersSrc().split('\n'):
164+
yield line
165+
166+
147167
def classContentSort(obj):
148168
isMethod = lambda x:x and x.isMethod
149169

java2python/mod/include/__init__.py

Whitespace-only changes.

java2python/mod/include/sync.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from threading import RLock
2+
3+
_locks = {}
4+
def lock_for_object(obj, locks=_locks):
5+
return locks.setdefault(id(obj), RLock())
6+
7+
8+
def synchronized(call):
9+
def inner(*args, **kwds):
10+
with lock_for_object(call):
11+
return call(*args, **kwds)
12+
return inner
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#!/usr/bin/env python
2+
3+
from threading import RLock
4+
5+
_locks = {}
6+
def lock_for_object(obj, locks=_locks):
7+
return locks.setdefault(id(obj), RLock())
8+
9+
def synchronized(call):
10+
def inner(*args, **kwds):
11+
with lock_for_object(call):
12+
return call(*args, **kwds)
13+
return inner
14+
15+
class Main(object):
16+
17+
def __init__(self):
18+
self.attr = object()
19+
20+
def b1(self):
21+
r = []
22+
with lock_for_object(self.attr):
23+
r.append(0)
24+
return r
25+
26+
def b2(self):
27+
r = []
28+
with lock_for_object(self.attr):
29+
r.append(0)
30+
return r
31+
32+
def m1(self):
33+
return id(lock_for_object(self))
34+
35+
def m2(self):
36+
return id(lock_for_object(self))
37+
38+
@classmethod
39+
def c1(cls):
40+
return id(lock_for_object(cls))
41+
42+
@classmethod
43+
def c2(cls):
44+
return id(lock_for_object(cls))
45+
46+
@synchronized
47+
def s1(self, *values, **kwargs):
48+
return [values, kwargs]
49+
50+
@synchronized
51+
def s2(self, *values, **kwargs):
52+
return [values, kwargs]
53+
54+
@classmethod
55+
@synchronized
56+
def cs1(cls, *values, **kwargs):
57+
return [cls, values, kwargs]
58+
59+
@classmethod
60+
@synchronized
61+
def cs2(cls, *values, **kwargs):
62+
return [cls, values, kwargs]
63+
64+
65+
66+
if __name__ == '__main__':
67+
x = Main()
68+
expected_count = 0
69+
70+
assert x.b1() == x.b2()
71+
expected_count += 1 # one for the attr, used twice
72+
73+
assert x.c1() == x.c2()
74+
expected_count += 1 # one for the class, used twice
75+
76+
assert x.m1() == x.m2()
77+
expected_count += 1 # one for the instance, used twice
78+
79+
assert x.s1() == x.s2()
80+
expected_count += 2 # one for each instance method
81+
82+
assert x.cs1() == x.cs2()
83+
expected_count += 2 # one for each class method
84+
85+
assert expected_count == len(_locks)
86+
87+
print '[PASS]'
88+

test/Synchronized1.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
class Synchronized1 {
2+
3+
public synchronized void run() {
4+
System.out.println(0);
5+
}
6+
7+
public static synchronized void class_run() {
8+
System.out.println(0);
9+
}
10+
11+
public static void main(String[] args) {
12+
Synchronized1 obj = new Synchronized1();
13+
obj.run();
14+
obj.class_run();
15+
16+
}
17+
}

0 commit comments

Comments
 (0)