001 /*
002 * YamFile.java
003 * Copyright (c) 1998-2008, The University of Sheffield.
004 *
005 * This code is from the GATE project (http://gate.ac.uk/) and is free
006 * software licenced under the GNU General Public License version 3. It is
007 * distributed without any warranty. For more details see COPYING.txt in the
008 * top level directory (or at http://gatewiki.sf.net/COPYING.txt).
009 */
010
011 package gate.yam;
012
013 import java.util.*;
014 import java.io.*;
015 import java.net.*;
016 import java.util.concurrent.*;
017 import org.apache.log4j.Logger;
018 import gnu.getopt.Getopt;
019 import org.springframework.core.io.*;
020 import org.springframework.context.support.*;
021 import org.springframework.util.StringUtils;
022 import gate.util.*;
023 import gate.yam.parse.*;
024 import gate.yam.translate.*;
025
026
027 /**
028 * Main interface to the YAM language and its translation.
029 * Target languages:
030 * <ul>
031 * <li>(X)HTML
032 * <li>LaTeX
033 * <li>PDF, which first translates to LaTeX then attempts to execute
034 * <tt>pdflatex</tt>
035 * <li>"TREE", which is a print representation of YAM parse trees used for
036 * testing
037 * <li>"PRETTY", which is a pretty print of YAM (whitespace changes only)
038 * </ul>
039 * @author Hamish Cunningham
040 */
041 public class YamFile implements Serializable
042 {
043 /** Construction. */
044 YamFile(FileSystemResource location) {
045 this.location = location;
046 outputTypes.add(FileType.HTML);
047 } // YamFile(FileSystemResource)
048
049 /** Construction. */
050 YamFile(File locationFile) {
051 this.location = new FileSystemResource(locationFile);
052 outputTypes.add(FileType.HTML);
053 } // YamFile(File)
054
055 /** Logger */
056 static Logger log = Logger.getLogger("gate.yam.YamFile");
057
058 /** Get the location of this file. */
059 public FileSystemResource getLocation() { return location; }
060
061 /** Get the canonical path of this file. */
062 /**
063 * Giveas the canonical path of this YamFile
064 * @return The canonical path of this YamFIle
065 * @throws GateException If the canonical path of this YamFile causes an
066 * IOException
067 */
068 public String getCanonicalPath() throws GateException {
069 String path = null;
070
071 try {
072 path = location.getFile().getCanonicalPath();
073 } catch (IOException ioe) {
074 throw new GateException(ioe);
075 }
076
077 return path;
078 } // getCanonicalPath()
079
080 /** Location of the .yam. */
081 transient FileSystemResource location;
082
083 /** Allow serialization of the Spring FSR field. */
084 private void writeObject(ObjectOutputStream oos) throws IOException {
085 oos.defaultWriteObject();
086 oos.writeObject(location.getFile().getPath());
087 } // writeObject(OOS)
088
089 /** Allow serialization of the Spring FSR field. */
090 private void readObject(ObjectInputStream ois)
091 throws ClassNotFoundException, IOException {
092 ois.defaultReadObject();
093 location = new FileSystemResource((String) ois.readObject());
094 } // readObject(OIS)
095
096 /**
097 * Static factory method for getting a YAM file. Implemented via
098 * {@link #get(FileSystemResource)}.
099 */
100 public static YamFile get(File location) {
101 YamFile yam = get(new FileSystemResource(location));
102 log.debug("location="+location+"; yam="+yam);
103 return yam;
104 } // get(File)
105
106 /**
107 * Static factory method for getting a YAM file. This method will create a
108 * YamFile for a particular location, or will
109 * return the YamFile dependency for a generated file (e.g. HTML). A null
110 * return value indicates that a dependent file was passed which is not
111 * generated from YAM.
112 * @param location a path that points to a YAM file or a file depending on a
113 * YAM file.
114 * @return either a YamFile instance or null.
115 */
116 public static YamFile get(FileSystemResource location) {
117 String path = location.getPath();
118 String lowerCasePath = path.toLowerCase();
119 log.debug("path = " + path);
120
121 // is this a .yam? if so just return a new YamFile
122 if(lowerCasePath.endsWith(FileType.YAM.suffix())) {
123 return new YamFile(location);
124
125 // is it a generated file? if so find and return the .yam
126 } else if(FileType.dependent(path)) {
127 String base = path.substring(0, path.lastIndexOf('.'));
128 FileSystemResource yamLocation =
129 new FileSystemResource(base + FileType.YAM.suffix());
130 File file = yamLocation.getFile();
131 if(file.exists() && ! file.isDirectory())
132 return new YamFile(yamLocation);
133 }
134
135 return null;
136 } // get(FileSystemResource)
137
138 /**
139 * Set the path in which to check for existence of relative links.
140 * @param contextPath the path to search in.
141 */
142 public void setContextPath(String contextPath) {
143 this.ioHandler.setContextPath(contextPath);
144 } // setContextPath(String)
145
146 /**
147 * Reset the location of this YamFile in the directory pointed to by the
148 * context path. I.e. the new location of the file will be contextPath +
149 * new file name. This method is useful for when a generate has been
150 * done out of place (e.g. in the CoW staging directory) but now dependency
151 * analysis needs to be done on the file as if it was in place (e.g. when
152 * saving an edit and checking in).
153 * @param name the file name of the
154 */
155 public void replaceInContext(String name) {
156 File newLoc = new File(ioHandler.getContextPath(), name);
157 location = new FileSystemResource(newLoc);
158 } // replaceInContext()
159
160 /**
161 * Set the URL which non-existent links will be pointed at (which presumably
162 * is the "create new page" URL for the parent wiki).
163 * @param createPageUrl the link to point to.
164 */
165 public void setCreatePageUrl(String createPageUrl) {
166 this.ioHandler.setCreatePageUrl(createPageUrl);
167 } // setCreatePageUrl(String)
168
169 /**
170 * Set the URL which non-existent links will be pointed at (which presumably
171 * is the "create new page" URL for the parent wiki).
172 * @param createPageUrl the link to point to.
173 */
174 public void setCreatePageUrl(UrlResource createPageUrl) {
175 this.ioHandler.setCreatePageUrl(createPageUrl);
176 } // setCreatePageUrl(UrlResource)
177
178 /**
179 * Set the URL within which citation keys will be resolved. The URL will be
180 * for a bibliogrpahy. Citation keys reference a part of this URL, an
181 * individual bibliographic entry. Citation keys will form the final fragment
182 * of this URL, and will be prefixed, as defined by the
183 * {@link #setBibAnchorPrefix(String) setBibAnchorPrefix} method
184 * @param bibPageUrl the URL of the bibliogrpahy.
185 */
186 public void setBibPageUrl(UrlResource bibPageUrl) {
187 this.ioHandler.setBibPageUrl(bibPageUrl);
188 }
189
190 /**
191 * Set the prefix added to citation keys, when forming a reference to a
192 * bibiography file entry
193 * @see #setBibPageUrl(UrlResource)
194 * @param bibAnchorPrefix The prefix added to citation keys.
195 */
196 public void setBibAnchorPrefix(String bibAnchorPrefix) {
197 this.ioHandler.setBibAnchorPrefix(bibAnchorPrefix);
198 }
199
200 /**
201 * Create a file of a particular output type.
202 */
203 public File getOutputFile(FileType type) {
204 String path = location.getFile().getPath();
205 if(StringUtils.getFilename(path).contains("."))
206 path = path.substring(0, path.lastIndexOf('.'));
207
208 return new File(path + type.suffix());
209 } // getOutputFile(FileType)
210
211 /** Get the File for HTML output. */
212 public File getHtmlFile() {
213 return getOutputFile(FileType.HTML);
214 } // getHtmlFile()
215
216 /** Get the path for an HTML output file. */
217 public String getHtmlPath() {
218 return getHtmlFile().getPath();
219 } // getHtmlPath()
220
221 /**
222 * Trigger generation of the various output types.
223 */
224 public void generateType(FileType type) {
225 outputTypes.add(type);
226 } // generateType(FileType)
227
228 /** What types of output to generate. */
229 Set<FileType> outputTypes = new HashSet<FileType>();
230
231 /**
232 * The various file types involved with YAM and their filename suffixes.
233 */
234 public enum FileType {
235 YAM {
236 public String suffix() { return ".yam"; }
237 public AbstractTranslator translator() { return null; }
238 },
239 HTML {
240 public String suffix() { return ".html"; }
241 public AbstractTranslator translator() { return new HtmlTranslator(); }
242 },
243 LATEX {
244 public String suffix() { return ".tex"; }
245 public AbstractTranslator translator() { return new LaTeXTranslator(); }
246 },
247 PDF {
248 public String suffix() { return ".pdf"; }
249 public AbstractTranslator translator() { return null; /*new PDFTranslator();*/ }
250 },
251 TREE {
252 public String suffix() { return ".tree"; }
253 public AbstractTranslator translator() { return new TreeTranslator(); }
254 },
255 PRETTY {
256 public String suffix() { return ".pretty"; }
257 public AbstractTranslator translator() { return new PrettyTranslator(); }
258 };
259
260 /** Get the file suffix for this type. */
261 public abstract String suffix();
262
263 /** Get the translator class for this type. */
264 public abstract AbstractTranslator translator();
265
266 /** Is this type generated from YAM files? */
267 public static boolean dependent(String path) {
268 path = path.toLowerCase();
269 return
270 path.endsWith(HTML.suffix()) ||
271 path.endsWith(LATEX.suffix()) ||
272 path.endsWith(PDF.suffix())
273 ;
274 } // dependent(path)
275 } // FileType
276
277 /**
278 * Get a List of those output file locations that are generated by
279 * this YamFile.
280 * @throws GateException if there is an exception on the YamFile location.
281 * @return a List of locations as Strings.
282 */
283 public List<String> getOutputLocations() throws GateException {
284 String id = null;
285 List<String> locations = new ArrayList<String>();
286
287 id = getCanonicalPath();
288
289 for(FileType type: outputTypes) {
290 // construct the output file name
291 StringBuilder outputPath = new StringBuilder(id);
292 int len = outputPath.length();
293 outputPath.replace(len - 4, len, type.suffix());
294 locations.add(outputPath.toString());
295 }
296
297 return locations;
298 } // getOutputLocations()
299
300 /**
301 * Record of generation processes that are in progress. Maps from canonical
302 * path of the YAM file to the file types that are being processed.
303 */
304 static Map<String, Set<FileType>> currentGenerateCalls =
305 new ConcurrentHashMap<String, Set<FileType>>();
306
307 /**
308 * This method checks whether a dependent file (one that is generated from
309 * YAM) needs regeneration, and if so it returns a YamFile instance
310 * corresponding to the generated file. If the file doesn't need
311 * regeneration or has not been generated from a YAM file the method returns
312 * null.
313 *
314 * A dependent file needs regeneration
315 * when
316 * <ul>
317 * <li>
318 * The YAM source file has an earlier modification time than the dependent
319 * file.
320 * </ul>
321 *
322 * @param location a file that may be generated from a YAM source file
323 * @return either the YAM file that needs generation or null
324 * @throws GateRuntimeException if we get an IO exception on the location
325 * file
326 */
327 public static YamFile needsGeneration(FileSystemResource location)
328 throws GateRuntimeException {
329 // is it dependent, and is there a corresponding .yam?
330 File dependentFile = location.getFile();
331 String path = null;
332 try {
333 path = dependentFile.getCanonicalPath();
334 } catch(IOException e) {
335 throw new GateRuntimeException(e);
336 }
337 YamFile yam = null;
338 if(FileType.dependent(path)) yam = YamFile.get(location);
339 if(yam == null) return null;
340
341 // is the .yam more recent than the dependent?
342 File dotYam = yam.getLocation().getFile();
343 if(dotYam.lastModified() > dependentFile.lastModified())
344 return yam;
345 else
346 return null;
347 } // needsGeneration(location)
348
349 /**
350 * The parse tree (and related state) created by {@link #generate()}.
351 */
352 transient YamParseTree parseTree = null;
353
354 /**
355 * Get the parse tree (and related state) created by {@link #generate()}.
356 * If generate hasn't been run this will be null.
357 */
358 public YamParseTree getParseTree() { return parseTree; }
359
360 /** Should we process included files or not? */
361 boolean doIncludes = true;
362
363 /** Should we process included files or not? */
364 public void setDoIncludes(boolean b) { doIncludes = b; }
365
366 /**
367 * Translate to target languages.
368 * @throws GateException if parsing or translation fails.
369 * @return the parse tree, or null if generation of any of our output
370 * types is currently underway in a different YamFile.
371 */
372 public YamParseTree generate() throws GateException {
373 parseTree = null;
374 Reader reader = null;
375 String id = null;
376
377 // this try block performs exclusion of our type(s) of generation on
378 // this .yam; the finally clause releases the exclusion lock (from
379 // the currentGenerateCalls map)
380 try {
381 id = location.getFile().getCanonicalPath();
382
383 // record the type of generation against the canonical path and return
384 // null if a translation of the same type is already in progress
385 synchronized(currentGenerateCalls) {
386 Set currentCalls = currentGenerateCalls.get(id);
387 if(currentCalls == null) {
388 currentGenerateCalls.put(id, outputTypes);
389 } else {
390 currentCalls.retainAll(outputTypes);
391 if(currentCalls.size() > 0)
392 return null;
393 }
394 }
395
396 // a reader for the .yam
397 reader = new FileReader(location.getFile());
398
399 // parse
400 YamParser parser = new YamParser(reader);
401 ioHandler.setSourceDir(location.getFile().getParentFile());
402 parser.setIOHandler(ioHandler);
403 parser.setDoIncludes(doIncludes);
404 parseTree = parser.parse();
405
406 // do the translation(s)
407 for(FileType type: outputTypes) {
408 // construct the output file name and get an appropriate writer
409 StringBuilder outputPath = new StringBuilder(id);
410 int len = outputPath.length();
411 outputPath.replace(len - 4, len, type.suffix());
412 Writer writer = new FileWriter(outputPath.toString());
413
414 try {
415 // translate
416 translate(type, writer, parseTree);
417 }
418 finally {
419 writer.close();
420 }
421 }
422
423 } catch(IOException e) {
424 throw new GateException(e);
425 } finally {
426 // remove this call from the currentGenerateCalls map
427 currentGenerateCalls.remove(id);
428
429 // close the io stuff
430 try {
431 if(reader != null) reader.close();
432 } catch(IOException ee) {
433 log.debug("problem closing IO in generate(), exception was: " + ee);
434 }
435 }
436
437 return parseTree;
438 } // generate()
439
440 /**
441 * Get the list of links from this YAM file to other local files, as a list
442 * of canonical paths of those files. For files dependent on a YAM file, then
443 * the path of the YAM file will be returned. For files not dpendent on YAM
444 * files, the path of the file will be returned. Any links that cannot be
445 * resolved from their local path are ignored.
446 * @return A list of the canonical paths of links in this YAM file. Null if
447 * the parse tree has not been generated via a call to generate(). Null if the
448 * parent directory of this YamFile cannot be resolved. Empty if
449 * there are no links.
450 */
451 public List<String> getLinks() {
452
453 // return null if no parse tree has been generated
454 if(parseTree == null) {
455 log.info("yamFile.getLinks() failed to get links: no parse tree");
456 return null;
457 }
458
459 // Return null if cannot get canonical path of parent directory
460 File base = null;
461 try {
462 base = location.getFile().getParentFile().getCanonicalFile();
463 } catch(IOException ioe) {
464 log.info("yamFile.getLinks() failed to get links");
465 log.info("yamFile.getLinks() failed to find parent directory: "
466 + ioe.getMessage());
467 }
468
469 // Return canonical paths of all links where we can get a canonical path
470 List<String> canonicalPaths = new ArrayList<String>();
471 for(String localPath : parseTree.getLinks()) {
472
473 File absPath = new File(base, localPath);
474 YamFile yam = YamFile.get(new FileSystemResource(absPath));
475
476 try {
477 if(yam == null) { // it's not yam or yam dependent
478 canonicalPaths.add(absPath.getCanonicalPath());
479 } else { // it's a yam or yam dependent
480 canonicalPaths.add(yam.getCanonicalPath());
481 }
482 } catch (IOException ioe) {
483 log.info("yamFile.getLinks() failed to resolve link to non-YAM file: "
484 + ioe.getMessage());
485 log.info("yamFile.getLinks() ignored link: " + absPath);
486 } catch (GateException ge) {
487 log.info("yamFile.getLinks() failed to resolve link to YAM file or "
488 + "YAM dependent file: "+ ge.getMessage());
489 log.info("yamFile.getLinks() ignored link: " + absPath);
490 }
491 }
492
493 return canonicalPaths;
494 } // getLinks()
495
496 /**
497 * Get the list of includes from this YAM file to other local files, as a list
498 * of canonical paths of those files. For files dependent on a YAM file, then
499 * the path of the YAM file will be returned. For files not dpendent on YAM
500 * files, the path of the file will be returned. Any includes that cannot be
501 * resolved from their local path are ignored.
502 * @return A list of the canonical paths of includes in this YAM file. Null if
503 * the parse tree has not been generated via a call to generate(). Null if the
504 * parent directory of this YamFile cannot be resolved. Empty if
505 * there are no includes.
506 */
507 public List<String> getIncludes() {
508
509 // return null if no parse tree has been generated
510 if(parseTree == null) {
511 log.info("yamFile.getIncludes() failed to get Includes: no parse tree");
512 return null;
513 }
514
515 // Return null if cannot get canonical path of parent directory
516 File base = null;
517 try {
518 base = location.getFile().getParentFile().getCanonicalFile();
519 } catch(IOException ioe) {
520 log.info("yamFile.getIncludes() failed to get includes");
521 log.info("yamFile.getIncludes() failed to find parent directory: "
522 + ioe.getMessage());
523 }
524
525 // Return canonical paths of all Includes where we can get a canonical path
526 List<String> canonicalPaths = new ArrayList<String>();
527 for(String localPath : parseTree.getIncludes()) {
528 File absPath = new File(base, localPath);
529 YamFile yam = YamFile.get(new FileSystemResource(absPath));
530
531 try {
532 if(yam == null) {
533 // It's not yam or yam dependent. This shouldn't happen. Includes
534 // should only be of yams.
535 log.info("yamFile.getIncludes() found include of non-YAM "
536 + "file: " + absPath);
537 log.info("yamFile.getIncludes() ignored include: " + absPath);
538
539 } else {
540 // It's a yam or yam dependent
541 canonicalPaths.add(yam.getCanonicalPath());
542 }
543 } catch (GateException ge) {
544 log.info("yamFile.getIncludes() failed to resolve include of YAM file: "
545 + ge.getMessage());
546 log.info("yamFile.getIncludes() ignored include: " + absPath);
547 }
548
549 }
550 return canonicalPaths;
551 } // getIncludes()
552
553
554 /**
555 * Construct an appropriate translator for the target language and run it.
556 */
557 void translate(
558 FileType outputType, Writer outputWriter, YamParseTree parseTree
559 ) throws GateException {
560 AbstractTranslator translator = outputType.translator();
561 translator.setIOHandler(ioHandler);
562 translator.setParseTree(parseTree);
563 translator.setWriter(outputWriter);
564 translator.translate();
565 } // translate(FileType, Writer, YamParseTree)
566
567 /**
568 * Get the language version number.
569 * Version 1 was derived from Terrence Parr's TML language (thanks Ter!
570 * See <a href=http://antlr.org>the ANTLR site</a>).
571 * Version 2 was the first version of YAM proper. Version 3 added
572 * various new facilities and was the basis for the first version of CLIE.
573 * Version 4 was a complete rewrite, for GATE version 4 and for use in
574 * <a href=http://gatewiki.sf.net/>CoW</a>. Version 5 is intended to be
575 * stable and backwards-compatible with future versions.
576 */
577 public String getVersion() { return "5.0"; }
578
579 /** Get the package name for YAM plugins. */
580 public final static String getPluginPackageName() {
581 return "gate.yam.plugins";
582 } // getPluginPackageName()
583
584 /**
585 * Return a String representing this YamFile: the String representation of the
586 * underlying File.
587 * @return A String representing this YamFile
588 */
589 public String toString() {
590 return location.toString();
591 }
592
593 /** Print errors and warnings from parsing. */
594 public static String printErrors(YamParseTree parseTree) {
595 StringBuffer buf = new StringBuffer();
596 List errors = parseTree.getErrors();
597 List warnings = parseTree.getWarnings();
598 if(errors.size() == 0 && warnings.size() == 0)
599 return buf.toString();
600 buf.append("%%%%%%%%%%%%%%% errors and warnings %%%%%%%%%%%%%%%%%%\n");
601 for(int i=0; i<errors.size(); i++)
602 buf.append(((ParsingProblem)errors.get(i)).getMessage() + "\n");
603 for(int i=0; i<warnings.size(); i++)
604 buf.append(((ParsingProblem)warnings.get(i)).getMessage() + "\n");
605 buf.append("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%");
606 return buf.toString();
607 } // printErrors(parseTree)
608
609 /** IOHandler to pass to the parser. */
610 IOHandler ioHandler = new IOHandlerImpl();
611
612 } // YamFile
|