View Javadoc
1   package eu.simuline.m2latex.core;
2   
3   //import org.apache.maven.project.io.xpp3.MavenXpp3Reader;
4   //import org.apache.maven.project.Model;
5   
6   import java.io.InputStream;
7   
8   import java.io.IOException;
9   import java.io.File;
10  
11  import java.util.Properties;
12  import java.util.Map;
13  import java.util.ArrayList;
14  import java.util.Enumeration;
15  import java.util.List;
16  import java.util.jar.Manifest;
17  import java.util.regex.Matcher;
18  import java.util.regex.Pattern;
19  import java.util.jar.Attributes;
20  import java.net.URISyntaxException;
21  import java.net.URL;
22  
23  // TBD: extract this class into a separate git repository 
24  // and use it as a submodule. 
25  /**
26   * Gives access to meta info on this piece of software stored in the jar-file 
27   * which provides this class. 
28   * Start with version information. 
29   */
30  public class MetaInfo {
31      
32  
33      /**
34       * Name of the folder <code>META-INF</code> in the jar file which provides this class. 
35       * This contains 
36       * <ul>
37       * <li>the folder <code>maven</code> created by maven and containing information 
38       * like pom properties and pom itself which we currently do not consider</li>
39       * <li>the manifest file named {@link MetaInfo.ManifestInfo#MANIFEST_FILE}.</li>
40       * </ul>
41       */
42      private final static String META_FOLDER = "META-INF/";
43      
44      
45      /**
46       * Creates the stream for the file given by <code>fileName</code>. 
47       * @param fileName
48       *    a filename
49       * @return
50       *    an input stream to read from <code>fileName</code>. 
51       * @throws BuildFailureException
52       *    TMI01: if the stream to <code>fileName</code> could not be created. 
53       */
54      static InputStream getStream(String fileName) 
55  	    throws BuildFailureException {
56  	InputStream res = MetaInfo.class.getClassLoader().getResourceAsStream(fileName);
57  	if (res == null) {
58  	    throw new BuildFailureException("TMI01: Cannot get stream to file '" 
59  		    + fileName + "'. ");
60  	}
61  	return res;
62      }
63      
64      /**
65       * @param fileName
66       *    an input stream to read from <code>fileName</code>. 
67       * @return
68       *    Properties read from <code>fileName</code>. 
69       * @throws BuildFailureException
70       *    <ul>
71       *    <li>TMI01: if the stream to <code>fileName</code> could not be created. </li>
72       *    <li>TMI02: if the properties could not be read from <code>fileName</code>. </li>
73       *    </ul>
74       */
75      static Properties getProperties(String fileName) 
76  	    throws BuildFailureException {
77  	try {
78  	    Properties properties = new Properties();
79  	    // may throw TFU01
80  	    properties.load(MetaInfo.getStream(fileName));
81  	    return properties;
82  	} catch(IOException e) {
83  	    // TBD: assign exception identifier 
84  	    throw new BuildFailureException("TMI02: Cannot load properties from file '" 
85  		    + fileName + "'. ");
86  	}
87      }
88    
89      
90  
91      /**
92       * Reflects the pieces of information given by the manifest file 
93       * with manifest version {@link #MANIFEST_VERSION} augmented 
94       * by {@link #getCreatedBy()} and by {@link #getBuildJdkSpec()} 
95       * which are both due to to the maven jar plugin and specific version 
96       * given by {@link #MVN_JAR_PLUGIN}. 
97       *  
98       * TBD: change user and see whether author changes then also 
99       * @author ernst
100      */
101     static class ManifestInfo {
102 	
103 	/**
104 	 * The manifest version this class is designed for. 
105 	 * When creating an instance, that version is checked 
106 	 * and if the version found differs from that one, an exception is thrown. 
107 	 */
108 	private final static String MANIFEST_VERSION = "1.0";
109 	
110 	/**
111 	 * An indicator that the jar file 
112 	 * containing the manifest represented by this object 
113 	 * was created by maven jar plugin with proper version. 
114 	 */
115 	private final static String MVN_JAR_PLUGIN = "Maven Jar Plugin 3.2.0";
116 
117 	/**
118 	 * The name of the manifest file which is in the folder {@link #META_FOLDER} 
119 	 * of the jar file which provides this class. 
120 	 */
121 	private final static String MANIFEST_FILE = "MANIFEST.MF";
122 
123 	/**
124 	 * These attributes are added by the maven jar plugin in version 3.2.0 and is specific for that. 
125 	 * Thus the according value is exactly the specification of that plugin with version info. 
126 	 */
127 	private final static Attributes.Name CREATED_BY     = new Attributes.Name("Created-By");
128 	
129 	/**
130 	 * The version of the jdk used to build the jar containing the manifest 
131 	 * represented by this object. 
132 	 * These attributes are added by the maven jar plugin version 3.2.0 and is specific for that. 
133 	 */
134 	private final static Attributes.Name BUILD_JDK_SPEC = new Attributes.Name("Build-Jdk-Spec");
135 	  
136 	// TBD: decide whether this is sensible 
137 	private final Manifest manifest;
138 	
139 	/**
140 	 * The main attributes of the manifest. 
141 	 */
142 	private final Attributes mAtts;
143 
144 	/**
145 	 * Creates Manifest info instance 
146 	 * @throws BuildFailureException
147 	 *    TFU01: if the stream to the manifest file is not readable 
148 	 *    TFU03: the manifest file is not readable although the stream is ok. 
149 	 */
150 	ManifestInfo() throws BuildFailureException {
151 	    
152 	    //System.out.println("MANIFEST: ");
153 	    try {
154 		// getStream may throw TFU01
155 		this.manifest = new Manifest(getStream(META_FOLDER + MANIFEST_FILE));
156 	    } catch (IOException e) {
157 		throw new BuildFailureException
158 		("TMI03: IOException reading manifest. ");
159 	    }
160 	    this.mAtts = this.manifest.getMainAttributes();
161 	    // check the manifest version 
162 	    if (!MANIFEST_VERSION.equals(getManifestVersion())) {
163 		throw new IllegalStateException("Found manifest with version '" 
164 			+ getManifestVersion() + " where expected version "
165 			+ MANIFEST_VERSION + ". ");
166 	    }
167 	    // CAUTION: don't use getCreatedBy() as this throws an exception 
168 	    // if this jar is not created with the correct maven plugin 
169 	    // (or even without maven at all)
170 	    if (!MVN_JAR_PLUGIN.equals(this.mAtts.get(CREATED_BY))) {
171 		// MVN_JAR_PLUGIN includes the version also 
172 		throw new IllegalStateException("Found manifest not created by '" 
173 			+ MVN_JAR_PLUGIN + "'. ");
174 	    }	    
175 
176 //		Map<String, Attributes> entriesMf = mf.getEntries();
177 
178 
179 		// seems to correspond with Attributes.Name
180 		// MANIFEST_VERSION,
181 		// IMPLEMENTATION_TITLE, IMPLEMENTATION_VERSION, IMPLEMENTATION_VENDOR,
182 		// SPECIFICATION_TITLE, SPECIFICATION_VERSION, SPECIFICATION_VENDOR,
183 		//
184 		// defined but not occurring:
185 		// SIGNATURE_VERSION, CONTENT_TYPE, CLASS_PATH, MAIN_CLASS, SEALED,
186 		// EXTENSION_LIST, EXTENSION_NAME, EXTENSION_INSTALLATION,
187 		// IMPLEMENTATION_VENDOR_ID
188 		// IMPLEMENTATION_URL,
189 		//
190 		// occurring but not defined in Attributes.Name:
191 		// Created-By: Maven Jar Plugin 3.2.0, Build-Jdk-Spec: 11
192 
193 		//this.implVersion = this.mAtts.get(Attributes.Name.IMPLEMENTATION_VERSION).toString();
194 
195 		// TBC: seems to be empty in this case.
196 		// in maven-jar-plugin set by manifestSections
197 		// System.out.println("Manifest entries"+entriesMf);
198 
199 		// Enumeration entriesJar = mf.entries();
200 		// System.out.println("jar entries"+entriesJar);
201 	}
202 
203 	private String getAttrValue(Object name) {
204 	    // is in fact a string always but this is to detect null pointer exceptions 
205 	    return this.mAtts.get(name).toString();
206 	}
207 	
208 	/**
209 	 * Returns the version of the implementation. 
210 	 * This is the version given by the maven coordinates.
211 	 * 
212 	 *  @return
213 	 */
214 	protected String getImplVersion() {
215 	    return getAttrValue(Attributes.Name.IMPLEMENTATION_VERSION);
216 	}
217 	
218 	/**
219 	 * Returns the vendor of the implementation. 
220 	 * This is the vendor given by the maven <code>project.organization.name</code>.
221 	 * 
222 	 *  @return
223 	 */
224 	protected String getImplVendor() {
225 	    return getAttrValue(Attributes.Name.IMPLEMENTATION_VENDOR);
226 	}
227 
228 	protected String getManifestVersion() {
229 	    return getAttrValue(Attributes.Name.MANIFEST_VERSION);
230 	}
231 	
232 	protected String getSpecVersion() {
233 	    return getAttrValue(Attributes.Name.SPECIFICATION_VERSION);
234 	}
235 	
236 	// specific for Maven Jar Plugin 3.2.0 which is also the string returned. 
237 	protected String getCreatedBy() {
238 	    return getAttrValue(CREATED_BY);
239 	}
240 	
241 	// specific for Maven Jar Plugin 3.2.0 
242 	protected String getBuildJdkSpec() {
243 	    return getAttrValue(BUILD_JDK_SPEC);
244 	}
245 
246 	
247 	protected String getPackageImplVersion() {
248 	    return this.getClass().getPackage().getImplementationVersion();
249 	}
250 
251 	// TBD: tie names to values. 
252 	@Override
253 	public String toString() {
254 	    StringBuilder stb = new StringBuilder();
255 	    for (String line : toStringArr()) {
256 		stb.append(line);
257 		stb.append('\n');
258 	    }
259 	    return stb.toString();
260 	}
261 	
262 	public String[] toStringArr() {
263 //	    List<String> lines = new ArrayList<String>();
264 //	    Object key, value;
265 //	    for (Map.Entry<Object,Object> entry : mAtts.entrySet()) {
266 //		key   = entry.getKey();
267 //		value = entry.getValue();
268 //		lines.add("   '" + key   + "' cls: " + key.getClass() +
269 //			"'->'" + value + "' cls: " + value.getClass());
270 //	    }
271 //	    return lines.toArray(new String[lines.size()]);
272 	    
273 	    return new String[] {
274 		    "MANIFEST: ("+ getManifestVersion() + ")",
275 		    "       Implementation-Version: '" + getImplVersion() + "'",
276 		    "PackageImplementation-Version: '" + getPackageImplVersion() + "'"
277 			    //"creator: '" + getCreatedBy() + "'",
278 		    //"version jdk: '" + getBuildJdkSpec() + "'"
279 	    };
280 	}
281 	
282     } // class ManifestInfo
283 
284 
285     /**
286      * Reflects the pieces of information 
287      * given by the <code>git-commit-id-plugin</code> in the current(?) version. 
288      * 
289      * 
290      * @author ernst
291      */
292     class GitProperties {
293 	private final static String GIT_PROPS_FILE = "git.properties";
294 	
295 	private final static String GIT_BUILD_VERSION = "git.build.version";
296 	private final static String GIT_COMMIT_ID_DESCRIBE = "git.commit.id.describe";
297 	private final static String GIT_CLOSEST_TAG_NAME = "git.closest.tag.name";
298 	private final static String GIT_CLOSEST_TAG_COMMIT_COUNT = "git.closest.tag.commit.count";
299 	private final static String GIT_COMMIT_ID_ABBREV = "git.commit.id.abbrev";
300 	private final static String GIT_DIRTY = "git.dirty";
301 	private final static String GIT_BUILD_TIME = "git.build.time";
302 
303 	
304 	private final Properties properties;
305 	
306 	/**
307 	 * @throws BuildFailureException
308 	 *    <ul>
309 	 *    <li>TMI01: if the stream to {@link #GIT_PROPS_FILE} could not be created. </li>
310 	 *    <li>TMI02: if the properties could not be read from @link #GIT_PROPS_FILE}. </li>
311 	 *    </ul>
312 	 */
313 	GitProperties() throws BuildFailureException {
314 	    // TBD: extract properties 
315 		this.properties = getProperties(GIT_PROPS_FILE);
316 		
317 		//String gitBuildVersion = getBuildVersion();
318 		// latex-maven-plugin-1.4-44-g555bde8-dirty
319 		// is constructed from 
320 		//String gitCommitIdDescribe = getCommitIdDescribe();
321 		// also interesting: 
322 	}
323 	
324 	private String getAttrValue(String key) {
325 	    // is in fact a string always but this is to detect null pointer exceptions 
326 	    return this.properties.get(key).toString();
327 	}
328 
329 	/**
330 	 * Returns the build version which is the same as the version in the coordinate 
331 	 * provided the <code>maven-release-plugin</code> is used correctly. 
332 	 * @return
333 	 *    the build version. 
334 	 */
335 	String getBuildVersion() {
336 	    return getAttrValue(GIT_BUILD_VERSION);
337 	}
338 	
339 	// latex-maven-plugin-1.4-44-g555bde8-dirty
340 	/**
341 	 * Returns the commit identifiers description TBC which consists of 
342 	 * <ul>
343 	 * <li> the closest tag name as given by {@link #getClosestTagName()} </li>
344 	 * <li> the number of commits since the last tag 
345 	 *      given by {@link #getClosestTagCommitCount()}</li>
346 	 * <li> something unknown which leads to the 'g'</li>
347 	 * <li> git.commit.id.abbrev=555bde8</li>
348 	 * <li> 'dirty' if {@link #getDirty()} returns true. </li>
349 	 * </ul>
350 	 * each segment separated by a dash
351 	 * 
352 	 * @return
353 	 *    the commit identifiers description
354 	 */
355 	String getCommitIdDescribe() {
356 	    return getAttrValue(GIT_COMMIT_ID_DESCRIBE);
357 	}
358 	
359 	// latex-maven-plugin-1.4
360 	/**
361 	 * This is the artifact id and, separated by a dash, 
362 	 * by the current version tag, if there is one, 
363 	 * else, i.e. for snapshots, by the next lowest. 
364 	 * The tag given is current, iff {@link #getClosestTagCommitCount()} returns 0.  
365 	 *
366 	 * @return
367 	 *    the closest tag name for the last commit. 
368 	 */
369 	String getClosestTagName() {
370 	    return getAttrValue(GIT_CLOSEST_TAG_NAME);
371 	}
372 	
373 	// TBD: shall be a number, not a string 
374 	/**
375 	 * Returns the number of commits since the last tag 
376 	 * which is given by {@link #getClosestTagName()}. 
377 	 * 
378 	 * @return
379 	 *    the number of commits since the last tag. 
380 	 */
381 	String getClosestTagCommitCount() {
382 	    return getAttrValue(GIT_CLOSEST_TAG_COMMIT_COUNT);
383 	}
384 	
385 	// TBC: maybe a string maybe an int or what. 
386 	/**
387 	 * Returns the abbreviated hash of the last commit.
388 	 * 
389 	 * @return
390 	 *    the abbreviated hash of the last commit.
391 	 */
392 	String getCommitIdAbbrev() {
393 	    return getAttrValue(GIT_COMMIT_ID_ABBREV);
394 	}
395 
396 	// TBD: this shall not be a string but a boolean 
397 	/**
398 	 * Returns whether the current working environment is dirty, 
399 	 * i.e. there is something not committed inside. 
400 	 * 
401 	 * @return
402 	 *    whether the current working environment is dirty.
403 	 */
404 	String getDirty() {
405 	    return getAttrValue(GIT_DIRTY);
406 	}
407 
408 	/**
409 	 * Returns the build time TBD: not just a string 
410 	 * @return
411 	 * the build time as a string. 
412 	 */
413 	String getBuildTime() {
414 	    return getAttrValue(GIT_BUILD_TIME);
415 	}
416 
417 	@Override
418 	public String toString() {
419 	    StringBuilder stb = new StringBuilder();
420 	    stb.append("build version:  '" + getBuildVersion()     + "'\n");
421 	    stb.append("commit id desc: '" + getCommitIdDescribe() + "'\n");
422 	    stb.append("buildTime:      '" + getBuildTime()        + "'\n");
423 	    return stb.toString();
424 	}
425 	
426 	void log() {
427 	    MetaInfo.this.log.info("build version:  '" + getBuildVersion()     + "'");
428 	    MetaInfo.this.log.info("commit id desc: '" + getCommitIdDescribe() + "'");
429 	    MetaInfo.this.log.info("buildTime:      '" + getBuildTime()        + "'");
430 	}
431 
432     } // class GitProperties
433 
434     static class Version implements Comparable<Version> {
435 
436 	private final static String VERSION_UNKNOWN = "version";
437 
438 	private final String text;
439 	private final Matcher matcher;
440 	private final String versionStr;
441 	private final List<Number> segments;
442 
443 	/**
444 	 * Create a version for a converter <code>conv</code> 
445 	 * invoking it with the proper option using <code>executor</code>
446 	 * 
447 	 * @param conv
448 	 *    a converter.
449 	 * @param executor
450 	 *    an executor to execute the command of the converter 
451 	 *    to obtain a feedback containing the version. 
452 	 * @throws BuildFailureException
453 	 *    TEX01 if invocation of <code>command</code> fails very basically.
454 	 */
455 	Version(Converter conv, CommandExecutor executor)
456 		throws BuildFailureException {
457 	    this(conv.getVersionEnvironment(),
458 		 conv.getVersionPattern(),
459 		 // may throw BuildFailureException
460 		 executor.execute(null, null,
461 			 conv.getCommand(), new String[] {
462 			    conv.getVersionOption()}));
463 	}
464 
465 	/**
466 	 * Create a version from given pattern 
467 	 * @param patternEnv
468 	 *    the regex pattern <code>text</code> shall match. 
469 	 *    The literal <code>%s</code> of this format string 
470 	 *    indicates the location of the version pattern <code>patternVrs</code>.
471 	 * @param patternVrs
472 	 *    the regex pattern expected for this version. 
473 	 *    This is in a form 
474 	 *    as returned by {@link Converter#getVersionPattern()}.
475 	 * @param text
476 	 *    some text containing (among other things)
477 	 *    version information described by <code>patternVrs</code> 
478 	 *    and conforming to <code>patternEnv</code>. 
479 	 *    The origin of that text may be output of invocation of a converter 
480 	 *    or a property file with allowed versions (as version intervals)
481 	 */
482 	Version(String patternEnv, String patternVrs, String text) {
483 	    this.text = text;
484 	    this.matcher = Pattern.compile(String.format(patternEnv, patternVrs))
485 		    .matcher(text);
486 	    if (!this.matcher.find()) {
487 		this.versionStr = VERSION_UNKNOWN;
488 		this.segments = null;
489 		return;
490 	    }
491 	    this.versionStr = this.matcher.group(1);
492 	    this.segments = new ArrayList<Number>(this.matcher.groupCount());
493 	    String segment;
494 	    Number num;
495 	    for (int idx = 2; idx <= this.matcher.groupCount(); idx++) {
496 		segment = this.matcher.group(idx);
497 		if (segment == null) {
498 		    break;
499 		}
500 		if (Pattern.matches("[a-z]", segment)) {
501 		    num = Byte.valueOf((byte)segment.codePointAt(0));
502 		} else {
503 		    // TBC: why works the 2nd version, but the 1st does not? 
504 //		    num = segment.indexOf('.') == -1
505 //			    ? Integer.valueOf(segment)
506 //		            : Double .valueOf(segment);
507 		    if (segment.indexOf('.') == -1) {
508 			num = Integer.valueOf(segment);
509 		    } else {
510 			num = Double.valueOf(segment);
511 		    }
512 		}
513 		this.segments.add(num);
514 	    }
515 	}
516 
517 	boolean isMatching() {
518 	    return this.segments != null;
519 	}
520 
521 	String getText() {
522 	    return this.text;
523 	}
524 
525 	String getString() {
526 	    return this.versionStr;
527 	}
528 
529 	private List<Number> getSegments() {
530 	    return this.segments;
531 	}
532 
533 	public int compareTo(Version other) {
534 	    int idxMax = Math.max(getSegments().size(), other.getSegments().size());
535 	    int sign;
536 	    for (int idx = 0; idx < idxMax; idx++) {
537 		// TBD: eliminate hack 
538 		sign = (int)Math.signum(getSegments().get(idx).doubleValue()
539 			-         other.getSegments().get(idx).doubleValue());
540 		    switch (sign) {
541 		    case  0:
542 			continue;
543 		    case -1:
544 			// fall through 
545 		    case +1:
546 			return sign;
547 			default:
548 			    throw new IllegalStateException
549 			    ("Found unexpected signum. ");
550 		    }
551 	    }
552 	    // TBD: unoconv is an example where 0.9 and 0.9.0 occur, 
553 	    // in different distributions but both versions identical 
554 	    // This must be taken into account 
555 	  return getSegments().size() - other.getSegments().size();
556 	}
557 
558     } // class Version 
559 
560     /**
561      * An interval of versions, representing intervals 
562      * between the boundaries {@link #min} and {@link #min} 
563      * which offers a containment predicate {@link #contains(Version)}. 
564      * <p>
565      * Tied to this is a string representation given by {@link #toString()} 
566      * and also used by the constructor {@link #VersionInterval(Converter, String)}.
567      */
568     static class VersionInterval {
569 
570 	/**
571 	 * Separator between minimum version and maximum version in string representation. 
572 	 * This must be a character not occurring within a version string. 
573 	 * We have a piece of software, see {@link Converter#Latex2rtf} 
574 	 * which has a blank in the version string and as a consequence, 
575 	 * we cannot use a blank here.  
576 	 */
577 	private final static char SEP = ';';
578 
579 	/**
580 	 * The pattern for a one point interval, essentially the version within brackets. 
581 	 * Note that the version string is represented as the formatter <code>%s</code>.
582 	 * 
583 	 * @see #ENV_PATTERN2LOW
584 	 * @see #ENV_PATTERN2HIG
585 	 */
586 	private final static String ENV_PATTERN1 = "^\\[%s\\]$";
587 
588 	/**
589 	 * The pattern for a non-degenerate interval, 
590 	 * minimum and maximum version separated by {@link #SEP} 
591 	 * and enclosed within brackets. 
592 	 * The minimum version string is represented as the formatter <code>%s</code> 
593 	 * whereas the maximum version string is represented by the general pattern. 
594 	 * 
595 	 * @see #ENV_PATTERN1
596 	 * @see #ENV_PATTERN2HIG
597 	 */
598 	private final static String ENV_PATTERN2LOW = "^\\[%s"+SEP+".*\\]$";
599 
600 	/**
601 	 * The pattern for a non-degenerate interval, 
602 	 * minimum and maximum version separated by {@link #SEP} 
603 	 * and enclosed within brackets. 
604 	 * The maximum version string is represented as the formatter <code>%s</code> 
605 	 * whereas the minimum version string is represented by the general pattern. 
606 	 * 
607 	 * @see #ENV_PATTERN1
608 	 * @see #ENV_PATTERN2LOW
609 	 */
610 	private final static String ENV_PATTERN2HIG = "^\\[.*"+SEP+"%s\\]$";
611 
612 	/**
613 	 * The minimum version (inclusive) contained in this interval. 
614 	 */
615 	private final Version min;
616 
617 	/**
618 	 * The maximum version (inclusive) contained in this interval. 
619 	 */
620 	private final Version max;
621 
622 	/**
623 	 * Creates a version interval from the converter and from the text string 
624 	 * read from the version properties file. 
625 	 * 
626 	 * @param conv
627 	 *    the converter for which to create the version interval. 
628 	 *    This is used mostly to determine {@link Converter#getVersionPattern()}.
629 	 * @param text
630 	 *    the text representation for the interval to be created 
631 	 *    which is as described for {@link #toString()}. 
632 	 *    This may well be <code>null</code>.
633 	 * @throws IllegalStateException
634 	 *    if either <code>text</code> is <code>null</code>
635 	 *    which means that there is no version for <code>conv</code>
636 	 *    or if this created version interval
637 	 *    does not have the representation given by <code>text</code>.
638 	 */
639 	VersionInterval(Converter conv, String text) {
640 	    String patternVrs = conv.getVersionPattern();
641 	    if (text == null) {
642 		throw new IllegalStateException
643 		("Found no expected version for converter " + conv + ". ");
644 	    }
645 	    if (text.indexOf(SEP) == -1) {
646 		// Here, we have an interval [element]
647 		this.min = new Version(ENV_PATTERN1, patternVrs, text);
648 		this.max = this.min;
649 		if (!this.min.isMatching()) {
650 		    throw new IllegalStateException
651 		    (String.format("Expected version interval '%s' " +
652 			    "does not match expression '^\\[%s\\]$'. ",
653 			    text, patternVrs));
654 		}
655 	    } else {
656 		// Here, we have an interval [min;max]
657 		this.min = new Version(ENV_PATTERN2LOW, patternVrs, text);
658 		this.max = new Version(ENV_PATTERN2HIG, patternVrs, text);
659 		if (!(this.min.isMatching() && this.max.isMatching())) {
660 		    throw new IllegalStateException
661 		    (String.format("Expected version interval '%s' " +
662 			    "does not match expression '^\\[%s%c%s\\]$'. ",
663 			    text, patternVrs, SEP, patternVrs));
664 		}
665 	    }
666 	    String expVersionItvStr = toString();
667 	    if (!text.equals(expVersionItvStr)) {
668 		throw new IllegalStateException
669 		(String.format("Expected version '%s' reconstructed as '%s'. ",
670 			text, expVersionItvStr));
671 	    }
672 	}
673 
674 	/**
675 	 * Returns whether <code>version</code> is contained in this interval. 
676 	 * 
677 	 * @param version
678 	 *    some version. 
679 	 * @return
680 	 *    whether <code>version</code> is contained in this interval. 
681 	 */
682 	boolean contains(Version version) {
683 	    return this.min.compareTo(version) <= 0
684 		&& this.max.compareTo(version) >= 0;
685 	}
686 
687 	/**
688 	 * Returns a representation inspired by math and used in version property file. 
689 	 * It is just the format 
690 	 * which is read by the constructor {@link #VersionInterval(Converter, String)}.
691 	 * 
692 	 * @return
693 	 *    string representation of the form <code>[min;max]</code> 
694 	 *    except if <code>min</code> and <code>max</code> coincide, 
695 	 *    then it is just <code>[min]</code>.
696 	 */
697 	@Override
698 	public String toString() {
699 	    StringBuilder res = new StringBuilder();
700 	    res.append('[');
701 	    res.append(this.min.getString());
702 
703 	    if (this.min != this.max) {
704 		res.append(SEP);
705 		res.append(this.max.getString());
706 	    }
707 	    res.append(']');
708 	    return res.toString();
709 	}
710     } // class VersionInterval
711 
712     /**
713      * Executor to find the version string.  
714      */
715     private final CommandExecutor executor;
716     
717     
718     /**
719      * Logs information on versions. 
720      * Typically, just info are logged, 
721      * but if a version could not be read or if a version is not as expected, 
722      * also a warning is logged. 
723      */
724     private final LogWrapper log;
725 
726     MetaInfo(CommandExecutor executor, 
727 		   LogWrapper log) {
728 	this.executor = executor;
729 	this.log = log;
730     }
731     
732     private final static String VERSION_PROPS_FILE = "version.properties";
733     private final static String TOOL_VERSION_FORMAT = "%s%-15s '%s'%s%s";
734 
735     // CAUTION, depends on the maven-jar-plugin and its version 
736     /**
737      * Prints meta information, mainly version information 
738      * on this software and on the converters used. 
739      * <p>
740      * WMI01: If the version string of a converter cannot be read. 
741      * WMI02: If the version of a converter is not as expected. 
742      * @return
743      *    whether a warning has been issued. 
744      * @throws BuildFailureException
745      *    <ul>
746      *    <li>TMI01: if the stream to either the manifest file 
747      *        or to a property file, either {@LINK #VERSION_PROPS_FILE} 
748      *        or {@link MetaInfo.GitProperties#GIT_PROPS_FILE} could not be created. </li>
749      *    <li>TMI02: if the properties could not be read 
750      *        from one of the two property files mentioned above. </li>
751      *    </ul>
752      */
753     public boolean printMetaInfo() throws BuildFailureException {
754 	ManifestInfo manifestInfo = new ManifestInfo();
755 	// TBC: how does maven determine that version?? 
756 	//String versionMf = manifestInfo.getImplVersion();
757 	//System.out.println("mf version: '" + versionMf + "'");
758 	this.log.info("Manifest properties: ");
759 	for (String line : manifestInfo.toStringArr()) {
760 	    this.log.info(line);
761 	}
762 
763 	String mavenDir = META_FOLDER + "maven/";
764 	URL url = MetaInfo.class.getClassLoader().getResource(mavenDir);
765 //	try {
766 //	    //System.out.println("path: "+url);
767 //	    //System.out.println("path: "+url.toURI());
768 //	    //File[] files = new File(url.toURI()).listFiles();
769 //	    //System.out.println("cd maven; ls: "+java.util.Arrays.asList(files));
770 //	} catch (URISyntaxException e) {
771 //	    // TODO Auto-generated catch block
772 //	    throw new IllegalStateException("Found unexpected type of url: "+url);
773 //	}
774 	String propertyFileName = META_FOLDER
775 	    + "maven/"
776 	    + "eu.simuline.m2latex/"
777 	    + "latex-maven-plugin/"
778 	    + "pom.properties";
779 	this.log.info("pom properties:");
780 	Properties properties = getProperties(propertyFileName);
781 	assert "[groupId, artifactId, version]"
782 	    .equals(properties.stringPropertyNames().toString())
783 	    : "Found unexpected properties ";
784 	String coordGroupId    = properties.getProperty("groupId");
785 	String coordArtifactId = properties.getProperty("artifactId");
786 	String coordVersion    = properties.getProperty("version");
787 	
788 	this.log.info("coordinate.groupId:    '" + coordGroupId    + "'");
789 	this.log.info("coordinate.artifactId: '" + coordArtifactId + "'");
790 	this.log.info("coordinate.version:    '" + coordVersion    + "'");
791 
792 //	propertyFileName = "META-INF/"
793 //	    + "maven/"
794 //	    + "eu.simuline.m2latex/"
795 //	    + "latex-maven-plugin/"
796 //	    + "pom.xml";
797 //	url = this.getClass().getClassLoader()
798 //	    .getResource(propertyFileName);
799 //	System.out.println("url:"+url);
800 //	MavenXpp3Reader reader = new MavenXpp3Reader();
801 //	Model model = reader.read(new InputStreamReader(url.openStream()));
802 	
803 
804 	GitProperties gitProperties = new GitProperties();
805 	this.log.info("git properties: ");
806 	gitProperties.log();
807 	String gitBuildVersion = gitProperties.getBuildVersion();
808 	//System.out.println("git.build.version: " + gitBuildVersion);
809 	assert gitBuildVersion.equals(manifestInfo.getImplVersion());
810 
811 //	String gitCommitIdDescribe = gitProperties.getCommitIdDescribe();
812 //	System.out.println("git.commit.id.describe: " + gitCommitIdDescribe);
813 //	String gitBuildTime = gitProperties.getBuildTime();
814 //	System.out.println("git.build.time: " + gitBuildTime);
815 
816 // TBD: rework; this is just the beginning 
817 //	System.out.println("version properties of converters: ");
818 //	Properties properties = getProperties("version.properties");
819 //
820 //	System.out.println("version.makeindex:"
821 //		   +properties.getProperty("version.makeindex"));
822 
823 	// headlines 
824 	this.log.info("tool versions: ");
825 	this.log.info(String.format(TOOL_VERSION_FORMAT, 
826 		"?warn?    ", "command:", "actual version", "(not)in", "[expected version interval]"));
827 
828 	Properties versionProperties = getProperties(VERSION_PROPS_FILE);
829 	if (versionProperties.size() > Converter.values().length) {
830 	    // Relation < need not be checked since all converters are checked below 
831 	    throw new IllegalStateException("Number of version properites " + 
832 	            versionProperties.size() +
833 		    " does not fit number of converters " + 
834 	            Converter.values().length + ". ");
835 	}
836 
837 	String cmd, expVersion, logMsg, warn, incl;
838 	Version actVersionObj;
839 	VersionInterval expVersionItv;
840 	boolean doWarn, doWarnAny = false;
841 	for (Converter conv : Converter.values()) {
842 	    doWarn = false;
843 	    cmd = conv.getCommand();
844 	    actVersionObj = new Version(conv, this.executor);
845 	    expVersion = versionProperties.getProperty(cmd);
846 	    expVersionItv = new VersionInterval(conv, expVersion);
847 
848 	    warn = "          ";
849 	    incl = "in";
850 	    if (!actVersionObj.isMatching()) {
851 		doWarn = true;
852 		this.log.warn("WMI01: Version string from converter " + conv + 
853 			" did not match expected form: \n"+actVersionObj.getText());
854 		incl = "not?in";
855 		warn = "       ";// mo warning number, still the above is valid
856 	    } else {
857 		doWarn = !expVersionItv.contains(actVersionObj);
858 		if (doWarn) {
859 		    incl = "not in";
860 		    warn = "WMI02: ";
861 		}
862 	    }
863 
864 	    logMsg = String.format(TOOL_VERSION_FORMAT,
865 		    warn, cmd+":", actVersionObj.getString(), incl, expVersion);
866 //	    this.log.info("actVersion: "+actVersionObj.getSegments());
867 //	    this.log.info("expVersion: "+expVersionObj.getSegments());
868 //	    this.log.info("actVersion: "+actVersionObj.getSegmentsAsStrings());
869 //	    this.log.info("expVersion: "+expVersionObj.getSegmentsAsStrings());
870 	    //this.log.info("actVersion?expVersion: "+actVersionObj.compareTo(expVersionObj));
871 	    if (doWarn) {
872 		this.log.warn(logMsg);
873 	    } else {
874 		this.log.info(logMsg);
875 	    }
876 	    doWarnAny |= doWarn;
877 	} // for 
878 	// TBD: try to work for makeindex also
879 	return doWarnAny;
880     }
881 }