Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

Welcome to Software Development on Codidact!

Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.

How can I set a private class instance variable to the File object used by an unmarshaller?

+1
−0

(Brought over from SE)


The problem

I'm coding a Java Swing application dealing with XML files, so I'm using JAXB in order to marshal classes into documents and unmarshal the other way around.

I want to include a private field in the class that gets marshalled, which will store the backing file the class is based on (if any), in the form of a File object. This way, I can determine if a backing file is in use, so when saving via a Save command, if the backing file is available, I can just marshal the class directly to that file, instead of obtaining it via a "Save file" dialog.

However, it seems that with the tools available in JAXB, I cannot get the File object from the Unmarshaller while opening it. How can I tackle the situation so that I can set that variable correctly?

As this variable is internal, I don't want to include a setter or expose it so that other classes can't change it.

Background

Being aware of class event callbacks and external listeners, I know I can use a class event callback to set a class instance private field either before or after unmarshalling, but it seems I can't retrieve the file object being in use by the Unmarshaller from inside that callback.

On the other hand, with an external listener I could get ahold of the File object being unmarshalled, as it would be at the same level with the unmarshal method call, but now the private field would either need to be public or include a setter in order for it to be set.

Sample code

The following is a minimal, reproducible example, split in two files: JAXBTest.java and MarshalMe.java, both placed at the same level.

MarshalMe.java

import java.io.File;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class MarshalMe {
    private File backingFile;

    public File getBackingFile() {
        return backingFile;
    }

    // Dummy function that sets the backing file beforehand.
    public void processSth() {
        backingFile = new File("dummy.hai");
    }
}

JAXBDemo.java

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

public class JAXBTest {
    public static void writeXML(MarshalMe me, File xml) {
        try {
            JAXBContext contextObj = JAXBContext.newInstance(MarshalMe.class);
            Marshaller marshallerObj = contextObj.createMarshaller();
            marshallerObj.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

            marshallerObj.marshal(me, new FileOutputStream(xml));
        } catch (JAXBException jaxbe) {
            jaxbe.printStackTrace();
        } catch (FileNotFoundException fnfe) {
            fnfe.printStackTrace();
        }
    }

    public static MarshalMe readXML(File xml) {
        MarshalMe me = null;

        try {
            JAXBContext contextObj = JAXBContext.newInstance(MarshalMe.class);
            Unmarshaller unmarshallerObj = contextObj.createUnmarshaller();

            me = (MarshalMe) unmarshallerObj.unmarshal(xml);
        } catch (JAXBException jaxbe) {
            jaxbe.printStackTrace();
        }

        return me;
    }

    public static void main(String[] args) {
        MarshalMe src = new MarshalMe();
        src.processSth();
        System.out.println(src.getBackingFile());

        File meFile = new File("me.xml");
        writeXML(new MarshalMe(), meFile);

        MarshalMe retrieved = readXML(meFile);
        System.out.println(retrieved.getBackingFile());
    }
}

Expected output

Running with Java 1.8 (or later, provided a JAXB library and runtime implementation is accesible), the minimal, reproducible example outputs:

dummy.hai
null

when I expect the output to be

dummy.hai
me.xml

as the class is initially written in a XML file named me.xml before being read back.

History
Why does this post require attention from curators or moderators?
You might want to add some details to your flag.
Why should this post be closed?

0 comment threads

1 answer

+0
−0

I've found a way to set the private field without exposing it or giving it a setter: Reflection.

Using external event listeners, I can get ahold of the File object. Then, inside the beforeUnmarshal method, and after checking that I got the correct object, I use reflection to get the private field, and with the setAccessible method, I can now control when I get access to the field using reflection only.

After lifting the access checks, it's only a matter of editing the value via setting it, and reinstating the checks after that.

Relevant changes

The following snippet includes the relevant changes:

unmarshallerObj.setListener(new Unmarshaller.Listener() {
    @Override
    public void beforeUnmarshal(Object target, Object parent) {
        if (!(target instanceof MarshalMe))
            return;

        MarshalMe me = (MarshalMe) target;
        try {
            Field meBackingFile = MarshalMe.class.getDeclaredField("backingFile");
            meBackingFile.setAccessible(true);
            meBackingFile.set(me, xml);
            meBackingFile.setAccessible(false);
        } catch (NoSuchFieldException | IllegalAccessException ex) {
            // Intentionally left blank.
        }
    }
});

Including the edit in the sample program

Edit the file JAXBDemo.java by adding the following code:

// Add to the import section
import java.lang.reflect.Field;

// Under this function
public static MarshalMe readXML(File xml) {
    MarshalMe me = null;

    try {
        JAXBContext contextObj = JAXBContext.newInstance(MarshalMe.class);
        Unmarshaller unmarshallerObj = contextObj.createUnmarshaller();

        /* Add this code vvv */
        unmarshallerObj.setListener(new Unmarshaller.Listener() {
            @Override
            public void beforeUnmarshal(Object target, Object parent) {
                if (!(target instanceof MarshalMe))
                    return;

                MarshalMe me = (MarshalMe) target;
                try {
                    Field meBackingFile = MarshalMe.class.getDeclaredField("backingFile");
                    meBackingFile.setAccessible(true);
                    meBackingFile.set(me, xml);
                    meBackingFile.setAccessible(false);
                } catch (NoSuchFieldException | IllegalAccessException ex) {
                    // Intentionally left blank.
                }
            }
        });
        /* Add this code ^^^ */

        me = (MarshalMe) unmarshallerObj.unmarshal(xml);
    } catch (JAXBException jaxbe) {
        jaxbe.printStackTrace();
    }

    return me;
}

After adding the import and the code between the /* Add this code */ lines, running the program again now outputs:

dummy.hai
me.xml

as expected.

History
Why does this post require attention from curators or moderators?
You might want to add some details to your flag.

1 comment thread

Reflection on private fields (1 comment)

Sign up to answer this question »