Skip to content

Commit 1dc4427

Browse files
committed
Added support for types in the java.time package
Fixes #15
1 parent 64ffdc0 commit 1dc4427

File tree

3 files changed

+226
-1
lines changed

3 files changed

+226
-1
lines changed

javaobj/core.py

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1659,6 +1659,31 @@ def _convert_type_to_char(self, type_char):
16591659

16601660
# ------------------------------------------------------------------------------
16611661

1662+
def read(data, fmt_str):
1663+
"""
1664+
Reads input bytes and extract the given structure. Returns both the read
1665+
elements and the remaining data
1666+
1667+
:param data: Data as bytes
1668+
:param fmt_str: Struct unpack format string
1669+
:return: A tuple (results as tuple, remaining data)
1670+
"""
1671+
size = struct.calcsize(fmt_str)
1672+
return struct.unpack(fmt_str, data[:size]), data[size:]
1673+
1674+
1675+
def read_string(data, length_fmt="H"):
1676+
"""
1677+
Reads a serialized string
1678+
1679+
:param data: Bytes where to read the string from
1680+
:param length_fmt: Structure format of the string length (H or Q)
1681+
:return: The deserialized string
1682+
"""
1683+
(length,), data = read(data, ">{0}".format(length_fmt))
1684+
ba, data = data[:length], data[length:]
1685+
return to_unicode(ba), data
1686+
16621687

