All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openide.text.CloneableEditorInitializer Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.openide.text;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Insets;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.JEditorPane;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JToolBar;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.text.Caret;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.Document;
import javax.swing.text.EditorKit;
import javax.swing.text.StyledDocument;
import org.openide.cookies.EditorCookie;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
import org.openide.util.RequestProcessor;
import org.openide.util.UserQuestionException;
import org.openide.windows.TopComponent;
import org.openide.windows.WindowManager;

/**
 * Initializer of the CloneableEditor component.
 *
 * @author Miloslav Metelka
 */
final class CloneableEditorInitializer implements Runnable {

    // Use CloneableEditor.LOG due to CloneableEditorCreationFinishedTest.FocusHandler.assertFocused()
    private static final Logger EDITOR_LOG = CloneableEditor.LOG;
    
    // -J-Dorg.openide.text.CloneableEditorInitializer.level=FINE
    private static final Logger LOG = Logger.getLogger(CloneableEditorInitializer.class.getName());
    
    private static final RequestProcessor RP = new RequestProcessor("org.openide.text Editor Initialization");
    
    static final Logger TIMER = Logger.getLogger("TIMER"); // NOI18N
    
    /**
     * Flag indicating if modal dialog for handling UQE is displayed. If it is
     * yes we cannot handle call of getEditorPane from modal EQ because it
     * results in deadlock.
     */
    static boolean modalDialog;

    static final List edtRequests = new ArrayList(2);
    
    static final Runnable processPendingEDTRequestsRunnable = new Runnable() {
        @Override
        public void run() {
            processPendingEDTRequests();
        }
    };

    static void waitForFinishedInitialization(CloneableEditor editor) { // Should only be called from EDT
        assert (SwingUtilities.isEventDispatchThread()) : "Method should only be called from EDT"; // NOI18N
        while (true) {
            // First check if initialization is even running (there might be init requests
            // from other CEs in edtRequests and there would be no need to run them)
            synchronized (edtRequests) {
                if (!editor.isInitializationRunning()) {
                    return;
                }
            }
            // Do not wait in case when query comes from EDT and initializer phase is just running
            // because such wait would never end.
            if (editor.isProvideUnfinishedPane()) {
                return;
            }
            processPendingEDTRequests();
            synchronized (edtRequests) {
                if (!editor.isInitializationRunning()) {
                    return;
                } else {
                    try {
                        // Wait since the initializer will notify once it's finished
                        // or when a next phase EDT request gets added to edtRequests
                        if (LOG.isLoggable(Level.FINE)) {
                            LOG.fine("CEI:Will wait() editor=" + System.identityHashCode(editor) + '\n'); // NOI18N
                        }
                        // Wait for a limited time in case the notify() would not arrive (to find a fix for issue #235319)
                        edtRequests.wait(5000);
                    } catch (InterruptedException ex) {
                        Exceptions.printStackTrace(ex);
                    }
                }
            }
        }
    }
    
    static void processPendingEDTRequests() { // Should only be called from EDT
        while (true) {
            Runnable request;
            synchronized (edtRequests) {
                if (!edtRequests.isEmpty()) {
                    request = edtRequests.remove(0);
                } else {
                    break;
                }
            }
            if (request != null) {
                request.run();
            }
        }
    }
    
    static void addEDTRequest(Runnable request) {
        synchronized (edtRequests) {
            edtRequests.add(request);
            // Notify possible EDT waiters to fetch and process the request
            notifyEDTRequestsMonitor();
        }
    }
    
    static void notifyEDTRequestsMonitor() {
        synchronized (edtRequests) {
            edtRequests.notifyAll();
        }
    }

    final CloneableEditor editor;
    
    final CloneableEditorSupport ces;
    
    final JEditorPane pane;
    
    StyledDocument doc;
    
    private Phase phase;

    private RequestProcessor.Task task;

    private EditorKit kit;

    private JLabel loadingLabel;
    
    private UserQuestionException uqe;
    
    boolean provideUnfinishedPane;

    enum Phase {
        
        DOCUMENT_OPEN(false),
        HANDLE_USER_QUESTION_EXCEPTION(true),
        ACTION_MAP(true),
        INIT_KIT(false),
        KIT_AND_DOCUMENT_TO_PANE(true),
        CUSTOM_EDITOR_AND_DECORATIONS(true),
        FIRE_PANE_READY(true),
        ANNOTATIONS(false),
        ;

        private final boolean runInEDT;
        
        Phase(boolean runInEDT) {
            this.runInEDT = runInEDT;
        }

        public boolean isRunInEDT() {
            return runInEDT;
        }
        
    }

    
    CloneableEditorInitializer(CloneableEditor editor, CloneableEditorSupport ces, JEditorPane pane) {
        this.editor = editor;
        this.ces = ces;
        this.pane = pane;
    }
    
