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 
measure( String sText )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
_AsciiReplaceFilter(XComponentContext Context )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         */
initialize( Object[] lArguments )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         */
getName()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         */
setName( String sName )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         */
setTargetDocument( com.sun.star.lang.XComponent xDocument )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         */
setSourceDocument( com.sun.star.lang.XComponent xDocument )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 
filter( com.sun.star.beans.PropertyValue[] lDescriptor )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         */
cancel()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          */
implts_import( com.sun.star.text.XTextDocument xTarget , FilterOptions aOptions )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          */
implts_export( com.sun.star.text.XTextDocument xSource , FilterOptions aOptions)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          */
implts_replace( StringBuffer rBuffer, FilterOptions aOptions )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          */
getSupportedServiceNames()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          */
supportsService( String sService )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          */
getImplementationName()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 
__getComponentFactory(String sImplName)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 
728