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 }