    void start() {
        boolean success = false;
        try {
            kit = ces.createEditorKit();
            addLoadingLabel();
            task = RP.create(this);
            task.setPriority(Thread.MIN_PRIORITY + 2);
            nextPhase(); // Process first phase
            success = true;
        } finally {
            if (!success) {
                cancelInitialization();
            }
        }
    }
    
    boolean nextPhase() {
        if (phase == null) {
            phase = Phase.DOCUMENT_OPEN;
        } else {
            int nextOrdinal = phase.ordinal() + 1;
            if (nextOrdinal < Phase.values().length) {
                phase = Phase.values()[nextOrdinal];
            } else {
                return false;
            }
        }

        boolean success = false;
        try {
            // Schedule the task
            if (phase.isRunInEDT()) {
                if (LOG.isLoggable(Level.FINE)) {
                    LOG.fine("CEI:addEDTRequest(): " + this + '\n'); // NOI18N
                }
                addEDTRequest(this);
                // Ensure that the requests gets processed
                WindowManager.getDefault().invokeWhenUIReady(processPendingEDTRequestsRunnable);
    //            Mutex.EVENT.readAccess(processPendingEDTRequestsRunnable);
            } else {
                if (LOG.isLoggable(Level.FINE)) {
                    LOG.fine("CEI:task.schedule(): " + this + '\n'); // NOI18N
                }
                task.schedule(0);
            }
            success = true;
        } finally {
            if (!success) {
                cancelInitialization();
            }
        }
        return true;
    }
    
    boolean isProvideUnfinishedPane() {
        return provideUnfinishedPane;
    }
    
