class PdfReaderController extends Observable implements Observer {

    /**
     * Treeview of the PDF file.
     */
    protected PdfTree pdfTree;
    /**
     * Tabbed Pane containing other components.
     */
    protected JTabbedPane navigationTabs;
    /**
     * JTable with all the pages and their labels.
     */
    protected PagesTable pages;
    /**
     * Treeview of the outlines.
     */
    protected OutlineTree outlines;
    /**
     * Treeview of the form.
     */
    protected FormTree form;
    /**
     * JTable corresponding with the CrossReference table.
     */
    protected XRefTable xref;
    /**
     * A panel that will show PdfObjects.
     */
    protected PdfObjectPanel objectPanel;
    /**
     * Tabbed Pane containing other components.
     */
    protected JTabbedPane editorTabs;
    /**
     * A panel that will show a stream.
     */
    protected StreamTextArea streamArea;

    /**
     * The factory producing tree nodes.
     */
    protected TreeNodeFactory nodes;

    /**
     * Constructs the PdfReaderController. This is an Observable object to which all iText related GUI components are
     * added as Observers.
     *
     * @param treeSelectionListener when somebody selects a tree node, this listener listens to the event
     * @param pageSelectionListener when somebody changes a page, this listener changes accordingly
     */
    public PdfReaderController(TreeSelectionListener treeSelectionListener,
            PageSelectionListener pageSelectionListener) {
        pdfTree = new PdfTree();
        pdfTree.addTreeSelectionListener(treeSelectionListener);
        addObserver(pdfTree);
        pages = new PagesTable(this, pageSelectionListener);
        addObserver(pages);
        outlines = new OutlineTree(this);
        addObserver(outlines);
        form = new FormTree(this);
        addObserver(form);
        xref = new XRefTable(this);
        addObserver(xref);
        navigationTabs = new JTabbedPane();
        navigationTabs.addTab("Pages", null, new JScrollPane(pages), "Pages");
        navigationTabs.addTab("Outlines", null, new JScrollPane(outlines), "Outlines (Bookmarks)");
        navigationTabs.addTab("Form", null, new JScrollPane(form), "Interactive Form");
        navigationTabs.addTab("XFA", null, form.getXfaTree(), "Tree view of the XFA form");
        navigationTabs.addTab("XRef", null, new JScrollPane(xref), "Cross-reference table");
        objectPanel = new PdfObjectPanel();
        addObserver(objectPanel);
        streamArea = new StreamTextArea();
        addObserver(streamArea);
        editorTabs = new JTabbedPane();
        editorTabs.addTab("Stream", null, streamArea, "Stream");
        editorTabs.addTab("XFA", null, form.getXfaTextArea(), "XFA Form XML file");
    }

    /**
     * Getter for the PDF Tree.
     *
     * @return the PdfTree object
     */
    public PdfTree getPdfTree() {
        return pdfTree;
    }

    /**
     * Getter for the tabs that allow you to navigate through the PdfTree quickly (pages, form, outlines, xref table).
     *
     * @return a JTabbedPane
     */
    public JTabbedPane getNavigationTabs() {
        return navigationTabs;
    }

    /**
     * Getter for the panel that will show the contents of a PDF Object (except for PdfStreams: only the Stream
     * Dictionary will be shown; the content stream is shown in a StreamTextArea object).
     *
     * @return the PdfObjectPanel
     */
    public PdfObjectPanel getObjectPanel() {
        return objectPanel;
    }

    /**
     * Getter for the tabs with the editor windows (to which the Console window will be added).
     *
     * @return the tabs with the editor windows
     */
    public JTabbedPane getEditorTabs() {
        return editorTabs;
    }

    /**
     * Getter for the object that holds the TextArea with the content stream of a PdfStream object.
     *
     * @return a StreamTextArea
     */
    public StreamTextArea getStreamArea() {
        return streamArea;
    }

    /**
     * Starts loading the PDF Objects in background.
     *
     * @param file the wrapper object that holds the PdfReader as member variable
     */
    public void startObjectLoader(PdfFile file) {
        setChanged();
        notifyObservers();
        setChanged();
        new ObjectLoader(this, file.getPdfReader());
    }

    /**
     * The GUI components that show the internals of a PDF file, can only be shown if all objects are loaded into the
     * IndirectObjectFactory using the ObjectLoader. As soon as this is done, the GUI components are notified.
     *
     * @param obj in this case the Object should be an ObjectLoader
     * @see java.util.Observable#notifyObservers(java.lang.Object)
     */
    @Override
    public void notifyObservers(Object obj) {
        if (obj instanceof ObjectLoader loader) {
            nodes = loader.getNodes();
            PdfTrailerTreeNode root = pdfTree.getRoot();
            root.setTrailer(loader.getReader().getTrailer());
            root.setUserObject("PDF Object Tree");
            nodes.expandNode(root);
        }
        super.notifyObservers(obj);
    }

    /**
     * Selects a node in the PdfTree.
     *
     * @param node a node in the PdfTree
     */
    public void selectNode(PdfObjectTreeNode node) {
        pdfTree.selectNode(node);
    }

    /**
     * Selects a node in the PdfTree.
     *
     * @param objectNumber a number of a node in the PdfTree
     */
    public void selectNode(int objectNumber) {
        selectNode(nodes.getNode(objectNumber));
    }

    /**
     * Renders the syntax of a PdfObject in the objectPanel. If the object is a PDF Stream, then the stream is shown in
     * the streamArea too.
     *
     * @param object the object to render
     */
    public void render(PdfObject object) {
        objectPanel.render(object);
        streamArea.render(object);
        if (object instanceof PRStream) {
            editorTabs.setSelectedComponent(streamArea);
        } else {
            editorTabs.setSelectedIndex(editorTabs.getComponentCount() - 1);
        }
    }

    /**
     * Selects the row in the pageTable that corresponds with a certain page number.
     *
     * @param pageNumber the page number that needs to be selected
     */
    public void gotoPage(int pageNumber) {
        pageNumber--;
        if (pages == null || pages.getSelectedRow() == pageNumber) {
            return;
        }
        if (pageNumber < pages.getRowCount()) {
            pages.setRowSelectionInterval(pageNumber, pageNumber);
        }
    }

    /**
     * Forwards updates from the RupsController to the Observers of this class.
     *
     * @param observable this should be the RupsController
     * @param obj        the object that has to be forwarded to the observers of PdfReaderController
     * @see java.util.Observer#update(java.util.Observable, java.lang.Object)
     */
    public void update(Observable observable, Object obj) {<FILL_FUNCTION_BODY>}
}

        if (RupsMenuBar.CLOSE.equals(obj)) {
            setChanged();
            notifyObservers(null);
            nodes = null;
        }
        if (obj instanceof PdfObjectTreeNode node) {
            nodes.expandNode(node);
            if (node.isRecursive()) {
                pdfTree.selectNode(node.getAncestor());
                return;
            }
            render(node.getPdfObject());
        }
    
<methods>public void <init>() ,public synchronized void addObserver(java.util.Observer) ,public synchronized int countObservers() ,public synchronized void deleteObserver(java.util.Observer) ,public synchronized void deleteObservers() ,public synchronized boolean hasChanged() ,public void notifyObservers() ,public void notifyObservers(java.lang.Object) <variables>private boolean changed,private Vector<java.util.Observer> obs