1 19 package org.netbeans.modules.xml.core.text; 20 21 import java.io.*; 22 import java.net.URL ; 23 import java.awt.event.*; 24 import java.text.*; 25 import java.util.Enumeration ; 26 import java.lang.ref.WeakReference ; 27 import java.beans.PropertyChangeListener ; 28 import java.beans.PropertyChangeEvent ; 29 30 import javax.swing.Timer ; 31 import javax.swing.JEditorPane ; 32 import javax.swing.event.*; 33 import javax.swing.text.*; 34 import javax.swing.JViewport ; 35 36 import org.openide.*; 37 import org.openide.awt.StatusDisplayer; 38 import org.openide.text.*; 39 import org.openide.util.*; 40 import org.openide.windows.CloneableTopComponent; 41 import org.openide.windows.CloneableOpenSupport; 42 import org.openide.windows.Workspace; 43 import org.openide.windows.Mode; 44 import org.openide.loaders.*; 45 import org.openide.cookies.*; 46 import org.openide.nodes.*; 47 import org.openide.filesystems.FileObject; 48 import org.openide.filesystems.FileLock; 49 50 import org.netbeans.modules.xml.core.*; 51 import org.netbeans.modules.xml.core.lib.*; 52 import org.netbeans.modules.xml.core.sync.*; 53 import org.netbeans.modules.xml.core.cookies.*; 54 55 import org.netbeans.modules.xml.core.settings.CoreSettings; 56 57 65 public class TextEditorSupport extends DataEditorSupport implements EditorCookie.Observable, EditCookie, CloseCookie, PrintCookie { 66 69 72 public static final String PROP_DOCUMENT_URL = "doc-url"; 73 74 75 76 static final CoreSettings settings = CoreSettings.getDefault(); 77 78 79 private Timer timer; 80 81 82 private static java.awt.Container awtLock; 83 84 85 private Representation rep; 87 88 92 93 protected TextEditorSupport(XMLDataObjectLook xmlDO, Env env, String mime_type) { 94 super((DataObject)xmlDO, env); 95 96 setMIMEType(mime_type); 97 98 initTimer(); 99 100 initListeners(); 101 102 103 } 121 122 123 public TextEditorSupport(XMLDataObjectLook xmlDO, String mime_type) { 124 this(xmlDO, new Env(xmlDO), mime_type); 125 } 126 127 128 132 135 private void initTimer() { 136 timer = new Timer (0, new java.awt.event.ActionListener () { 137 public void actionPerformed(java.awt.event.ActionEvent e) { 139 if ( Util.THIS.isLoggable() ) Util.THIS.debug("$$ TextEditorSupport::initTimer::actionPerformed: event = " + e); 140 141 RequestProcessor.postRequest( new Runnable () { 142 public void run() { 143 syncDocument(false); 144 } 145 }); 146 } 147 }); 148 149 timer.setInitialDelay(settings.getAutoParsingDelay()); 150 timer.setRepeats(false); 151 } 152 153 154 157 private void initListeners() { 158 159 161 final DocumentListener docListener = new DocumentListener() { 162 public void insertUpdate(DocumentEvent e) { 163 if ( Util.THIS.isLoggable() ) Util.THIS.debug("** TextEditorSupport::DocumentListener::insertUpdate: event = " + e); 164 165 restartTime(); 166 } 167 168 public void changedUpdate(DocumentEvent e) { 169 if ( Util.THIS.isLoggable() ) Util.THIS.debug("** TextEditorSupport::DocumentListener::changedUpdate: event = " + e); 170 171 } 173 174 public void removeUpdate(DocumentEvent e) { 175 if ( Util.THIS.isLoggable() ) Util.THIS.debug("** TextEditorSupport::DocumentListener::removeUpdate: event = " + e); 176 177 restartTime(); 178 } 179 180 private void restartTime() { 181 if ( Util.THIS.isLoggable() ) Util.THIS.debug("** TextEditorSupport::DocumentListener::restartTime: isInSync = " + 182 getXMLDataObjectLook().getSyncInterface().isInSync()); 183 184 if (getXMLDataObjectLook().getSyncInterface().isInSync()) { 185 return; 186 } 187 restartTimer(false); 188 } 189 }; 190 191 193 addChangeListener(new ChangeListener () { 194 public void stateChanged(ChangeEvent evt) { 195 196 if (isDocumentLoaded()) { 197 198 Document doc = getDocument(); 199 if (doc == null) 201 return; 202 doc.addDocumentListener(WeakListeners.document(docListener, doc)); 203 204 if (rep == null) { 205 XMLDataObjectLook dobj = (XMLDataObjectLook) getDataObject(); 206 Synchronizator sync = dobj.getSyncInterface(); 207 208 if (dobj instanceof org.netbeans.modules.xml.core.XMLDataObject) { 211 rep = new XMLTextRepresentation(TextEditorSupport.this, sync); 212 } else if (dobj instanceof DTDDataObject) { 213 rep = new DTDTextRepresentation(TextEditorSupport.this, sync); 214 } else if (dobj instanceof EntityDataObject) { 215 rep = new EntityTextRepresentation(TextEditorSupport.this, sync); 216 } 217 218 if (rep != null) { 219 sync.addRepresentation(rep); 220 } 221 } 222 223 225 230 } 231 } 232 }); 233 234 } 235 236 237 241 protected void notifyClosed() { 242 super.notifyClosed(); 243 244 if (this.getClass() != TextEditorSupport.class) return; 246 247 XMLDataObjectLook dobj = (XMLDataObjectLook) getDataObject(); 248 Synchronizator sync = dobj.getSyncInterface(); 249 Representation oldRep = rep; 250 rep = null; 251 if ( oldRep != null ) { sync.removeRepresentation(oldRep); 253 } 254 255 } 260 261 263 Env getEnv() { 264 return (Env) env; 265 } 266 267 268 270 protected XMLDataObjectLook getXMLDataObjectLook() { 271 return getEnv().getXMLDataObjectLook(); 272 } 273 274 277 protected boolean notifyModified() { 278 if (getEnv().isModified()) { 279 return true; 280 } 281 if (!!! super.notifyModified()) { 282 return false; 283 } else { 284 CookieManagerCookie manager = getEnv().getXMLDataObjectLook().getCookieManager(); 285 manager.addCookie(getEnv()); 286 return true; 287 } 288 } 289 290 293 protected void notifyUnmodified() { 294 if ( Util.THIS.isLoggable() ) Util.THIS.debug("Notifing unmodified"); 296 super.notifyUnmodified(); 297 CookieManagerCookie manager = getEnv().getXMLDataObjectLook().getCookieManager(); 298 manager.removeCookie(getEnv()); 299 } 300 301 303 private volatile boolean encodingErr = false; 305 306 308 protected void loadFromStreamToKit(StyledDocument doc, InputStream in, EditorKit kit) throws IOException, BadLocationException { 309 String enc = EncodingHelper.detectEncoding(in); 311 if (enc == null) { 312 enc = "UTF8"; } 314 try { 315 Reader reader = new InputStreamReader(in, enc); 316 kit.read(reader, doc, 0); 317 } catch (CharConversionException ex) { 318 if ( Util.THIS.isLoggable() ) Util.THIS.debug("\n!!! TextEditorSupport.loadFromStreamToKit: enc = '" + enc + "'", ex); 319 320 encodingErr = true; 321 } catch (UnsupportedEncodingException ex) { 322 if ( Util.THIS.isLoggable() ) Util.THIS.debug("\n!!! TextEditorSupport.loadFromStreamToKit: enc = '" + enc + "'", ex); 323 324 encodingErr = true; 325 } 326 327 } 328 329 331 protected void saveFromKitToStream(StyledDocument doc, EditorKit kit, OutputStream out) throws IOException, BadLocationException { 332 String enc = EncodingHelper.detectEncoding(doc); 333 if (enc == null) { 334 enc = "UTF8"; } 336 try { 337 338 if ( Util.THIS.isLoggable() ) Util.THIS.debug("Saving using encoding"); if ( Util.THIS.isLoggable() ) Util.THIS.debug("!!! TextEditorSupport::saveFromKitToStream: enc = " + enc); 340 341 new OutputStreamWriter(new ByteArrayOutputStream(1), enc); 343 344 if ( Util.THIS.isLoggable() ) Util.THIS.debug("!!! ::saveFromKitToStream: after first test -> OK"); 345 346 Writer writer = new OutputStreamWriter(out, enc); 347 348 if ( Util.THIS.isLoggable() ) Util.THIS.debug("!!! ::saveFromKitToStream: writer = " + writer); 349 350 kit.write(writer, doc, 0, doc.getLength()); 351 } catch (UnsupportedEncodingException ex) { 352 354 ErrorManager emgr = ErrorManager.getDefault(); 355 IOException ioex = new IOException("Unsupported encoding " + enc); emgr.annotate(ioex, Util.THIS.getString("MSG_unsupported_encoding", enc)); 357 throw ioex; 358 } 359 } 360 361 365 public void saveDocument() throws IOException { 366 if ( Util.THIS.isLoggable() ) Util.THIS.debug("saveDocument()..."); 368 final StyledDocument doc = getDocument(); 369 370 String enc = EncodingHelper.detectEncoding(doc); 371 372 if ( Util.THIS.isLoggable() ) Util.THIS.debug("!!! TextEditorSupport::saveDocument: enc = " + enc); 373 374 if (enc == null) { 375 enc = "UTF8"; } 377 378 try { 379 new OutputStreamWriter(new ByteArrayOutputStream(1), enc); 381 if (!checkCharsetConversion(Convertors.java2iana(enc))){ 382 if ( Util.THIS.isLoggable() ) Util.THIS.debug("Let unsaved."); return; 384 } 385 super.saveDocument(); 386 getDataObject().setModified(false); 388 getXMLDataObjectLook().getSyncInterface().representationChanged(Document.class); 389 390 } catch (UnsupportedEncodingException ex) { 391 392 394 NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation(java.text.MessageFormat.format(Util.THIS.getString("TEXT_SAVE_AS_UTF"), new Object [] {enc})); 395 Object res = DialogDisplayer.getDefault().notify(descriptor); 396 397 if (res.equals(NotifyDescriptor.YES_OPTION)) { 398 399 401 try { 402 final int MAX_PROLOG = 1000; 403 int maxPrologLen = Math.min(MAX_PROLOG, doc.getLength()); 404 final char prolog[] = doc.getText(0, maxPrologLen).toCharArray(); 405 int prologLen = 0; 407 if (prolog[0] == '<' && prolog[1] == '?' && prolog[2] == 'x') { 409 410 for (int i = 3; i<maxPrologLen; i++) { 412 if (prolog[i] == '?' && prolog[i+1] == '>') { 413 prologLen = i + 1; 414 break; 415 } 416 } 417 } 418 419 final int passPrologLen = prologLen; 420 421 Runnable edit = new Runnable () { 422 public void run() { 423 try { 424 425 doc.remove(0, passPrologLen + 1); doc.insertString(0, "<?xml version='1.0' encoding='UTF-8' ?> \n<!-- was: " + new String (prolog, 0, passPrologLen + 1) + " -->", null); 428 } catch (BadLocationException e) { 429 if (System.getProperty("netbeans.debug.exceptions") != null) e.printStackTrace(); 431 } 432 } 433 }; 434 435 NbDocument.runAtomic(doc, edit); 436 437 super.saveDocument(); 438 getDataObject().setModified(false); 440 getXMLDataObjectLook().getSyncInterface().representationChanged(Document.class); 441 442 if ( Util.THIS.isLoggable() ) Util.THIS.debug("Saved."); 444 } catch (BadLocationException lex) { 445 446 ErrorManager.getDefault().notify(lex); 447 } 448 449 } else { if ( Util.THIS.isLoggable() ) Util.THIS.debug("Let unsaved."); 452 return; 453 } 454 } } 456 457 458 private boolean checkCharsetConversion(String encoding) { 459 boolean value = true; 460 try { 461 java.nio.charset.CharsetEncoder coder = java.nio.charset.Charset.forName(encoding).newEncoder(); 462 if (!coder.canEncode(getDocument().getText(0, getDocument().getLength()))){ 463 NotifyDescriptor nd = new NotifyDescriptor.Confirmation( 464 NbBundle.getMessage(TextEditorSupport.class, "MSG_BadCharConversion", new Object [] { getDataObject().getPrimaryFile().getNameExt(), 466 encoding}), 467 NotifyDescriptor.YES_NO_OPTION, 468 NotifyDescriptor.WARNING_MESSAGE); 469 nd.setValue(NotifyDescriptor.NO_OPTION); 470 DialogDisplayer.getDefault().notify(nd); 471 if(nd.getValue() != NotifyDescriptor.YES_OPTION) 472 value = false; 473 } 474 } catch (javax.swing.text.BadLocationException e){ 475 ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); 476 } 477 480 return value; 481 } 482 484 487 protected void syncDocument(boolean fromFocus) { 488 if ( Util.THIS.isLoggable() ) Util.THIS.debug("@@ TextEditorSupport::syncDocument: fromFocus = " + fromFocus); 489 if ( Util.THIS.isLoggable() ) Util.THIS.debug("@@ ::syncDocument: timer.isRunning = " + timer.isRunning()); 490 491 if (fromFocus && !timer.isRunning()) 492 return; 493 if (timer.isRunning()) 494 timer.stop(); 495 496 XMLDataObjectLook sync = getXMLDataObjectLook(); 497 if (sync != null) { sync.getSyncInterface().representationChanged(Document.class); 499 } 500 501 } 502 503 504 507 void restartTimer(boolean onlyIfRunning) { 508 if ( Util.THIS.isLoggable() ) Util.THIS.debug("## TextEditorSupport::restartTimer: onlyIfRunning = " + onlyIfRunning); 509 if ( Util.THIS.isLoggable() ) Util.THIS.debug("## ::restartTimer: timer.isRunning = " + timer.isRunning()); 510 511 if (onlyIfRunning && !timer.isRunning()) 512 return; 513 514 int delay = settings.getAutoParsingDelay(); 515 if (delay > 0) { 516 timer.setInitialDelay(delay); 517 timer.restart(); 518 } 519 } 520 521 523 527 public final void edit() { 528 529 try { 530 openDocument(); if (encodingErr) { 532 String pattern = Util.THIS.getString("TEXT_WRONG_ENCODING"); 533 String msg = MessageFormat.format(pattern, new Object [] { getDataObject().getPrimaryFile().toString() }); 534 DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message(msg, NotifyDescriptor.ERROR_MESSAGE)); 535 536 } else { 537 Mutex.EVENT.writeAccess(new Runnable () { 538 public void run() { 539 CloneableTopComponent editor = openCloneableEditor(); 540 editor.requestActive(); 541 } 542 }); 543 } 544 } catch (UserQuestionException e){ open(); 546 if(isDocumentLoaded()) { 547 if (encodingErr) { 548 String pattern = Util.THIS.getString("TEXT_WRONG_ENCODING"); 549 String msg = MessageFormat.format(pattern, new Object [] { getDataObject().getPrimaryFile().toString() }); 550 DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message(msg, NotifyDescriptor.ERROR_MESSAGE)); 551 552 } else { 553 Mutex.EVENT.writeAccess(new Runnable () { 554 public void run() { 555 CloneableTopComponent editor = openCloneableEditor(); 556 editor.requestActive(); 557 } 558 }); 559 } 560 } 561 } catch (IOException ex) { 562 String pattern = Util.THIS.getString("TEXT_LOADING_ERROR"); 563 String msg = MessageFormat.format(pattern, new Object [] { getDataObject().getPrimaryFile().toString() }); 564 DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message(msg, NotifyDescriptor.ERROR_MESSAGE)); 565 } 566 567 } 568 569 570 575 protected final CloneableEditor openCloneableEditor() { 576 577 CloneableEditor ret = null; 578 579 synchronized (getLock()) { 580 581 String msg = messageOpening(); 582 if (msg != null) { 583 StatusDisplayer.getDefault().setStatusText(msg); 584 } 585 586 Enumeration en = allEditors.getComponents(); 587 while ( en.hasMoreElements() ) { 588 CloneableTopComponent editor = (CloneableTopComponent)en.nextElement(); 589 if ( editor instanceof CloneableEditor ) { 590 editor.open(); 591 ret = (CloneableEditor) editor; 592 } 593 } 594 595 597 if (ret == null) { 598 CloneableEditor editor = (CloneableEditor)createCloneableTopComponent(); editor.setReference(allEditors); 600 editor.open(); 601 ret = editor; 602 } 603 604 msg = messageOpened(); 605 if (msg == null) { 606 msg = ""; } 608 StatusDisplayer.getDefault().setStatusText(msg); 609 610 return ret; 611 } 612 } 613 614 618 protected Object getLock() { 619 if (awtLock == null) { 620 awtLock = new java.awt.Container (); 621 } 622 return awtLock.getTreeLock(); 623 } 624 625 628 protected CloneableEditor createCloneableEditor() { 629 return new TextEditorComponent(this); 630 } 631 632 protected final CloneableTopComponent createCloneableTopComponent() { 634 return super.createCloneableTopComponent(); } 636 637 639 public static final TextEditorSupportFactory findEditorSupportFactory(XMLDataObjectLook xmlDO, String mime) { 640 return new TextEditorSupportFactory(xmlDO, mime); 641 } 642 643 647 650 protected static class Env extends DataEditorSupport.Env implements SaveCookie { 651 652 653 private static final long serialVersionUID=-5285524519399090028L; 654 655 656 public Env(XMLDataObjectLook obj) { 657 super((DataObject)obj); 658 } 659 660 662 protected XMLDataObjectLook getXMLDataObjectLook() { 663 return (XMLDataObjectLook) getDataObject(); 664 } 665 666 668 protected FileObject getFile() { 669 return getDataObject().getPrimaryFile(); 670 } 671 672 674 protected FileLock takeLock() throws IOException { 675 return ((MultiDataObject)getDataObject()).getPrimaryEntry().takeLock(); 676 } 677 678 679 681 public synchronized void save() throws IOException { 682 findTextEditorSupport().saveDocument(); 683 } 684 685 687 public CloneableOpenSupport findCloneableOpenSupport() { 688 return findTextEditorSupport(); 689 } 690 691 693 public TextEditorSupport findTextEditorSupport() { 694 return (TextEditorSupport) getDataObject().getCookie(EditCookie.class); 695 } 696 697 public void propertyChange(PropertyChangeEvent ev) { 699 if (DataObject.PROP_PRIMARY_FILE.equals(ev.getPropertyName())) { 700 changeFile(); 701 } 702 super.propertyChange(ev); 703 } 704 705 706 } 708 709 713 716 public static class TextEditorSupportFactory implements CookieSet.Factory { 717 718 private WeakReference editorRef; 719 720 private final XMLDataObjectLook dataObject; 722 private final String mime; 724 728 729 public TextEditorSupportFactory(XMLDataObjectLook dobj, String mime) { 730 this.dataObject = dobj; 731 this.mime = mime; 732 } 733 734 735 737 protected Class [] supportedCookies() { 738 return new Class [] { EditorCookie.class, 739 EditorCookie.Observable.class, 740 EditCookie.class, 741 CloseCookie.class, 742 PrintCookie.class, 743 }; 744 } 745 746 748 public final void registerCookies(CookieSet cookieSet) { 749 Class [] supportedCookies = supportedCookies(); 750 for (int i = 0; i < supportedCookies.length; i++) { 751 cookieSet.add(supportedCookies[i], this); 752 } 753 } 754 755 758 public final Node.Cookie createCookie(Class klass) { 759 Class [] supportedCookies = supportedCookies(); 760 for (int i = 0; i < supportedCookies.length; i++) { 761 if ( supportedCookies[i].isAssignableFrom(klass) ) { 762 return createEditor(); 763 } 764 } 765 return null; 766 } 767 768 770 private final synchronized TextEditorSupport createEditor() { TextEditorSupport editorSupport = null; 772 773 if ( editorRef != null ) { 774 editorSupport = (TextEditorSupport) editorRef.get(); 775 } 776 if ( editorSupport == null ) { 777 editorSupport = prepareEditor(); 778 editorRef = new WeakReference (editorSupport); 779 } 780 781 return editorSupport; 782 } 783 784 786 protected TextEditorSupport prepareEditor() { 787 if ( Util.THIS.isLoggable() ) Util.THIS.debug("Initializing TextEditorSupport ..."); 789 return new TextEditorSupport(getDataObject(), getMIMEType()); 790 } 791 792 794 protected final XMLDataObjectLook getDataObject() { 795 return dataObject; 796 } 797 798 800 protected final String getMIMEType() { 801 return mime; 802 } 803 804 } 806 } 807 | Popular Tags |