Skip to content

Commit 2a973a4

Browse files
committed
Synthetic fields with references to outer class use compiler dependent names (XTSR-769).
git-svn-id: http://svn.codehaus.org/xstream/trunk@2357 9830eeb5-ddf4-0310-9ef7-f4b9a3e3227e
1 parent bed7a30 commit 2a973a4

File tree

3 files changed

+154
-26
lines changed

3 files changed

+154
-26
lines changed

xstream-distribution/src/content/changes.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ <h1 id="upcoming-1.4.x">Upcoming 1.4.x maintenance release</h1>
5858
<h2>Minor changes</h2>
5959

6060
<ul>
61+
<li>JIRA:XSTR-769: Synthetic fields with references to outer class use compiler dependent names.</li>
6162
<li>UUID is an immutable type by default.</li>
6263
</ul>
6364

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,35 @@
11
/*
22
* Copyright (C) 2005 Joe Walnes.
3-
* Copyright (C) 2006, 2007, 2009, 2014 XStream Committers.
3+
* Copyright (C) 2006, 2007, 2009, 2014, 2015 XStream Committers.
44
* All rights reserved.
55
*
66
* The software in this package is published under the terms of the BSD
77
* style license a copy of which has been included with this distribution in
88
* the LICENSE.txt file.
9-
*
9+
*
1010
* Created on 31. January 2005 by Joe Walnes
1111
*/
1212
package com.thoughtworks.xstream.mapper;
1313

