summaryrefslogtreecommitdiff
path: root/odk/examples/DevelopersGuide/OfficeDev/FilterDevelopment/AsciiFilter/AsciiReplaceFilter.java
blob: 6d5f2cc27546564bd1a501d5fc96a3fdf6851891 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
/*************************************************************************
 *
 *  The Contents of this file are made available subject to the terms of
 *  the BSD license.
 *
 *  Copyright 2000, 2010 Oracle and/or its affiliates.
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *  3. Neither the name of Sun Microsystems, Inc. nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 *  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 *  TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 *  USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *************************************************************************/

package FilterDevelopment.AsciiFilter;

import com.sun.star.lib.uno.helper.WeakBase;
import com.sun.star.uno.XComponentContext;
import com.sun.star.lang.XMultiComponentFactory;
import com.sun.star.lang.XSingleComponentFactory;
import com.sun.star.uno.UnoRuntime;
import com.sun.star.uno.AnyConverter;
import com.sun.star.lang.XInitialization;
import com.sun.star.lang.XServiceInfo;
import com.sun.star.container.XNamed;
import com.sun.star.document.XImporter;
import com.sun.star.document.XExporter;
import com.sun.star.document.XFilter;

/*-************************************************************************
  @title        implements a filter to import pure ascii text files
  @description  This filter can use an existing In/OutputStream of given
                MediaDescriptor or use an existing URL instead of that
                to open the file directly. But for second case the local
                file system will be used only. There is no support for remote.

                During import/export special functionality can be used to
                e.g. to change some characters inside the file content
                or replace some strings (no using of regular expressions!).
                User can decide at runtime which functionality really should
                be used by selecting it in an extra filter property dialog.

                So we show how a filter works for import/export, use or create
                streams and how a filter can offer properties for filtering
                which can be edit by the user.
 ************************************************************************-*/

