Infinite loop during XML file serialization (java.beans)

36 Views Asked by At

In the process of serializing to XML file, I'm encountering an infinite loop issue. I'm using java.beans, and through XMLEncoder, I'm attempting to serialize three classes: Category, Contact, and Event. Unfortunately, I cannot create an XML file due to the fact that the Contact class contains a field List events, and similarly, the Event class has a field List contacts, creating a mutual reference between them. The problem is that XMLEncoder, while trying to serialize everything, falls into the reference loop between the Contact and Event classes, resulting in an error as it may continue indefinitely.

Serializer:

public class XMLSerializer
{
    public void encode(List<Category> categories, List<Event> events, List<Contact> contacts)
    {
        try (XMLEncoder xmlEncoder = new XMLEncoder(new BufferedOutputStream(new FileOutputStream("data/xml_files/data.xml"))))
        {
            xmlEncoder.setPersistenceDelegate(LocalDateTime.class, new LocalDateTimePersistenceDelegate());
            xmlEncoder.setPersistenceDelegate(LocalTime.class, new LocalTimePersistenceDelegate());

            List<Object> mergedList = new ArrayList<>();
            mergedList.addAll(categories);
            mergedList.addAll(events);
            mergedList.addAll(contacts);

            xmlEncoder.writeObject(mergedList);
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }

    public class LocalDateTimePersistenceDelegate extends DefaultPersistenceDelegate
    {
        @Override
        protected Expression instantiate(Object oldInstance, Encoder out)
        {
            LocalDateTime ldt = (LocalDateTime) oldInstance;
            return new Expression(ldt, oldInstance.getClass(), "parse", new Object[] { ldt.toString() });
        }
    }

    public class LocalTimePersistenceDelegate extends DefaultPersistenceDelegate
    {
        @Override
        protected Expression instantiate(Object oldInstance, Encoder out)
        {
            LocalTime lt = (LocalTime) oldInstance;
            return new Expression(lt, oldInstance.getClass(), "parse", new Object[] { lt.toString() });
        }
    }
}

Models:

public class Category implements Comparable<Category>
{
    private int id;
    private String name;
    private String colorHex;

    public Category()
    {

    }

    @Override
    public String toString()
    {
        return name;
    }

    public int getId()
    {
        return id;
    }

    public void setId(int id)
    {
        this.id = id;
    }

    public String getName()
    {
        return name;
    }

    public void setName(String name)
    {
        this.name = name;
    }

    public String getColorHex()
    {
        return colorHex;
    }

    public void setColorHex(String colorHex)
    {
        this.colorHex = colorHex;
    }

    @Override
    public int compareTo(Category o)
    {
        return this.name.compareTo(o.getName());
    }
}
public class Event implements Comparable<Event>
{
    private int id;
    private String name;
    private LocalDateTime date;
    private LocalTime notifyOffset;
    private String location;
    private String description;
    private Category category;
    private List<Contact> contacts = new ArrayList<Contact>();

    public Event()
    {

    }

    @Override
    public String toString()
    {
        String locationStr = location.isEmpty() ? "No location" : location;
        String descriptionStr = description.isEmpty() ? "No description" : description;
        String categoryName = category != null ? category.getName() : "No category";

        return String.format("%s | %s | %s%n%s%n%s", name, getFormattedDate(), locationStr, descriptionStr, categoryName);
    }

    public int getId()
    {
        return id;
    }

    public void setId(int id)
    {
        this.id = id;
    }

    public String getName()
    {
        return name;
    }

    public void setName(String name)
    {
        this.name = name;
    }

    public LocalDateTime getDate()
    {
        return date;
    }

    public void setDate(LocalDateTime date)
    {
        this.date = date;
    }

    public LocalTime getNotifyOffset()
    {
        return notifyOffset;
    }

    public void setNotifyOffset(LocalTime notifyOffset)
    {
        this.notifyOffset = notifyOffset;
    }

    public String getLocation()
    {
        return location;
    }

    public void setLocation(String location)
    {
        this.location = location;
    }

    public String getDescription()
    {
        return description;
    }

    public void setDescription(String description)
    {
        this.description = description;
    }

    public Category getCategory()
    {
        return category;
    }

    public void setCategory(Category category)
    {
        this.category = category;
    }

    public List<Contact> getContacts()
    {
        return Collections.unmodifiableList(contacts);
    }

    public void setContacts(List<Contact> contacts)
    {
        this.contacts = contacts;

        for (Contact contact : contacts)
        {
            contact.addEvent(this);
        }
    }

