1/*
2 * Copyright (C) 2010 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.google.clearsilver.jsilver.precompiler;
18
19import com.google.clearsilver.jsilver.autoescape.EscapeMode;
20import com.google.clearsilver.jsilver.exceptions.JSilverAutoEscapingException;
21import com.google.common.annotations.VisibleForTesting;
22import com.google.common.collect.ImmutableMap;
23
24import java.io.FileNotFoundException;
25import java.io.IOException;
26import java.io.InputStream;
27import java.io.InputStreamReader;
28import java.io.LineNumberReader;
29import java.io.Reader;
30import java.util.HashMap;
31import java.util.Map;
32import java.util.StringTokenizer;
33
34/**
35 * Utility class that reads in the file output by BatchCompiler that is a list of template names and
36 * corresponding class names and returns a Map of template filenames to class names which can be fed
37 * to {@see com.google.clearsilver.jsilver.JSilverOptions#setPrecompiledTemplateMap}
38 */
39public class PrecompiledTemplateMapFileReader {
40
41  private final String mapFileName;
42  private final String dirPattern;
43  private final String rootDir;
44
45  private Map<Object, String> templateMap = null;
46
47  /**
48   * Helper object that reads in the specified resource file and generates a mapping of template
49   * filenames to corresponding BaseCompiledTemplate class names.
50   *
51   * @param filename name of the resource file to read the map from.
52   * @param dirPattern prefix to remove from read in template names. Used in conjunction with
53   *        rootDir to update template file paths.
54   * @param rootDir optional string to prepend to all non-absolute template filenames. Should be set
55   *        to the location of the templates in production via a flag.
56   */
57  public PrecompiledTemplateMapFileReader(String filename, String dirPattern, String rootDir) {
58    this.mapFileName = filename;
59    this.dirPattern = dirPattern;
60    this.rootDir = rootDir;
61  }
62
63  public Map<Object, String> getTemplateMap() throws IOException {
64    if (templateMap == null) {
65      templateMap = makeTemplateMap(mapFileName, rootDir);
66    }
67    return templateMap;
68  }
69
70  private Map<Object, String> makeTemplateMap(String templateMapFile, String rootDir)
71      throws IOException {
72    Map<Object, String> templateMap = new HashMap<Object, String>();
73    LineNumberReader reader = null;
74    try {
75      reader = new LineNumberReader(getMapFileReader(templateMapFile));
76      for (String line = reader.readLine(); line != null; line = reader.readLine()) {
77        // Process single line from the templateMapFile
78        // and put found templates into templateMap.
79        processTemplateMapFileLine(line, reader.getLineNumber(), templateMap, templateMapFile,
80            rootDir);
81      }
82    } finally {
83      if (reader != null) {
84        reader.close();
85      }
86    }
87    return ImmutableMap.copyOf(templateMap);
88  }
89
90  private void processTemplateMapFileLine(String line, int lineNumber,
91      Map<Object, String> templateMap, String templateMapFile, String rootDir) {
92
93    line = line.trim();
94    if (line.isEmpty() || line.startsWith("#")) {
95      // Ignore blank lines and comment lines.
96      return;
97    }
98    StringTokenizer st = new StringTokenizer(line);
99    if (!st.hasMoreTokens()) {
100      throw new IllegalArgumentException("No template file name found in " + templateMapFile
101          + " on line " + lineNumber + ": " + line);
102    }
103    String templateName = st.nextToken();
104    if (dirPattern != null && templateName.startsWith(dirPattern)) {
105      templateName = templateName.substring(dirPattern.length());
106    }
107    if (rootDir != null) {
108      // If it is not an absolute path and we were given a root directory,
109      // prepend it.
110      templateName = rootDir + templateName;
111    }
112    if (!st.hasMoreTokens()) {
113      throw new IllegalArgumentException("No class name found in " + templateMapFile + " on line "
114          + lineNumber + ": " + line);
115    }
116    String className = st.nextToken();
117    EscapeMode escapeMode;
118    if (!st.hasMoreTokens()) {
119      escapeMode = EscapeMode.ESCAPE_NONE;
120    } else {
121      String escapeCmd = st.nextToken();
122      try {
123        escapeMode = EscapeMode.computeEscapeMode(escapeCmd);
124      } catch (JSilverAutoEscapingException e) {
125        throw new IllegalArgumentException("Invalid escape mode found in " + templateMapFile
126            + " on line " + lineNumber + ": " + escapeCmd);
127      }
128    }
129    PrecompiledTemplateMapKey key = new PrecompiledTemplateMapKey(templateName, escapeMode);
130    templateMap.put(key, className);
131  }
132
133  @VisibleForTesting
134  protected Reader getMapFileReader(String templateMapFile) throws IOException {
135    ClassLoader classLoader = getClass().getClassLoader();
136    InputStream in = classLoader.getResourceAsStream(templateMapFile);
137    if (in == null) {
138      throw new FileNotFoundException("Unable to locate resource: " + templateMapFile);
139    }
140    return new InputStreamReader(in, "UTF-8");
141  }
142
143}
144