public class AsciiReplaceFilter
{
    public static class _AsciiReplaceFilter extends     WeakBase
                                            implements  XInitialization ,
                                                        XServiceInfo    ,
                                                        XNamed          ,
                                                        XImporter       ,
                                                        XExporter       ,
                                                        XFilter
    {


        // the supported service names, the first one being the service name of the component itself
        private static final String[] m_serviceNames = { "com.sun.star.comp.ansifilter.AsciiReplaceFilter" , "com.sun.star.document.ImportFilter", "com.sun.star.document.ExportFilter" };

        // filterprocess states
        private static final int    FILTERPROC_RUNS          = 0;
        private static final int    FILTERPROC_BREAK         = 1;
        private static final int    FILTERPROC_STOPPED       = 2;


        // member


        /// The initial component context, that gives access to the service manager, supported singletons, ...
        private XComponentContext m_Ctx;
        /// The service manager, that gives access to all registered services and which is passed to the FilterOptions class for instantiating a ucb service
        private XMultiComponentFactory m_xMCF;
        /// we must provide our name
        private String m_sInternalName;
        /// saved document reference for import or export (depends on other member m_bImport!)
        private com.sun.star.text.XTextDocument m_xDocument;
        /// because we implement an import AND an export filter, we must know which one is required
        private boolean m_bImport;
        // we need a flag to cancel any running filter operation
        private int m_nFilterProcState;


        // native interface
       /**
        * special debug helper to get an idea how expensive
        * the implemented filter operations are really.
        * May be useful for own purposes.
        *
        * To see the output inside an office environment
        * use "soffice ...params... >output.txt"
        */
        private final long m_nStart;
        private long m_nLast ;

        private void measure( String sText )
        {
            long nNow = System.currentTimeMillis();
            System.err.println(sText+"\t"+(nNow-m_nStart)+"\t"+(nNow-m_nLast));
            m_nLast = nNow;
        }


        // native interface
        /**
        * The constructor to initialize every instance
        *
        * @param Context
        *  the component context of the office
        */
        public _AsciiReplaceFilter(XComponentContext Context )
        {
            measure("ctor started");
            try
            {
                m_Ctx = Context                     ;
                m_xMCF = m_Ctx.getServiceManager()  ;
            }
            catch( Exception e )
            {
                e.printStackTrace();
            }

            // these are safe, thus no errorhandling needed:
            m_sInternalName    = ""       ;
            m_xDocument        = null               ;
            m_bImport          = true               ;
            m_nFilterProcState = FILTERPROC_STOPPED ;

            m_nLast  = System.currentTimeMillis();
            m_nStart = m_nLast;
        }


        // interface XInitialization
        /**
        * used for initializing after creation
        * If an instance of this service is created by UNO we will be called
        * automatically after that to get optional parameters of this creation call.
        * E.g.: The service com.sun.star.document.FilterFactory use such mechanism
        * to pass our own configuration data to this instance.
        *
        * @param lArguments
        *  This array of arbitrary objects represent our own filter configuration
        *  and may optional given parameters of the createWithArguments() call.
        *
        * @throws com.sun.star.uno.Exception
        *  Every exception will not be handled, but will be
        *  passed to the caller.
        */
        public void initialize( Object[] lArguments ) throws com.sun.star.uno.Exception
        {
            measure("initialize {");

            if (lArguments.length<1)
                return;

            // lArguments[0] = own configuration data
            com.sun.star.beans.PropertyValue[] lConfig = (com.sun.star.beans.PropertyValue[])lArguments[0];

            /*
            // lArguments[1..n] = optional arguments of create request
            for (int n=1; n<lArguments.length; ++n)
            {
            }
            */

            // analyze own configuration data for our own internal filter name!
            // Important for generic filter services, which are registered more than once.
            // They can use this information to find out, which specialization of it
            // is required.
            for (int i=0; i<lConfig.length; ++i)
            {
                if (lConfig[i].Name.equals("Name"))
                {
                    synchronized(this)
                    {
                        try
                        {
                            m_sInternalName = AnyConverter.toString(lConfig[i].Value);
                        }
                        catch(com.sun.star.lang.IllegalArgumentException exConvert) {}
                    }
                }
            }

            measure("} initialize");
        }


        // interface XNamed
        /**
        * For external user of us we must provide our internal filter name
        * (which is registered inside configuration package TypeDetection).
        * User will be able then to ask there for further information about us.
        * Otherwhise we must implement a full featured XPropertySet...
        *
        * @return our internal filter name of configuration
        */
        public String getName()
        {
            synchronized(this)
            {
                return m_sInternalName;
            }
        }

        /**
        * It's not allowed for us - neither very easy to change our internal
        * name during runtime of an office. Because every filter name must
        * be unambigous ...
        * So we don't implement this method here.
        */
        public void setName( String sName )
        {
        }


        // interface XImporter
        /**
        * This interface is used to tell us: "you will be used for importing a document".
        * We must save the given model reference to use it inside our own filter request.
        *
        * @param xDocument
        *          the document model for importing
        *
        * @throw IllegalArgumentException
        *          if given document isn't the right one or seems to be corrupt
        */
        public void setTargetDocument( com.sun.star.lang.XComponent xDocument ) throws com.sun.star.lang.IllegalArgumentException
        {
            measure("setTargetDocument {");

            if (xDocument==null)
                throw new com.sun.star.lang.IllegalArgumentException("null reference detected");

            com.sun.star.lang.XServiceInfo xInfo = UnoRuntime.queryInterface(
                                    com.sun.star.lang.XServiceInfo.class, xDocument);
            if ( ! xInfo.supportsService("com.sun.star.text.TextDocument") )
                throw new com.sun.star.lang.IllegalArgumentException( "wrong document type" );

            // safe it as target document for import
            // Don't forget to mark this filter used for importing too
            synchronized(this)
            {
                m_xDocument = UnoRuntime.queryInterface(
                        com.sun.star.text.XTextDocument.class, xDocument);
                m_bImport = true;
            }

            measure("} setTargetDocument");
        }


        // interface XExporter
        /**
        * This interface is used to tell us: "you will be used for exporting a document".
        * We must save the given model reference to use it inside our own filter request.
        *
        * @param xDocument
        *          the document model for exporting
        *
        * @throw IllegalArgumentException
        *          if given document isn't the right one or seems to be corrupt
        */
        public void setSourceDocument( com.sun.star.lang.XComponent xDocument ) throws com.sun.star.lang.IllegalArgumentException
        {
            measure("setSourceDocument {");

            if (xDocument==null)
                throw new com.sun.star.lang.IllegalArgumentException( "null reference given" );

            com.sun.star.lang.XServiceInfo xInfo = UnoRuntime.queryInterface(
                        com.sun.star.lang.XServiceInfo.class, xDocument);
            if ( ! xInfo.supportsService("com.sun.star.text.TextDocument") )
                throw new com.sun.star.lang.IllegalArgumentException( "wrong document type" );

            // safe it as source document for export
            // Don't forget to mark this filter used for exporting too
            synchronized(this)
            {
                m_xDocument = UnoRuntime.queryInterface(
                        com.sun.star.text.XTextDocument.class, xDocument);
                m_bImport = false;
            }

            measure("} setSourceDocument");
        }



        // interface XFilter
        /**
        * Implements the real filter method. We detect if it must be an import or an export.
        * Depends on that we use an existing stream (given inside the MediaDescriptor)
        * or open it by using an URL (must be a part of the descriptor too).
        *
        * @param lDescriptor
        *          the MediaDescriptor which describes the document
        *
        * @return a bool value which describes if method was successfully or not.
        */

        public boolean filter( com.sun.star.beans.PropertyValue[] lDescriptor )
        {
            measure("filter {");

            // first get state of filter operation (import/export)
            // and try to create or get corresponding streams
            // Means: analyze given MediaDescriptor
            // By the way: use synchronized section to get some copies of other
            // internal states too.
            FilterOptions                   aOptions = null ;
            boolean                         bImport  = false;
            com.sun.star.text.XTextDocument xText    = null ;
            synchronized(this)
            {
                aOptions = new FilterOptions(m_xMCF, m_Ctx, m_bImport, lDescriptor);
                bImport  = m_bImport;
                xText    = m_xDocument;
            }

            measure("options analyzed");

            if (!aOptions.isValid())
                return false;

            // start real filtering
            boolean bState = false;
            if (bImport)
                bState = implts_import( xText, aOptions );
            else
                bState = implts_export( xText, aOptions );

            measure("} filter");

            return bState;
        }

        /**
        * Makes the filter process breakable. To do so the outside code may use threads.
        * We use a internal "condition" variable which is queried by the real filter method on
        * every loop they do. So it's more a polling mechanism.
        */
        public void cancel()
        {
            measure("cancel {");

            synchronized(this)
            {
                if (m_nFilterProcState==FILTERPROC_RUNS)
                    m_nFilterProcState=FILTERPROC_BREAK;
            }

            while (true)
            {
                synchronized(this)
                {
                    if (m_nFilterProcState==FILTERPROC_STOPPED)
                        break;
                }
            }

            measure("} cancel");
        }


        // private helper
        /**
         * This helper function imports a simple ascii text file into
         * a text model. We copy every letter to the document.
         * But if some optional filter options are given
         * we make some changes: replace chars or complete strings.
         *
         * Note: It's not allowed for a filter to seek inside the stream.
         * Because the outside frameloader has to set the stream position
         * right and a filter must read till EOF occurs only.
         *
         * @param xTarget
         *          the target text model to put the data in
         *
         * @param aOptions
         *          capsulate all other necessary information for this filter request
         *          (streams, replace values ...)
         *
         * @return a bool value which describes if method was successfully or not.
         */
        private boolean implts_import( com.sun.star.text.XTextDocument xTarget  ,
                                       FilterOptions                   aOptions )
        {
            measure("implts_import {");

            com.sun.star.text.XSimpleText xText = UnoRuntime.queryInterface(
                com.sun.star.text.XSimpleText.class,
                xTarget.getText());

            measure("cast XSimpleText");

            boolean bBreaked = false;

            try
            {
                StringBuffer sBuffer  = new StringBuffer(100000);
                byte[][]     lData    = new byte[1][];
                int          nRead    = aOptions.m_xInput.readBytes( lData, 4096 );

                measure("read first bytes");

                while (nRead>0 && !bBreaked)
                {
                    // copy data from stream to temp. buffer
                    sBuffer.append( new String(lData[0]) );
                    measure("buffer append ["+nRead+"]");

                    nRead = aOptions.m_xInput.readBytes( lData, 2048 );
                    measure("read next bytes");

                    // check for cancelled filter proc on every loop!
                    synchronized(this)
                    {
                        if (m_nFilterProcState==FILTERPROC_BREAK)
                        {
                            m_nFilterProcState = FILTERPROC_STOPPED;
                            return false;
                        }
                    }
                    measure("break check");
                }

                // Make some replacements inside the buffer.
                String sText = implts_replace( sBuffer, aOptions );
                measure("replace");

                // copy current buffer to the document model.
                // Create a new paragraph for every line inside original file.
                // May not all data could be readed - but that doesn't matter here.
                // Reason: somewhere cancelled this function.
                // But check for optioanl replace request before ...
                int nStart  =  0;
                int nEnd    = -1;
                int nLength = sText.length();

                com.sun.star.text.XTextRange xCursor = UnoRuntime.queryInterface(
                    com.sun.star.text.XTextRange.class,
                    xText.createTextCursor());

                while (true)
                {
                    nEnd = sText.indexOf('\n',nStart);

                    if (nEnd==-1 && nStart<nLength)
                        nEnd = nLength;

                    if (nEnd==-1)
                        break;

                    String sLine = sText.substring(nStart,nEnd);
                    nStart = nEnd+1;

                    xText.insertString(xCursor,sLine,false);
                    xText.insertControlCharacter(xCursor,com.sun.star.text.ControlCharacter.PARAGRAPH_BREAK,false);

                    // check for cancelled filter proc on every loop!
                    synchronized(this)
                    {
                        if (m_nFilterProcState==FILTERPROC_BREAK)
                        {
                            m_nFilterProcState = FILTERPROC_STOPPED;
                            return false;
                        }
                    }
                    measure("break check");
                }

                measure("set on model");

                // 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!).
                com.sun.star.util.XRefreshable xRefresh = UnoRuntime.queryInterface(
                com.sun.star.util.XRefreshable.class,
                xTarget);
                xRefresh.refresh();

                // If we created used stream - we must close it too.
                if (aOptions.m_bStreamOwner)
                {
                    aOptions.m_xInput.closeInput();
                    measure("stream close");
                }
            }
            catch(com.sun.star.lang.IllegalArgumentException  exArgument ) { bBreaked = true; }
            catch(com.sun.star.io.BufferSizeExceededException exExceed   ) { bBreaked = true; }
            catch(com.sun.star.io.NotConnectedException       exConnect  ) { bBreaked = true; }
            catch(com.sun.star.io.IOException                 exIO       ) { bBreaked = true; }



            measure("} implts_import");

            return !bBreaked;
        }

        /**
         * This helper function exports a simple ansi text file from
         * a text model. We copy every letter from the document.
         * There are no checks.
         *
         * Note: It's not allowed for a filter to seek inside the stream.
         * Because the outside frameloader has to set the stream position
         * right and a filter must read till EOF occurs only.
         *
         * @param xSource
         *          the source text model to get the data from
         *
         * @param aOptions
         *          capsulate all other necessary information for this filter request
         *          (streams, replace values ...)
         *
         * @return a bool value which describes if method was successfully or not.
         */
        private boolean implts_export( com.sun.star.text.XTextDocument xSource ,
                                       FilterOptions                   aOptions)
        {
            measure("implts_export {");

            com.sun.star.text.XTextRange xText = UnoRuntime.queryInterface(
                com.sun.star.text.XSimpleText.class,
                xSource.getText());

            measure("cast XTextRange");

            boolean bBreaked = false;

            try
            {
                StringBuffer sBuffer = new StringBuffer(xText.getString());
                String       sText   = implts_replace(sBuffer,aOptions);

                measure("get text from model");

                // Normally this function isn't really cancelable
                // But we following operation can be very expensive. So
                // this place is the last one to stop it.
                synchronized(this)
                {
                    if (m_nFilterProcState==FILTERPROC_BREAK)
                    {
                        m_nFilterProcState = FILTERPROC_STOPPED;
                        return false;
                    }
                }

                aOptions.m_xOutput.writeBytes(sText.getBytes());
                aOptions.m_xOutput.flush();

                measure("written to file");

                // If we created used stream - we must close it too.
                if (aOptions.m_bStreamOwner)
                {
                    aOptions.m_xOutput.closeOutput();
                    measure("stream close");
                }
            }
            catch(com.sun.star.io.BufferSizeExceededException exExceed  ) { bBreaked = true; }
            catch(com.sun.star.io.NotConnectedException       exConnect ) { bBreaked = true; }
            catch(com.sun.star.io.IOException                 exIO      ) { bBreaked = true; }

            measure("} implts_export");

            return !bBreaked;
        }

        /**
         * helper function to convert the used StringBuffer into a String value.
         * And we use this chance to have a look on optional filter options
         * which can invite replacing of strings.
         */
        private String implts_replace( StringBuffer rBuffer, FilterOptions aOptions )
        {
            // replace complete strings first
            // Because its easier on a buffer then on a string
            if ( ! aOptions.m_sOld.equals(aOptions.m_sNew) )
            {
                int nStart  = rBuffer.indexOf(aOptions.m_sOld);
                int nLength = aOptions.m_sNew.length();
                int nEnd    = nStart+nLength;
                while (nStart!=-1)
                {
                    rBuffer.replace(nStart,nEnd,aOptions.m_sNew);
                    nStart = rBuffer.indexOf(aOptions.m_sOld,nEnd);
                    nEnd   = nStart+nLength;
                }
            }

            // convert buffer into return format [string]
            // and convert to lower or upper case if required.
            String sResult = rBuffer.toString();
            if (aOptions.m_bCaseChange)
            {
                if (aOptions.m_bLower)
                    sResult = sResult.toLowerCase();
                else
                    sResult = sResult.toUpperCase();
            }

            return sResult;
        }



        // interface XServiceInfo
        /**
         * This method returns an array of all supported service names.
         *
         * @return Array of supported service names.
         */
        public String[] getSupportedServiceNames()
        {
             return m_serviceNames;
        }

        /**
         * This method returns true, if the given service will be
         * supported by this component.
         *
         * @param sService
         *  the requested service name
         *
         * @return True, if the given service name will be supported;
         *         False otherwise.
         */
        public boolean supportsService( String sService )
        {
            return (
                    sService.equals( m_serviceNames[0] ) ||
                    sService.equals( m_serviceNames[1] ) ||
                    sService.equals( m_serviceNames[2] )
                    );
        }

        /**
         * Return the real class name of the component
         *
         * @return Class name of the component.
         */
        public String getImplementationName()
        {
            return _AsciiReplaceFilter.class.getName();
        }


    }
    // end of inner class, the wrapper class just has the two methods required for registering the component

    /**
     * Gives a factory for creating the service.
     * This method is called by the <code>JavaLoader</code>
     *
     * @param sImplName
     *  The implementation name of the component.
     *
     * @return Returns a <code>XSingleComponentFactory</code> for
     *         creating the component.
     *
     * @see com.sun.star.comp.loader.JavaLoader
     */

    public static XSingleComponentFactory __getComponentFactory(String sImplName)
    {
        XSingleComponentFactory xFactory = null;

        if ( sImplName.equals( _AsciiReplaceFilter.class.getName() ) )
            xFactory = com.sun.star.lib.uno.helper.Factory.createComponentFactory(_AsciiReplaceFilter.class,
                                             _AsciiReplaceFilter.m_serviceNames);
        return xFactory;
    }
}