1 /************************************************************** 2 * 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * 20 *************************************************************/ 21 22 23 24 package OfficeDev.samples.Filter; 25 26 import com.sun.star.lib.uno.helper.Factory; 27 import com.sun.star.lib.uno.helper.WeakBase; 28 import com.sun.star.registry.XRegistryKey; 29 import com.sun.star.uno.XComponentContext; 30 import com.sun.star.lang.XMultiComponentFactory; 31 import com.sun.star.lang.XSingleComponentFactory; 32 import com.sun.star.uno.UnoRuntime; 33 import com.sun.star.uno.Type; 34 import com.sun.star.uno.AnyConverter; 35 import com.sun.star.beans.PropertyValue; 36 37 import com.sun.star.lang.XInitialization; 38 import com.sun.star.lang.XServiceInfo; 39 import com.sun.star.container.XNamed; 40 import com.sun.star.document.XImporter; 41 import com.sun.star.document.XExporter; 42 import com.sun.star.document.XFilter; 43 44 45 import com.sun.star.lang.IllegalArgumentException; 46 47 /*-************************************************************************ 48 @title implements a filter to import pure ascii text files 49 @description This filter can use an existing In/OutputStream of given 50 MediaDescriptor or use an existing URL instead of that 51 to open the file directly. But for second case the local 52 file system will be used only. There is no support for remote. 53 54 During import/export special functionality can be used to 55 e.g. to change some characters inside the file content 56 or replace some strings (no using of regular expressions!). 57 User can decide at runtime which functionality really should 58 be used by selecting it in an extra filter property dialog. 59 60 So we show how a filter works for import/export, use or create 61 streams and how a filter can offer properties for filtering 62 which can be edit by the user. 63 ************************************************************************-*/ 64 65 public class AsciiReplaceFilter 66 { 67 68 69 70 public static class _AsciiReplaceFilter extends WeakBase 71 implements XInitialization , 72 XServiceInfo , 73 XNamed , 74 XImporter , 75 XExporter , 76 XFilter 77 { 78 //______________________________ 79 // const 80 81 // the supported service names, the first one being the service name of the component itself 82 public static final String[] m_serviceNames = { "com.sun.star.comp.ansifilter.AsciiReplaceFilter" , "com.sun.star.document.ImportFilter", "com.sun.star.document.ExportFilter" }; 83 84 // filterprocess states 85 public static final int FILTERPROC_RUNS = 0; 86 public static final int FILTERPROC_BREAK = 1; 87 public static final int FILTERPROC_STOPPED = 2; 88 89 //______________________________ 90 // member 91 92 93 /// The initial component context, that gives access to the service manager, supported singletons, ... 94 private XComponentContext m_Ctx; 95 /// The service manager, that gives access to all registered services and which is passed to the FilterOptions class for instantiating a ucb service 96 private XMultiComponentFactory m_xMCF; 97 /// we must provide our name 98 private String m_sInternalName; 99 /// saved document reference for import or export (depends on other member m_bImport!) 100 private com.sun.star.text.XTextDocument m_xDocument; 101 /// because we implement an import AND an export filter, we must know which one is required 102 private boolean m_bImport; 103 // we need a flag to cancel any running filter operation 104 private int m_nFilterProcState; 105 106 //______________________________ 107 // native interface 108 /** 109 * special debug helper to get an idea how expensive 110 * the implemented filter operations are really. 111 * May be usefully for own purposes. 112 * 113 * To see the output inside an office environment 114 * use "soffice ...params... >output.txt" 115 */ 116 private long m_nStart; 117 private long m_nLast ; 118 119 private void measure( String sText ) 120 { 121 long nNow = System.currentTimeMillis(); 122 System.err.println(sText+"\t"+(nNow-m_nStart)+"\t"+(nNow-m_nLast)); 123 m_nLast = nNow; 124 } 125 126 //______________________________ 127 // native interface 128 /** 129 * The constructor to initialize every instance 130 * 131 * @param xCompContext 132 * the component context of the office 133 */ 134 //ctor 135 public _AsciiReplaceFilter(XComponentContext Context ) 136 { 137 measure("ctor started"); 138 try 139 { 140 m_Ctx = Context ; 141 m_xMCF = m_Ctx.getServiceManager() ; 142 } 143 catch( Exception e ) 144 { 145 e.printStackTrace(); 146 } 147 148 // these are safe, thus no errorhandling needed: 149 m_sInternalName = new String() ; 150 m_xDocument = null ; 151 m_bImport = true ; 152 m_nFilterProcState = FILTERPROC_STOPPED ; 153 154 m_nLast = System.currentTimeMillis(); 155 m_nStart = m_nLast; 156 } 157 158 //______________________________ 159 // interface XInitialization 160 /** 161 * used for initializing after creation 162 * If an instance of this service is created by UNO we will be called 163 * automatically after that to get optional parameters of this creation call. 164 * E.g.: The service com.sun.star.document.FilterFactory use such mechanism 165 * to pass our own configuration data to this instance. 166 * 167 * @param lArguments 168 * This array of arbitrary objects represent our own filter configuration 169 * and may optional given parameters of the createWithArguments() call. 170 * 171 * @throws Exception 172 * Every exception will not be handled, but will be 173 * passed to the caller. 174 */ 175 public void initialize( Object[] lArguments ) throws com.sun.star.uno.Exception 176 { 177 measure("initialize {"); 178 179 if (lArguments.length<1) 180 return; 181 182 // lArguments[0] = own configuration data 183 com.sun.star.beans.PropertyValue[] lConfig = (com.sun.star.beans.PropertyValue[])lArguments[0]; 184 185 /* 186 // lArguments[1..n] = optional arguments of create request 187 for (int n=1; n<lArguments.length; ++n) 188 { 189 } 190 */ 191 192 // analyze own configuration data for our own internal filter name! 193 // Important for generic filter services, which are registered more then once. 194 // They can use this information to find out, which specialization of it 195 // is required. 196 for (int i=0; i<lConfig.length; ++i) 197 { 198 if (lConfig[i].Name.equals("Name")) 199 { 200 synchronized(this) 201 { 202 try 203 { 204 m_sInternalName = AnyConverter.toString(lConfig[i].Value); 205 } 206 catch(com.sun.star.lang.IllegalArgumentException exConvert) {} 207 } 208 } 209 } 210 211 measure("} initialize"); 212 } 213 214 //______________________________ 215 // interface XNamed 216 /** 217 * For external user of us we must provide our internal filter name 218 * (which is registered inside configuration package TypeDetection). 219 * User will be able then to ask there for further information about us. 220 * Otherwise we must implement a full featured XPropertySet ... 221 * 222 * @return our internal filter name of configuration 223 */ 224 public String getName() 225 { 226 synchronized(this) 227 { 228 return m_sInternalName; 229 } 230 } 231 232 /** 233 * It's not allowed for us - neither very easy to change our internal 234 * name during runtime of an office. Because every filter name must 235 * be unambiguous ... 236 * So we don't implement this method here. 237 */ 238 public void setName( String sName ) 239 { 240 } 241 242 //______________________________ 243 // interface XImporter 244 /** 245 * This interface is used to tell us: "you will be used for importing a document". 246 * We must save the given model reference to use it inside our own filter request. 247 * 248 * @param xDocument 249 * the document model for importing 250 * 251 * @throw IllegalArgumentException 252 * if given document isn't the right one or seems to be corrupt 253 */ 254 public void setTargetDocument( com.sun.star.lang.XComponent xDocument ) throws com.sun.star.lang.IllegalArgumentException 255 { 256 measure("setTargetDocument {"); 257 258 if (xDocument==null) 259 throw new com.sun.star.lang.IllegalArgumentException("null reference detected"); 260 261 com.sun.star.lang.XServiceInfo xInfo = (com.sun.star.lang.XServiceInfo)UnoRuntime.queryInterface( 262 com.sun.star.lang.XServiceInfo.class, xDocument); 263 if ( ! xInfo.supportsService("com.sun.star.text.TextDocument") ) 264 throw new com.sun.star.lang.IllegalArgumentException( "wrong document type" ); 265 266 // safe it as target document for import 267 // Don't forget to mark this filter used for importing too 268 synchronized(this) 269 { 270 m_xDocument = (com.sun.star.text.XTextDocument)UnoRuntime.queryInterface( 271 com.sun.star.text.XTextDocument.class, xDocument); 272 m_bImport = true; 273 } 274 275 measure("} setTargetDocument"); 276 } 277 278 //______________________________ 279 // interface XExporter 280 /** 281 * This interface is used to tell us: "you will be used for exporting a document". 282 * We must save the given model reference to use it inside our own filter request. 283 * 284 * @param xDocument 285 * the document model for exporting 286 * 287 * @throw IllegalArgumentException 288 * if given document isn't the right one or seems to be corrupt 289 */ 290 public void setSourceDocument( com.sun.star.lang.XComponent xDocument ) throws com.sun.star.lang.IllegalArgumentException 291 { 292 measure("setSourceDocument {"); 293 294 if (xDocument==null) 295 throw new com.sun.star.lang.IllegalArgumentException( "null reference given" ); 296 297 com.sun.star.lang.XServiceInfo xInfo = (com.sun.star.lang.XServiceInfo)UnoRuntime.queryInterface( 298 com.sun.star.lang.XServiceInfo.class, xDocument); 299 if ( ! xInfo.supportsService("com.sun.star.text.TextDocument") ) 300 throw new com.sun.star.lang.IllegalArgumentException( "wrong document type" ); 301 302 // safe it as source document for export 303 // Don't forget to mark this filter used for exporting too 304 synchronized(this) 305 { 306 m_xDocument = (com.sun.star.text.XTextDocument)UnoRuntime.queryInterface( 307 com.sun.star.text.XTextDocument.class, xDocument); 308 m_bImport = false; 309 } 310 311 measure("} setSourceDocument"); 312 } 313 314 315 //______________________________ 316 // interface XFilter 317 /** 318 * Implements the real filter method. We detect if it must be an import or an export. 319 * Depends on that we use an existing stream (given inside the MediaDescriptor) 320 * or open it by using an URL (must be a part of the descriptor too). 321 * 322 * @param lDescriptor 323 * the MediaDescriptor which describes the document 324 * 325 * @return a bool value which describes if method was successfully or not. 326 */ 327 328 public boolean filter( com.sun.star.beans.PropertyValue[] lDescriptor ) 329 { 330 measure("filter {"); 331 332 // first get state of filter operation (import/export) 333 // and try to create or get corresponding streams 334 // Means: analyze given MediaDescriptor 335 // By the way: use synchronized section to get some copies of other 336 // internal states too. 337 FilterOptions aOptions = null ; 338 boolean bImport = false; 339 com.sun.star.text.XTextDocument xText = null ; 340 synchronized(this) 341 { 342 aOptions = new FilterOptions(m_xMCF, m_Ctx, m_bImport, lDescriptor); 343 bImport = m_bImport; 344 xText = m_xDocument; 345 } 346 347 measure("options analyzed"); 348 349 if (aOptions.isValid()==false) 350 return false; 351 352 // start real filtering 353 boolean bState = false; 354 if (bImport) 355 bState = implts_import( xText, aOptions ); 356 else 357 bState = implts_export( xText, aOptions ); 358 359 measure("} filter"); 360 361 return bState; 362 } 363 364 /** 365 * Makes the filter process breakable. To do so the outside code may use threads. 366 * We use a internal "condition" variable which is queried by the real filter method on 367 * every loop they do. So it's more a polling mechanism. 368 */ 369 public void cancel() 370 { 371 measure("cancel {"); 372 373 synchronized(this) 374 { 375 if (m_nFilterProcState==FILTERPROC_RUNS) 376 m_nFilterProcState=FILTERPROC_BREAK; 377 } 378 379 while (true) 380 { 381 synchronized(this) 382 { 383 if (m_nFilterProcState==FILTERPROC_STOPPED) 384 break; 385 } 386 } 387 388 measure("} cancel"); 389 } 390 391 //______________________________ 392 // private helper 393 /** 394 * This helper function imports a simple ascii text file into 395 * a text model. We copy every letter to the document. 396 * But if some optional filter options are given 397 * we make some changes: replace chars or complete strings. 398 * 399 * Note: It's not allowed for a filter to seek inside the stream. 400 * Because the outside frameloader has to set the stream position 401 * right and a filter must read till EOF occurs only. 402 * 403 * @param xTarget 404 * the target text model to put the data in 405 * 406 * @param aOptions 407 * capsulate all other necessary informations for this filter request 408 * (streams, replace values ...) 409 * 410 * @return a bool value which describes if method was successfully or not. 411 */ 412 private boolean implts_import( com.sun.star.text.XTextDocument xTarget , 413 FilterOptions aOptions ) 414 { 415 measure("implts_import {"); 416 417 com.sun.star.text.XSimpleText xText = (com.sun.star.text.XSimpleText)UnoRuntime.queryInterface( 418 com.sun.star.text.XSimpleText.class, 419 xTarget.getText()); 420 421 measure("cast XSimpleText"); 422 423 boolean bBreaked = false; 424 425 try 426 { 427 StringBuffer sBuffer = new StringBuffer(100000); 428 byte[][] lData = new byte[1][]; 429 int nRead = aOptions.m_xInput.readBytes( lData, 4096 ); 430 431 measure("read first bytes"); 432 433 while (nRead>0 && !bBreaked) 434 { 435 // copy data from stream to temp. buffer 436 sBuffer.append( new String(lData[0]) ); 437 measure("buffer append ["+nRead+"]"); 438 439 nRead = aOptions.m_xInput.readBytes( lData, 2048 ); 440 measure("read next bytes"); 441 442 // check for canceled filter proc on every loop! 443 synchronized(this) 444 { 445 if (m_nFilterProcState==FILTERPROC_BREAK) 446 { 447 m_nFilterProcState = FILTERPROC_STOPPED; 448 return false; 449 } 450 } 451 measure("break check"); 452 } 453 454 // Make some replacements inside the buffer. 455 String sText = implts_replace( sBuffer, aOptions ); 456 measure("replace"); 457 458 // copy current buffer to the document model. 459 // Create a new paragraph for every line inside original file. 460 // May not all data could be read - but that doesn't matter here. 461 // Reason: somewhere canceled this function. 462 // But check for optional replace request before ... 463 int nStart = 0; 464 int nEnd = -1; 465 int nLength = sText.length(); 466 467 com.sun.star.text.XTextRange xCursor = (com.sun.star.text.XTextRange)UnoRuntime.queryInterface( 468 com.sun.star.text.XTextRange.class, 469 xText.createTextCursor()); 470 471 while (true) 472 { 473 nEnd = sText.indexOf('\n',nStart); 474 475 if (nEnd==-1 && nStart<nLength) 476 nEnd = nLength; 477 478 if (nEnd==-1) 479 break; 480 481 String sLine = sText.substring(nStart,nEnd); 482 nStart = nEnd+1; 483 484 xText.insertString(xCursor,sLine,false); 485 xText.insertControlCharacter(xCursor,com.sun.star.text.ControlCharacter.PARAGRAPH_BREAK,false); 486 487 // check for canceled filter proc on every loop! 488 synchronized(this) 489 { 490 if (m_nFilterProcState==FILTERPROC_BREAK) 491 { 492 m_nFilterProcState = FILTERPROC_STOPPED; 493 return false; 494 } 495 } 496 measure("break check"); 497 } 498 499 measure("set on model"); 500 501 // with refreshing the document we are on the safe-side, otherwise the first time the filter is used the document is not fully shown (flaw!). 502 com.sun.star.util.XRefreshable xRefresh = (com.sun.star.util.XRefreshable)UnoRuntime.queryInterface( 503 com.sun.star.util.XRefreshable.class, 504 xTarget); 505 xRefresh.refresh(); 506 507 // If we created used stream - we must close it too. 508 if (aOptions.m_bStreamOwner==true) 509 { 510 aOptions.m_xInput.closeInput(); 511 measure("stream close"); 512 } 513 } 514 catch(com.sun.star.lang.IllegalArgumentException exArgument ) { bBreaked = true; } 515 catch(com.sun.star.io.BufferSizeExceededException exExceed ) { bBreaked = true; } 516 catch(com.sun.star.io.NotConnectedException exConnect ) { bBreaked = true; } 517 catch(com.sun.star.io.IOException exIO ) { bBreaked = true; } 518 519 520 521 measure("} implts_import"); 522 523 return !bBreaked; 524 } 525 526 /** 527 * This helper function exports a simple ansi text file from 528 * a text model. We copy every letter from the document. 529 * There are no checks. 530 * 531 * Note: It's not allowed for a filter to seek inside the stream. 532 * Because the outside frameloader has to set the stream position 533 * right and a filter must read till EOF occurs only. 534 * 535 * @param xSource 536 * the source text model to get the data from 537 * 538 * @param aOptions 539 * capsulate all other necessary informations for this filter request 540 * (streams, replace values ...) 541 * 542 * @return a bool value which describes if method was successfully or not. 543 */ 544 private boolean implts_export( com.sun.star.text.XTextDocument xSource , 545 FilterOptions aOptions) 546 { 547 measure("implts_export {"); 548 549 com.sun.star.text.XTextRange xText = (com.sun.star.text.XSimpleText)UnoRuntime.queryInterface( 550 com.sun.star.text.XSimpleText.class, 551 xSource.getText()); 552 553 measure("cast XTextRange"); 554 555 boolean bBreaked = false; 556 557 try 558 { 559 StringBuffer sBuffer = new StringBuffer(xText.getString()); 560 String sText = implts_replace(sBuffer,aOptions); 561 562 measure("get text from model"); 563 564 // Normally this function isn't really cancelable 565 // But the following operation can be very expensive. So 566 // this place is the last one to stop it. 567 synchronized(this) 568 { 569 if (m_nFilterProcState==FILTERPROC_BREAK) 570 { 571 m_nFilterProcState = FILTERPROC_STOPPED; 572 return false; 573 } 574 } 575 576 aOptions.m_xOutput.writeBytes(sText.getBytes()); 577 aOptions.m_xOutput.flush(); 578 579 measure("written to file"); 580 581 // If we created used stream - we must close it too. 582 if (aOptions.m_bStreamOwner==true) 583 { 584 aOptions.m_xOutput.closeOutput(); 585 measure("stream close"); 586 } 587 } 588 catch(com.sun.star.io.BufferSizeExceededException exExceed ) { bBreaked = true; } 589 catch(com.sun.star.io.NotConnectedException exConnect ) { bBreaked = true; } 590 catch(com.sun.star.io.IOException exIO ) { bBreaked = true; } 591 592 measure("} implts_export"); 593 594 return !bBreaked; 595 } 596 597 /** 598 * helper function to convert the used StringBuffer into a String value. 599 * And we use this chance to have a look on optional filter options 600 * which can invite replacing of strings. 601 */ 602 private String implts_replace( StringBuffer rBuffer, FilterOptions aOptions ) 603 { 604 // replace complete strings first 605 // Because it's easier on a buffer than on a string 606 if ( ! aOptions.m_sOld.equals(aOptions.m_sNew) ) 607 { 608 int nStart = rBuffer.indexOf(aOptions.m_sOld); 609 int nLength = aOptions.m_sNew.length(); 610 int nEnd = nStart+nLength; 611 while (nStart!=-1) 612 { 613 rBuffer.replace(nStart,nEnd,aOptions.m_sNew); 614 nStart = rBuffer.indexOf(aOptions.m_sOld,nEnd); 615 nEnd = nStart+nLength; 616 } 617 } 618 619 // convert buffer into return format [string] 620 // and convert to lower or upper case if required. 621 String sResult = rBuffer.toString(); 622 if (aOptions.m_bCaseChange==true) 623 { 624 if (aOptions.m_bLower==true) 625 sResult = sResult.toLowerCase(); 626 else 627 sResult = sResult.toUpperCase(); 628 } 629 630 return sResult; 631 } 632 633 634 //______________________________ 635 // interface XServiceInfo 636 /** 637 * This method returns an array of all supported service names. 638 * 639 * @return Array of supported service names. 640 */ 641 public String[] getSupportedServiceNames() 642 { 643 return m_serviceNames; 644 } 645 646 /** 647 * This method returns true, if the given service will be 648 * supported by this component. 649 * 650 * @param sService 651 * the requested service name 652 * 653 * @return True, if the given service name will be supported; 654 * False otherwise. 655 */ 656 public boolean supportsService( String sService ) 657 { 658 return ( 659 sService.equals( m_serviceNames[0] ) || 660 sService.equals( m_serviceNames[1] ) || 661 sService.equals( m_serviceNames[2] ) 662 ); 663 } 664 665 /** 666 * Return the real class name of the component 667 * 668 * @return Class name of the component. 669 */ 670 public String getImplementationName() 671 { 672 return _AsciiReplaceFilter.class.getName(); 673 } 674 675 676 } 677 // end of inner class, the wrapper class just has the two methods required for registering the component 678 679 /** 680 * Gives a factory for creating the service. 681 * This method is called by the <code>JavaLoader</code> 682 * 683 * @param sImplName 684 * The implementation name of the component. 685 * 686 * @return Returns a <code>XSingleComponentFactory</code> for 687 * creating the component. 688 * 689 * @see com.sun.star.comp.loader.JavaLoader 690 */ 691 692 public static XSingleComponentFactory __getComponentFactory(String sImplName) 693 { 694 XSingleComponentFactory xFactory = null; 695 696 if ( sImplName.equals( _AsciiReplaceFilter.class.getName() ) ) 697 xFactory = com.sun.star.lib.uno.helper.Factory.createComponentFactory(_AsciiReplaceFilter.class, 698 _AsciiReplaceFilter.m_serviceNames); 699 return xFactory; 700 } 701 702 703 /** 704 * Writes the service information into the given registry key. 705 * This method is called by the <code>JavaLoader</code>. 706 * 707 * @param xRegistryKey 708 * Makes structural information (except regarding tree 709 * structures) of a single registry key accessible. 710 * 711 * @return returns true if the operation succeeded 712 * 713 * @see com.sun.star.comp.loader.JavaLoader 714 */ 715 // This method not longer necessary since OOo 3.4 where the component registration 716 // was changed to passive component registration. For more details see 717 // https://wiki.openoffice.org/wiki/Passive_Component_Registration 718 719 // public static boolean __writeRegistryServiceInfo( com.sun.star.registry.XRegistryKey xRegistryKey ) 720 // { 721 // return Factory.writeRegistryServiceInfo( 722 // _AsciiReplaceFilter.class.getName(), 723 // _AsciiReplaceFilter.m_serviceNames, 724 // xRegistryKey ); 725 // } 726 } 727