Skip to content

Commit d4919ff

Browse files
authored
Merge pull request #169 from sabomichal/fix/issue-61-uppercase-collection-properties
fix: support for fields whose getter names don't match the standard c…
2 parents 9268380 + ddf6cca commit d4919ff

File tree

9 files changed

+146
-14
lines changed

9 files changed

+146
-14
lines changed

src/main/java/com/github/sabomichal/immutablexjc/PluginImpl.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -865,6 +865,39 @@ private JMethod getGetterProperty(final JFieldVar field, final JDefinedClass cla
865865
JFormatter f = new JFormatter(sw);
866866
annotationValue.generate(f);
867867
getter = clazz.getMethod("get" + sw.toString().replaceAll("\"", ""), NO_ARGS);
868+
if (getter == null) {
869+
getter = clazz.getMethod("is" + sw.toString().replaceAll("\"", ""), NO_ARGS);
870+
}
871+
}
872+
}
873+
}
874+
// Also check @XmlAttribute name (issue #61)
875+
if (getter == null) {
876+
Optional<JAnnotationUse> xmlAttributeAnnotation = getAnnotation(field.annotations(), XmlAttribute.class.getCanonicalName());
877+
if (xmlAttributeAnnotation.isPresent()) {
878+
JAnnotationValue annotationValue = xmlAttributeAnnotation.get().getAnnotationMembers().get("name");
879+
if (annotationValue != null) {
880+
StringWriter sw = new StringWriter();
881+
JFormatter f = new JFormatter(sw);
882+
annotationValue.generate(f);
883+
getter = clazz.getMethod("get" + sw.toString().replaceAll("\"", ""), NO_ARGS);
884+
if (getter == null) {
885+
getter = clazz.getMethod("is" + sw.toString().replaceAll("\"", ""), NO_ARGS);
886+
}
887+
}
888+
}
889+
}
890+
// Case-insensitive fallback for uppercase property names (issue #61)
891+
if (getter == null) {
892+
String getterNameLower = ("get" + field.name()).toLowerCase();
893+
String isGetterNameLower = ("is" + field.name()).toLowerCase();
894+
for (JMethod method : clazz.methods()) {
895+
if (method.params().isEmpty()) {
896+
String methodNameLower = method.name().toLowerCase();
897+
if (methodNameLower.equals(getterNameLower) || methodNameLower.equals(isGetterNameLower)) {
898+
getter = method;
899+
break;
900+
}
868901
}
869902
}
870903
}

src/test/java/com/github/sabomichal/immutablexjc/test/TestAllFlags.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,4 +204,14 @@ public void testWithIfNotNullOnWrapperType() {
204204
public void testCopyConstructorNullThrowsNPE() {
205205
assertThrows(NullPointerException.class, () -> Declaration.declarationBuilder(null));
206206
}
207+
208+
@Test
209+
public void testUppercaseCollectionMutable() {
210+
// -Ximm-skipcollections: URI collection should be mutable
211+
Declaration d = Declaration.declarationBuilder()
212+
.withType("t").withName("n").build();
213+
d.getURI().add("http://example.com");
214+
assertEquals(1, d.getURI().size());
215+
assertEquals("http://example.com", d.getURI().get(0));
216+
}
207217
}