14+
import java.lang.reflect.Field;
15+
import java.util.Arrays;
16+
import java.util.Collections;
17+
import java.util.concurrent.ConcurrentHashMap;
18+
import java.util.concurrent.ConcurrentMap;
19+
20+
import com.thoughtworks.xstream.core.Caching;
21+
22+
1423
/**
1524
* Mapper that uses a more meaningful alias for the field in an inner class (this$0) that refers to the outer class.
16-
*
25+
*
1726
* @author Joe Walnes
1827
*/
19-
public class OuterClassMapper extends MapperWrapper {
28+
public class OuterClassMapper extends MapperWrapper implements Caching {
2029

30+
private static final String[] EMPTY_NAMES = new String[0];
2131
private final String alias;
32+
private final ConcurrentMap<String, String[]> innerFields;
2233

2334
public OuterClassMapper(final Mapper wrapped) {
2435
this(wrapped, "outer-class");
@@ -27,23 +38,60 @@ public OuterClassMapper(final Mapper wrapped) {
2738
public OuterClassMapper(final Mapper wrapped, final String alias) {
2839
super(wrapped);
2940
this.alias = alias;
41+
innerFields = new ConcurrentHashMap<String, String[]>();
42+
innerFields.put(Object.class.getName(), EMPTY_NAMES);
3043
}
3144

3245
@Override
3346
public String serializedMember(final Class<?> type, final String memberName) {
34-
if (memberName.equals("this$0")) {
35-
return alias;
36-
} else {
37-
return super.serializedMember(type, memberName);
47+
if (memberName.startsWith("this$")) {
48+
final String[] innerFieldNames = getInnerFieldNames(type);
49+
for (int i = 0; i < innerFieldNames.length; ++i) {
50+
if (innerFieldNames[i].equals(memberName)) {
51+
return i == 0 ? alias : alias + '-' + i;
52+
}
53+
}
3854
}
55+
return super.serializedMember(type, memberName);
3956
}
4057

4158
@Override
4259
public String realMember(final Class<?> type, final String serialized) {
43-
if (serialized.equals(alias)) {
44-
return "this$0";
45-
} else {
46-
return super.realMember(type, serialized);
60+
if (serialized.startsWith(alias)) {
61+
int idx = -1;
62+
final int len = alias.length();
63+
if (len == serialized.length()) {
64+
idx = 0;
65+
} else if (serialized.length() > len + 1 && serialized.charAt(len) == '-') {
66+
idx = Integer.valueOf(serialized.substring(len + 1));
67+
}
68+
if (idx >= 0) {
69+
final String[] innerFieldNames = getInnerFieldNames(type);
70+
if (idx < innerFieldNames.length) {
71+
return innerFieldNames[idx];
72+
}
73+
}
4774
}
75+
return super.realMember(type, serialized);
76+
}
77+
78+
private String[] getInnerFieldNames(final Class<?> type) {
79+
String[] innerFieldNames = innerFields.get(type.getName());
80+
if (innerFieldNames == null) {
81+
innerFieldNames = getInnerFieldNames(type.getSuperclass());
82+
for (final Field field : type.getDeclaredFields()) {
83+
if (field.getName().startsWith("this$")) {
84+
innerFieldNames = Arrays.copyOf(innerFieldNames, innerFieldNames.length + 1);
85+
innerFieldNames[innerFieldNames.length - 1] = field.getName();
86+
}
87+
}
88+
innerFields.putIfAbsent(type.getName(), innerFieldNames);
89+
}
90+
return innerFieldNames;
91+
}
92+
93+
@Override
94+
public void flushCache() {
95+
innerFields.keySet().retainAll(Collections.singletonList(Object.class.getName()));
4896
}
4997
}

xstream/src/test/com/thoughtworks/acceptance/InnerClassesTest.java

Lines changed: 93 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
/*
22
* Copyright (C) 2005, 2006 Joe Walnes.
3-
* Copyright (C) 2006, 2007, 2014 XStream Committers.
3+
* Copyright (C) 2006, 2007, 2014, 2015 XStream Committers.
44
* All rights reserved.
55
*
66
* The software in this package is published under the terms of the BSD
77
* style license a copy of which has been included with this distribution in
88
* the LICENSE.txt file.
9-
*
9+
*
1010
* Created on 31. January 2005 by Joe Walnes
1111
*/
1212
package com.thoughtworks.acceptance;
@@ -16,14 +16,14 @@ public class InnerClassesTest extends AbstractAcceptanceTest {
1616
public void testSerializedInnerClassMaintainsReferenceToOuterClass() {
1717
xstream.allowTypes(Outer.class, Outer.Inner.class);
1818

19-
Outer outer = new Outer("THE-OUTER-NAME", "THE-INNER-NAME");
20-
Outer.Inner inner = outer.getInner();
19+
final Outer outer = new Outer("THE-OUTER-NAME", "THE-INNER-NAME");
20+
final Outer.Inner inner = outer.getInner();
2121

2222
assertEquals("Hello from THE-INNER-NAME (inside THE-OUTER-NAME)", inner.getMessage());
2323

24-
String xml = xstream.toXML(inner);
24+
final String xml = xstream.toXML(inner);
2525

26-
String expectedXml = ""
26+
final String expectedXml = ""
2727
+ "<com.thoughtworks.acceptance.Outer_-Inner>\n"
2828
+ " <innerName>THE-INNER-NAME</innerName>\n"
2929
+ " <outer-class>\n"
@@ -33,18 +33,99 @@ public void testSerializedInnerClassMaintainsReferenceToOuterClass() {
3333
+ "</com.thoughtworks.acceptance.Outer_-Inner>";
3434
assertEquals(expectedXml, xml);
3535

36-
Outer.Inner newInner = (Outer.Inner) xstream.fromXML(xml);
36+
final Outer.Inner newInner = (Outer.Inner)xstream.fromXML(xml);
3737

3838
assertEquals("Hello from THE-INNER-NAME (inside THE-OUTER-NAME)", newInner.getMessage());
3939
}
40+
41+
public static class OuterType {
42+
private final String outerName = "Outer Name";
43+
public InnerType inner = new InnerType();
44+
private final InnerType.Dynamic1 dyn1 = inner.new Dynamic1();
45+
private final InnerType.Dynamic1.Dynamic2 dyn2 = dyn1.new Dynamic2();
46+
private final InnerType.Dynamic3 dyn3 = inner.new Dynamic3(dyn1);
47+
48+
public class InnerType {
49+
private final String innerName = "Inner Name";
50+
51+
public class Dynamic1 {
52+
private final String name1 = "Name 1";
53+
54+
public class Dynamic2 {
55+
private final String name2 = "Name 2";
56+
}
57+
}
58+
59+
public class Dynamic3 extends Dynamic1.Dynamic2 {
60+
private final String name3 = "Name 3";
61+
private final Dynamic1.Dynamic2 dyn4;
62+
63+
public Dynamic3(final Dynamic1 outer) {
64+
outer.super();
65+
class Dynamic4 extends Dynamic1.Dynamic2 {
66+
private final String name4 = "Name 4";
67+
private final Dynamic5 dyn5 = new Dynamic5();
68+
class Dynamic5 {
69+
private final String name5 = "Name 5";
70+
}
71+
Dynamic4(Dynamic1 outer) {
72+
outer.super();
73+
}
74+
}
75+
dyn4 = new Dynamic4(outer);
76+
}
77+
}
78+
}
79+
}
80+
81+
public void testNestedDynamicTypes() {
82+
xstream.alias("inner", OuterType.InnerType.class);
83+
84+
final OuterType outer = new OuterType();
85+
86+
final String expectedXml = ""
87+
+ "<inner>\n"
88+
+ " <innerName>Inner Name</innerName>\n"
89+
+ " <outer-class>\n"
90+
+ " <outerName>Outer Name</outerName>\n"
91+
+ " <inner reference=\"../..\"/>\n"
92+
+ " <dyn1>\n"
93+
+ " <name1>Name 1</name1>\n"
94+
+ " <outer-class reference=\"../../..\"/>\n"
95+
+ " </dyn1>\n"
96+
+ " <dyn2>\n"
97+
+ " <name2>Name 2</name2>\n"
98+
+ " <outer-class reference=\"../../dyn1\"/>\n"
99+
+ " </dyn2>\n"
100+
+ " <dyn3>\n"
101+
+ " <name2>Name 2</name2>\n"
102+
+ " <outer-class reference=\"../../dyn1\"/>\n"
103+
+ " <name3>Name 3</name3>\n"
104+
+ " <dyn4 class=\"com.thoughtworks.acceptance.InnerClassesTest$OuterType$InnerType$Dynamic3$1Dynamic4\">\n"
105+
+ " <name2>Name 2</name2>\n"
106+
+ " <outer-class defined-in=\"com.thoughtworks.acceptance.InnerClassesTest$OuterType$InnerType$Dynamic1$Dynamic2\" reference=\"../../../dyn1\"/>\n"
107+
+ " <name4>Name 4</name4>\n"
108+
+ " <dyn5>\n"
109+
+ " <name5>Name 5</name5>\n"
110+
+ " <outer-class reference=\"../..\"/>\n"
111+
+ " </dyn5>\n"
112+
+ " <outer-class reference=\"../..\"/>\n"
113+
+ " </dyn4>\n"
114+
+ " <outer-class-1 reference=\"../../..\"/>\n"
115+
+ " </dyn3>\n"
116+
+ " </outer-class>\n"
117+
+ "</inner>";
118+
119+
assertBothWays(outer.inner, expectedXml);
120+
}
40121
}
41122

42123
class Outer {
43124

44-
private Inner inner;
45-
private String outerName;
125+
private final Inner inner;
126+
private final String outerName;
46127

47-
public Outer(String outerName, String innerName) {
128+
public Outer(final String outerName, final String innerName) {
48129
inner = new Inner(innerName);
49130
this.outerName = outerName;
50131
}
@@ -54,9 +135,9 @@ public Inner getInner() {
54135
}
55136

56137
public class Inner {
57-
private String innerName;
138+
private final String innerName;
58139

59-
public Inner(String innerName) {
140+
public Inner(final String innerName) {
60141
this.innerName = innerName;
61142
}
62143

@@ -65,5 +146,3 @@ public String getMessage() {
65146
}
66147
}
67148
}
68-
69-

0 commit comments

Comments
 (0)