16631688
class DefaultObjectTransformer(object):
16641689
"""
@@ -1759,6 +1784,168 @@ def __extra_loading__(self, unmarshaller, ident=0):
17591784
# Annotation[1] == size of the set
17601785
self.update(self.annotations[2:])
17611786

1787+
class JavaTime(JavaObject):
1788+
"""
1789+
Represents the classes found in the java.time package
1790+
1791+
The semantic of the fields depends on the type of time that has been
1792+
parsed
1793+
"""
1794+
DURATION_TYPE = 1
1795+
INSTANT_TYPE = 2
1796+
LOCAL_DATE_TYPE = 3
1797+
LOCAL_TIME_TYPE = 4
1798+
LOCAL_DATE_TIME_TYPE = 5
1799+
ZONE_DATE_TIME_TYPE = 6
1800+
ZONE_REGION_TYPE = 7
1801+
ZONE_OFFSET_TYPE = 8
1802+
OFFSET_TIME_TYPE = 9
1803+
OFFSET_DATE_TIME_TYPE = 10
1804+
YEAR_TYPE = 11
1805+
YEAR_MONTH_TYPE = 12
1806+
MONTH_DAY_TYPE = 13
1807+
PERIOD_TYPE = 14
1808+
1809+
def __init__(self, unmarshaller):
1810+
# type: (JavaObjectUnmarshaller) -> None
1811+
JavaObject.__init__(self)
1812+
self.type = -1
1813+
self.year = None
1814+
self.month = None
1815+
self.day = None
1816+
self.hour = None
1817+
self.minute = None
1818+
self.second = None
1819+
self.nano = None
1820+
self.offset = None
1821+
self.zone = None
1822+
1823+
self.time_handlers = {
1824+
self.DURATION_TYPE: self.do_duration,
1825+
self.INSTANT_TYPE: self.do_instant,
1826+
self.LOCAL_DATE_TYPE: self.do_local_date,
1827+
self.LOCAL_DATE_TIME_TYPE: self.do_local_date_time,
1828+
self.LOCAL_TIME_TYPE: self.do_local_time,
1829+
self.ZONE_DATE_TIME_TYPE: self.do_zoned_date_time,
1830+
self.ZONE_OFFSET_TYPE: self.do_zone_offset,
1831+
self.ZONE_REGION_TYPE: self.do_zone_region,
1832+
self.OFFSET_TIME_TYPE: self.do_offset_time,
1833+
self.OFFSET_DATE_TIME_TYPE: self.do_offset_date_time,
1834+
self.YEAR_TYPE: self.do_year,
1835+
self.YEAR_MONTH_TYPE: self.do_year_month,
1836+
self.MONTH_DAY_TYPE: self.do_month_day,
1837+
self.PERIOD_TYPE: self.do_period,
1838+
}
1839+
1840+
def __str__(self):
1841+
return (
1842+
"JavaTime(type=0x{s.type}, "
1843+
"year={s.year}, month={s.month}, day={s.day}, "
1844+
"hour={s.hour}, minute={s.minute}, second={s.second}, "
1845+
"nano={s.nano}, offset={s.offset}, zone={s.zone})"
1846+
).format(s=self)
1847+
1848+
def __extra_loading__(self, unmarshaller, ident=0):
1849+
# type: (JavaObjectUnmarshaller, int) -> None
1850+
"""
1851+
Loads the content of the map, written with a custom implementation
1852+
"""
1853+
# Convert back annotations to bytes
1854+
# latin-1 is used to ensure that bytes are kept as is
1855+
content = to_bytes(self.annotations[0], "latin1")
1856+
(self.type,), content = read(content, ">b")
1857+
1858+
try:
1859+
self.time_handlers[self.type](unmarshaller, content)
1860+
except KeyError as ex:
1861+
log_error("Unhandled kind of time: {}".format(ex))
1862+
1863+
def do_duration(self, unmarshaller, data):
1864+
(self.second, self.nano), data = read(data, ">qi")
1865+
return data
1866+
1867+
def do_instant(self, unmarshaller, data):
1868+
(self.second, self.nano), data = read(data, ">qi")
1869+
return data
1870+
1871+
def do_local_date(self, unmarshaller, data):
1872+
(self.year, self.month, self.day), data = read(data, '>ibb')
1873+
return data
1874+
1875+
def do_local_time(self, unmarshaller, data):
1876+
(hour,), data = read(data, '>b')
1877+
minute = 0
1878+
second = 0
1879+
nano = 0
1880+
1881+
if hour < 0:
1882+
hour = ~hour
1883+
else:
1884+
(minute,), data = read(data, '>b')
1885+
if minute < 0:
1886+
minute = ~minute
1887+
else:
1888+
(second,), data = read(data, '>b')
1889+
if second < 0:
1890+
second = ~second
1891+
else:
1892+
(nano,), data = read(data, '>i')
1893+
1894+
self.hour = hour
1895+
self.minute = minute
1896+
self.second = second
1897+
self.nano = nano
1898+
return data
1899+
1900+
def do_local_date_time(self, unmarshaller, data):
1901+
data = self.do_local_date(unmarshaller, data)
1902+
data = self.do_local_time(unmarshaller, data)
1903+
return data
1904+
1905+
def do_zoned_date_time(self, unmarshaller, data):
1906+
data = self.do_local_date_time(unmarshaller, data)
1907+
data = self.do_zone_offset(unmarshaller, data)
1908+
data = self.do_zone_region(unmarshaller, data)
1909+
return data
1910+
1911+
def do_zone_offset(self, unmarshaller, data):
1912+
(offset_byte,), data = read(data, ">b")
1913+
if offset_byte == 127:
1914+
(self.offset,), data = read(data, ">i")
1915+
else:
1916+
self.offset = offset_byte * 900
1917+
return data
1918+
1919+
def do_zone_region(self, unmarshaller, data):
1920+
self.zone, data = read_string(data)
1921+
return data
1922+
1923+
def do_offset_time(self, unmarshaller, data):
1924+
data = self.do_local_time(unmarshaller, data)
1925+
data = self.do_zone_offset(unmarshaller, data)
1926+
return data
1927+
1928+
def do_offset_date_time(self, unmarshaller, data):
1929+
data = self.do_local_date_time(unmarshaller, data)
1930+
data = self.do_zone_offset(unmarshaller, data)
1931+
return data
1932+
1933+
def do_year(self, unmarshaller, data):
1934+
(self.year,), data = read(data, ">i")
1935+
return data
1936+
1937+
def do_year_month(self, unmarshaller, data):
1938+
(self.year, self.month), data = read(data, ">ib")
1939+
return data
1940+
1941+
def do_month_day(self, unmarshaller, data):
1942+
(self.month, self.day), data = read(data, ">bb")
1943+
return data
1944+
1945+
def do_period(self, unmarshaller, data):
1946+
(self.year, self.month, self.day), data = read(data, ">iii")
1947+
return data
1948+
17621949
TYPE_MAPPER = {
17631950
"java.util.ArrayList": JavaList,
17641951
"java.util.LinkedList": JavaList,
@@ -1767,6 +1954,7 @@ def __extra_loading__(self, unmarshaller, ident=0):
17671954
"java.util.TreeMap": JavaMap,
17681955
"java.util.HashSet": JavaSet,
17691956
"java.util.TreeSet": JavaTreeSet,
1957+
"java.time.Ser": JavaTime,
17701958
}
17711959

17721960
def create(self, classdesc, unmarshaller=None):

tests/java/src/test/java/OneTest.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@
66
import java.io.ObjectInputStream;
77
import java.io.ObjectOutputStream;
88
import java.io.Serializable;
9+
import java.time.Duration;
10+
import java.time.Instant;
11+
import java.time.LocalDate;
12+
import java.time.LocalDateTime;
13+
import java.time.LocalTime;
14+
import java.time.ZoneId;
15+
import java.time.ZonedDateTime;
916
import java.util.HashSet;
1017
import java.util.Hashtable;
1118
import java.util.LinkedHashSet;
@@ -303,7 +310,21 @@ public void testTreeSet() throws Exception {
303310
set.add(42);
304311
oos.writeObject(set);
305312
oos.flush();
306-
}
313+
}
314+
315+
@Test
316+
public void testTime() throws Exception {
317+
oos.writeObject(new Object[] {
318+
Duration.ofSeconds(10),
319+
Instant.now(),
320+
LocalDate.now(),
321+
LocalTime.now(),
322+
LocalDateTime.now(),
323+
ZoneId.systemDefault(),
324+
ZonedDateTime.now(),
325+
});
326+
oos.flush();
327+
}
307328

308329
@Test
309330
public void testSwingObject() throws Exception {

tests/tests.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,22 @@ def test_sets(self):
327327
self.assertIsInstance(pobj, set)
328328
self.assertSetEqual({i.value for i in pobj}, {1, 2, 42})
329329

330+
def test_times(self):
331+
jobj = self.read_file("testTime.ser")
332+
pobj = javaobj.loads(jobj)
333+
_logger.debug(pobj)
334+
335+
# First one is a duration of 10s
336+
duration = pobj[0]
337+
self.assertEquals(duration.second, 10)
338+
339+
# Check types
340+
self.assertIsInstance(pobj, javaobj.core.JavaArray)
341+
for obj in pobj:
342+
self.assertIsInstance(
343+
obj, javaobj.DefaultObjectTransformer.JavaTime
344+
)
345+
330346
# def test_exception(self):
331347
# jobj = self.read_file("objException.ser")
332348
# pobj = javaobj.loads(jobj)

0 commit comments

Comments
 (0)