Source code: xdoclet/GenerationManager.java
1 /*
2 * Copyright (c) 2001, 2002 The XDoclet team
3 * All rights reserved.
4 */
5 package xdoclet;
6
7 import java.io.File;
8 import java.net.URL;
9 import java.text.MessageFormat;
10 import java.util.ArrayList;
11 import java.util.Arrays;
12
13 import java.util.HashMap;
14 import java.util.Iterator;
15 import java.util.List;
16 import java.util.Map;
17
18 import org.apache.commons.logging.Log;
19 import xjavadoc.XClass;
20 import xjavadoc.XJavaDoc;
21
22 import xdoclet.loader.ModuleFinder;
23 import xdoclet.tagshandler.AbstractProgramElementTagsHandler;
24 import xdoclet.tagshandler.PackageTagsHandler;
25 import xdoclet.template.TemplateEngine;
26 import xdoclet.template.TemplateException;
27 import xdoclet.template.TemplateParser;
28 import xdoclet.util.LogUtil;
29
30 /**
31 * Verify if the generation is needed for Java files and Xml files based templates.
32 *
33 * @author Vincent Harcq (vincent.harcq@hubmethods.com)
34 * @created March 30, 2002
35 * @version $Revision: 1.19 $
36 */
37 public class GenerationManager
38 {
39 private final static File newestJar = ModuleFinder.getNewestFileOnClassPath();
40
41 private static Map parserDb = null;
42
43 private final TemplateSubTask subTask;
44
45 private boolean guessGenerationNeeded = true;
46
47 private XJavaDoc _xJavaDoc;
48
49 /**
50 * Describe what the GenerationManager constructor does
51 *
52 * @param subTask Describe what the parameter does
53 * @param xJavaDoc
54 */
55 public GenerationManager(XJavaDoc xJavaDoc, TemplateSubTask subTask)
56 {
57 if (xJavaDoc == null) {
58 throw new IllegalArgumentException("xJavaDoc can't be null");
59 }
60 _xJavaDoc = xJavaDoc;
61 this.subTask = subTask;
62 }
63
64 /**
65 * Return (and construct) the template database. It is a map between a <code>String</code> representing the template
66 * file and an array of <String> representing the merge files that are part of the generation.
67 *
68 * @return the <code>Map</code>
69 */
70 private static Map getParserDb()
71 {
72 if (parserDb == null) {
73 parserDb = new HashMap();
74 }
75 return parserDb;
76 }
77
78 /**
79 * During parsing we build the Template database. We store it on file.
80 *
81 * @param templateURL the template file
82 * @param files the merge files involved in the generation
83 */
84 private static void updateParserDb(URL templateURL, String[] files)
85 {
86 // Merge existing list with new list
87 String[] mergeFiles = (String[]) getParserDb().get(new File(templateURL.getFile()).getName());
88 List complete = new ArrayList(Arrays.asList(files));
89
90 if (mergeFiles != null) {
91 for (int j = 0; j < mergeFiles.length; j++) {
92 String file = mergeFiles[j];
93
94 if (!complete.contains(file)) {
95 complete.add(file);
96 }
97 }
98 }
99 getParserDb().put(new File(templateURL.getFile()).getName(), complete.toArray(new String[complete.size()]));
100 }
101
102 /**
103 * Gets the GuessGenerationNeeded attribute of the GenerationManager object
104 *
105 * @return The GuessGenerationNeeded value
106 */
107 public boolean isGuessGenerationNeeded()
108 {
109 return guessGenerationNeeded;
110 }
111
112 /**
113 * Test if a Java source mmust be generated or not depending of timestamp of elements involved.
114 *
115 * @param clazz the Class from wich we generate
116 * @param file the File that will be generated
117 * @param withTemplate
118 * @return true if generation is needed
119 * @exception XDocletException
120 */
121 public boolean isGenerationNeeded(XClass clazz, File file, boolean withTemplate)
122 throws XDocletException
123 {
124 Log log = LogUtil.getLog(GenerationManager.class, "generation");
125
126 if (subTask.getContext().isForce()) {
127 log.debug("Force generation enabled");
128 return true;
129 }
130
131 if (isGuessGenerationNeeded() == false) {
132 log.debug("guessGenerationNeeded enabled");
133 return true;
134 }
135
136 // 1. Check whether a file on classpath is newer than the destination file
137 if (isClasspathNewerThanFile(file))
138 return true;
139
140 // 2. Check whether the class (or any superclass) is newer than the destination file
141 if (isClassHierarchyNewerThanFile(clazz, file))
142 return true;
143
144 // 3. Check whether the template file or any merge files are newer than the destination file
145 if (isTemplateNewerThanFile(withTemplate, file))
146 return true;
147
148 return false;
149 }
150
151 /**
152 * Verify if the generation of a file to generate is needed because either the Template used to generate the file
153 * have a later timestamp, or because ALL the Java sources imported in this task have a sooner timestamp. This is
154 * used to test if xml files generation is needed.
155 *
156 * @param file The file to check
157 * @return true if the generation is needed
158 * @exception XDocletException
159 */
160 public boolean isGenerationNeeded(File file)
161 throws XDocletException
162 {
163 Log log = LogUtil.getLog(GenerationManager.class, "generation");
164
165 log.debug("Generation need check for " + file.getName());
166
167 if (subTask.getContext().isForce()) {
168 log.debug("Force generation enabled");
169 return true;
170 }
171
172 if (isGuessGenerationNeeded() == false) {
173 log.debug("guessGenerationNeeded enabled");
174 return true;
175 }
176
177 // 1. Check on Jar timestamp
178 if (isClasspathNewerThanFile(file) == true)
179 return true;
180
181 // 2. Check the timestamp of template file and merge files
182 if (isGenerationNeeded(file, subTask.getTemplateURL())) {
183 return true;
184 }
185
186 log.debug("Generation need check for " + file.getName());
187
188 // 3. Check Timestamp of all java sources in sourcepath
189
190 for (Iterator i = _xJavaDoc.getSourceClasses().iterator(); i.hasNext(); ) {
191 if (isGenerationNeeded((XClass) i.next(), file, false)) {
192 return true;
193 }
194 }
195
196 return false;
197 }
198
199 /**
200 * Sets the GuessGenerationNeeded attribute of the GenerationManager object.
201 *
202 * @param guessGenerationNeeded The new GuessGenerationNeeded value
203 */
204 public void setGuessGenerationNeeded(boolean guessGenerationNeeded)
205 {
206 this.guessGenerationNeeded = guessGenerationNeeded;
207 }
208
209 private boolean isClassHierarchyNewerThanFile(XClass clazz, File file)
210 {
211 Log log = LogUtil.getLog(GenerationManager.class, "generation");
212
213 while (clazz != null) {
214 if (clazz.getQualifiedName().equals("java.lang.Object")) {
215 return false;
216 }
217 if (file.lastModified() < clazz.lastModified()) {
218 if (log.isDebugEnabled()) {
219 log.debug("Generation needed for '" + file.getAbsolutePath() + "' because " + clazz.getQualifiedName() + " is newer (it's in the class hierarchy)");
220 }
221 return true;
222 }
223 clazz = clazz.getSuperclass();
224 }
225
226 return false;
227 }
228
229 private boolean isTemplateNewerThanFile(boolean withTemplate, File file) throws XDocletException
230 {
231 Log log = LogUtil.getLog(GenerationManager.class, "generation");
232
233 log.debug("Checking template. withTemplate=" + withTemplate);
234
235 if (withTemplate) {
236 if (isGenerationNeeded(file, subTask.getTemplateURL())) {
237 if (log.isDebugEnabled()) {
238 log.debug("Generation needed for '" + file.getAbsolutePath() + "' because template file is newer.");
239 }
240
241 return true;
242 }
243 }
244
245 return false;
246 }
247
248 private boolean isClasspathNewerThanFile(File file)
249 {
250 Log log = LogUtil.getLog(GenerationManager.class, "generation");
251
252 if (file.lastModified() < newestJar.lastModified()) {
253 if (log.isDebugEnabled()) {
254 log.debug("Generation needed for '" + file.getAbsolutePath() + "' because " + newestJar.getName() + " is newer.");
255 }
256 return true;
257 }
258
259 if (log.isDebugEnabled()) {
260 log.debug("No files on classpath are newer than '" + file.getAbsolutePath() + "'");
261 }
262
263 return false;
264 }
265
266 /**
267 * Verify if the generation of a file is needed because either the template file has a sooner timestamp, or because
268 * one of the merge files have a sooner timestamp
269 *
270 * @param file The file to generate
271 * @param templateURL the Template file to use
272 * @return true if generation is needed.
273 * @exception XDocletException
274 */
275 private boolean isGenerationNeeded(File file, URL templateURL)
276 throws XDocletException
277 {
278 Log log = LogUtil.getLog(GenerationManager.class, "xml");
279
280 if (log.isDebugEnabled()) {
281 log.debug("Generation need check for " + file.getAbsolutePath());
282 }
283
284 // 1. Check Timestamp of Template file
285 File templateFile = new File(subTask.getTemplateURL().getFile());
286
287 if (templateFile.exists() && file.lastModified() < templateFile.lastModified()) {
288 if (log.isDebugEnabled()) {
289 log.debug("Generation needed for '" + file.getAbsolutePath() + "' because of timestamp of " + subTask.getTemplateURL());
290 }
291 return true;
292 }
293 if (log.isDebugEnabled()) {
294 log.debug("Reject file '" + file.getAbsolutePath() + "' because of timestamp of " + subTask.getTemplateURL());
295 }
296
297 // 2. Check timestamp of Merge files found inside Template
298 String[] files;
299
300 if (getParserDb().get(templateFile) == null) {
301 TemplateEngine the_engine = subTask.getEngine();
302 TemplateParser the_parser = TemplateParser.getParserInstance();
303
304 subTask.setEngine(the_parser);
305
306 // Why is setOutput called here? We're only checking _IF_ we're going to generate! (Aslak)
307 the_parser.setOutput(file);
308 the_parser.setTemplateURL(templateURL);
309
310 try {
311 the_parser.start();
312 }
313 catch (TemplateException e) {
314 throw new XDocletException(e, e.toString());
315 }
316
317 files = the_parser.getMergeFiles();
318 if (files != null) {
319 updateParserDb(templateURL, files);
320 }
321
322 //restore
323 subTask.setEngine(the_engine);
324 }
325 else {
326 files = (String[]) getParserDb().get(new File(templateURL.getFile()).getName());
327 for (int i = 0; i < files.length; i++) {
328 if (log.isDebugEnabled()) {
329 log.debug(templateURL.getFile() + " : " + files[i]);
330 }
331 }
332 }
333
334 log.debug("Number of Merge files involved = " + files.length);
335
336 for (int i = 0; i < files.length; i++) {
337 String mergeFilePattern = files[i];
338 List mergeFiles = new ArrayList();
339
340 if (mergeFilePattern.indexOf("{0}") != -1) {
341
342 for (Iterator j = _xJavaDoc.getSourceClasses().iterator(); j.hasNext(); ) {
343 XClass aClass = (XClass) j.next();
344 String ejbName = MessageFormat.format(mergeFilePattern, new Object[]{AbstractProgramElementTagsHandler.getClassNameFor(aClass)});
345 String mergeFileName = PackageTagsHandler.packageNameAsPathFor(aClass.getContainingPackage()) + File.separator + ejbName;
346
347 if (subTask.getMergeDir() != null)
348 mergeFiles.add(new File(subTask.getMergeDir(), mergeFileName));
349 }
350 }
351 else {
352 if (subTask.getMergeDir() != null)
353 mergeFiles.add(new File(subTask.getMergeDir(), mergeFilePattern));
354 }
355 for (Iterator iterator = mergeFiles.iterator(); iterator.hasNext(); ) {
356 File mergeFile = (File) iterator.next();
357
358 log.debug("Generation check for '" + file.getAbsolutePath() + "' because of " + mergeFile.getName());
359
360 if (mergeFile.exists()) {
361 if (file.lastModified() < mergeFile.lastModified()) {
362 log.debug("Generation needed for '" + file.getAbsolutePath() + "' because of timestamp of " + mergeFile.getName());
363 return true;
364 }
365 log.debug("Reject file '" + file.getAbsolutePath() + "' because of timestamp of " + mergeFile.getName());
366 }
367 }
368 }
369 return false;
370 }
371 }