| /* |
| * 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.struts; |
| |
| 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.event.DocumentEvent; |
| import javax.swing.event.DocumentListener; |
| import javax.swing.text.BadLocationException; |
| import javax.swing.text.StyledDocument; |
| import org.netbeans.core.api.multiview.MultiViews; |
| import org.netbeans.modules.xml.api.EncodingUtil; |
| import org.openide.DialogDescriptor; |
| import org.openide.DialogDisplayer; |
| import org.openide.NotifyDescriptor; |
| import org.openide.filesystems.FileLock; |
| import org.openide.filesystems.FileObject; |
| import org.openide.nodes.Node.Cookie; |
| import org.openide.text.DataEditorSupport; |
| import org.openide.cookies.*; |
| import org.openide.text.CloneableEditorSupport; |
| import org.openide.text.NbDocument; |
| import org.openide.util.NbBundle; |
| import org.openide.util.RequestProcessor; |
| |
| /** |
| * |
| * @author Petr Pisl |
| */ |
| public class StrutsConfigEditorSupport extends DataEditorSupport |
| implements OpenCookie, EditCookie, EditorCookie.Observable, PrintCookie, CloseCookie { |
| |
| /** 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 { |
| StrutsConfigDataObject obj = (StrutsConfigDataObject) getDataObject (); |
| // invoke parsing before save |
| restartTimer(); |
| obj.parsingDocument(); |
| if (obj.isDocumentValid()) { |
| saveDocument(); |
| }else { |
| DialogDescriptor dialog = new DialogDescriptor( |
| NbBundle.getMessage (StrutsConfigEditorSupport.class, "MSG_invalidXmlWarning"), //NOI18N |
| NbBundle.getMessage (StrutsConfigEditorSupport.class, "TTL_invalidXmlWarning")); //NOI18N |
| java.awt.Dialog d = org.openide.DialogDisplayer.getDefault().createDialog(dialog); |
| d.setVisible(true); |
| if (dialog.getValue() == org.openide.DialogDescriptor.OK_OPTION) { |
| saveDocument(); |
| } |
| } |
| } |
| }; |
| |
| private StrutsConfigDataObject dataObject; |
| private RequestProcessor.Task parsingDocumentTask; |
| /** Delay for automatic parsing - in miliseconds */ |
| private static final int AUTO_PARSING_DELAY = 2000; |
| |
| public StrutsConfigEditorSupport(StrutsConfigDataObject dobj) { |
| super(dobj, null, new XmlEnv(dobj)); |
| setMIMEType(StrutsConfigLoader.MIME_TYPE); |
| dataObject = dobj; |
| //initialize the listeners on the document |
| initialize(); |
| } |
| |
| @Override |
| protected boolean asynchronousOpen() { |
| return false; |
| } |
| |
| @Override |
| protected Pane createPane() { |
| return (CloneableEditorSupport.Pane) MultiViews.createCloneableMultiView(StrutsConfigLoader.MIME_TYPE, getDataObject()); |
| } |
| |
| 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) { |
| if (EditorCookie.Observable.PROP_DOCUMENT.equals(evt.getPropertyName()) |
| && isDocumentLoaded()) { |
| StyledDocument doc = getDocument(); |
| if (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). |
| */ |
| 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 (StrutsConfigEditorSupport.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 (StrutsConfigEditorSupport.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(); |
| //moved from Env.save() |
| // DataObject.setModified() already called as part of super.saveDocument(). The save action is now asynchronous |
| // in the IDE and super.saveDocument() checks for possible extra document modifications performed during save |
| // and sets the DO.modified flag accordingly. |
| // getDataObject().setModified (false); |
| } |
| 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() { |
| 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 |
| e.printStackTrace(); |
| } |
| } |
| }; |
| |
| NbDocument.runAtomic(doc, edit); |
| |
| super.saveDocument(); |
| //moved from Env.save() |
| // DataObject.setModified() already called as part of super.saveDocument(). The save action is now asynchronous |
| // in the IDE and super.saveDocument() checks for possible extra document modifications performed during save |
| // and sets the DO.modified flag accordingly. |
| // getDataObject().setModified (false); |
| } |
| 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 = RequestProcessor.getDefault().post(r, AUTO_PARSING_DELAY); |
| else |
| parsingDocumentTask = RequestProcessor.getDefault().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 |
| */ |
| protected boolean notifyModified () { |
| if (!super.notifyModified()) |
| return false; |
| |
| addSaveCookie(); |
| |
| return true; |
| } |
| |
| /** Overrides superclass method. Adds removing of save cookie. */ |
| protected void notifyUnmodified () { |
| super.notifyUnmodified(); |
| |
| removeSaveCookie(); |
| } |
| |
| /** Helper method. Adds save cookie to the data object. */ |
| private void addSaveCookie() { |
| StrutsConfigDataObject obj = (StrutsConfigDataObject)getDataObject(); |
| |
| // Adds save cookie to the data object. |
| if(obj.getCookie(SaveCookie.class) == null) { |
| obj.getCookieSet0().add(saveCookie); |
| obj.setModified(true); |
| } |
| } |
| |
| /** Helper method. Removes save cookie from the data object. */ |
| private void removeSaveCookie() { |
| StrutsConfigDataObject obj = (StrutsConfigDataObject)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); |
| } |
| } |
| |
| /** A description of the binding between the editor support and the object. |
| * Note this may be serialized as part of the window system and so |
| * should be static, and use the transient modifier where needed. |
| */ |
| 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 (StrutsConfigDataObject 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 ((StrutsConfigDataObject) 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 |
| */ |
| public org.openide.windows.CloneableOpenSupport findCloneableOpenSupport () { |
| return (StrutsConfigEditorSupport) getDataObject ().getCookie (StrutsConfigEditorSupport.class); |
| } |
| } |
| } |