summaryrefslogtreecommitdiff
path: root/source/de/anomic/tools/mediawikiIndex.java
blob: b4149e84bce186a66ed519c58131dff6289cd35a (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
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
// mediawikiIndex.java
// (C) 2008 by Michael Peter Christen; mc@yacy.net, Frankfurt a. M., Germany
// first published 20.11.2008 on http://yacy.net
//
// This is a part of YaCy, a peer-to-peer based web search engine
//
// $LastChangedDate: 2006-04-02 22:40:07 +0200 (So, 02 Apr 2006) $
// $LastChangedRevision: 1986 $
// $LastChangedBy: orbiter $
//
// LICENSE
// 
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

package de.anomic.tools;

import org.apache.tools.bzip2.CBZip2InputStream;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Date;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import de.anomic.data.wiki.wikiCode;
import de.anomic.data.wiki.wikiParser;
import de.anomic.kelondro.util.ByteBuffer;
import de.anomic.plasma.plasmaParser;
import de.anomic.plasma.plasmaParserDocument;
import de.anomic.plasma.parser.ParserException;
import de.anomic.yacy.yacyURL;
import de.anomic.yacy.logging.Log;

/*
 * this class provides data structures to read a mediawiki dump file in xml format
 * as referenced with xmlns="http://www.mediawiki.org/xml/export-0.3/"
 */

public class mediawikiIndex extends Thread {

    private static final String textstart = "<text";
    private static final String textend = "</text>";
    private static final String pagestart = "<page>";
    private static final String pageend = "</page>";
    private static final byte[] pagestartb = pagestart.getBytes();
    private static final byte[] pageendb = pageend.getBytes();
    
    private wikiParser wparser;
    private plasmaParser hparser;
    private String urlStub;
    public File sourcefile;
    public File targetdir;
    public int count;
    private long start;
    private long docsize;
    private int approxdocs;
    
    private static final int docspermbinxmlbz2 = 800;  // documents per megabyte in a xml.bz2 wikimedia dump
    
    public static mediawikiIndex job; // if started from a servlet, this object is used to store the thread
    
    public mediawikiIndex(File sourcefile, File targetdir, String baseURL) throws MalformedURLException {
    	this.sourcefile = sourcefile;
    	this.docsize = sourcefile.length();
    	this.approxdocs = (int) (this.docsize * (long) docspermbinxmlbz2 / 1024L / 1024L);
    	this.targetdir = targetdir;
        this.urlStub = baseURL;
        this.wparser = new wikiCode(new URL(baseURL).getHost());
        this.hparser = new plasmaParser();
        this.count = 0;
        this.start = 0;
        // must be called before usage:
        plasmaParser.initHTMLParsableMimeTypes("text/html");
        plasmaParser.initParseableMimeTypes(plasmaParser.PARSER_MODE_CRAWLER, "text/html");
    }
    
    /**
     * return the number of articles per second
     * @return
     */
    public int speed() {
        if (count == 0) return 0;
        return (int) ((long) count / runningTime());
    }
    
    /**
     * return the remaining seconds for the completion of all records in milliseconds
     * @return
     */
    public long remainingTime() {
        return Math.max(0, this.approxdocs - count) / speed();
    }
    
    public long runningTime() {
        return (System.currentTimeMillis() - start) / 1024;
    }
    
    public void run() {
        this.start = System.currentTimeMillis();
        try {
            String targetstub = sourcefile.getName();
            targetstub = targetstub.substring(0, targetstub.length() - 8);
            InputStream is = new BufferedInputStream(new FileInputStream(sourcefile), 1 * 1024 * 1024);
            if (sourcefile.getName().endsWith(".bz2")) {
                int b = is.read();
                if (b != 'B') throw new IOException("Invalid bz2 content.");
                b = is.read();
                if (b != 'Z') throw new IOException("Invalid bz2 content.");
                is = new CBZip2InputStream(is);
            }
            BufferedReader r = new BufferedReader(new java.io.InputStreamReader(is, "UTF-8"), 4 * 1024 * 1024);
            String t;
            StringBuilder sb = new StringBuilder();
            boolean page = false, text = false;
            String title = null;
            plasmaParser.initHTMLParsableMimeTypes("text/html");
            plasmaParser.initParseableMimeTypes(plasmaParser.PARSER_MODE_CRAWLER, "text/html");
            wikiparserrecord poison = newRecord();
            int threads = Math.max(2, Runtime.getRuntime().availableProcessors() - 1);
            BlockingQueue<wikiparserrecord> in = new ArrayBlockingQueue<wikiparserrecord>(threads * 10);
            BlockingQueue<wikiparserrecord> out = new ArrayBlockingQueue<wikiparserrecord>(threads * 10);
            ExecutorService service = Executors.newFixedThreadPool(threads + 1);
            convertConsumer[] consumers = new convertConsumer[threads];
            Future<?>[] consumerResults = new Future[threads];
            for (int i = 0; i < threads; i++) {
                consumers[i] = new convertConsumer(in, out, poison);
                consumerResults[i] = service.submit(consumers[i]);
            }
            convertWriter   writer = new convertWriter(out, poison, targetdir, targetstub);
            Future<Integer> writerResult = service.submit(writer);
            
            wikiparserrecord record;
            int p;
            while ((t = r.readLine()) != null) {
                if (t.indexOf(pagestart) >= 0) {
                    page = true;
                    continue;
                }
                if ((p = t.indexOf(textstart)) >= 0) {
                    text = page;
                    int q = t.indexOf('>', p + textstart.length());
                    if (q > 0) {
                        int u = t.indexOf(textend, q + 1);
                        if (u > q) {
                            sb.append(t.substring(q + 1, u));
                            Log.logInfo("WIKITRANSLATION", "[INJECT] Title: " + title);
                            if (sb.length() == 0) {
                                Log.logInfo("WIKITRANSLATION", "ERROR: " + title + " has empty content");
                                continue;
                            }
                            record = newRecord(title, sb);
                            try {
                                in.put(record);
                                this.count++;
                            } catch (InterruptedException e1) {
                                e1.printStackTrace();
                            }
                            sb = new StringBuilder(200);
                            continue;
                        } else {
                            sb.append(t.substring(q + 1));
                        }
                    }
                    continue;
                }
                if (t.indexOf(textend) >= 0) {
                    text = false;
                    Log.logInfo("WIKITRANSLATION", "[INJECT] Title: " + title);
                    if (sb.length() == 0) {
                        Log.logInfo("WIKITRANSLATION", "ERROR: " + title + " has empty content");
                        continue;
                    }
                    record = newRecord(title, sb);
                    try {
                        in.put(record);
                        this.count++;
                    } catch (InterruptedException e1) {
                        e1.printStackTrace();
                    }
                    sb = new StringBuilder(200);
                    continue;
                }
                if (t.indexOf(pageend) >= 0) {
                    page = false;
                    continue;
                }
                if ((p = t.indexOf("<title>")) >= 0) {
                    title = t.substring(p + 7);
                    int q = title.indexOf("</title>");
                    if (q >= 0) title = title.substring(0, q);
                    continue;
                }
                if (text) {
                    sb.append(t);
                    sb.append('\n');
                }
            }
            r.close();
            
            try {
                for (int i = 0; i < threads; i++) {
                    in.put(poison);
                }
                for (int i = 0; i < threads; i++) {
                    consumerResults[i].get(10000, TimeUnit.MILLISECONDS);
                }
                out.put(poison);
                writerResult.get(10000, TimeUnit.MILLISECONDS);
            } catch (InterruptedException e1) {
                e1.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            } catch (TimeoutException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static void checkIndex(File wikimediaxml) {
        File idx = idxFromWikimediaXML(wikimediaxml);
        if (idx.exists()) return;
        new indexMaker(wikimediaxml).start();
    }
    
    public static class indexMaker extends Thread {
        
        File wikimediaxml;
        public indexMaker(File wikimediaxml) {
            this.wikimediaxml = wikimediaxml;
        }
        
        public void run() {
            try {
                createIndex(this.wikimediaxml);
            } catch (final IOException e) {
            } catch (final Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    public static File idxFromWikimediaXML(File wikimediaxml) {
        return new File(wikimediaxml.getAbsolutePath() + ".idx.xml");
    }
    
    public static void createIndex(File dumpFile) throws IOException {
        // calculate md5
        //String md5 = serverCodings.encodeMD5Hex(dumpFile);
        
        // init reader, producer and consumer
        PositionAwareReader in = new PositionAwareReader(dumpFile);
        indexProducer producer = new indexProducer(100, idxFromWikimediaXML(dumpFile));
        wikiConsumer consumer = new wikiConsumer(100, producer);
        ExecutorService service = Executors.newFixedThreadPool(2);
        Future<Integer> producerResult = service.submit(consumer);
        Future<Integer> consumerResult = service.submit(producer);
        service.shutdown();
        
        // read the wiki dump
        long start, stop;
        while (in.seek(pagestartb)) {
            start = in.pos() - 6;
            in.resetBuffer();
            if (!in.seek(pageendb)) break;
            stop = in.pos();
            consumer.consume(new wikiraw(in.bytes(), start, stop));
            in.resetBuffer();
        }
        
        // shut down the services
        try {
            consumer.consume(wikiConsumer.poison);
            try {consumerResult.get(5000, TimeUnit.MILLISECONDS);} catch (TimeoutException e) {}
            producer.consume(indexProducer.poison);
            if (!consumerResult.isDone()) consumerResult.get();
            producerResult.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
            return;
        } catch (ExecutionException e) {
            e.printStackTrace();
            return;
        }
        in.close();
    }

    private static class indexProducer implements Callable<Integer> {

        private BlockingQueue<wikisourcerecord> entries;
        PrintWriter out;
        private static wikisourcerecord poison = new wikisourcerecord("", 0, 0);
        int count;
        
        public indexProducer(int bufferCount, File indexFile) throws IOException {
            entries = new ArrayBlockingQueue<wikisourcerecord>(bufferCount);
            out = new PrintWriter(new BufferedWriter(new FileWriter(indexFile)));
            count = 0;
            out.println("<index>");
            
        }
        
        public void consume(wikisourcerecord b) {
            try {
                entries.put(b);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        public Integer call() {
            wikisourcerecord r;
            try {
                while(true) {
                    r = entries.take();
                    if (r == poison) {
                        Log.logInfo("WIKITRANSLATION", "producer / got poison");
                        break;
                    }
                    out.println("  <page start=\"" + r.start + "\" length=\"" + (r.end - r.start) + "\">");
                    out.println("    <title>" + r.title + "</title>");
                    out.println("  </page>");
                    Log.logInfo("WIKITRANSLATION", "producer / record start: " + r.start + ", title : " + r.title);
                    count++;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            entries.clear();
            out.println("</index>");
            out.close();
            return Integer.valueOf(count);
        }
        
    }
    
    private static class wikiConsumer implements Callable<Integer> {

        private BlockingQueue<wikiraw> entries;
        private static wikiraw poison = new wikiraw(new byte[0], 0, 0);
        private indexProducer producer;
        private int count;
        
        public wikiConsumer(int bufferCount, indexProducer producer) {
            entries = new ArrayBlockingQueue<wikiraw>(bufferCount);
            this.producer = producer;
            count = 0;
        }
        
        public void consume(wikiraw b) {
            try {
                entries.put(b);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        public Integer call() {
            wikisourcerecord r;
            wikiraw c;
            try {
                while(true) {
                    c = entries.take();
                    if (c == poison) {
                        Log.logInfo("WIKITRANSLATION", "consumer / got poison");
                        break;
                    }
                    try {
                        r = new wikisourcerecord(c.b, c.start, c.end);
                        producer.consume(r);
                        Log.logInfo("WIKITRANSLATION", "consumer / record start: " + r.start + ", title : " + r.title);
                        count++;
                    } catch (RuntimeException e) {}
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            entries.clear();
            return Integer.valueOf(count);
        }
        
    }

    private static class wikiraw {
        public long start, end;
        public byte[] b;
        public wikiraw(byte[] b, long start, long end) {
            this.b = b;
            this.start = start;
            this.end = end;
        }
    }
    
    public static class wikisourcerecord {
        public long start, end;
        public String title;
        public wikisourcerecord(String title, long start, long end) {
            this.title = title;
            this.start = start;
            this.end = end;
        }
        public wikisourcerecord(byte[] chunk, long start, long end) {
            String s;
            try {
                s = new String(chunk, "UTF-8");
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException(e.getMessage());
            }
            int t0 = s.indexOf("<title>");
            if (t0 >= 0) {
                int t1 = s.indexOf("</title>", t0);
                if (t1 >= 0) {
                    this.title = s.substring(t0 + 7, t1);
                } else {
                    throw new RuntimeException("no title end in record");
                }
            } else {
                throw new RuntimeException("no title start in record");
            }
            
            this.start = start;
            this.end = end;
        }
    }
    public wikiparserrecord newRecord() {
        return new wikiparserrecord(null, null);
    }
    public wikiparserrecord newRecord(String title, StringBuilder sb) {
        return new wikiparserrecord(title, sb);
    }
    
    public class wikiparserrecord {
        public String title;
        String source;
        String html;
        yacyURL url;
        plasmaParserDocument document;
        public wikiparserrecord(String title, StringBuilder sb) {
            this.title = title;
            this.source = (sb == null) ? null : sb.toString();
        }
        public void genHTML() throws IOException {
            try {
                html = wparser.transform(source);
            } catch (Exception e) {
            	e.printStackTrace();
                throw new IOException(e.getMessage());
            }
        }
        public void genDocument() throws InterruptedException, ParserException {
            try {
				url = new yacyURL(urlStub + title, null);
				document = hparser.parseSource(url, "text/html", "utf-8", html.getBytes("UTF-8"));
				// the wiki parser is not able to find the proper title in the source text, so it must be set here
				document.setTitle(title);
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			} catch (MalformedURLException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
        }
        public void writeXML(OutputStreamWriter os) throws IOException {
            document.writeXML(os, new Date());
        }
    }
    
    private static class PositionAwareReader {
    
        private InputStream is;
        private long seekpos;
        private ByteBuffer bb;
        
        public PositionAwareReader(File dumpFile) throws FileNotFoundException {
            this.is = new BufferedInputStream(new FileInputStream(dumpFile), 64 *1024);
            this.seekpos = 0;
            this.bb = new ByteBuffer();
        }
        
        public void resetBuffer() {
            if (bb.length() > 10 * 1024) bb = new ByteBuffer(); else bb.clear();
        }
        
        public boolean seek(byte[] pattern) throws IOException {
            int pp = 0;
            int c;
            while ((c = is.read()) >= 0) {
                seekpos++;
                bb.append(c);
                if (pattern[pp] == c) pp++; else pp = 0;
                if (pp == pattern.length) return true;
            }
            return false;
        }
        
        public long pos() {
            return seekpos;
        }
        
        public byte[] bytes() {
            return bb.getBytes();
        }
        
        public void close() {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static byte[] read(File f, long start, int len) {
        byte[] b = new byte[len];
        RandomAccessFile raf = null;
        try {
            raf = new RandomAccessFile(f, "r");
            raf.seek(start);
            raf.read(b);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        } finally {
            if (raf != null) try {
                raf.close();
                try{raf.getChannel().close();} catch (IOException e) {}
            } catch (IOException e) { }
        }
        return b;
    }
    
    public static wikisourcerecord find(String title, File f) throws IOException {
        PositionAwareReader in = new PositionAwareReader(f);
        long start;
        String m = "<title>" + title + "</title>";
        String s;
        while (in.seek("<page ".getBytes())) {
            start = in.pos() - 6;
            in.resetBuffer();
            if (!in.seek(pageendb)) break;
            s = new String(in.bytes(), "UTF-8");
            in.resetBuffer();
            if (s.indexOf(m) >= 0) {
                // we found the record
                //Log.logInfo("WIKITRANSLATION", "s = " + s);
                int p = s.indexOf("start=\"");
                if (p < 0) return null;
                p += 7;
                int q = s.indexOf('"', p + 1);
                if (q < 0) return null;
                start = Long.parseLong(s.substring(p, q));
                p = s.indexOf("length=\"", q);
                if (p < 0) return null;
                p += 8;
                q = s.indexOf('"', p + 1);
                if (q < 0) return null;
                int length = Integer.parseInt(s.substring(p, q));
                //Log.logInfo("WIKITRANSLATION", "start = " + start + ", length = " + length);
                return new wikisourcerecord(title, start, start + length);
            }
        }
        return null;
    }
    
    private static class convertConsumer implements Callable<Integer> {

        private BlockingQueue<wikiparserrecord> in, out;
        private wikiparserrecord poison;
        
        public convertConsumer(BlockingQueue<wikiparserrecord> in, BlockingQueue<wikiparserrecord> out, wikiparserrecord poison) {
            this.poison = poison;
            this.in = in;
            this.out = out;
        }
        
        public Integer call() {
            wikiparserrecord record;
            try {
                while(true) {
                    record = in.take();
                    if (record == poison) {
                        Log.logInfo("WIKITRANSLATION", "convertConsumer / got poison");
                        break;
                    }
                    try {
                        record.genHTML();
                        record.genDocument();
                        out.put(record);
                    } catch (RuntimeException e) {
                        e.printStackTrace();
                    } catch (ParserException e) {
                        e.printStackTrace();
                    } catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Log.logInfo("WIKITRANSLATION", "*** convertConsumer has terminated");
            return Integer.valueOf(0);
        }
        
    }
    
    private static class convertWriter implements Callable<Integer> {

        private BlockingQueue<wikiparserrecord> in;
        private wikiparserrecord poison;
        private OutputStreamWriter osw;
        private String targetstub;
        private File targetdir;
        private int fc, rc;
        private String outputfilename;
        
        public convertWriter(
                BlockingQueue<wikiparserrecord> in,
                wikiparserrecord poison,
                File targetdir,
                String targetstub) {
            this.poison = poison;
            this.in = in;
            this.osw = null;
            this.targetdir = targetdir;
            this.targetstub = targetstub;
            this.fc = 0;
            this.rc = 0;
            this.outputfilename = null;
        }
        
        public Integer call() {
            wikiparserrecord record;
            try {
                while(true) {
                    record = in.take();
                    if (record == poison) {
                        Log.logInfo("WIKITRANSLATION", "convertConsumer / got poison");
                        break;
                    }
                    
                    if (osw == null) {
                        // start writing a new file
                        this.outputfilename = targetstub + "." + fc + ".xml.prt";
                        this.osw = new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(new File(targetdir, outputfilename))), "UTF-8");
                        osw.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<surrogates xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n");
                    }
                    Log.logInfo("WIKITRANSLATION", "[CONSUME] Title: " + record.title);
                    record.document.writeXML(osw, new Date());
                    rc++;
                    if (rc >= 10000) {
                        osw.write("</surrogates>\n");
                        osw.close();
                        String finalfilename = targetstub + "." + fc + ".xml";
                        new File(targetdir, outputfilename).renameTo(new File(targetdir, finalfilename));
                        rc = 0;
                        fc++;
                        outputfilename = targetstub + "." + fc + ".xml.prt";
                        osw = new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(new File(targetdir, outputfilename))), "UTF-8");
                        osw.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<surrogates xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n");
                    }

                    
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
	            try {
					osw.write("</surrogates>\n");
		            osw.close();
		            String finalfilename = targetstub + "." + fc + ".xml";
		            new File(targetdir, outputfilename).renameTo(new File(targetdir, finalfilename));
				} catch (IOException e) {
					e.printStackTrace();
				}
            }
            Log.logInfo("WIKITRANSLATION", "*** convertWriter has terminated");
            return Integer.valueOf(0);
        }
        
    }
    
    public static void main(String[] s) {
        if (s.length == 0) {
            Log.logInfo("WIKITRANSLATION", "usage:");
            Log.logInfo("WIKITRANSLATION", " -index <wikipedia-dump>");
            Log.logInfo("WIKITRANSLATION", " -read  <start> <len> <idx-file>");
            Log.logInfo("WIKITRANSLATION", " -find  <title> <wikipedia-dump>");
            Log.logInfo("WIKITRANSLATION", " -convert <wikipedia-dump-xml.bz2> <convert-target-dir> <url-stub>");
            System.exit(0);
        }

        // example:
        // java -Xmx2000m -cp classes:lib/bzip2.jar de.anomic.tools.mediawikiIndex -convert DATA/HTCACHE/dewiki-20090311-pages-articles.xml.bz2 DATA/SURROGATES/in/ http://de.wikipedia.org/wiki/

        if (s[0].equals("-convert") && s.length > 2 && s[1].endsWith(".xml.bz2") && s[3].startsWith("http://")) {
            File sourcefile = new File(s[1]);
            File targetdir = new File(s[2]);
            String urlStub = s[3]; // i.e. http://de.wikipedia.org/wiki/
            //String language = urlStub.substring(7,9);
            try {
                mediawikiIndex mi = new mediawikiIndex(sourcefile, targetdir, urlStub);
                mi.start();
                mi.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        
        if (s[0].equals("-index")) {   
            try {
                createIndex(new File(s[1]));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        
        if (s[0].equals("-read")) {
            long start = Integer.parseInt(s[1]);
            int  len   = Integer.parseInt(s[2]);
            try {
                System.out.println(new String(read(new File(s[3]), start, len), "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
        
        if (s[0].equals("-find")) {
            try {
                wikisourcerecord w = find(s[1], new File(s[2] + ".idx.xml"));
                if (w == null) {
                    Log.logInfo("WIKITRANSLATION", "not found");
                } else {
                    System.out.println(new String(read(new File(s[2]), w.start, (int) (w.end - w.start)), "UTF-8"));
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            
        }
        System.exit(0);
    }
    
}