| /* |
| * 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.netbeans.modules.web.jsf; |
| |
| import java.beans.PropertyChangeEvent; |
| import java.beans.PropertyChangeListener; |
| import java.io.IOException; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import javax.swing.SwingUtilities; |
| import javax.swing.event.DocumentEvent; |
| import javax.swing.event.DocumentListener; |
| import javax.swing.text.BadLocationException; |
| import javax.swing.text.StyledDocument; |
| import org.netbeans.core.api.multiview.MultiViewHandler; |
| import org.netbeans.core.api.multiview.MultiViews; |
| import org.netbeans.modules.web.jsf.api.ConfigurationUtils; |
| import org.netbeans.modules.web.jsf.api.facesmodel.JSFConfigModel; |
| import org.netbeans.modules.web.jsf.impl.facesmodel.JSFConfigModelUtilities; |
| import org.netbeans.modules.xml.api.EncodingUtil; |
| import org.openide.DialogDescriptor; |
| import org.openide.DialogDisplayer; |
| import org.openide.NotifyDescriptor; |
| import org.openide.awt.UndoRedo; |
| import org.openide.filesystems.FileLock; |
| import org.openide.filesystems.FileObject; |
| import org.openide.nodes.Node.Cookie; |
| import org.openide.text.CloneableEditor; |
| import org.openide.text.CloneableEditorSupport.Pane; |
| import org.openide.text.DataEditorSupport; |
| import org.openide.cookies.*; |
| import org.openide.text.CloneableEditorSupport; |
| import org.openide.text.NbDocument; |
| import org.openide.util.Exceptions; |
| import org.openide.util.NbBundle; |
| import org.openide.util.RequestProcessor; |
| import org.openide.windows.CloneableTopComponent; |
| import org.openide.windows.TopComponent; |
| |
| /** |
| * |
| * @author Petr Pisl |
| */ |
| public class JSFConfigEditorSupport extends DataEditorSupport |
| implements OpenCookie, EditCookie, EditorCookie.Observable, PrintCookie, CloseCookie { |
| |
| private static final RequestProcessor RP = new RequestProcessor(JSFConfigEditorSupport.class); |
| /** SaveCookie for this support instance. The cookie is adding/removing |
| * data object's cookie set depending on if modification flag was set/unset. */ |
| private final SaveCookie saveCookie = new SaveCookie() { |
| /** Implements <code>SaveCookie</code> interface. */ |
| public void save() throws java.io.IOException { |
| JSFConfigDataObject obj = (JSFConfigDataObject) getDataObject(); |
| // invoke parsing before save |
| restartTimer(); |
| obj.parsingDocument(); |
| if (obj.isDocumentValid()) { |
| saveDocument(); |
| }else { |
| DialogDescriptor dialog = new DialogDescriptor( |
| NbBundle.getMessage(JSFConfigEditorSupport.class, "MSG_invalidXmlWarning"), |
| NbBundle.getMessage(JSFConfigEditorSupport.class, "TTL_invalidXmlWarning")); |
| java.awt.Dialog d = org.openide.DialogDisplayer.getDefault().createDialog(dialog); |
| d.setVisible(true); |
| if (dialog.getValue() == org.openide.DialogDescriptor.OK_OPTION) { |
| saveDocument(); |
| } |
| } |
| } |
| }; |
| private JSFConfigDataObject dataObject; |
| private RequestProcessor.Task parsingDocumentTask; |
| private TopComponent mvtc; |
| |
| /** Delay for automatic parsing - in miliseconds */ |
| private static final int AUTO_PARSING_DELAY = 2000; |
| |
| public JSFConfigEditorSupport(JSFConfigDataObject dobj) { |
| super(dobj, null, new XmlEnv(dobj)); |
| dataObject = dobj; |
| setMIMEType(JSFConfigLoader.MIME_TYPE); //NOI18N |
| |
| //initialize the listeners on the document |
| initialize(); |
| } |
| |
| @Override |
| protected boolean asynchronousOpen() { |
| return false; |
| } |
| |
| @Override |
| protected Pane createPane() { |
| return (CloneableEditorSupport.Pane) MultiViews.createCloneableMultiView(JSFConfigLoader.MIME_TYPE, getDataObject()); |
| } |
| |
| @Override |
| protected void initializeCloneableEditor(CloneableEditor editor) { |
| super.initializeCloneableEditor(editor); |
| } |
| |
| @Override |
| protected CloneableTopComponent createCloneableTopComponent() { |
| CloneableTopComponent tc = super.createCloneableTopComponent(); |
| this.mvtc = tc; |
| updateDisplayName (); |
| return tc; |
| } |
| |
| public UndoRedo.Manager getUndoRedoManager() { |
| return super.getUndoRedo(); |
| } |
| |
| protected void setMVTC(TopComponent mvtc) { |
| this.mvtc = mvtc; |
| updateDisplayName(); |
| } |
| |
| private int click = 0; |
| public void updateDisplayName() { |
| |
| final TopComponent tc = mvtc; |
| if (tc == null) |
| return; |
| |
| SwingUtilities.invokeLater(new Runnable() { |
| public void run() { |
| String displayName = messageHtmlName(); |
| |
| if (! displayName.equals(tc.getDisplayName())){ |
| tc.setHtmlDisplayName(displayName); |
| } |
| // XXX should probably set htmlDisplayName too, from messageHtmlName |
| // XXX should probably use messageToolTip instead |
| tc.setToolTipText(dataObject.getPrimaryFile().getPath()); |
| } |
| }); |
| } |
| |
| private void initialize() { |
| // Create DocumentListener |
| final DocumentListener docListener = new DocumentListener() { |
| public void insertUpdate(DocumentEvent e) { change(e); } |
| public void changedUpdate(DocumentEvent e) { } |
| public void removeUpdate(DocumentEvent e) { change(e); } |
| |
| private void change(DocumentEvent e) { |
| if (!dataObject.isNodeDirty()) restartTimer(); |
| } |
| }; |
| // the listener add only when the document is move to memory |
| addPropertyChangeListener(new PropertyChangeListener() { |
| public void propertyChange(PropertyChangeEvent evt) { |
| StyledDocument doc = getDocument(); |
| if (EditorCookie.Observable.PROP_DOCUMENT.equals(evt.getPropertyName()) |
| && isDocumentLoaded() && doc != null) { |
| doc.addDocumentListener(docListener); |
| } |
| } |
| }); |
| } |
| |
| /* |
| * Save document using encoding declared in XML prolog if possible otherwise |
| * at UTF-8 (in such case it updates the prolog). |
| */ |
| @Override |
| public void saveDocument() throws java.io.IOException { |
| final javax.swing.text.StyledDocument doc = getDocument(); |
| String defaultEncoding = "UTF-8"; // NOI18N |
| // dependency on xml/core |
| String enc = EncodingUtil.detectEncoding(doc); |
| boolean changeEncodingToDefault = false; |
| if (enc == null) { |
| enc = defaultEncoding; |
| } |
| |
| //test encoding |
| if (!isSupportedEncoding(enc)) { |
| NotifyDescriptor nd = new NotifyDescriptor.Confirmation( |
| NbBundle.getMessage(JSFConfigEditorSupport.class, "MSG_BadEncodingDuringSave", //NOI18N |
| new Object[]{getDataObject().getPrimaryFile().getNameExt(), |
| enc, |
| defaultEncoding}), |
| NotifyDescriptor.YES_NO_OPTION, |
| NotifyDescriptor.WARNING_MESSAGE); |
| nd.setValue(NotifyDescriptor.NO_OPTION); |
| DialogDisplayer.getDefault().notify(nd); |
| if (nd.getValue() != NotifyDescriptor.YES_OPTION) { |
| return; |
| } |
| changeEncodingToDefault = true; |
| } |
| |
| if (!changeEncodingToDefault) { |
| // is it possible to save the document in the encoding? |
| try { |
| java.nio.charset.CharsetEncoder coder = java.nio.charset.Charset.forName(enc).newEncoder(); |
| if (!coder.canEncode(doc.getText(0, doc.getLength()))) { |
| NotifyDescriptor nd = new NotifyDescriptor.Confirmation( |
| NbBundle.getMessage(JSFConfigEditorSupport.class, "MSG_BadCharConversion", //NOI18N |
| new Object[]{getDataObject().getPrimaryFile().getNameExt(), |
| enc}), |
| NotifyDescriptor.YES_NO_OPTION, |
| NotifyDescriptor.WARNING_MESSAGE); |
| nd.setValue(NotifyDescriptor.NO_OPTION); |
| DialogDisplayer.getDefault().notify(nd); |
| if (nd.getValue() != NotifyDescriptor.YES_OPTION) { |
| return; |
| } |
| } |
| } catch (javax.swing.text.BadLocationException e) { |
| Logger.getLogger("global").log(Level.INFO, null, e); |
| } |
| super.saveDocument(); |
| } else { |
| // update prolog to new valid encoding |
| try { |
| final int MAX_PROLOG = 1000; |
| int maxPrologLen = Math.min(MAX_PROLOG, doc.getLength()); |
| final char prolog[] = doc.getText(0, maxPrologLen).toCharArray(); |
| int prologLen = 0; // actual prolog length |
| |
| //parse prolog and get prolog end |
| if (prolog[0] == '<' && prolog[1] == '?' && prolog[2] == 'x') { |
| |
| // look for delimitting ?> |
| for (int i = 3; i < maxPrologLen; i++) { |
| if (prolog[i] == '?' && prolog[i + 1] == '>') { |
| prologLen = i + 1; |
| break; |
| } |
| } |
| } |
| |
| final int passPrologLen = prologLen; |
| |
| Runnable edit = new Runnable() { |
| @Override |
| public void run() { |
| try { |
| doc.remove(0, passPrologLen + 1); // +1 it removes exclusive |
| doc.insertString(0, "<?xml version='1.0' encoding='UTF-8' ?> \n<!-- was: " + new String(prolog, 0, passPrologLen + 1) + " -->", null); // NOI18N |
| |
| } catch (BadLocationException e) { |
| if (System.getProperty("netbeans.debug.exceptions") != null) { // NOI18N |
| Exceptions.printStackTrace(e); |
| } |
| } |
| } |
| }; |
| |
| NbDocument.runAtomic(doc, edit); |
| super.saveDocument(); |
| } catch (javax.swing.text.BadLocationException e) { |
| Logger.getLogger("global").log(Level.INFO, null, e); |
| } |
| } |
| } |
| |
| private boolean isSupportedEncoding(String encoding){ |
| boolean supported; |
| try{ |
| supported = java.nio.charset.Charset.isSupported(encoding); |
| } catch (java.nio.charset.IllegalCharsetNameException e){ |
| supported = false; |
| } |
| |
| return supported; |
| } |
| |
| |
| /** Restart the timer which starts the parser after the specified delay. |
| * @param onlyIfRunning Restarts the timer only if it is already running |
| */ |
| public void restartTimer() { |
| if (parsingDocumentTask==null || parsingDocumentTask.isFinished() || |
| parsingDocumentTask.cancel()) { |
| dataObject.setDocumentDirty(true); |
| Runnable r = new Runnable() { |
| public void run() { |
| dataObject.parsingDocument(); |
| } |
| }; |
| if (parsingDocumentTask != null) |
| parsingDocumentTask = RP.post(r, AUTO_PARSING_DELAY); |
| else |
| parsingDocumentTask = RP.post(r, 100); |
| } |
| } |
| |
| /** |
| * Overrides superclass method. Adds adding of save cookie if the document has been marked modified. |
| * @return true if the environment accepted being marked as modified |
| * or false if it has refused and the document should remain unmodified |
| */ |
| @Override |
| protected boolean notifyModified() { |
| boolean notif = super.notifyModified(); |
| if (!notif){ |
| return false; |
| } |
| updateDisplayName(); |
| addSaveCookie(); |
| return true; |
| } |
| |
| @Override |
| protected void notifyClosed() { |
| mvtc = null; |
| super.notifyClosed(); |
| syncModel(); |
| } |
| |
| private void syncModel() { |
| final JSFConfigModel configModel = ConfigurationUtils.getConfigModel(dataObject.getPrimaryFile(), true); |
| RP.post(new Runnable() { |
| @Override |
| public void run() { |
| long time = System.currentTimeMillis(); |
| try { |
| // synchronize the model with the document. See issue #116315 |
| if (configModel != null) { |
| // the model can be null, if the file wasn't opened. |
| configModel.sync(); |
| } |
| } catch (IOException ex) { |
| // Logger.getLogger("global").log(Level.INFO, null, ex); |
| } |
| Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Sync Config Model took: "+ (System.currentTimeMillis() - time) + " ms"); //NOI18N |
| } |
| }); |
| } |
| |
| /** Overrides superclass method. Adds removing of save cookie. */ |
| @Override |
| protected void notifyUnmodified() { |
| super.notifyUnmodified(); |
| updateDisplayName(); |
| removeSaveCookie(); |
| } |
| |
| /** Helper method. Adds save cookie to the data object. */ |
| private void addSaveCookie() { |
| // Adds save cookie to the data object. |
| if(dataObject.getCookie(SaveCookie.class) == null) { |
| dataObject.getCookieSet0().add(saveCookie); |
| dataObject.setModified(true); |
| } |
| } |
| |
| /** Helper method. Removes save cookie from the data object. */ |
| private void removeSaveCookie() { |
| JSFConfigDataObject obj = (JSFConfigDataObject)getDataObject(); |
| |
| // Remove save cookie from the data object. |
| Cookie cookie = obj.getCookie(SaveCookie.class); |
| |
| if(cookie != null && cookie.equals(saveCookie)) { |
| obj.getCookieSet0().remove(saveCookie); |
| obj.setModified(false); |
| } |
| } |
| |
| @Override |
| public void open() { |
| super.open(); |
| // parse once after opening the document |
| restartTimer(); |
| updateDisplayName(); |
| } |
| |
| @Override |
| public void edit(){ |
| // open the top component |
| open(); |
| |
| // ask for opening the last (source) editor |
| runInAwtDispatchThread(new Runnable() { |
| public void run() { |
| MultiViewHandler handler = MultiViews.findMultiViewHandler(mvtc); |
| // The handler can be null, when user uninstall a module, which |
| // provides view that was opened last time |
| if (handler != null) { |
| handler.requestVisible(handler.getPerspectives()[handler.getPerspectives().length - 1]); |
| mvtc.requestActive(); |
| } |
| } |
| }); |
| } |
| |
| private static void runInAwtDispatchThread(Runnable runnable) { |
| if (SwingUtilities.isEventDispatchThread()) { |
| runnable.run(); |
| } else { |
| SwingUtilities.invokeLater(runnable); |
| } |
| |
| } |
| |
| // /** Implementation of CloseOperationHandler for multiview. |
| // */ |
| // @MimeRegistration(mimeType=JSFConfigLoader.MIME_TYPE, service=CloseOperationHandler.class) |
| // public static class CloseHandler implements CloseOperationHandler, Serializable { |
| // private static final long serialVersionUID = 1L; |
| // |
| // private JSFConfigDataObject dataObject; |
| // |
| // private CloseHandler() { |
| // } |
| // |
| // public CloseHandler(JSFConfigDataObject facesConfig) { |
| // dataObject = facesConfig; |
| // } |
| // |
| // @Override |
| // public boolean resolveCloseOperation(CloseOperationState[] elements) { |
| // boolean can = dataObject.getEditorSupport().canClose(); |
| // if (can) { |
| // dataObject.getEditorSupport().notifyClosed(); |
| // } |
| // return can; |
| // } |
| // } |
| |
| private static class XmlEnv extends DataEditorSupport.Env { |
| |
| private static final long serialVersionUID = -800036748848958489L; |
| |
| //private static final long serialVersionUID = ...L; |
| |
| /** Create a new environment based on the data object. |
| * @param obj the data object to edit |
| */ |
| public XmlEnv(JSFConfigDataObject obj) { |
| super(obj); |
| } |
| |
| /** Get the file to edit. |
| * @return the primary file normally |
| */ |
| protected FileObject getFile() { |
| return getDataObject().getPrimaryFile(); |
| } |
| |
| /** Lock the file to edit. |
| * Should be taken from the file entry if possible, helpful during |
| * e.g. deletion of the file. |
| * @return a lock on the primary file normally |
| * @throws IOException if the lock could not be taken |
| */ |
| protected FileLock takeLock() throws java.io.IOException { |
| return ((JSFConfigDataObject) getDataObject()).getPrimaryEntry().takeLock(); |
| } |
| |
| /** Find the editor support this environment represents. |
| * Note that we have to look it up, as keeping a direct |
| * reference would not permit this environment to be serialized. |
| * @return the editor support |
| */ |
| @Override |
| public org.openide.windows.CloneableOpenSupport findCloneableOpenSupport() { |
| return getDataObject().getCookie(JSFConfigEditorSupport.class); |
| } |
| } |
| } |