    void cancelInitialization() {
        if (LOG.isLoggable(Level.FINE)) {
            LOG.fine("CEI:cancelInitialization(): " + this + '\n'); // NOI18N
        }
        editor.markInitializationFinished(false);

        // Close the top component
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                TopComponent toClose = (TopComponent) SwingUtilities.getAncestorOfClass(TopComponent.class, editor);
                if (null == toClose) {
                    toClose = editor;
                }
                toClose.close();
            }
        });
    }
    
    void finishInitialization() {
        if (LOG.isLoggable(Level.FINE)) {
            LOG.fine("CEI:finishInitialization(): " + this + '\n'); // NOI18N
        }
        editor.markInitializationFinished(true);
    }

    private void addLoadingLabel() {
        editor.setLayout(new BorderLayout());
        loadingLabel = new JLabel(NbBundle.getMessage(CloneableEditor.class, "LBL_EditorLoading")); // NOI18N
        loadingLabel.setOpaque(true);
        loadingLabel.setHorizontalAlignment(SwingConstants.CENTER);
        loadingLabel.setBorder(new EmptyBorder(new Insets(11, 11, 11, 11)));
        loadingLabel.setVisible(false);
        editor.add(loadingLabel, BorderLayout.CENTER);
    }

    @SuppressWarnings("fallthrough")
    public void run() {
        long now = System.currentTimeMillis();
        boolean success = false; // determine if phase ended with success
        try {
            switch (phase) {
                case DOCUMENT_OPEN:
                    success = initDocument();
                    break;
                case HANDLE_USER_QUESTION_EXCEPTION:
                    success = handleUserQuestionExceptionInEDT();
                    break;
                case ACTION_MAP:
                    success = initActionMapInEDT();
                    break;
                case INIT_KIT:
                    success = initKit();
                    break;
                case KIT_AND_DOCUMENT_TO_PANE:
                    success = setKitAndDocumentToPaneInEDT();
                    break;
                case CUSTOM_EDITOR_AND_DECORATIONS:
                    success = initCustomEditorAndDecorationsInEDT();
                    break;
                case FIRE_PANE_READY:
                    success = firePaneReadyInEDT();
                    break;
                case ANNOTATIONS:
                    // Initialization of annotations should not affect the opening process success
                    initAnnotations();
                    success = true;
                    break;
                    
                default:
                    throw new IllegalStateException("Wrong state: " + phase + " for " + ces);
            }

        } catch (RuntimeException ex) {
            Exceptions.printStackTrace(ex);
            // Re-throw the exception. The EDT clients may recieve the exception
            // if the current phase runs in EDT. If this would be a problem rethrowing
            // may be abandoned and replaced with 'return' only.
            throw ex;
        } finally {
            if (!success) {
                cancelInitialization(); // Cancel init - noitify possible EDT waiter(s)
                return;
            }
        }
    
        success = false;
        try {
            long howLong = System.currentTimeMillis() - now;
            if (TIMER.isLoggable(Level.FINE)) {
                String thread = SwingUtilities.isEventDispatchThread() ? "EDT" : "RP"; // NOI18N
                Document d = doc;
                Object who = d == null ? null : d.getProperty(Document.StreamDescriptionProperty);
                if (who == null) {
                    who = ces.messageName();
                }
                TIMER.log(Level.FINE,
                        "Open Editor, phase " + phase + ", " + thread + " [ms]",
                        new Object[]{who, howLong});
            }
            success = true;
        } finally {
            if (!success) {
                cancelInitialization();
            }
        }

        success = false;
        try {
            nextPhase();
            success = true;
        } finally {
            if (!success) {
                cancelInitialization();
            }
        }
        // Note: finishInitialization() called as part of CUSTOM_EDITOR_AND_DECORATIONS phase
    }

    private boolean initDocument() {
        if (EDITOR_LOG.isLoggable(Level.FINE)) {
            EDITOR_LOG.log(Level.FINE, "CloneableEditorInitializer.initDocument() Enter"
                    + " Time:" + System.currentTimeMillis()
                    + " Thread:" + Thread.currentThread().getName()
                    + " ce:[" + Integer.toHexString(System.identityHashCode(editor)) + "]"
                    + " support:[" + Integer.toHexString(System.identityHashCode(ces)) + "]"
                    + " Name:" + editor.getName());
        }

        try {
            ces.prepareDocument(); // Ensure prepareDocument() is called when existing component is deserialized after IDE restart
            setDocument(ces.openDocument());
            ces.getPositionManager().documentOpened(new WeakReference(doc));
            assert (doc != null) : "ces.openDocument() returned null"; // NOI18N
            return true;
        } catch (UserQuestionException ex) {
            uqe = ex; // Will be handled in next phase
            return true;

        } catch (IOException ex) {
            if (ex.getCause() != null) {
                Exceptions.printStackTrace(ex.getCause());
            }
            return false;
        }
    }
    
    boolean handleUserQuestionExceptionInEDT() {
        assert SwingUtilities.isEventDispatchThread() : "Not EDT"; // NOI18N
        if (uqe != null) {
            if (EDITOR_LOG.isLoggable(Level.FINE)) {
                EDITOR_LOG.fine("CEI:handleUserQuestionExceptionInEDT: uqe=" + uqe + "\n");
            }
            UserQuestionExceptionHandler handler = new UserQuestionExceptionHandler(ces, uqe) {
                @Override
                protected void opened(StyledDocument openDoc) {
                    setDocument(openDoc);
                }

                @Override
                protected void handleStart() {
                    modalDialog = true;
                }

                @Override
                protected void handleEnd() {
                    modalDialog = false;
                }
            };
            if (handler.handleUserQuestionException()) {
                uqe = null; // UQE answered processed and init can continue
            } else {
                cancelInitialization();
            }
        }

        // Here the document should be ready or initialization should be cancelled
        if (doc == null && editor.isInitializationRunning()) {
            throw new IllegalStateException("Null document for non-cancelled initialization. uqe=" + uqe);
        }
        
        return (uqe == null);
    }
    
    private void setDocument(StyledDocument doc) {
        this.doc = doc;
    }

    private boolean initActionMapInEDT() {
        // Init action map: cut,copy,delete,paste actions.
        javax.swing.ActionMap am = editor.getActionMap();

        //#43157 - editor actions need to be accessible from outside using the TopComponent.getLookup(ActionMap.class) call.
        // used in main menu enabling/disabling logic.
        javax.swing.ActionMap paneMap = pane.getActionMap();
        // o.o.windows.DelegateActionMap.setParent() leads to CloneableEditor.getEditorPane()
        provideUnfinishedPane = true;
        try {
            am.setParent(paneMap);
        } finally {
            provideUnfinishedPane = false;
        }

        //#41223 set the defaults befor the custom editor + kit get initialized, giving them opportunity to
        // override defaults..
        paneMap.put(DefaultEditorKit.cutAction, getAction(DefaultEditorKit.cutAction));
        paneMap.put(DefaultEditorKit.copyAction, getAction(DefaultEditorKit.copyAction));
        paneMap.put("delete", getAction(DefaultEditorKit.deleteNextCharAction)); // NOI18N
        paneMap.put(DefaultEditorKit.pasteAction, getAction(DefaultEditorKit.pasteAction));
        return true;
    }
    
    private boolean initKit() {
        if (kit instanceof Callable) {
            try {
                ((Callable) kit).call();
            } catch (Exception e) {
                Exceptions.printStackTrace(e);
            }
        }
        return true;
    }

    private Action getAction(String key) {
        if (key == null) {
            return null;
        }

        // Try to find the action from kit.
        if (kit == null) { // kit is cleared in closeDocument()
            return null;
        }

        Action[] actions = kit.getActions();
        for (int i = 0; i < actions.length; i++) {
            if (key.equals(actions[i].getValue(Action.NAME))) {
                return actions[i];
            }
        }
        return null;
    }
    
    private void initCustomEditor() {
        if (doc instanceof NbDocument.CustomEditor) {
            NbDocument.CustomEditor ce = (NbDocument.CustomEditor) doc;
            Component customComponent;
            provideUnfinishedPane = true;
            try {
                customComponent = ce.createEditor(pane);
            } finally {
                provideUnfinishedPane = false;
            }
            if (customComponent == null) {
                throw new IllegalStateException(
                        "Document:" + doc // NOI18N
                        + " implementing NbDocument.CustomEditor may not" // NOI18N
                        + " return null component" // NOI18N
                        );
            }
            editor.setCustomComponent(customComponent);
            editor.add(ces.wrapEditorComponent(customComponent), BorderLayout.CENTER);

        } else {
            // remove default JScrollPane border, borders are provided by window system
            JScrollPane noBorderPane = new JScrollPane(pane);
            pane.setBorder(null);
            editor.add(ces.wrapEditorComponent(noBorderPane), BorderLayout.CENTER);

        }
    }

    private void initDecoration() {
        if (doc instanceof NbDocument.CustomToolbar) {
            NbDocument.CustomToolbar ce = (NbDocument.CustomToolbar) doc;
            JToolBar customToolbar;
            provideUnfinishedPane = true;
            try {
                customToolbar = ce.createToolbar(pane);
            } finally {
                provideUnfinishedPane = false;
            }
            if (customToolbar == null) {
                throw new IllegalStateException(
                        "Document:" + doc // NOI18N
                        + " implementing NbDocument.CustomToolbar may not" // NOI18N
                        + " return null toolbar"); // NOI18N
            }
            Border b = (Border) UIManager.get("Nb.Editor.Toolbar.border"); //NOI18N
            customToolbar.setBorder(b);
            editor.add(customToolbar, BorderLayout.NORTH);
        }
    }

    private boolean setKitAndDocumentToPaneInEDT() {
        provideUnfinishedPane = true;
        try {
            pane.setEditorKit(kit);
            // #132669, do not fire prior setting the kit, which by itself sets a bogus document, etc.
            // if this is a problem please revert the change and initialize QuietEditorPane.working = FIRE
            // and reopen #132669
            ((QuietEditorPane)pane).setWorking(QuietEditorPane.FIRE);

            pane.setDocument(doc); // doc should be non-null here
        } finally {
            provideUnfinishedPane = false;
        }
        return true;
    }
    
    private boolean initCustomEditorAndDecorationsInEDT() {
        initCustomEditor();
        initDecoration();
        editor.remove(loadingLabel);
        ((QuietEditorPane)pane).setWorking(QuietEditorPane.ALL);
        // set the caret to right possition if this component was deserialized
        int cursorPosition = editor.getCursorPosition();
        if (cursorPosition != -1) {
            Caret caret = pane.getCaret();
            if (caret != null) {
                caret.setDot(cursorPosition);
            }
        }
        ActionMap actionMap = editor.getActionMap();
        ActionMap p = actionMap.getParent();
        actionMap.setParent(null);
        actionMap.setParent(p);

        //#134910: If editor TopComponent is already activated request focus
        //to it again to get focus to correct subcomponent eg. QuietEditorPane which
        //is added above.
        if (shouldRequestFocus(pane)) {
            EDITOR_LOG.log(Level.FINE, "requestFocusInWindow {0}", pane);
            editor.requestFocusInWindow();
        }
        //#162961, #167289: Force repaint of editor. Sometimes editor stays empty.
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                editor.revalidate();
            }
        });

        // Mark the initialization finished here so that CloneableEditor.isEditorPaneReady() returns true
        // Do it before custom editor and decorations since they might query getEditorPane()
        // which would wait indeinitely for initialization completion.
        finishInitialization();
        return true;
    }
    
    private boolean firePaneReadyInEDT() {
        // Fire. Is EDT expected for firing PROP_OPENED_PANES?? Generally probably not but
        // getOpenedPanes() should come from EDT so it's handy.
        ces.firePropertyChange(EditorCookie.Observable.PROP_OPENED_PANES, null, null);
        return true;
    }
    
    private void initAnnotations() {
        ces.ensureAnnotationsLoaded();
    }

    private boolean shouldRequestFocus(Component c) {
        TopComponent active = TopComponent.getRegistry().getActivated();
        while (c != null) {
            if (c == active) {
                return true;
            }
            c = c.getParent();
        }
        return false;
    }

    @Override
    public String toString() {
        return "phase=" + phase + ", editor=" + System.identityHashCode(editor);
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy