0

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());
    }
}
2
  • I see that you have relationships between classes, you have a Contact list in Event and an event list in Contact class. Logically I couldn't understand why you needed an event list in contact class because it doesn't make sense to me. Do you have a particular reason for that? I think you can keep relations just in the Event class. Commented Jan 27, 2024 at 0:07
  • 1
    @HalilUral In my project, it is a prerequisite to have references in events pointing to contacts, and conversely, contacts must have references to events. This ensures more effective collaboration with the database and is a predefined requirement for my project. I've reached the same conclusion as you did – the issue with serialization is not an infinite loop caused by references, but rather the inability to serialize to XML due to unmodifiable lists in the getters of the lists in the Event and Contact classes. Commented Jan 27, 2024 at 11:16

1 Answer 1

0

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>

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.