views:

10132

answers:

2

Hello!

I have several complex data structures like

Map< A, Set< B > >
Set< Map< A, B > >
Set< Map< A, Set< B > > >
Map< A, Map< B, Set< C > > >
and so on (more complex data structures)

Note: In my case it doesn't really matter if I use Set or List.

Now I know that JAXB let me define XmlAdapter's, that's fine, but I don't want to define an XmlAdapter for every of the given data structures (it would be just too much copy-and-paste code).

I tried to achieve my goal by declaring two generalizing XmlAdapters:

  • one for Map: MapAdapter<K,V>
  • one for Set: SetAdapter<V>

The problem:
JAXB complains as following:

javax.xml.bind.JAXBException:
class java.util.Collections$UnmodifiableMap nor any of its
  super class is known to this context.

Here is my adapter class:

import java.util.*;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.*;

public class Adapters {

public final static class MapAdapter<K, V>
  extends XmlAdapter<MapAdapter.Adapter<K, V>, Map<K, V>> {

 @XmlType
 @XmlRootElement
 public final static class Adapter<K, V> {

  @XmlElement
  protected List<MyEntry<K, V>> key = new LinkedList<MyEntry<K, V>>();

  private Adapter() {
  }

  public Adapter(Map<K, V> original) {
   for (Map.Entry<K, V> entry : original.entrySet()) {
    key.add(new MyEntry<K, V>(entry));
   }
  }

 }

 @XmlType
 @XmlRootElement
 public final static class MyEntry<K, V> {

  @XmlElement
  protected K key;

  @XmlElement
  protected V value;

  private MyEntry() {
  }

  public MyEntry(Map.Entry<K, V> original) {
   key = original.getKey();
   value = original.getValue();
  }

 }

 @Override
 public Adapter<K, V> marshal(Map<K, V> obj) {
  return new Adapter<K, V>(obj);
 }

 @Override
 public Map<K, V> unmarshal(Adapter<K, V> obj) {
  throw new UnsupportedOperationException("unmarshalling is never performed");
 }

}

}

Here is my JUnit test case:

import java.io.*;
import java.util.*;
import javax.xml.bind.*;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.*;
import org.junit.*;
import static java.lang.System.*;

public class SomeTest {

@Test
public void _map2()
  throws Exception {

 Map<String, Map<String, String>> dataStructure =
   new HashMap<String, Map<String, String>>();

 Map<String, String> inner1 = new HashMap<String, String>();
 Map<String, String> inner2 = new HashMap<String, String>();

 dataStructure.put("a", inner1);
 dataStructure.put("b", inner1);

 inner1.put("a1", "1");
 inner1.put("a2", "2");
 inner2.put("b1", "1");
 inner2.put("b2", "2");

 JAXBContext context = JAXBContext.newInstance(Adapters.XMap.class,
   Adapters.XCount.class, Adapters.XEntry.class);

 Marshaller marshaller = context.createMarshaller();
 marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
 marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

 marshaller.setAdapter(new Adapters.MapAdapter());

 StringWriter sw = new StringWriter();

 marshaller.marshal(dataStructure, sw);
 out.println(sw.toString());
}

}
A: 
Jason S
Thank you for your answer! No, I'm not wrapping my maps anywhere, that's the strange thing. Might happen somewhere internally in JAXB.
ivan_ivanovich_ivanoff
have you tried debugging, e.g. putting breakpoints on your marshaling/unmarshaling code?
Jason S
I ended up writing own "wrappers", without XmlAdapter, see my answer.
ivan_ivanovich_ivanoff
+3  A: 

I've solved the problem without XmlAdapter's.

I've written JAXB-annotated objects for Map, Map.Entry and Collection.
The main idea is inside the method xmlizeNestedStructure(...):

Take a look at the code:

public final class Adapters {

private Adapters() {
}

public static Class<?>[] getXmlClasses() {
 return new Class<?>[]{
    XMap.class, XEntry.class, XCollection.class, XCount.class
   };
}

public static Object xmlizeNestedStructure(Object input) {
 if (input instanceof Map<?, ?>) {
  return xmlizeNestedMap((Map<?, ?>) input);
 }
 if (input instanceof Collection<?>) {
  return xmlizeNestedCollection((Collection<?>) input);
 }

 return input; // non-special object, return as is
}

public static XMap<?, ?> xmlizeNestedMap(Map<?, ?> input) {
 XMap<Object, Object> ret = new XMap<Object, Object>();

 for (Map.Entry<?, ?> e : input.entrySet()) {
  ret.add(xmlizeNestedStructure(e.getKey()),
    xmlizeNestedStructure(e.getValue()));
 }

 return ret;
}

public static XCollection<?> xmlizeNestedCollection(Collection<?> input) {
 XCollection<Object> ret = new XCollection<Object>();

 for (Object entry : input) {
  ret.add(xmlizeNestedStructure(entry));
 }

 return ret;
}

@XmlType
@XmlRootElement
public final static class XMap<K, V> {

 @XmlElementWrapper(name = "map")
 @XmlElement(name = "entry")
 private List<XEntry<K, V>> list = new LinkedList<XEntry<K, V>>();

 public XMap() {
 }

 public void add(K key, V value) {
  list.add(new XEntry<K, V>(key, value));
 }

}

@XmlType
@XmlRootElement
public final static class XEntry<K, V> {

 @XmlElement
 private K key;

 @XmlElement
 private V value;

 private XEntry() {
 }

 public XEntry(K key, V value) {
  this.key = key;
  this.value = value;
 }

}

@XmlType
@XmlRootElement
public final static class XCollection<V> {

 @XmlElementWrapper(name = "list")
 @XmlElement(name = "entry")
 private List<V> list = new LinkedList<V>();

 public XCollection() {
 }

 public void add(V obj) {
  list.add(obj);
 }

}

}

It works!

Let's look at a demo output:

<xMap>
 <map>
  <entry>
   <key xsi:type="xCount" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"&gt;
    <count>1</count>
    <content xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema"&gt;a&lt;/content&gt;
   </key>
   <value xsi:type="xCollection" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"&gt;
    <list>
     <entry xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema"&gt;a1&lt;/entry&gt;
     <entry xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema"&gt;a2&lt;/entry&gt;
     <entry xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema"&gt;a3&lt;/entry&gt;
    </list>
   </value>
  </entry>
  <entry>
   <key xsi:type="xCount" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"&gt;
    <count>2</count>
    <content xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema"&gt;b&lt;/content&gt;
   </key>
   <value xsi:type="xCollection" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"&gt;
    <list>
     <entry xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema"&gt;b1&lt;/entry&gt;
     <entry xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema"&gt;b3&lt;/entry&gt;
     <entry xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema"&gt;b2&lt;/entry&gt;
    </list>
   </value>
  </entry>
  <entry>
   <key xsi:type="xCount" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"&gt;
    <count>3</count>
    <content xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema"&gt;c&lt;/content&gt;
   </key>
   <value xsi:type="xCollection" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"&gt;
    <list>
     <entry xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema"&gt;c1&lt;/entry&gt;
     <entry xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema"&gt;c2&lt;/entry&gt;
     <entry xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema"&gt;c3&lt;/entry&gt;
    </list>
   </value>
  </entry>
 </map>
</xMap>

Sorry, the demo output uses also a data structure called "count" which is not mentioned in the Adapter's source code.

BTW: does anyone know how to remove all these annoying and (in my case) unnecessary xsi:type attributes?

ivan_ivanovich_ivanoff
Cool! I may have to do this myself sometime -- could you also post a summary of what you had to do in your .xjb bindings file?
Jason S
I've never used the .xjb bindings file ;)The proposed solution just works ;)
ivan_ivanovich_ivanoff