src/test/java/com/github/sabomichal/immutablexjc/test/TestBasic.java

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ public void testUnmarshal() throws Exception {
4646
assertEquals("a variable", decl.getComment());
4747
assertEquals(2, decl.getBy().size());
4848
assertEquals("test documentation", decl.getDocumentation());
49+
// uppercase property fields
50+
assertEquals(2, decl.getURI().size());
51+
assertEquals("http://example.com/1", decl.getURI().get(0));
52+
assertEquals("cid-123", decl.getCID());
4953
// tasks
5054
assertNotNull(model.getTasks());
5155
// metadata
@@ -62,8 +66,8 @@ public void testMarshal() throws Exception {
6266
byList.add(new NameExpression("b"));
6367

6468
Declaration decl = new Declaration(
65-
Collections.emptyList(), "x", null, new HashMap<>(),
66-
byList, null, "doc", "Double");
69+
Collections.emptyList(), "x", null, null, new HashMap<>(),
70+
byList, Collections.emptyList(), null, "doc", "Double");
6771

6872
Parameters params = new Parameters(Collections.singletonList(decl));
6973
Model model = new Model(params, null, null, null, StatusType.ACTIVE);
@@ -123,36 +127,38 @@ public void testProtectedNoArgConstructor() throws Exception {
123127
@Test
124128
public void testPublicAllArgsConstructor() throws Exception {
125129
Constructor<?> ctor = Declaration.class.getConstructor(
126-
List.class, String.class, String.class, Map.class,
127-
List.class, String.class, String.class, String.class);
130+
List.class, String.class, String.class, String.class, Map.class,
131+
List.class, List.class, String.class, String.class, String.class);
128132
assertTrue(Modifier.isPublic(ctor.getModifiers()));
129133
}
130134

131135
@Test
132136
public void testListCollectionIsUnmodifiable() {
133137
List<NameExpression> byList = new ArrayList<>();
134138
byList.add(new NameExpression("a"));
135-
Variable v = new Variable(Collections.emptyList(), "n", null, new HashMap<>(), byList, null);
139+
Variable v = new Variable(Collections.emptyList(), "n", null, null, new HashMap<>(), byList, Collections.emptyList(), null);
136140
assertThrows(UnsupportedOperationException.class, () -> v.getBy().add(new NameExpression("z")));
137141
}
138142

139143
@Test
140144
public void testMapCollectionIsUnmodifiable() {
141145
Map<QName, String> attrs = new HashMap<>();
142146
attrs.put(new QName("test"), "value");
143-
Variable v = new Variable(Collections.emptyList(), "n", null, attrs, Collections.emptyList(), null);
147+
Variable v = new Variable(Collections.emptyList(), "n", null, null, attrs, Collections.emptyList(), Collections.emptyList(), null);
144148
assertThrows(UnsupportedOperationException.class, () -> v.getOtherAttributes().put(new QName("x"), "y"));
145149
}
146150

147151
@Test
148152
public void testEmptyCollectionReturnedForNull() {
149-
Variable v = new Variable(null, "n", null, null, null, null);
153+
Variable v = new Variable(null, "n", null, null, null, null, null, null);
150154
assertNotNull(v.getBy());
151155
assertTrue(v.getBy().isEmpty());
152156
assertNotNull(v.getOtherAttributes());
153157
assertTrue(v.getOtherAttributes().isEmpty());
154158
assertNotNull(v.getTags());
155159
assertTrue(v.getTags().isEmpty());
160+
assertNotNull(v.getURI());
161+
assertTrue(v.getURI().isEmpty());
156162
}
157163

158164
@Test
@@ -193,7 +199,7 @@ public void testEnumTypeGenerated() {
193199
public void testDefensiveCopyForList() {
194200
List<NameExpression> original = new ArrayList<>();
195201
original.add(new NameExpression("a"));
196-
Variable v = new Variable(Collections.emptyList(), "n", null, new HashMap<>(), original, null);
202+
Variable v = new Variable(Collections.emptyList(), "n", null, null, new HashMap<>(), original, Collections.emptyList(), null);
197203
original.add(new NameExpression("z")); // modify original after construction
198204
assertEquals(1, v.getBy().size()); // object unaffected
199205
}
@@ -202,8 +208,22 @@ public void testDefensiveCopyForList() {
202208
public void testDefensiveCopyForMap() {
203209
Map<QName, String> original = new HashMap<>();
204210
original.put(new QName("test"), "value");
205-
Variable v = new Variable(Collections.emptyList(), "n", null, original, Collections.emptyList(), null);
211+
Variable v = new Variable(Collections.emptyList(), "n", null, null, original, Collections.emptyList(), Collections.emptyList(), null);
206212
original.put(new QName("extra"), "extra"); // modify original after construction
207213
assertEquals(1, v.getOtherAttributes().size()); // object unaffected
208214
}
215+
216+
@Test
217+
public void testUppercaseCollectionFieldIsUnmodifiable() {
218+
List<String> uris = new ArrayList<>();
219+
uris.add("http://example.com");
220+
Variable v = new Variable(Collections.emptyList(), "n", null, null, new HashMap<>(), Collections.emptyList(), uris, null);
221+
assertThrows(UnsupportedOperationException.class, () -> v.getURI().add("http://other.com"));
222+
}
223+
224+
@Test
225+
public void testUppercaseAttributeFieldWorks() {
226+
Variable v = new Variable(Collections.emptyList(), "n", null, "cid-abc", new HashMap<>(), Collections.emptyList(), Collections.emptyList(), null);
227+
assertEquals("cid-abc", v.getCID());
228+
}
209229
}

src/test/java/com/github/sabomichal/immutablexjc/test/TestBuilder.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,4 +232,31 @@ public void testNoCopyConstructorWithoutCcFlag() {
232232
assertThrows(NoSuchMethodException.class, () ->
233233
Declaration.class.getMethod("declarationBuilder", Declaration.class));
234234
}
235+
236+
@Test
237+
public void testBuilderWithUppercaseCollection() {
238+
List<String> uris = new ArrayList<>();
239+
uris.add("http://example.com/1");
240+
uris.add("http://example.com/2");
241+
242+
Declaration d = Declaration.declarationBuilder()
243+
.withType("t")
244+
.withName("n")
245+
.addUri("http://example.com/a")
246+
.withUri(uris)
247+
.build();
248+
assertEquals(2, d.getURI().size());
249+
assertEquals("http://example.com/1", d.getURI().get(0));
250+
assertThrows(UnsupportedOperationException.class, () -> d.getURI().add("x"));
251+
}
252+
253+
@Test
254+
public void testBuilderWithUppercaseAttribute() {
255+
Declaration d = Declaration.declarationBuilder()
256+
.withType("t")
257+
.withName("n")
258+
.withCid("cid-xyz")
259+
.build();
260+
assertEquals("cid-xyz", d.getCID());
261+
}
235262
}

src/test/java/com/github/sabomichal/immutablexjc/test/TestInheritBuilder.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,24 @@ public void testCopyConstructor() {
124124
assertEquals(d1.getTags().size(), d2.getTags().size());
125125
}
126126

127+
@Test
128+
public void testCopyConstructorWithUppercaseFields() {
129+
Declaration d1 = Declaration.builder()
130+
.withType("t")
131+
.withName("n")
132+
.withCid("cid-123")
133+
.addUri("http://example.com/1")
134+
.addUri("http://example.com/2")
135+
.build();
136+
137+
Declaration d2 = Declaration.builder(d1).build();
138+
assertNotNull(d2);
139+
assertEquals("cid-123", d2.getCID());
140+
assertEquals(2, d2.getURI().size());
141+
assertEquals("http://example.com/1", d2.getURI().get(0));
142+
assertEquals("http://example.com/2", d2.getURI().get(1));
143+
}
144+
127145
@Test
128146
public void testCopyConstructorWithAbstractSuperclass() {
129147
// TidyBedroom extends abstract Task — copy must preserve Task's cost

src/test/java/com/github/sabomichal/immutablexjc/test/TestNoFinalClasses.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ public static class MyDeclaration extends Declaration {
4141
private final String myAdditionalElement;
4242

4343
public MyDeclaration(String myAdditionalElement) {
44-
super(new ArrayList<>(), "name", null, new HashMap<>(),
45-
new ArrayList<>(), null, "doc", "type");
44+
super(new ArrayList<>(), "name", null, null, new HashMap<>(),
45+
new ArrayList<>(), new ArrayList<>(), null, "doc", "type");
4646
this.myAdditionalElement = myAdditionalElement;
4747
}
4848

@@ -112,9 +112,9 @@ public void testMarshalWithSubclass() throws Exception {
112112
@Test
113113
public void testCollectionsStillUnmodifiable() {
114114
Declaration d = new Declaration(
115-
Collections.emptyList(), "n", null, new HashMap<>(),
115+
Collections.emptyList(), "n", null, null, new HashMap<>(),
116116
Collections.singletonList(new NameExpression("a")),
117-
null, "doc", "t");
117+
Collections.emptyList(), null, "doc", "t");
118118
assertThrows(UnsupportedOperationException.class, () ->
119119
d.getBy().add(new NameExpression("z")));
120120
}

src/test/java/com/github/sabomichal/immutablexjc/test/TestOptionalGetter.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,4 +241,24 @@ public void testDecimalExtensionTypeCopyConstructor() {
241241
public void testCopyConstructorNullThrowsNPE() {
242242
assertThrows(NullPointerException.class, () -> Declaration.declarationBuilder(null));
243243
}
244+
245+
@Test
246+
public void testUppercaseAttributeOptionalGetter() throws Exception {
247+
// CID is optional attribute → Optional<String>
248+
Method getCID = BaseEntity.class.getMethod("getCID");
249+
assertEquals(Optional.class, getCID.getReturnType());
250+
251+
Variable v = Variable.variableBuilder().withName("n").build();
252+
assertTrue(v.getCID().isEmpty());
253+
254+
Variable v2 = Variable.variableBuilder().withName("n").withCid("cid-123").build();
255+
assertEquals("cid-123", v2.getCID().orElse(null));
256+
}
257+
258+
@Test
259+
public void testUppercaseCollectionNotWrappedInOptional() throws Exception {
260+
// URI collection getter returns List (not Optional<List>)
261+
Method getURI = Variable.class.getMethod("getURI");
262+
assertEquals(java.util.List.class, getURI.getReturnType());
263+
}
244264
}

src/test/resources/model.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
<model xmlns="http://immutablexjc.sabomichal.github.com/test" status="ACTIVE">
44
<parameters>
5-
<parameter type="Double" name="x" comment="a variable">
5+
<parameter type="Double" name="x" comment="a variable" CID="cid-123">
66
<tags>math</tags>
77
<tags>numeric</tags>
88
<by name="a"/>
99
<by name="b"/>
10+
<URI>http://example.com/1</URI>
11+
<URI>http://example.com/2</URI>
1012
<documentation>test documentation</documentation>
1113
</parameter>
1214
</parameters>

src/test/xsd/unified.xsd

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
</xs:sequence>
4545
<xs:attribute name="name" type="xs:string" use="required"/>
4646
<xs:attribute name="description" type="xs:string" use="optional"/>
47+
<xs:attribute name="CID" type="xs:string" use="optional"/>
4748
<xs:anyAttribute/>
4849
</xs:complexType>
4950

@@ -53,6 +54,7 @@
5354
<xs:extension base="BaseEntity">
5455
<xs:sequence>
5556
<xs:element name="by" type="NameExpression" maxOccurs="unbounded" minOccurs="0"/>
57+
<xs:element name="URI" type="xs:string" maxOccurs="unbounded" minOccurs="0"/>
5658
</xs:sequence>
5759
<xs:attribute name="comment" type="xs:string" use="optional"/>
5860
</xs:extension>

0 commit comments

Comments
 (0)