001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.web.jsf;
043:
044: import java.beans.PropertyChangeEvent;
045: import java.beans.PropertyChangeListener;
046: import java.io.IOException;
047: import java.io.Serializable;
048: import java.util.ArrayList;
049: import java.util.logging.Level;
050: import java.util.logging.Logger;
051: import javax.swing.SwingUtilities;
052: import javax.swing.event.DocumentEvent;
053: import javax.swing.event.DocumentListener;
054: import javax.swing.text.BadLocationException;
055: import org.netbeans.core.api.multiview.MultiViewHandler;
056: import org.netbeans.core.api.multiview.MultiViews;
057: import org.netbeans.core.spi.multiview.CloseOperationHandler;
058: import org.netbeans.core.spi.multiview.CloseOperationState;
059: import org.netbeans.core.spi.multiview.MultiViewDescription;
060: import org.netbeans.core.spi.multiview.MultiViewFactory;
061: import org.netbeans.modules.web.jsf.api.ConfigurationUtils;
062: import org.netbeans.modules.web.jsf.api.editor.JSFConfigEditorContext;
063: import org.netbeans.modules.web.jsf.api.facesmodel.JSFConfigModel;
064: import org.netbeans.modules.xml.api.EncodingUtil;
065: import org.openide.DialogDescriptor;
066: import org.openide.DialogDisplayer;
067: import org.openide.NotifyDescriptor;
068: import org.openide.awt.UndoRedo;
069: import org.openide.filesystems.FileLock;
070: import org.openide.filesystems.FileObject;
071: import org.openide.nodes.Node.Cookie;
072: import org.openide.text.CloneableEditor;
073: import org.openide.text.CloneableEditorSupport.Pane;
074: import org.openide.text.DataEditorSupport;
075: import org.openide.cookies.*;
076: import org.openide.text.CloneableEditorSupport;
077: import org.openide.text.NbDocument;
078: import org.openide.util.Exceptions;
079: import org.openide.util.NbBundle;
080: import org.openide.util.RequestProcessor;
081: import org.openide.windows.CloneableTopComponent;
082: import org.openide.windows.TopComponent;
083:
084: /**
085: *
086: * @author Petr Pisl
087: */
088: public class JSFConfigEditorSupport extends DataEditorSupport implements
089: OpenCookie, EditCookie, EditorCookie.Observable, PrintCookie,
090: CloseCookie {
091:
092: /** SaveCookie for this support instance. The cookie is adding/removing
093: * data object's cookie set depending on if modification flag was set/unset. */
094: private final SaveCookie saveCookie = new SaveCookie() {
095: /** Implements <code>SaveCookie</code> interface. */
096: public void save() throws java.io.IOException {
097: JSFConfigDataObject obj = (JSFConfigDataObject) getDataObject();
098: // invoke parsing before save
099: restartTimer();
100: obj.parsingDocument();
101:
102: if (obj.isDocumentValid()) {
103: saveDocument();
104: } else {
105: //obj.displayErrorMessage();
106: //StatusDisplayer.getDefault().setStatusText("");
107: DialogDescriptor dialog = new DialogDescriptor(NbBundle
108: .getMessage(JSFConfigEditorSupport.class,
109: "MSG_invalidXmlWarning"), NbBundle
110: .getMessage(JSFConfigEditorSupport.class,
111: "TTL_invalidXmlWarning"));
112: java.awt.Dialog d = org.openide.DialogDisplayer
113: .getDefault().createDialog(dialog);
114: d.setVisible(true);
115: if (dialog.getValue() == org.openide.DialogDescriptor.OK_OPTION) {
116: saveDocument();
117: }
118: /*else {
119: RequestProcessor.getDefault().post(new Runnable() {
120: public void run(){
121: StatusDisplayer.getDefault().setStatusText("");
122: }
123: },100);
124: }*/
125: }
126: }
127: };
128: private JSFConfigDataObject dataObject;
129: private RequestProcessor.Task parsingDocumentTask;
130: private TopComponent mvtc;
131:
132: /** Delay for automatic parsing - in miliseconds */
133: private static final int AUTO_PARSING_DELAY = 2000;
134:
135: public JSFConfigEditorSupport(JSFConfigDataObject dobj) {
136: super (dobj, new XmlEnv(dobj));
137: dataObject = dobj;
138: setMIMEType("text/x-jsf+xml"); //NOI18N
139:
140: //initialize the listeners on the document
141: initialize();
142: }
143:
144: @Override
145: protected Pane createPane() {
146: JSFConfigEditorContext context = new JSFConfigEditorContextImpl(
147: (JSFConfigDataObject) getDataObject());
148: ArrayList<MultiViewDescription> descriptions = new ArrayList<MultiViewDescription>(
149: JSFConfigEditorViewFactorySupport
150: .createViewDescriptions(context));
151: if (descriptions.size() > 0) {
152: descriptions.add(new JSFConfigMultiviewDescriptor(context));
153: return (CloneableEditorSupport.Pane) MultiViewFactory
154: .createCloneableMultiView(
155: descriptions
156: .toArray(new MultiViewDescription[descriptions
157: .size()]),
158: descriptions.get(0),
159: new CloseHandler(
160: (JSFConfigDataObject) getDataObject()));
161: } else {
162: return super .createPane();
163: }
164: }
165:
166: @Override
167: protected void initializeCloneableEditor(CloneableEditor editor) {
168: super .initializeCloneableEditor(editor);
169: }
170:
171: @Override
172: protected CloneableTopComponent createCloneableTopComponent() {
173: CloneableTopComponent tc = super .createCloneableTopComponent();
174: this .mvtc = tc;
175: updateDisplayName();
176: return tc;
177: }
178:
179: public UndoRedo.Manager getUndoRedoManager() {
180: return super .getUndoRedo();
181: }
182:
183: protected void setMVTC(TopComponent mvtc) {
184: this .mvtc = mvtc;
185: updateDisplayName();
186: }
187:
188: private int click = 0;
189:
190: public void updateDisplayName() {
191:
192: final TopComponent tc = mvtc;
193: if (tc == null)
194: return;
195:
196: SwingUtilities.invokeLater(new Runnable() {
197: public void run() {
198: String displayName = messageName();
199:
200: if (!displayName.equals(tc.getDisplayName())) {
201: tc.setDisplayName(displayName);
202: }
203: tc
204: .setToolTipText(dataObject.getPrimaryFile()
205: .getPath());
206: }
207: });
208: }
209:
210: private void initialize() {
211: // Create DocumentListener
212: final DocumentListener docListener = new DocumentListener() {
213: public void insertUpdate(DocumentEvent e) {
214: change(e);
215: }
216:
217: public void changedUpdate(DocumentEvent e) {
218: }
219:
220: public void removeUpdate(DocumentEvent e) {
221: change(e);
222: }
223:
224: private void change(DocumentEvent e) {
225: if (!dataObject.isNodeDirty())
226: restartTimer();
227: }
228: };
229: // the listener add only when the document is move to memory
230: addPropertyChangeListener(new PropertyChangeListener() {
231: public void propertyChange(PropertyChangeEvent evt) {
232: if (EditorCookie.Observable.PROP_DOCUMENT.equals(evt
233: .getPropertyName())
234: && isDocumentLoaded() && getDocument() != null) {
235: getDocument().addDocumentListener(docListener);
236: }
237: }
238: });
239: }
240:
241: /*
242: * Save document using encoding declared in XML prolog if possible otherwise
243: * at UTF-8 (in such case it updates the prolog).
244: */
245: @Override
246: public void saveDocument() throws java.io.IOException {
247: final javax.swing.text.StyledDocument doc = getDocument();
248: String defaultEncoding = "UTF-8"; // NOI18N
249: // dependency on xml/core
250: String enc = EncodingUtil.detectEncoding(doc);
251: boolean changeEncodingToDefault = false;
252: if (enc == null)
253: enc = defaultEncoding;
254:
255: //test encoding
256: if (!isSupportedEncoding(enc)) {
257: NotifyDescriptor nd = new NotifyDescriptor.Confirmation(
258: NbBundle.getMessage(JSFConfigEditorSupport.class,
259: "MSG_BadEncodingDuringSave", //NOI18N
260: new Object[] {
261: getDataObject().getPrimaryFile()
262: .getNameExt(), enc,
263: defaultEncoding }),
264: NotifyDescriptor.YES_NO_OPTION,
265: NotifyDescriptor.WARNING_MESSAGE);
266: nd.setValue(NotifyDescriptor.NO_OPTION);
267: DialogDisplayer.getDefault().notify(nd);
268: if (nd.getValue() != NotifyDescriptor.YES_OPTION)
269: return;
270: changeEncodingToDefault = true;
271: }
272:
273: if (!changeEncodingToDefault) {
274: // is it possible to save the document in the encoding?
275: try {
276: java.nio.charset.CharsetEncoder coder = java.nio.charset.Charset
277: .forName(enc).newEncoder();
278: if (!coder.canEncode(doc.getText(0, doc.getLength()))) {
279: NotifyDescriptor nd = new NotifyDescriptor.Confirmation(
280: NbBundle
281: .getMessage(
282: JSFConfigEditorSupport.class,
283: "MSG_BadCharConversion", //NOI18N
284: new Object[] {
285: getDataObject()
286: .getPrimaryFile()
287: .getNameExt(),
288: enc }),
289: NotifyDescriptor.YES_NO_OPTION,
290: NotifyDescriptor.WARNING_MESSAGE);
291: nd.setValue(NotifyDescriptor.NO_OPTION);
292: DialogDisplayer.getDefault().notify(nd);
293: if (nd.getValue() != NotifyDescriptor.YES_OPTION)
294: return;
295: }
296: } catch (javax.swing.text.BadLocationException e) {
297: Logger.getLogger("global").log(Level.INFO, null, e);
298: }
299: super .saveDocument();
300: //moved from Env.save()
301: getDataObject().setModified(false);
302: } else {
303: // update prolog to new valid encoding
304:
305: try {
306: final int MAX_PROLOG = 1000;
307: int maxPrologLen = Math
308: .min(MAX_PROLOG, doc.getLength());
309: final char prolog[] = doc.getText(0, maxPrologLen)
310: .toCharArray();
311: int prologLen = 0; // actual prolog length
312:
313: //parse prolog and get prolog end
314: if (prolog[0] == '<' && prolog[1] == '?'
315: && prolog[2] == 'x') {
316:
317: // look for delimitting ?>
318: for (int i = 3; i < maxPrologLen; i++) {
319: if (prolog[i] == '?' && prolog[i + 1] == '>') {
320: prologLen = i + 1;
321: break;
322: }
323: }
324: }
325:
326: final int passPrologLen = prologLen;
327:
328: Runnable edit = new Runnable() {
329: public void run() {
330: try {
331:
332: doc.remove(0, passPrologLen + 1); // +1 it removes exclusive
333: doc.insertString(0,
334: "<?xml version='1.0' encoding='UTF-8' ?> \n<!-- was: "
335: + new String(prolog, 0,
336: passPrologLen + 1)
337: + " -->", null); // NOI18N
338:
339: } catch (BadLocationException e) {
340: if (System
341: .getProperty("netbeans.debug.exceptions") != null) // NOI18N
342: e.printStackTrace();
343: }
344: }
345: };
346:
347: NbDocument.runAtomic(doc, edit);
348:
349: super .saveDocument();
350: //moved from Env.save()
351: getDataObject().setModified(false);
352: } catch (javax.swing.text.BadLocationException e) {
353: Logger.getLogger("global").log(Level.INFO, null, e);
354: }
355: }
356: }
357:
358: private boolean isSupportedEncoding(String encoding) {
359: boolean supported;
360: try {
361: supported = java.nio.charset.Charset.isSupported(encoding);
362: } catch (java.nio.charset.IllegalCharsetNameException e) {
363: supported = false;
364: }
365:
366: return supported;
367: }
368:
369: /** Restart the timer which starts the parser after the specified delay.
370: * @param onlyIfRunning Restarts the timer only if it is already running
371: */
372: public void restartTimer() {
373: if (parsingDocumentTask == null
374: || parsingDocumentTask.isFinished()
375: || parsingDocumentTask.cancel()) {
376: dataObject.setDocumentDirty(true);
377: Runnable r = new Runnable() {
378: public void run() {
379: dataObject.parsingDocument();
380: }
381: };
382: if (parsingDocumentTask != null)
383: parsingDocumentTask = RequestProcessor.getDefault()
384: .post(r, AUTO_PARSING_DELAY);
385: else
386: parsingDocumentTask = RequestProcessor.getDefault()
387: .post(r, 100);
388: }
389: }
390:
391: /**
392: * Overrides superclass method. Adds adding of save cookie if the document has been marked modified.
393: * @return true if the environment accepted being marked as modified
394: * or false if it has refused and the document should remain unmodified
395: */
396: @Override
397: protected boolean notifyModified() {
398: boolean notif = super .notifyModified();
399: if (!notif) {
400: return false;
401: }
402: updateDisplayName();
403: addSaveCookie();
404: return true;
405: }
406:
407: @Override
408: protected void notifyClosed() {
409: mvtc = null;
410: super .notifyClosed();
411: try {
412: // synchronize the model with the document. See issue #116315
413: JSFConfigModel configModel = ConfigurationUtils
414: .getConfigModel(dataObject.getPrimaryFile(), true);
415: if (configModel != null) {
416: // the model can be null, if the file wasn't opened.
417: configModel.sync();
418: }
419: } catch (IOException ex) {
420: Exceptions.printStackTrace(ex);
421: }
422:
423: }
424:
425: /** Overrides superclass method. Adds removing of save cookie. */
426: @Override
427: protected void notifyUnmodified() {
428: super .notifyUnmodified();
429: updateDisplayName();
430: removeSaveCookie();
431: }
432:
433: /** Helper method. Adds save cookie to the data object. */
434: private void addSaveCookie() {
435: // Adds save cookie to the data object.
436: if (dataObject.getCookie(SaveCookie.class) == null) {
437: dataObject.getCookieSet0().add(saveCookie);
438: dataObject.setModified(true);
439: }
440: }
441:
442: /** Helper method. Removes save cookie from the data object. */
443: private void removeSaveCookie() {
444: JSFConfigDataObject obj = (JSFConfigDataObject) getDataObject();
445:
446: // Remove save cookie from the data object.
447: Cookie cookie = obj.getCookie(SaveCookie.class);
448:
449: if (cookie != null && cookie.equals(saveCookie)) {
450: obj.getCookieSet0().remove(saveCookie);
451: obj.setModified(false);
452: }
453: }
454:
455: @Override
456: public void open() {
457: super .open();
458: // parse once after opening the document
459: restartTimer();
460: updateDisplayName();
461: }
462:
463: @Override
464: public void edit() {
465: // open the top component
466: open();
467:
468: // ask for opening the last (source) editor
469: runInAwtDispatchThread(new Runnable() {
470: public void run() {
471: MultiViewHandler handler = MultiViews
472: .findMultiViewHandler(mvtc);
473: // The handler can be null, when user uninstall a module, which
474: // provides view that was opened last time
475: if (handler != null) {
476: handler
477: .requestVisible(handler.getPerspectives()[handler
478: .getPerspectives().length - 1]);
479: mvtc.requestActive();
480: }
481: }
482: });
483: }
484:
485: private static void runInAwtDispatchThread(Runnable runnable) {
486: if (SwingUtilities.isEventDispatchThread()) {
487: runnable.run();
488: } else {
489: SwingUtilities.invokeLater(runnable);
490: }
491:
492: }
493:
494: /** Implementation of CloseOperationHandler for multiview.
495: */
496: private static class CloseHandler implements CloseOperationHandler,
497: Serializable {
498: private static final long serialVersionUID = 1L;
499:
500: private JSFConfigDataObject dataObject;
501:
502: private CloseHandler() {
503: }
504:
505: public CloseHandler(JSFConfigDataObject facesConfig) {
506: dataObject = facesConfig;
507: }
508:
509: public boolean resolveCloseOperation(
510: CloseOperationState[] elements) {
511: boolean can = dataObject.getEditorSupport().canClose();
512: if (can)
513: dataObject.getEditorSupport().notifyClosed();
514: return can;
515: }
516: }
517:
518: private static class XmlEnv extends DataEditorSupport.Env {
519:
520: private static final long serialVersionUID = -800036748848958489L;
521:
522: //private static final long serialVersionUID = ...L;
523:
524: /** Create a new environment based on the data object.
525: * @param obj the data object to edit
526: */
527: public XmlEnv(JSFConfigDataObject obj) {
528: super (obj);
529: }
530:
531: /** Get the file to edit.
532: * @return the primary file normally
533: */
534: protected FileObject getFile() {
535: return getDataObject().getPrimaryFile();
536: }
537:
538: /** Lock the file to edit.
539: * Should be taken from the file entry if possible, helpful during
540: * e.g. deletion of the file.
541: * @return a lock on the primary file normally
542: * @throws IOException if the lock could not be taken
543: */
544: protected FileLock takeLock() throws java.io.IOException {
545: return ((JSFConfigDataObject) getDataObject())
546: .getPrimaryEntry().takeLock();
547: }
548:
549: /** Find the editor support this environment represents.
550: * Note that we have to look it up, as keeping a direct
551: * reference would not permit this environment to be serialized.
552: * @return the editor support
553: */
554: @Override
555: public org.openide.windows.CloneableOpenSupport findCloneableOpenSupport() {
556: return (JSFConfigEditorSupport) getDataObject().getCookie(
557: JSFConfigEditorSupport.class);
558: }
559: }
560: }
|