View Javadoc
1   /*
2    * The akquinet maven-latex-plugin project
3    *
4    * Copyright (c) 2011 by akquinet tech@spree GmbH
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package eu.simuline.m2latex.core;
20  
21  import java.io.BufferedReader;
22  import java.io.BufferedWriter;
23  import java.io.File;
24  import java.io.FileNotFoundException;
25  import java.io.FileReader;
26  import java.io.FileWriter;
27  import java.io.FileFilter;
28  import java.io.Closeable;
29  import java.io.FileInputStream;
30  import java.io.FileOutputStream;
31  import java.io.InputStream;
32  import java.io.OutputStream;
33  import java.io.IOException;
34  
35  import java.nio.file.Path;
36  import java.nio.CharBuffer;
37  
38  import java.util.Collection;
39  import java.util.TreeSet;
40  
41  import java.util.regex.Pattern;
42  import java.util.regex.Matcher;
43  
44  // FIXME: jdee bug: delete static imports: does not find superfluous 
45  
46  /**
47   * Sole interface to <code>org.apache.commons.io.</code>. 
48   * A collection of utility methods for file manipulation. 
49   */
50  class TexFileUtils {
51  
52      private final static String PREFIX_HIDDEN = ".";
53  
54      private final static String PATTERN_INS_LATEX_MAIN = "T\\$T";
55  
56      private final LogWrapper log;
57  
58      TexFileUtils(LogWrapper log) {
59          this.log = log;
60      }
61  
62      /**
63       * Returns the listing of the directory <code>dir</code> 
64       * or <code>null</code> if it is not readable 
65       * and emit an according warning if so. 
66       * <p>
67       * Logging: 
68       * WFU01: Cannot read directory 
69       *
70       * @param dir
71       *    an existing directory. 
72       * @return
73       *    the list of entries of <code>dir</code> 
74       *    or <code>null</code> if it is not readable. 
75       */
76      // used only in 
77      // constructor of DirNode 
78      // copyOutputToTargetFolder, deleteX
79      File[] listFilesOrWarn(File dir) {
80  	assert dir != null && dir.isDirectory() : "Expected folder found "+dir;
81          File[] files = dir.listFiles();
82  	warnIfNull(files, dir);
83  	return files;
84      }
85  
86      /**
87       * Returns the listing of the directory <code>dir</code> 
88       * filtered by <code>filter</code> 
89       * or <code>null</code> if <code>dir</code> is not readable 
90       * and emit an according warning if so. 
91       * <p>
92       * Logging: 
93       * WFU01: Cannot read directory 
94       *
95       * @param dir
96       *    an existing directory. 
97       * @param filter
98       *    a file filter 
99       * @return
100      *    the list of entries of <code>dir</code> 
101      *    accepted by <code>filter</code>
102      *    or <code>null</code> if <code>dir</code> is not readable. 
103      */
104     // used by LatexProcessor.runMakeIndexByNeed only 
105     File[] listFilesOrWarn(File dir, FileFilter filter) {
106 	assert dir != null && dir.isDirectory() : "Expected folder found "+dir;
107         File[] files = dir.listFiles(filter);
108 	warnIfNull(files, dir);
109 	return files;
110     }
111 
112     private void warnIfNull(File[] files, File dir) {
113 	if (files == null) {
114 	    this.log.warn("WFU01: Cannot read directory '" + dir + 
115 			  "'; build may be incomplete. ");
116 	}
117     }
118 
119     /**
120      * Returns the directory containing <code>sourceFile</code> 
121      * with the prefix <code>sourceBaseDir</code> 
122      * replaced by <code>targetBaseDir</code>. 
123      * E.g. <code>sourceFile=/tmp/adir/afile</code>, 
124      * <code>sourceBaseDir=/tmp</code>, <code>targetBaseDir=/home</code> 
125      * returns <code>/home/adir/</code>. 
126      *
127      * @param srcFile
128      *    the source file the parent directory of which 
129      *    shall be converted to the target. 
130      * @param srcBaseDir
131      *    the base directory of the source. 
132      *    Immediately or not, 
133      *    <code>sourceFile</code> shall be in <code>sourceBaseDir</code>. 
134      * @param targetBaseDir
135      *    the base directory of the target. 
136      * @return
137      *    the directory below <code>targetBaseDir</code>
138      *    which corresponds to the parent directory of <code>sourceFile</code> 
139      *    which is below <code>sourceBaseDir</code>. 
140      * @throws BuildFailureException
141      *    TFU01: if the target directory that would be returned 
142      *    exists already as a regular file. 
143      */
144     // used by LatexProcessor.create() only 
145     File getTargetDirectory(File srcFile,
146 			    File srcBaseDir,
147 			    File targetBaseDir) throws BuildFailureException {
148 	Path srcParentPath = srcFile.getParentFile().toPath();
149 	Path srcBasePath = srcBaseDir.toPath();
150 
151 	// FIXME: really ok? what if strings but not paths are prefixes? 
152 	// I think should be via Path: toPath. 
153 	assert srcParentPath.startsWith(srcBasePath);
154 	srcParentPath = srcBasePath.relativize(srcParentPath);
155 
156 	// FIXME: CAUTION: this may exist and be no directory!
157 	File targetDir = new File(targetBaseDir, srcParentPath.toString());
158 
159 	targetDir.mkdirs();
160 
161 	if (!targetDir.isDirectory()) {
162 	    throw new BuildFailureException
163 		("TFU01: Cannot create destination directory '" + targetDir + 
164 		 "'. ");
165 	}
166 	assert targetDir.isDirectory();
167 	return targetDir;
168     }
169 
170     /**
171      * Returns a file filter matching neither directories 
172      * nor <code>texFile</code> 
173      * but else all files with names matching <code>pattern</code>, 
174      * where the special sequence {@link #PATTERN_INS_LATEX_MAIN} 
175      * is replaced by the prefix of <code>texFile</code>. 
176      *
177      * @param texFile
178      *    a latex main file for which a file filter has to be created. 
179      * @param pattern
180      *    a pattern 
181      *    for which the special sequence {@link #PATTERN_INS_LATEX_MAIN} 
182      *    is replaced by the prefix of <code>texFile</code> 
183      *    before a file filter is created from it. 
184      * @return
185      *    a non-null file filter matching neither directories 
186      *    nor <code>texFile</code> 
187      *    but else all files with names matching <code>pattern</code>, 
188      *    where the special sequence {@link #PATTERN_INS_LATEX_MAIN} 
189      *    is replaced by the prefix of <code>texFile</code>. 
190      */
191     // used only: in methods 
192     // - LatexProcessor.create on tex-file to determine output files. 
193     // - LatexPreProcessor.clearTargetTex to clear also intermediate files. 
194     FileFilter getFileFilter(File texFile, String pattern) {
195 	final String patternAccept = pattern
196 	    .replaceAll(PATTERN_INS_LATEX_MAIN, 
197 			getFileNameWithoutSuffix(texFile));
198 	return new FileFilter() {
199 	    public boolean accept(File file) {
200 		// the second is superfluous for copying 
201 		// and only needed for deletion. 
202 		if (file.isDirectory() || file.equals(texFile)) {
203 		    return false;
204 		}
205 		return file.getName().matches(patternAccept);
206 	    }
207 	};
208     }
209 
210     /**
211      * Returns a file filter matching no directories 
212      * but else all files with names matching <code>xxx<pattern>.idx</code>, 
213      * where <code>idxFile</code> has the form <code>xxx.idx</code>. 
214      *
215      * @param idxFile
216      *    an idx file for which a file filter has to be created. 
217      * @param pattern
218      *    a pattern which is inserted in the name of <code>idxFile</code> 
219      *    right before the suffix. 
220      * @return
221      *    a non-null file filter matching no directories 
222      *    but else all files matching <code>xxx<pattern>.idx</code>. 
223      */
224     // used by LatexProcessor.runMakeIndexByNeed only 
225     FileFilter getFileFilterReplace(File idxFile, String pattern) {
226 	final String patternAccept = getFileNameWithoutSuffix(idxFile) 
227 	    + pattern + getSuffix(idxFile); 
228 	return new FileFilter() {
229 	    public boolean accept(File file) {
230 		if (file.isDirectory()) {
231 		    return false;
232 		}
233 		return file.getName().matches(patternAccept);
234 	    }
235 	};
236     }
237 
238     /**
239      * Copies output of the current goal to target folder. 
240      * The source is the parent folder of <code>texFile</code>, 
241      * all its files passing <code>fileFilter</code> 
242      * are considered as output files and 
243      * are copied to <code>targetDir</code>. 
244      * This is invoked by {@link LatexProcessor#create()} only. 
245      * <p>
246      * Logging: 
247      * <ul>
248      * <li> WFU01: Cannot read directory... 
249      * <li> WFU03: Cannot close 
250      * </ul>
251      *
252      * @param texFile
253      *    the latex main file which was processed. 
254      *    Its parent directory 
255      *    is the working directory of the compilation process 
256      *    in which the output files are created. 
257      *    Thus it must be readable (in fact it must also be writable; 
258      *    otherwise the output files could not have been created). 
259      * @param fileFilter
260      *    the filter accepting the files (and best only the files) 
261      *    which are the result of the processing. 
262      * @param targetDir
263      *    the target directory the output files have to be copied to. 
264      *    If this exists already, it must be a directory 
265      *    and it must be writable. 
266      *    If it does not exist, it must be creatable. 
267      * @throws BuildFailureException
268      *    <ul>
269      *    <li>TFU04, TFU05 if 
270      *    the destination file exists 
271      *    and is either a directory (TFU04) or is not writable (TFU05). 
272      *    <li>TFU06 if 
273      *    an IO-error orrurs when copying: opening streams, reading or writing. 
274      *    </ul>
275      */
276     // used in LatexProcessor.create() only 
277     void copyOutputToTargetFolder(File texFile, 
278 				  FileFilter fileFilter, 
279 				  File targetDir) throws BuildFailureException {
280 	assert    texFile.exists() && ! texFile.isDirectory()
281 	    : "Expected existing (regular) tex file "+texFile;
282 	assert !targetDir.exists() || targetDir.isDirectory()
283 	    : "Expected existing target folder "+targetDir;
284 
285 	File texFileDir = texFile.getParentFile();
286 	// may log warning WFU01 
287         File[] outputFiles = listFilesOrWarn(texFileDir);
288         if (outputFiles == null) {
289 	    // Here, logging WFU01 already done 
290 	    return;
291 	}
292 	assert outputFiles != null;
293 
294 	File srcFile, destFile;
295 	for (int idx = 0; idx < outputFiles.length; idx++) {
296 	    srcFile = outputFiles[idx];
297 	    assert srcFile.exists() : "Missing " + srcFile;
298 	    if (!fileFilter.accept(srcFile)) {
299 		continue;
300 	    }
301 	    assert srcFile.exists() && !srcFile.isDirectory()
302 	    : "Expected existing (regular) tex file "+texFile;
303 	    // since !targetDir.exists() || targetDir.isDirectory() 
304 	    assert !srcFile.equals(targetDir);
305 	    assert !srcFile.equals(texFile);
306 
307 
308 	    destFile = new File(targetDir, srcFile.getName());
309 
310 	    if (destFile.isDirectory()) {
311 		throw new BuildFailureException
312 		    ("TFU04: Cannot overwrite directory '" + destFile + "'. ");
313 	    }
314 
315 	    this.log.debug("Copying '" + srcFile.getName() + 
316 			   "' to '" + targetDir + "'. ");
317 	    try {
318 		// may throw IOException: opening streams, read/write 
319 		// may log warning WFU03: Cannot close 
320 		doCopyFile(srcFile, destFile);
321 	    } catch (IOException e) {
322 		throw new BuildFailureException
323 		    ("TFU06: Cannot copy '" + srcFile.getName() + 
324 		     "' to '" + targetDir + "'. ",
325 		     e);
326 	    }
327 	} // for 
328     }
329 
330     // FIXME: copied from FileUtils 
331     /**
332      * Internal copy file method. 
333      * <p>
334      * Logging: 
335      * WFU03: Cannot close 
336      * 
337      * @param srcFile   
338      *    the source file. 
339      * @param destFile   
340      *    the destination file. 
341      * @throws IOException  
342      *    if an error occurs: opening input/output streams, 
343      *    reading from file/writing to file. 
344      */
345     private void doCopyFile(File srcFile, File destFile) throws IOException {
346 	// may throw FileNotFoundException <= IOException 
347 	// if cannot be opened for reading: e.g. not exists, is a directory,...
348         FileInputStream input = new FileInputStream(srcFile);
349         try {
350 	    // may throw FileNotFoundException <= IOException 
351             FileOutputStream output = new FileOutputStream(destFile);
352 	    // if cannot be opened for writing: 
353 	    // e.g. not exists, is a directory,...
354 	    try {
355 		// may throw IOException if an I/O-error occurs 
356 		// when reading or writing 
357                 copyStream(input, output);
358             } finally {
359 		// may log warning WFU03
360                 closeQuietly(output);
361             }
362 	} finally {
363 	    // may log warning WFU03
364  	    closeQuietly(input);
365 	}
366 
367 	assert !destFile.isDirectory() && destFile.canWrite()
368 	    : "Expected existing (regular) writable file "+destFile;
369 	destFile.setLastModified(srcFile.lastModified());
370     }
371 
372     /**
373      * The default buffer size ({@value}) to use for 
374      * {@link #copyStream(InputStream, OutputStream)}
375      */
376     private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
377 
378     /**
379      * Copy bytes from a large (over 2GB) <code>InputStream</code> to an
380      * <code>OutputStream</code>.
381      * <p>
382      * This method uses the provided buffer, so there is no need to use a
383      * <code>BufferedInputStream</code>.
384      *
385      * @param input
386      *    the <code>InputStream</code> to read from
387      * @param output
388      *    the <code>OutputStream</code> to write to
389      * @throws IOException 
390      *    if an I/O error occurs while reading or writing 
391      */
392     private static void copyStream(InputStream input, 
393 				   OutputStream output) throws IOException {
394 	byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
395         int n;
396 	// may throw IOException 
397         while (-1 != (n = input.read(buffer))) {
398 	    // may throw IOException 
399             output.write(buffer, 0, n);
400         }
401     }
402 
403     // FIXME: almost copy from IOUtils 
404     /**
405      * Unconditionally close a <code>Closeable</code>.
406      * <p>
407      * Equivalent to {@link Closeable#close()}, 
408      * except any exceptions will be ignored. FIXME 
409      * This is typically used in finally blocks.
410      * <p>
411      * Example code:
412      * <pre>
413      *   Closeable closeable = null;
414      *   try {
415      *       closeable = new FileReader("foo.txt");
416      *       // process closeable
417      *       closeable.close();
418      *   } catch (Exception e) {
419      *       // error handling
420      *   } finally {
421      *       IOUtils.closeQuietly(closeable);
422      *   }
423      * </pre>
424      * <p>
425      * Logging: 
426      * WFU03: Cannot close 
427      *
428      * @param closeable 
429      * the object to close, may be null or already closed
430      */
431    private void closeQuietly(Closeable closeable) {
432         try {
433 	    closeable.close();
434 	} catch (IOException ioe) {
435 	    this.log.warn("WFU03: Cannot close '" + closeable + "'. ", ioe);
436          }
437     }
438 
439     // TBD: move elsewhere because this is specific for inkscape
440     // TBD: better even to eliminate. 
441     /**
442      * The new preamble of the tex file originally created by inkscape 
443      * with ending <code>eps_tex</code>.
444      * FIXME: version to be included. 
445      */
446     private final static String INKSCAPE_PREAMBLE =
447        "%% LatexMavenPlugin (version unknown) modified " +
448        "two of the following lines\n";
449 
450     /**
451      * This is just a workaround because of inkscape's current flaw. 
452      * It reads file <code>srcFile</code> 
453      * which is expected to have name with ending <code>eps_tex</code> 
454      * and writes a file with same name 
455      * replacing ending by <code>tex</code> with following modifications: 
456      * <ul>
457      * <li>Adds line {@link #INKSCAPE_PREAMBLE} atop </li>
458      *    <li>Replaces line '%%Accompanies ...' by 
459      *     '%% Accompanies image files 'xxx.pdf/eps/ps'</li>
460      *    <li>Replaces line 
461      *     '... \includegraphics[width=\\unitlength]{xxx.eps}...' 
462      *     by 
463      *     '... \includegraphics[width=\\unitlength]{xxx}...'</li>
464      * </ul>
465      * <p>
466      * Logging: 
467      * EFU07, EFU08, EFU09: cannot fiter
468      *
469      * @param srcFile
470      *    A file created by inkscape with ending <code>eps_tex</code> 
471      *    containing a lines 
472      *    <code>
473      *    %% Accompanies image file 'xxx.eps' (pdf, eps, ps)</code> and 
474      *    <code>\put(0,0){\includegraphics[width=\\unitlength]{xxx.eps}}</code>
475      *    with variable <code>xxx</code> and leading blanks\
476      */
477     public void filterInkscapeIncludeFile(File srcFile) {
478 	assert LatexPreProcessor.SUFFIX_EPSTEX.equals(getSuffix(srcFile))
479 	    : "Expected suffix '" + LatexPreProcessor.SUFFIX_EPSTEX +
480 	    "' found '" + getSuffix(srcFile) + "'";
481 	File destFile = replaceSuffix(srcFile, LatexPreProcessor.SUFFIX_PTX);
482 	File bareFile = replaceSuffix(srcFile, LatexPreProcessor.SUFFIX_VOID);
483 	//FileReader reader = null;
484 	BufferedReader bufferedReader = null;
485 	FileWriter writer = null;
486 	try {
487 	    // may throw FileNotFoundException < IOExcption 
488 	    FileReader reader = new FileReader(srcFile);
489 	    // BufferedReader for perfromance and to be able to read a line
490 	    bufferedReader = new BufferedReader(reader);
491 
492 	    // may throw IOExcption 
493 	    writer = new FileWriter(destFile);
494 	    //BufferedWriter bufferedWriter = new BufferedWriter(writer);
495 	    String line;
496 	    // write preamble
497 	    // readLine may throw IOException 
498 	    writer.write(INKSCAPE_PREAMBLE);
499 	    // first two lines: write as read 
500 	    line = bufferedReader.readLine();
501 	    writer.write(line + "\n");
502 	    line = bufferedReader.readLine();
503 	    writer.write(line + "\n");
504 
505 	    // third line must be changed. 
506 	    line = bufferedReader.readLine();
507 	    line = line.replace(bareFile.getName()
508 				+ LatexPreProcessor.SUFFIX_EPS
509 				+ "' (pdf, eps, ps)",
510 				bareFile.getName() + ".pdf/eps/ps'\n");
511 	    writer.write(line);
512 
513 	    // readLine may throw IOException
514 	    // TBD: eliminate magic numbers 
515 	    for (int idx = 4; idx < 56; idx++) {
516 		line = bufferedReader.readLine();
517 		writer.write(line + "\n");
518 	    }
519 
520 	    // readLine may throw IOException 
521 	    line = bufferedReader.readLine();
522 	    line = line.replace(bareFile.getName()
523 				+ LatexPreProcessor.SUFFIX_EPS
524 				+ "}}%",
525 				bareFile.getName() + "}}%\n");
526 	    writer.write(line);
527 
528 	    line = bufferedReader.readLine();
529 	    do {
530 		writer.write(line + "\n");
531 		 // readLine may thr. IOException
532 		line = bufferedReader.readLine();
533 	    } while(line != null);
534 	} catch (IOException e) {
535 	    if (bufferedReader == null) {
536 		// Here, FileNotFoundException on srcFile
537 		this.log.error("EFU07: File '" + srcFile +
538 			       "' to be filtered cannot be read. ");
539 		return;
540 	    }
541 	    if (writer == null) {
542 		this.log.error("EFU08: Destination file '" + destFile +
543 			       "' for filtering cannot be written. ");
544 		return;
545 	    }
546 	    this.log.error("EFU09: Cannot filter file '" + srcFile +
547 			   "' into '" + destFile + "'. ");
548 	} finally {
549 	    // Here, an IOException may have occurred 
550 	    // may log warning WFU03
551 	    // TBD: what if null? 
552 	    closeQuietly(bufferedReader);
553 	    closeQuietly(writer);
554 	}
555     }
556 
557     /**
558      * Return the name of the given file without the suffix. 
559      * If the suffix is empty, this is just the name of that file. 
560      *
561      * @see #getSuffix(File)
562      */
563     String getFileNameWithoutSuffix(File file) {
564         String nameFile = file.getName();
565 	int idxDot = nameFile.lastIndexOf(".");
566 	return idxDot == -1
567 	    ? nameFile
568 	    : nameFile.substring(0, idxDot);
569     }
570 
571     /**
572      * Return the suffix of the name of the given file 
573      * including the <code>.</code>, 
574      * except there is no <code>.</code>. 
575      * Then the suffix is empty. 
576      *
577      * @see #getFileNameWithoutSuffix(File)
578      */
579     // used only by 
580     // LatexPreProcessor.processGraphicsSelectMain(Collection) 
581     // LatexPreProcessor.clearCreated(DirNode) 
582     // FIXME: problem if filename starts with . and has no further . 
583     // then we have a hidden file and the suffix is all but the . 
584     // This is not appropriate. 
585     // One may ensure that this does not happen via an assertion 
586     // and by modifying getFilesRec in a way that hidden files are skipped 
587     String getSuffix(File file) {
588         String nameFile = file.getName();
589 	int idxDot = nameFile.lastIndexOf(".");
590 	return idxDot == -1 
591 	    ? "" 
592 	    : nameFile.substring(idxDot, nameFile.length());
593     }
594 
595     // logFile may be .log or .blg or something 
596     /**
597      * Returns whether the given file <code>file</code> (which shall exist) 
598      * contains the given pattern <code>pattern</code> 
599      * or <code>null</code> in case of problems reading <code>file</code>. 
600      * This is typically applied to log files, 
601      * but also to latex-files to find the latex main files. 
602      * <p>
603      * Logging: 
604      * WFU03 cannot close <br>
605      * Note that in case <code>null</code> is returned, 
606      * no error/warning is logged. 
607      * This must be done by the invoking method. 
608      *
609      * @param file
610      *    an existing proper file, not a folder. 
611      * @param regex
612      *    the pattern (regular expression) to look for in <code>file</code>. 
613      * @return
614      *    whether the given file <code>file</code> (which shall exist) 
615      *    contains the given pattern <code>pattern</code>. 
616      *    If the file does not exist or an IOException occurs 
617      *    while reading, <code>null</code> is returned. 
618      */
619     // used only in 
620     // LatexPreProcessor.isLatexMainFile(File)
621     // LatexProcessor.needRun(...)
622     // AbstractLatexProcessor.hasErrsWarns(File, String)
623     Boolean matchInFile(File file, String regex) {
624 	Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE);//
625 	boolean fromStart = regex.startsWith("\\A");
626 	String lines = "";
627 
628 	try {
629 	    // may throw FileNotFoundException < IOExcption 
630 	    FileReader fileReader = new FileReader(file);
631 	    // BufferedReader for perfromance and to be able to read a line
632 	    BufferedReader bufferedReader = new BufferedReader(fileReader);
633 	    //CharBuffer chars = CharBuffer.allocate(1000);
634 	    try {
635 		// may throw IOException 
636 		// int numRead = bufferedReader.read(chars);
637 		// System.out.println("file: "+file);
638 		// System.out.println("numRead: "+numRead);
639 		// System.out.println("chars: '"+chars+"'");
640 		
641 
642 		// FIXME: seemingly, 
643 		// find may not terminate in case ^(\s*)* but with ^s* 
644 		// but this seems a bug in java's regex engine 
645 //		return pattern.matcher(chars).find();
646 
647 
648 		// readLine may throw IOException 
649 		for (String line = bufferedReader.readLine();
650 		     line != null;
651 		     // readLine may thr. IOException
652 		     line = bufferedReader.readLine()) {
653 // FIXME: linewise matching is not appropriate 
654 // for further patterns line patternReRunLatex 
655 // FIXME: seemingly, find may not terminate in case ^(\s*)* but with ^s* 
656 // but this seems a bug in java's regex engine 
657 
658 		    lines = fromStart ? lines += "\n"+line : line;
659 		    if (pattern.matcher(lines).find()) {
660 			return true;
661 		    }
662 		}
663 		return false;
664 	    } catch (IOException ioe) {
665 		// Error/Warning must be issued by invoking method 
666 		return null;
667 	    } finally {
668 		// Here, an IOException may have occurred 
669 		// may log warning WFU03
670 		closeQuietly(bufferedReader);
671 	    }
672 	} catch (FileNotFoundException ffe) {
673 	    // Error/Warning must be issued by invoking method 
674 	    return null;
675 	}
676     }
677 
678     /**
679      * Returns the set of strings representing the <code>idxGroup</code> 
680      * of the pattern <code>regex</code> matching a line 
681      * in file <code>file</code> or returns <code>null</code> 
682      * in case of problems reading <code>file</code>. 
683      * <p>
684      * This is used only to collect the identifiers 
685      * of explicitly given indices in an idx-file. 
686      * @param file
687      *    an existing proper file, not a folder. 
688      *    In practice this is an idx file. 
689      * @param regex
690      *    the pattern (regular expression) to look for in <code>file</code>. 
691      * @param idxGroup
692      *    the number of a group of the pattern <code>regex</code>. 
693      * @return
694      *    the set of strings representing the <code>idxGroup</code> 
695      *    of the pattern <code>regex</code> matching a line 
696      *    in file <code>file</code> or returns <code>null</code> 
697      *    in case of problems reading <code>file</code>. 
698      */
699     // used in LatexProcessor.runMakeIndexByNeed only 
700     // **** a lot of copying from method matchInFile 
701     Collection<String> collectMatches(File file, String regex, int idxGroup) {
702 	Collection<String> res = new TreeSet<String>();
703 	Pattern pattern = Pattern.compile(regex);
704 
705  	try {
706 	    // may throw FileNotFoundException < IOExcption 
707 	    FileReader fileReader = new FileReader(file);
708 	    // BufferedReader for perfromance 
709 	    BufferedReader bufferedReader = new BufferedReader(fileReader);
710 
711 	    try {
712 		// readLine may throw IOException 
713 		Matcher matcher;
714 		for (String line = bufferedReader.readLine();
715 		     line != null;
716 		     // readLine may thr. IOException
717 		     line = bufferedReader.readLine()) {
718 
719 		    matcher = pattern.matcher(line);
720 		    if (matcher.find()) {
721 			// Here, a match has been found 
722 			res.add(matcher.group(idxGroup));
723 		    }
724 		} // for 
725 
726 		return res;
727 	    } catch (IOException ioe) {
728 		// Error/Warning must be issued by invoking method 
729 		return null;
730 	    } finally {
731 		// Here, an IOException may have occurred 
732 		// may log warning WFU03
733 		closeQuietly(bufferedReader);
734 	    }
735 	} catch (FileNotFoundException ffe) {
736 	    // Error/Warning must be issued by invoking method 
737 	    return null;
738 	}
739     }
740 
741     // used in LatexPreProcessor and in LatexProcessor and in LatexDec
742     // at numerous places 
743     File replaceSuffix(File file, String suffix) {
744         return new File(file.getParentFile(),
745 			getFileNameWithoutSuffix(file) + suffix );
746     }
747 
748     /**
749      * Deletes all files in the same folder as <code>pFile</code> directly, 
750      * i.e. not in subfolders, which are accepted by <code>filter</code>. 
751      * <p>
752      * Logging: 
753      * <ul>
754      * <li> WFU01: Cannot read directory...
755      * <li> EFU05: Failed to delete file 
756      * </ul>
757      *
758      * @param pFile
759      *    a file in a folder to be deleted from. 
760      *    This is either a metapost file or a latex main file. 
761      * @param filter
762      *    a filter which decides which files 
763      *    from the parent directory of <code>pFile</code> to delete. 
764      */
765     // used in LatexPreProcessor.clearTargetMp
766     // used in LatexPreProcessor.clearTargetTex only 
767     void deleteX(File pFile, FileFilter filter) {
768 	// FIXME: not true for clear target. 
769 	// Required: cleanup in order reverse to creation. 
770 	assert pFile.exists() && !pFile.isDirectory()
771 	    : "Expected existing (regular) file "+pFile;
772 	File dir = pFile.getParentFile();
773 	// may log warning WFU01 
774 	File[] found = listFilesOrWarn(dir);
775 	if (found == null) {
776 	    // Here, logging WFU01 already done 
777 	    return;
778 	}
779 	for (File delFile : found) {
780 	    // FIXME: not true for clear target. 
781 	    // Required: cleanup in order reverse to creation. 
782 	    assert delFile.exists();
783 	    if (filter.accept(delFile)) {
784 		assert delFile.exists() && !delFile.isDirectory()
785 		    : "Expected existing (regular) file "+delFile;
786 		// may log EFU05: failed to delete 
787 		deleteOrError(delFile);
788 	    }
789 	}
790     }
791 
792     /**
793      * Deletes <code>delFile</code> or logs a warning. 
794      * <p>
795      * Logging: 
796      * EFU05: failed to delete 
797      *
798      * @param delFile
799      *    the existing file to be deleted. 
800      *    This must not be a directory. 
801      */
802     void deleteOrError(File delFile) {
803 	assert delFile.exists() && !delFile.isDirectory()
804 	    : "Expected existing (regular) file "+delFile;
805 	if (!delFile.delete()) {
806 	    this.log.error("EFU05: Cannot delete file '" + 
807 			   delFile + "'. ");
808 	}
809     }
810 
811     /**
812      * Moves file <code>fromFile</code> to <code>toFile</code> 
813      * or logs a warning. 
814      * <p>
815      * Logging: 
816      * EFU06: failed to move. 
817      *
818      * @param fromFile
819      *    the existing file to be moved. 
820      *    This must not be a directory. 
821      * @param toFile
822      *    the file to be moved to 
823      *    This must not be a directory. 
824      */
825     void moveOrError(File fromFile, File toFile) {
826 	assert fromFile.exists() && !fromFile.isDirectory()
827 	    : "Expected existing (regular) source file "+fromFile;
828 	assert                      !  toFile.isDirectory()
829 	    : "Expected (regular) target file "+toFile;
830 	boolean success = fromFile.renameTo(toFile);
831 	if (!success) {
832 	    this.log.error("EFU06: Cannot move file '" + 
833 			   fromFile + "' to '" + toFile + "'. ");
834 	}
835     }
836 
837     /**
838      * Deletes all files in <code>texDir</code> including subdirectories 
839      * which are not in <code>orgNode</code>. 
840      * The background is, that <code>orgNode</code> represents the files 
841      * originally in <code>texDir</code>. 
842      * <p>
843      * Logging: 
844      * <ul>
845      * <li> WFU01: Cannot read directory 
846      * <li> EFU05: Cannot delete... 
847      * </ul>
848      *
849      * @param orgNode
850      *    
851      * @param texDir
852      *    
853      */
854     // used in LatexProcessor.create() only 
855     // FIXME: warn if deletion failed. 
856     void cleanUp(DirNode orgNode, File texDir) {
857 	// constructor DirNode may log warning WFU01 Cannot read directory 
858 	// cleanUpRec may log warning EFU05 Cannot delete... 
859  	cleanUpRec(texDir, orgNode, new DirNode(texDir, this));
860     }
861 
862     /**
863      * Deletes all files in <code>currNode</code> 
864      * which are not in <code>orgNode</code> recursively 
865      * including subdirectories. 
866      * The background is, that <code>orgNode</code> represents the files 
867      * originally in the directory and <code>currNode</code> 
868      * the current ones at the end of the creating goal. 
869      * <p>
870      * Logging: 
871      * EFU05: Cannot delete... 
872      *
873      * @param orgNode
874      *    the node representing the original files. 
875      *    This is the latex source directory or a subdirectory. 
876      * @param currNode
877      *    the node representing the current files. 
878      *    This is the latex source directory or a subdirectory. 
879      */
880     // used in cleanUp only 
881     private void cleanUpRec(File dir, DirNode../../../../eu/simuline/m2latex/core/DirNode.html#DirNode">DirNode orgNode, DirNode currNode) {
882    	assert       orgNode.getSubdirs().keySet()
883 	    .equals(currNode.getSubdirs().keySet());
884 	File file;
885   	for (String key : orgNode.getSubdirs().keySet()) {
886 	    file = new File(dir, key);
887 	    cleanUpRec(file,
888 		       orgNode .getSubdirs().get(key), 
889 		       currNode.getSubdirs().get(key));
890     	}
891      	Collection<String> currFileNames = currNode.getRegularFileNames();
892     	currFileNames.removeAll(orgNode.getRegularFileNames());
893 
894    	for (String fileName : currFileNames) {
895  	    file = new File(dir, fileName);
896     	    // may log error EFU05: Cannot delete file
897     	    deleteOrError(file);
898     	}
899     }
900 
901     public static void main(String[] args) {
902 	String regex = args[0];
903 	String text  = args[1];
904 	text = "xx\nyzzz";
905 	System.out.println("regex: "+regex);
906 	System.out.println("text: "+text);
907 	System.out.println("len: "+text.length());
908 
909 	
910  	Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE);
911 	java.util.regex.Matcher matcher = pattern.matcher(text);
912 	matcher.useAnchoringBounds(true);
913 	System.out.println("find:   "+matcher.find());
914 	System.out.println("hitEnd: "+matcher.hitEnd());
915 	System.out.println("hitEnd: "+matcher.end());
916     }
917  }