    public void addContact(Contact contact)
    {
        if (!this.contacts.contains(contact))
        {
            this.contacts.add(contact);
            contact.addEvent(this);
        }
    }

    public void removeContact(Contact contact)
    {
        if (this.contacts.contains(contact))
        {
            this.contacts.remove(contact);
            contact.removeEvent(this);
        }
    }

    public String getFormattedDate()
    {
        return this.date.format(DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm"));
    }

    public String getFormattedDateWithOffset()
    {
        LocalDateTime offsetDateTime = this.date.minusHours(notifyOffset.getHour()).minusMinutes(notifyOffset.getMinute());
        return offsetDateTime.format(DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm"));
    }

    public LocalDateTime getDateWithOffset()
    {
        LocalDateTime offsetDateTime = this.date.minusHours(notifyOffset.getHour()).minusMinutes(notifyOffset.getMinute());
        return offsetDateTime;
    }

    @Override
    public int compareTo(Event o)
    {
        return this.date.compareTo(o.getDate());
    }
}
public class Contact implements Comparable<Contact>
{
    private int id;
    private String firstName;
    private String lastName;
    private String phoneNumber;
    private List<Event> events = new ArrayList<Event>();

    public Contact()
    {

    }

    @Override
    public String toString()
    {
        return String.format("%s %s | %s", firstName, lastName, phoneNumber);
    }

    public int getId()
    {
        return id;
    }

    public void setId(int id)
    {
        this.id = id;
    }

    public String getFirstName()
    {
        return firstName;
    }

    public void setFirstName(String firstName)
    {
        this.firstName = firstName;
    }

    public String getLastName()
    {
        return lastName;
    }

    public void setLastName(String lastName)
    {
        this.lastName = lastName;
    }

    public String getPhoneNumber()
    {
        return phoneNumber;
    }

    public void setPhoneNumber(String phoneNumber)
    {
        this.phoneNumber = phoneNumber;
    }

    public List<Event> getEvents()
    {
        return Collections.unmodifiableList(events);
    }

    public void setEvents(List<Event> events)
    {
        this.events = events;

        for (Event event : events)
        {
            event.addContact(this);
        }
    }

    public void addEvent(Event event)
    {
        if (!this.events.contains(event))
        {
            this.events.add(event);
            event.addContact(this);
        }
    }

    public void removeEvent(Event event)
    {
        if (this.events.contains(event))
        {
            this.events.remove(event);
            event.removeContact(this);
        }
    }

    @Override
    public int compareTo(Contact o)
    {
        return this.firstName.compareTo(o.getFirstName());
    }
}
1

There are 1 best solutions below

0
Halil Ural On

I was able to generate XML with the below classes.

Contact Class.

public class Contact implements Comparable<Contact> {
    private int id;
    private String firstName;
    private String lastName;
    private String phoneNumber;
//    private List<Event> events = new ArrayList<Event>();

    public Contact() {

    }

    public Contact(int id, String firstName, String lastName, String phoneNumber) {
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
        this.phoneNumber = phoneNumber;
    }

    @Override
    public String toString() {
        return String.format("%s %s | %s", firstName, lastName, phoneNumber);
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getPhoneNumber() {
        return phoneNumber;
    }

    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }

//    public List<Event> getEvents() {
//        return Collections.unmodifiableList(events);
//    }
//
//    public void setEvents(List<Event> events) {
//        this.events = events;
//
//        for (Event event : events) {
//            event.addContact(this);
//        }
//    }

//    public void addEvent(Event event) {
//        if (!this.events.contains(event)) {
//            this.events.add(event);
//            event.addContact(this);
//        }
//    }
//
//    public void removeEvent(Event event) {
//        if (this.events.contains(event)) {
//            this.events.remove(event);
//            event.removeContact(this);
//        }
//    }

    @Override
    public int compareTo(Contact o) {
        return this.firstName.compareTo(o.getFirstName());
    }
}

Event Class

public class Event implements Comparable<Event> {
    private int id;
    private String name;
    private LocalDateTime date;
    private LocalTime notifyOffset;
    private String location;
    private String description;
    private Category category;
    private List<Contact> contacts = new ArrayList<Contact>();

    public Event() {

    }

    public Event(int id, String name, LocalDateTime date, LocalTime notifyOffset, String location, String description, Category category, List<Contact> contacts) {
        this.id = id;
        this.name = name;
        this.date = date;
        this.notifyOffset = notifyOffset;
        this.location = location;
        this.description = description;
        this.category = category;
        this.contacts = contacts;
    }

    @Override
    public String toString() {
        String locationStr = location.isEmpty() ? "No location" : location;
        String descriptionStr = description.isEmpty() ? "No description" : description;
        String categoryName = category != null ? category.getName() : "No category";

        return String.format("%s | %s | %s%n%s%n%s", name, getFormattedDate(), locationStr, descriptionStr, categoryName);
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public LocalDateTime getDate() {
        return date;
    }

    public void setDate(LocalDateTime date) {
        this.date = date;
    }

    public LocalTime getNotifyOffset() {
        return notifyOffset;
    }

    public void setNotifyOffset(LocalTime notifyOffset) {
        this.notifyOffset = notifyOffset;
    }

    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public Category getCategory() {
        return category;
    }

    public void setCategory(Category category) {
        this.category = category;
    }

    public List<Contact> getContacts() {
        return Collections.unmodifiableList(contacts);
    }

//    public void setContacts(List<Contact> contacts) {
//        this.contacts = contacts;
//
//        for (Contact contact : contacts) {
//            contact.addEvent(this);
//        }
//    }
//
//    public void addContact(Contact contact) {
//        if (!this.contacts.contains(contact)) {
//            this.contacts.add(contact);
//            contact.addEvent(this);
//        }
//    }
//
//    public void removeContact(Contact contact) {
//        if (this.contacts.contains(contact)) {
//            this.contacts.remove(contact);
//            contact.removeEvent(this);
//        }
//    }

    public String getFormattedDate() {
        return this.date.format(DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm"));
    }

    public String getFormattedDateWithOffset() {
        LocalDateTime offsetDateTime = this.date.minusHours(notifyOffset.getHour()).minusMinutes(notifyOffset.getMinute());
        return offsetDateTime.format(DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm"));
    }

    public LocalDateTime getDateWithOffset() {
        LocalDateTime offsetDateTime = this.date.minusHours(notifyOffset.getHour()).minusMinutes(notifyOffset.getMinute());
        return offsetDateTime;
    }

    @Override
    public int compareTo(Event o) {
        return this.date.compareTo(o.getDate());
    }
}

data.xml

<?xml version="1.0" encoding="UTF-8"?>
<java version="17.0.9" class="java.beans.XMLDecoder">
 <object class="java.util.ArrayList">
  <void method="add">
   <object class="com.example.stackoverflow_77889401.Category" id="Category0">
    <void property="colorHex">
     <string>blue</string>
    </void>
    <void property="id">
     <int>1</int>
    </void>
    <void property="name">
     <string>category 1</string>
    </void>
   </object>
  </void>
  <void method="add">
   <object class="com.example.stackoverflow_77889401.Category">
    <void property="colorHex">
     <string>red</string>
    </void>
    <void property="id">
     <int>2</int>
    </void>
    <void property="name">
     <string>category 2</string>
    </void>
   </object>
  </void>
  <void method="add">
   <object class="com.example.stackoverflow_77889401.Event">
    <void property="category">
     <object idref="Category0"/>
    </void>
    <void property="date">
     <object class="java.time.LocalDateTime" method="parse">
      <string>2024-01-27T01:14:01.848394</string>
     </object>
    </void>
    <void property="description">
     <string>desc 1</string>
    </void>
    <void property="id">
     <int>1</int>
    </void>
    <void property="location">
     <string>loc 1</string>
    </void>
    <void property="name">
     <string>event 1</string>
    </void>
    <void property="notifyOffset">
     <object class="java.time.LocalTime" method="parse">
      <string>01:14:01.848394</string>
     </object>
    </void>
   </object>
  </void>
  <void method="add">
   <object class="com.example.stackoverflow_77889401.Contact">
    <void property="firstName">
     <string>halil</string>
    </void>
    <void property="id">
     <int>1</int>
    </void>
    <void property="lastName">
     <string>ural</string>
    </void>
    <void property="phoneNumber">
     <string>55555555555</string>
    </void>
   </object>
  </void>
  <void method="add">
   <object class="com.example.stackoverflow_77889401.Contact">
    <void property="firstName">
     <string>ceren</string>
    </void>
    <void property="id">
     <int>2</int>
    </void>
    <void property="lastName">
     <string>guney</string>
    </void>
    <void property="phoneNumber">
     <string>55555555555</string>
    </void>
   </object>
  </void>
 </object>
</java>