1 19 20 package org.netbeans.modules.xml.multiview; 21 22 import java.io.ByteArrayOutputStream ; 23 import java.io.UnsupportedEncodingException ; 24 import java.lang.reflect.InvocationTargetException ; 25 import java.nio.charset.Charset ; 26 import java.nio.charset.CharsetEncoder ; 27 import javax.swing.SwingUtilities ; 28 import org.netbeans.core.api.multiview.MultiViewHandler; 29 import org.netbeans.core.api.multiview.MultiViews; 30 import org.netbeans.core.spi.multiview.CloseOperationHandler; 31 import org.netbeans.core.spi.multiview.CloseOperationState; 32 import org.netbeans.core.spi.multiview.MultiViewDescription; 33 import org.netbeans.core.spi.multiview.MultiViewElement; 34 import org.netbeans.core.spi.multiview.MultiViewFactory; 35 import org.netbeans.modules.xml.api.EncodingUtil; 36 import org.netbeans.modules.xml.multiview.ui.ToolBarDesignEditor; 37 import org.openide.DialogDisplayer; 38 import org.openide.NotifyDescriptor; 39 import org.openide.ErrorManager; 40 import org.openide.filesystems.FileUtil; 41 import org.openide.text.NbDocument; 42 import org.openide.util.Task; 43 import org.openide.util.RequestProcessor; 44 import org.openide.util.NbBundle; 45 import org.openide.cookies.EditCookie; 46 import org.openide.cookies.EditorCookie; 47 import org.openide.cookies.OpenCookie; 48 import org.openide.cookies.PrintCookie; 49 import org.openide.filesystems.FileLock; 50 import org.openide.filesystems.FileObject; 51 import org.openide.text.DataEditorSupport; 52 import org.openide.text.CloneableEditor; 53 import org.openide.windows.CloneableOpenSupport; 54 import org.openide.windows.CloneableTopComponent; 55 import org.openide.windows.Mode; 56 import org.openide.windows.TopComponent; 57 import org.openide.windows.WindowManager; 58 59 import javax.swing.text.StyledDocument ; 60 import javax.swing.text.Document ; 61 import javax.swing.text.BadLocationException ; 62 import javax.swing.text.EditorKit ; 63 import javax.swing.event.DocumentListener ; 64 import java.io.IOException ; 65 import java.io.Serializable ; 66 import java.io.OutputStream ; 67 import java.io.InputStream ; 68 import java.io.InputStreamReader ; 69 import java.io.OutputStreamWriter ; 70 import java.beans.PropertyChangeListener ; 71 import java.beans.PropertyChangeEvent ; 72 import java.util.Set ; 73 import java.util.Iterator ; 74 import java.util.Enumeration ; 75 import java.lang.*; 76 77 84 public class XmlMultiViewEditorSupport extends DataEditorSupport implements Serializable , EditCookie, OpenCookie, 85 EditorCookie.Observable, PrintCookie { 86 87 private XmlMultiViewDataObject dObj; 88 private DocumentListener docListener; 89 private int xmlMultiViewIndex; 90 private TopComponent mvtc; 91 private int lastOpenView = 0; 92 private TopComponentsListener topComponentsListener; 93 private MultiViewDescription[] multiViewDescriptions; 94 private XmlMultiViewEditorSupport.DocumentSynchronizer documentSynchronizer; 95 private int loading = 0; 96 private FileLock saveLock; 97 private static final String PROPERTY_MODIFICATION_LISTENER = "modificationListener"; 101 private boolean suppressXmlView = false; 102 103 public XmlMultiViewEditorSupport() { 104 super(null, null); 105 } 106 107 108 public XmlMultiViewEditorSupport(XmlMultiViewDataObject dObj) { 109 super(dObj, new XmlEnv(dObj)); 110 this.dObj = dObj; 111 documentSynchronizer = new DocumentSynchronizer(dObj); 112 113 setMIMEType("text/xml"); 116 docListener = new DocumentListener () { 117 public void changedUpdate(javax.swing.event.DocumentEvent e) { 118 doUpdate(); 119 } 120 121 public void insertUpdate(javax.swing.event.DocumentEvent e) { 122 doUpdate(); 123 } 124 125 public void removeUpdate(javax.swing.event.DocumentEvent e) { 126 doUpdate(); 127 } 128 129 private void doUpdate() { 130 if (saveLock == null) { 131 documentSynchronizer.requestUpdateData(); 132 } 133 } 134 }; 135 136 addPropertyChangeListener(new java.beans.PropertyChangeListener () { 138 public void propertyChange(java.beans.PropertyChangeEvent evt) { 139 if (EditorCookie.Observable.PROP_DOCUMENT.equals(evt.getPropertyName())) { 140 Document document = getDocument(); 141 if (document != null) { 142 document.addDocumentListener(docListener); 143 } 144 } 145 } 146 }); 147 148 149 } 150 151 153 org.openide.awt.UndoRedo getUndoRedo0() { 154 return super.getUndoRedo(); 155 } 156 157 public XmlEnv getXmlEnv() { 158 return (XmlEnv) env; 159 } 160 161 163 protected CloneableEditor createCloneableEditor() { 164 return super.createCloneableEditor(); 165 } 166 167 public InputStream getInputStream() throws IOException { 168 return super.getInputStream(); 169 } 170 171 protected Task reloadDocument() { 172 loading++; 173 documentSynchronizer.reloadingStarted(); 174 FileLock reloadLock; 175 try { 176 reloadLock = dObj.waitForLock(); 177 dObj.getDataCache().loadData(dObj.getPrimaryFile(), reloadLock); 178 } catch (IOException e) { 179 reloadLock = null; 180 ErrorManager.getDefault().notify(e); 181 } 182 final Task reloadDocumentTask = XmlMultiViewEditorSupport.super.reloadDocument(); 183 final FileLock lock = reloadLock; 184 RequestProcessor.getDefault().post(new Runnable () { 185 public void run() { 186 try { 187 if (!reloadDocumentTask.isFinished()) { 188 reloadDocumentTask.waitFinished(5000); 189 } 190 } catch (InterruptedException e) { 191 ErrorManager.getDefault().annotate(e, NbBundle.getMessage(XmlMultiViewEditorSupport.class, 192 "CANNOT_UPDATE_LOCKED_DATA_OBJECT")); 193 } finally { 194 if (lock != null) { 195 lock.releaseLock(); 196 } 197 documentSynchronizer.reloadingFinished(); 198 loading--; 199 } 200 } 201 }); 202 return reloadDocumentTask; 203 } 204 205 protected void loadFromStreamToKit(StyledDocument doc, InputStream stream, EditorKit kit) 206 throws IOException , BadLocationException { 207 kit.read(new InputStreamReader (stream, dObj.getEncodingHelper().getEncoding()), doc, 0); 208 } 209 210 protected void saveFromKitToStream(StyledDocument doc, EditorKit kit, OutputStream stream) 211 throws IOException , BadLocationException { 212 kit.write(new OutputStreamWriter (stream, dObj.getEncodingHelper().getEncoding()), doc, 0, doc.getLength()); 213 } 214 215 public StyledDocument openDocument() throws IOException { 216 dObj.getDataCache().getStringData(); 217 return super.openDocument(); 218 } 219 220 public void saveDocument() throws IOException { 221 if (loading > 0) { 222 return; 223 } 224 FileLock dataLock = ((XmlMultiViewDataObject) getDataObject()).waitForLock(); 225 try { 226 ((XmlMultiViewDataObject) getDataObject()).getDataCache().saveData(dataLock); 227 } finally { 228 dataLock.releaseLock(); 229 } 230 } 231 232 void saveDocument(FileLock dataLock) throws IOException { 233 if (saveLock != dataLock) { 234 saveLock = dataLock; 235 documentSynchronizer.reloadModel(); 236 try { 237 doSaveDocument(); 238 dObj.getDataCache().resetFileTime(); 239 } finally { 240 saveLock = null; 241 } 242 } 243 } 244 245 private void doSaveDocument() throws IOException { 246 248 final StyledDocument doc = getDocument(); 249 String enc = EncodingUtil.detectEncoding(doc); 251 if (enc == null) enc = "UTF8"; 253 try { 254 new OutputStreamWriter (new ByteArrayOutputStream (1), enc); 256 if (!checkCharsetConversion(enc)) { 257 return; 258 } 259 super.saveDocument(); 260 getDataObject().setModified(false); 262 } catch (UnsupportedEncodingException ex) { 263 String message = NbBundle.getMessage(XmlMultiViewEditorSupport.class,"TEXT_SAVE_AS_UTF", enc); 265 NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation(message); 266 Object res = DialogDisplayer.getDefault().notify(descriptor); 267 268 if (res.equals(NotifyDescriptor.YES_OPTION)) { 269 270 272 try { 273 final int MAX_PROLOG = 1000; 274 int maxPrologLen = Math.min(MAX_PROLOG, doc.getLength()); 275 final char prolog[] = doc.getText(0, maxPrologLen).toCharArray(); 276 int prologLen = 0; 278 if (prolog[0] == '<' && prolog[1] == '?' && prolog[2] == 'x') { 280 281 for (int i = 3; i<maxPrologLen; i++) { 283 if (prolog[i] == '?' && prolog[i+1] == '>') { 284 prologLen = i + 1; 285 break; 286 } 287 } 288 } 289 290 final int passPrologLen = prologLen; 291 292 Runnable edit = new Runnable () { 293 public void run() { 294 try { 295 296 doc.remove(0, passPrologLen + 1); doc.insertString(0, "<?xml version='1.0' encoding='UTF-8' ?> \n<!-- was: " + new String (prolog, 0, passPrologLen + 1) + " -->", null); 299 } catch (BadLocationException e) { 300 if (System.getProperty("netbeans.debug.exceptions") != null) e.printStackTrace(); 302 } 303 } 304 }; 305 306 NbDocument.runAtomic(doc, edit); 307 308 super.saveDocument(); 309 getDataObject().setModified(false); 311 ((XmlMultiViewDataObject) getDataObject()).getDataCache().reloadData(); 313 314 315 } catch (BadLocationException lex) { 316 ErrorManager.getDefault().notify(lex); 317 } 318 319 } else { return; 321 } 322 } 323 } 324 325 private boolean checkCharsetConversion(final String encoding) { 326 boolean value = true; 327 try { 328 CharsetEncoder coder = Charset.forName(encoding).newEncoder(); 329 if (!coder.canEncode(getDocument().getText(0, getDocument().getLength()))){ 330 NotifyDescriptor nd = new NotifyDescriptor.Confirmation( 331 NbBundle.getMessage(XmlMultiViewEditorSupport.class, "MSG_BadCharConversion", 332 new Object [] { getDataObject().getPrimaryFile().getNameExt(), 333 encoding}), 334 NotifyDescriptor.YES_NO_OPTION, 335 NotifyDescriptor.WARNING_MESSAGE); 336 nd.setValue(NotifyDescriptor.NO_OPTION); 337 DialogDisplayer.getDefault().notify(nd); 338 if(nd.getValue() != NotifyDescriptor.YES_OPTION) { 339 value = false; 340 } 341 } 342 } catch (BadLocationException e){ 343 ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); 344 } 345 return value; 346 } 347 348 protected CloneableTopComponent createCloneableTopComponent() { 349 MultiViewDescription[] descs = getMultiViewDescriptions(); 350 351 CloneableTopComponent mvtc = 352 MultiViewFactory.createCloneableMultiView(descs, descs[0], new MyCloseHandler(dObj)); 353 354 Mode editorMode = WindowManager.getDefault().findMode(org.openide.text.CloneableEditorSupport.EDITOR_MODE); 356 357 if (editorMode != null) { 358 editorMode.dockInto(mvtc); 359 } 360 this.mvtc = mvtc; 361 return mvtc; 362 } 363 364 public MultiViewDescription[] getMultiViewDescriptions() { 365 if (multiViewDescriptions == null) { 366 if (suppressXmlView) { 367 multiViewDescriptions = dObj.getMultiViewDesc(); 368 xmlMultiViewIndex = 0; 369 } else { 370 MultiViewDescription[] customDesc = dObj.getMultiViewDesc(); 371 MultiViewDescription xmlDesc = new XmlViewDesc(dObj); 372 373 multiViewDescriptions = new MultiViewDescription[customDesc.length + 1]; 374 System.arraycopy(customDesc, 0, multiViewDescriptions, 0, customDesc.length); 375 multiViewDescriptions[customDesc.length] = xmlDesc; 376 xmlMultiViewIndex = customDesc.length; 377 } 378 } 379 return multiViewDescriptions; 380 } 381 382 public void setSuppressXmlView(boolean suppressXmlView) { 383 this.suppressXmlView = suppressXmlView; 384 multiViewDescriptions = null; 385 } 386 387 391 public void edit() { 392 openView(-1); 393 } 394 395 400 void openView(final int index) { 401 Utils.runInAwtDispatchThread(new Runnable () { 402 public void run() { 403 CloneableTopComponent mvtc = openCloneableTopComponent(); 404 MultiViewHandler handler = MultiViews.findMultiViewHandler(mvtc); 405 handler.requestVisible(handler.getPerspectives()[index < 0 ? xmlMultiViewIndex : index]); 406 mvtc.requestActive(); 407 } 408 }); 409 try { 410 openDocument(); 411 } catch (IOException ex) { 412 ErrorManager.getDefault().notify(ex); 413 } 414 } 415 416 418 public void open() { 419 openView(lastOpenView); 420 } 421 422 void goToXmlPerspective() { 423 Utils.runInAwtDispatchThread(new Runnable () { 424 public void run() { 425 MultiViewHandler handler = MultiViews.findMultiViewHandler(mvtc); 426 handler.requestVisible(handler.getPerspectives()[xmlMultiViewIndex]); 427 } 428 }); 429 } 430 433 protected void notifyClosed() { 434 mvtc = null; 435 if (topComponentsListener != null) { 436 TopComponent.getRegistry().removePropertyChangeListener(topComponentsListener); 437 topComponentsListener = null; 438 } 439 Document document = getDocument(); 440 if (document!=null) document.removeDocumentListener(docListener); 441 super.notifyClosed(); 442 } 443 444 org.netbeans.core.api.multiview.MultiViewPerspective getSelectedPerspective() { 445 if (mvtc != null) { 446 return MultiViews.findMultiViewHandler(mvtc).getSelectedPerspective(); 447 } 448 return null; 449 } 450 451 454 public void updateDisplayName() { 455 if (mvtc != null) { 456 Utils.runInAwtDispatchThread(new Runnable () { 457 public void run() { 458 String displayName = messageName(); 459 if (!displayName.equals(mvtc.getDisplayName())) { 460 mvtc.setDisplayName(displayName); 461 } 462 mvtc.setToolTipText(FileUtil.getFileDisplayName(dObj.getPrimaryFile())); 463 } 464 }); 465 } 466 } 467 468 469 473 public static class XmlEnv extends DataEditorSupport.Env { 474 475 private static final long serialVersionUID = 2882981960507292985L; private final XmlMultiViewDataObject xmlMultiViewDataObject; 477 478 481 public XmlEnv(XmlMultiViewDataObject obj) { 482 super(obj); 483 xmlMultiViewDataObject = obj; 484 changeFile(); 485 } 486 487 490 protected FileObject getFile() { 491 return xmlMultiViewDataObject.getPrimaryFile(); 492 } 493 494 500 protected FileLock takeLock() throws IOException { 501 return xmlMultiViewDataObject.getPrimaryEntry().takeLock(); 502 } 503 504 505 510 public CloneableOpenSupport findCloneableOpenSupport() { 511 return xmlMultiViewDataObject.getEditorSupport(); 512 } 513 514 public InputStream inputStream() throws IOException { 515 return xmlMultiViewDataObject.getDataCache().createInputStream(); 516 } 517 518 protected OutputStream getFileOutputStream() throws IOException { 519 return super.outputStream(); 520 } 521 522 public OutputStream outputStream() throws IOException { 523 if (xmlMultiViewDataObject.getEditorSupport().saveLock != null) { 524 return super.outputStream(); 525 } else { 526 return xmlMultiViewDataObject.getDataCache().createOutputStream(); 527 } 528 } 529 530 public boolean isModified() { 531 return super.isModified(); 532 } 533 } 534 535 private static class XmlViewDesc implements MultiViewDescription, java.io.Serializable { 536 537 private static final long serialVersionUID = 8085725367398466167L; 538 XmlMultiViewDataObject dObj; 539 540 XmlViewDesc() { 541 } 542 543 XmlViewDesc(XmlMultiViewDataObject dObj) { 544 this.dObj = dObj; 545 } 546 547 public MultiViewElement createElement() { 548 return new XmlMultiViewElement(dObj); 549 } 550 551 public String getDisplayName() { 552 return org.openide.util.NbBundle.getMessage(XmlMultiViewEditorSupport.class, "LBL_XML_TAB"); 553 } 554 555 public org.openide.util.HelpCtx getHelpCtx() { 556 return dObj.getHelpCtx(); 557 } 558 559 public java.awt.Image getIcon() { 560 return dObj.getXmlViewIcon(); 561 } 562 563 public int getPersistenceType() { 564 return TopComponent.PERSISTENCE_ONLY_OPENED; 565 } 566 567 public String preferredID() { 568 return "multiview_xml"; } 570 } 571 572 public TopComponent getMVTC() { 573 return mvtc; 574 } 575 576 void setMVTC(TopComponent mvtc) { 577 this.mvtc = mvtc; 578 if (topComponentsListener == null) { 579 topComponentsListener = new TopComponentsListener(); 580 TopComponent.getRegistry().addPropertyChangeListener(topComponentsListener); 581 } 582 } 583 584 void setLastOpenView(int index) { 585 lastOpenView = index; 586 } 587 588 private class DocumentSynchronizer extends XmlMultiViewDataSynchronizer { 589 590 private final RequestProcessor.Task reloadUpdatedTask = requestProcessor.create(new Runnable () { 591 public void run() { 592 Document document = getDocument(); 593 DocumentListener listener = document == null ? null : 594 (DocumentListener ) document.getProperty(PROPERTY_MODIFICATION_LISTENER); 595 if (listener != null) { 596 document.removeDocumentListener(listener); 597 } 598 try { 599 reloadModel(); 600 } finally { 601 if (listener != null) { 602 document.addDocumentListener(listener); 603 } 604 } 605 } 606 }); 607 608 609 public DocumentSynchronizer(XmlMultiViewDataObject dataObject) { 610 super(dataObject, 100); 611 getXmlEnv().addPropertyChangeListener(new PropertyChangeListener () { 612 public void propertyChange(PropertyChangeEvent evt) { 613 String propertyName = evt.getPropertyName(); 614 if (Env.PROP_TIME.equals(propertyName) && getDocument() == null) { 615 dObj.getDataCache().loadData(); 616 } 617 } 618 }); 619 } 620 621 protected boolean mayUpdateData(boolean allowDialog) { 622 return true; 623 } 624 625 protected void dataUpdated(long timeStamp) { 626 if (loading == 0) { 627 reloadUpdatedTask.schedule(0); 628 } 629 } 630 631 protected Object getModel() { 632 return getDocument(); 633 } 634 635 protected void updateDataFromModel(Object model, final FileLock lock, final boolean modify) { 636 final Document doc = (Document) model; 637 if (doc == null) { 638 try { 639 dObj.getDataCache().setData(lock, "", modify); } catch (IOException e) { 641 ErrorManager.getDefault().notify(e); 642 } 643 } else { 644 doc.render(new Runnable () { 646 public void run() { 647 try { 648 dObj.getDataCache().setData(lock, doc.getText(0, doc.getLength()), modify); 649 } catch (BadLocationException e) { 650 } catch (IOException e) { 652 ErrorManager.getDefault().notify(e); 653 } 654 } 655 }); 656 } 657 } 658 659 protected void reloadModelFromData() { 660 if (loading == 0) { 661 Utils.replaceDocument((StyledDocument )getDocument(), dObj.getDataCache().getStringData()); 662 } 663 } 664 } 665 666 static class MyCloseHandler implements CloseOperationHandler, java.io.Serializable { 667 static final long serialVersionUID = -6512103928294991474L; 668 private XmlMultiViewDataObject dObj; 669 MyCloseHandler() { 670 } 671 672 MyCloseHandler(XmlMultiViewDataObject dObj) { 673 this.dObj = dObj; 674 } 675 676 public boolean resolveCloseOperation(CloseOperationState[] elements) { 677 for (int i = 0; i < elements.length; i++) { 678 CloseOperationState element = elements[i]; 679 if (ToolBarDesignEditor.PROPERTY_FLUSH_DATA.equals(element.getCloseWarningID())) { 680 return false; 681 } 682 } 683 if (dObj.isModified()) { 684 XmlMultiViewEditorSupport support = dObj.getEditorSupport(); 685 String msg = support.messageSave(); 686 687 java.util.ResourceBundle bundle = 688 org.openide.util.NbBundle.getBundle(org.openide.text.CloneableEditorSupport.class); 689 690 javax.swing.JButton saveOption = new javax.swing.JButton (bundle.getString("CTL_Save")); saveOption.getAccessibleContext().setAccessibleDescription(bundle.getString("ACSD_CTL_Save")); saveOption.getAccessibleContext().setAccessibleName(bundle.getString("ACSN_CTL_Save")); javax.swing.JButton discardOption = new javax.swing.JButton (bundle.getString("CTL_Discard")); discardOption.getAccessibleContext() 695 .setAccessibleDescription(bundle.getString("ACSD_CTL_Discard")); discardOption.getAccessibleContext().setAccessibleName(bundle.getString("ACSN_CTL_Discard")); discardOption.setMnemonic(bundle.getString("CTL_Discard_Mnemonic").charAt(0)); 699 NotifyDescriptor nd = new NotifyDescriptor( 700 msg, 701 bundle.getString("LBL_SaveFile_Title"), 702 NotifyDescriptor.YES_NO_CANCEL_OPTION, 703 NotifyDescriptor.QUESTION_MESSAGE, 704 new Object []{saveOption, discardOption, NotifyDescriptor.CANCEL_OPTION}, 705 saveOption 706 ); 707 708 Object ret = org.openide.DialogDisplayer.getDefault().notify(nd); 709 710 if (NotifyDescriptor.CANCEL_OPTION.equals(ret) || NotifyDescriptor.CLOSED_OPTION.equals(ret)) { 711 return false; 712 } 713 714 if (saveOption.equals(ret)) { 715 try { 716 if (dObj.acceptEncoding()) { 717 support.saveDocument(); 718 } else { 719 return false; 720 } 721 } catch (java.io.IOException e) { 722 org.openide.ErrorManager.getDefault().notify(e); 723 return false; 724 } 725 } else if (discardOption.equals(ret)) { 726 dObj.getEditorSupport().reloadDocument().waitFinished(); 727 support.notifyClosed(); 728 } 729 } 730 return true; 731 } 732 } 733 734 protected String messageName() { 736 return super.messageName(); 737 } 738 739 private class TopComponentsListener implements PropertyChangeListener { 740 public void propertyChange(PropertyChangeEvent evt) { 741 if (TopComponent.Registry.PROP_OPENED.equals(evt.getPropertyName())) { 742 Set closed = ((Set ) evt.getOldValue()); 744 closed.removeAll((Set ) evt.getNewValue()); 745 for (Iterator iterator = closed.iterator(); iterator.hasNext();) { 746 Object o = iterator.next(); 747 if (o instanceof CloneableTopComponent) { 748 final CloneableTopComponent topComponent = (CloneableTopComponent) o; 749 Enumeration en = topComponent.getReference().getComponents(); 750 if (mvtc == topComponent) { 751 if (en.hasMoreElements()) { 752 mvtc = (CloneableTopComponent) en.nextElement(); 754 } else { 755 notifyClosed(); 757 } 758 } 759 } 760 } 761 } 762 } 763 } 764 } 765 | Popular Tags |