1 | /* |
2 | * $Id: ClassGenerator.java,v 1.4 2004/06/16 19:23:57 hastings Exp $ |
3 | * |
4 | * (c) Copyright, Moebius Solutions, Inc., 2004 |
5 | * |
6 | * All Rights Reserved |
7 | * |
8 | * This material may be reproduced by or for the U. S. Government |
9 | * pursuant to the copyright license under the clause at |
10 | * DFARS 252.227-7014 (OCT 2001). |
11 | */ |
12 | |
13 | package com.moesol.generator; |
14 | |
15 | import java.io.BufferedReader; |
16 | import java.io.BufferedWriter; |
17 | import java.io.File; |
18 | import java.io.FileNotFoundException; |
19 | import java.io.FileReader; |
20 | import java.io.FileWriter; |
21 | import java.io.IOException; |
22 | import java.io.InputStream; |
23 | import java.io.Reader; |
24 | import java.io.Writer; |
25 | import java.lang.reflect.Modifier; |
26 | import java.util.Properties; |
27 | |
28 | import com.moesol.generator.core.ApplyException; |
29 | import com.moesol.generator.core.MergeException; |
30 | import com.moesol.generator.core.MergeTagGroker; |
31 | import com.moesol.generator.core.Template; |
32 | import com.moesol.generator.core.VisitClass; |
33 | |
34 | /** |
35 | * Abstract base class for code generators |
36 | */ |
37 | public abstract class ClassGenerator { |
38 | public ClassGenerator(Class clazz, boolean quiet, TranslationContext map) { |
39 | m_class = clazz; |
40 | m_quiet = quiet; |
41 | m_trans_ctx = map; |
42 | } |
43 | |
44 | /** |
45 | * Template method, default implementation returns true for |
46 | * public classes and interfaces. |
47 | * @return true if the generation should happen for this class. |
48 | */ |
49 | protected boolean shouldGenerate() { |
50 | if (Modifier.isPublic(m_class.getModifiers())) { |
51 | return true; |
52 | } |
53 | if (Modifier.isProtected(m_class.getModifiers())) { |
54 | return getTranslationContext().genIncludesProtected(); |
55 | } |
56 | if (Modifier.isPrivate(m_class.getModifiers())) { |
57 | return getTranslationContext().genIncludesPrivate(); |
58 | } |
59 | return getTranslationContext().genIncludesPackage(); |
60 | } |
61 | /** Template method */ |
62 | protected void extendClassProperties(Properties p) throws ApplyException{ } |
63 | /** Template method */ |
64 | protected abstract String getTemplateName() throws ApplyException; |
65 | /** Template method */ |
66 | protected abstract ClassGeneratorVisitor createClassVisitor(Writer out) throws ApplyException; |
67 | /** Template method */ |
68 | protected abstract String getFileSuffix(); |
69 | |
70 | /** Helper */ |
71 | public static Class ClassForName(String className) throws ApplyException { |
72 | try { |
73 | return Class.forName(className, false, className.getClass().getClassLoader()); |
74 | } catch (Exception e) { |
75 | throw new ApplyException(e); |
76 | } |
77 | } |
78 | public void generate(String output_directory) throws ApplyException { |
79 | if (!shouldGenerate()) { |
80 | if (!m_quiet) { |
81 | System.out.print("skipping: "); |
82 | System.out.println(m_class.getName()); |
83 | } |
84 | return; |
85 | } |
86 | Writer out = null; |
87 | try { |
88 | m_trans_ctx.setMapForClass(m_class); |
89 | out = createClassOutput(output_directory); |
90 | Properties p = createClassProperties(); |
91 | extendClassProperties(p); |
92 | Template t = createClassTemplate(); |
93 | t.apply(p, out); |
94 | out.close(); |
95 | commitTempFile(); |
96 | } catch (IOException e) { |
97 | throw new ApplyException(e); |
98 | } finally { |
99 | try { out.close(); } catch (Exception e) { } |
100 | try { m_trans_ctx.setMapForClass(null); } catch (Exception e) { } |
101 | } |
102 | } |
103 | |
104 | /** |
105 | * Create an output file suitable for return from createClassOutput. |
106 | * The output file created will be based on the fully qualified |
107 | * class name replacing each '.' with '/'. For example |
108 | * java.lang.System will create |
109 | * <output_directory>/java.lang.System<suffix>. |
110 | * |
111 | * <p> If the <code>output_directory</code> does not exist then this |
112 | * method will throw a java.io.IOException. |
113 | * |
114 | * @param output_directory a string defining which directory to create |
115 | * the output file in. |
116 | * @returns file writer created |
117 | * used to be createClassOuputFile |
118 | */ |
119 | protected Writer createClassOutput(String output_directory) |
120 | throws IOException |
121 | { |
122 | computeFullPath(output_directory); |
123 | makeClassDirectory(output_directory); |
124 | computeTempPath(); |
125 | return new BufferedWriter(new FileWriter(m_temp_path)); |
126 | } |
127 | private void computeFullPath(String output_directory) { |
128 | String path = getClassDirectory(); |
129 | String suffix = m_trans_ctx.getSuffix(); |
130 | if (suffix == null) { |
131 | suffix = getFileSuffix(); |
132 | } |
133 | |
134 | m_full_path = new File(output_directory, path + suffix); |
135 | if (!m_quiet) { |
136 | System.out.println(m_full_path); |
137 | } |
138 | } |
139 | /** depends on m_full_path */ |
140 | private void computeTempPath() { |
141 | m_temp_path = new File(m_full_path + ".new"); |
142 | } |
143 | private void makeClassDirectory(String output_directory) { |
144 | File dir_path = new File(output_directory); |
145 | if (!dir_path.exists()) { |
146 | return; |
147 | } |
148 | |
149 | File parent_path = m_full_path.getParentFile(); |
150 | parent_path.mkdirs(); |
151 | } |
152 | /** |
153 | * @return the filesystem path for m_class. |
154 | */ |
155 | protected String getClassDirectory() { |
156 | return getClassDirectory(m_class); |
157 | } |
158 | /** |
159 | * @return the filesystem path for the <code>a_class</code> |
160 | * parameter. |
161 | */ |
162 | protected String getClassDirectory(Class a_class) { |
163 | StringBuffer path = new StringBuffer(a_class.getName()); |
164 | for (int i = 0; i < path.length(); i++) { |
165 | if ('.' == path.charAt(i)) { |
166 | path.setCharAt(i, '/'); |
167 | } |
168 | } |
169 | return path.toString(); |
170 | } |
171 | |
172 | /** |
173 | * Finds the translation template using a standard search. The |
174 | * search starts with the prefix plus the full class name, then |
175 | * prefix plus the string "super" plus the full super class name. |
176 | * If neither of the above find a translation, then if the class |
177 | * is an interface search for prefix plus "interface", otherwise |
178 | * just search for prefix by itself. |
179 | * |
180 | * <P>For example, if the prefix is "java.class" and the class |
181 | * being generated is "com.moebiussolutions.test.Test" that |
182 | * extends from "com.moebiussolutions.test.TestSuper", then the |
183 | * search is: <BR> |
184 | * "java.class.com.moebiussolutions.test.Test",<BR> |
185 | * "java.class.super.com.moebiussolutions.test.TestSuper",<BR> |
186 | * "java.class" |
187 | */ |
188 | protected String findClassTemplate(String prefix, String type) throws ApplyException { |
189 | String suffixes[] = { |
190 | m_class.getName(), |
191 | stripPackageName(m_class.getName()), |
192 | "super." + (m_class.getSuperclass() != null ? m_class.getSuperclass().getName() : "<none>"), |
193 | m_class.isInterface() ? "interface.*" : "*", |
194 | }; |
195 | |
196 | String tmpl_name; |
197 | for (int i = 0; i < suffixes.length; i++) { |
198 | // System.out.println(prefix + suffixes[i]); |
199 | tmpl_name = getTransProperty(getLookupString(prefix, type, suffixes[i])); |
200 | if (tmpl_name != null) { |
201 | return tmpl_name; |
202 | } |
203 | } |
204 | // We are about to fail, dump all the searches |
205 | for (int i = 0; i < suffixes.length; i++) { |
206 | System.out.println(getLookupString(prefix, type, suffixes[i])); |
207 | } |
208 | throw new ApplyException("No " + prefix + "." + type + " template for " + m_class.getName()); |
209 | } |
210 | private String getLookupString(String default_prefix, String type, String suffix) { |
211 | String prefix = m_trans_ctx.getPrefix(); |
212 | if (prefix == null) { |
213 | prefix = default_prefix; |
214 | } |
215 | StringBuffer result = new StringBuffer(prefix); |
216 | result.append('.'); |
217 | result.append(type); |
218 | result.append('.'); |
219 | result.append(suffix); |
220 | return result.toString(); |
221 | } |
222 | |
223 | /** |
224 | * Create an instance of Template that is configured to callback |
225 | * onto this. |
226 | */ |
227 | protected Template createClassTemplate() throws ApplyException { |
228 | InputStream tmpl_strm = getTranslationContext().getTmplAsStream(getTemplateName()); |
229 | if (tmpl_strm == null) { |
230 | throw new ApplyException("Template " |
231 | + getTemplateName() + " for " |
232 | + m_class.getName() + " not found"); |
233 | } |
234 | Template t = new Template(tmpl_strm); |
235 | t.setObject(this); |
236 | t.setErrorPrefix(getTemplateName() + ": "); |
237 | return t; |
238 | } |
239 | |
240 | /** Helper */ |
241 | protected static String getTransProperty(String key) throws ApplyException { |
242 | return m_trans_ctx.getProperty(key); |
243 | } |
244 | /* Helper */ |
245 | public TranslationContext getTranslationContext() { |
246 | return m_trans_ctx; |
247 | } |
248 | |
249 | /** Impl */ |
250 | private Properties createClassProperties() throws ApplyException { |
251 | Properties p = new Properties(); |
252 | p.setProperty("automatic", "Automatically generated from " + getTemplateName()); |
253 | p.setProperty("package", m_class.getPackage().getName()); |
254 | p.setProperty("modifiers", Modifier.toString(m_class.getModifiers())); |
255 | p.setProperty("name", stripPackageName(m_class.getName())); |
256 | p.setProperty("jni_class", getClassDirectory()); |
257 | return p; |
258 | } |
259 | /** Impl */ |
260 | static String stripPackageName(String full_name) { |
261 | return full_name.substring(full_name.lastIndexOf('.') + 1); |
262 | } |
263 | |
264 | /** |
265 | * Called from class templates |
266 | */ |
267 | public void cni_forward(Writer out) throws ApplyException { |
268 | JavaForwardVisitor v = new JavaForwardVisitor(out, getTranslationContext()); |
269 | VisitClass.visitReflect(m_class, v); |
270 | v.visitDone(); |
271 | } |
272 | |
273 | /** |
274 | * Called from class templates |
275 | */ |
276 | public void visit(Writer out) throws ApplyException { |
277 | ClassGeneratorVisitor v = createClassVisitor(out); |
278 | v.setPassNumber(m_n_visit++); |
279 | v.visit(m_class); |
280 | } |
281 | public void visit(Writer out, String arg) throws ApplyException { |
282 | ClassGeneratorVisitor v = createClassVisitor(out); |
283 | v.setPassAppend(arg); |
284 | v.visit(m_class); |
285 | } |
286 | |
287 | /** |
288 | * Called from class templats |
289 | */ |
290 | public void merge(Writer out) throws ApplyException { |
291 | try { |
292 | if (m_full_path.exists()) { |
293 | FileReader fr = new FileReader(m_full_path); |
294 | MergeTagGroker mtg = new MergeTagGroker(fr); |
295 | mtg.grokeTagsOnto(out); |
296 | fr.close(); |
297 | } |
298 | } |
299 | catch (MergeException me) { |
300 | throw new ApplyException(me); |
301 | } |
302 | catch (IOException ioe) { |
303 | throw new ApplyException(ioe); |
304 | } |
305 | } |
306 | /** |
307 | * Called from class templats |
308 | */ |
309 | public void merge_import(Writer out) throws ApplyException { |
310 | try { |
311 | if (m_full_path.exists()) { |
312 | FileReader fr = new FileReader(m_full_path); |
313 | MergeTagGroker mtg = new MergeTagGroker( |
314 | "BEGIN_IMPORT", "END_IMPORT", fr); |
315 | mtg.grokeTagsOnto(out); |
316 | fr.close(); |
317 | } |
318 | } |
319 | catch (MergeException me) { |
320 | throw new ApplyException(me); |
321 | } |
322 | catch (IOException ioe) { |
323 | throw new ApplyException(ioe); |
324 | } |
325 | } |
326 | |
327 | private void commitTempFile() throws IOException { |
328 | if (isGeneratedSame()) { |
329 | m_temp_path.delete(); |
330 | } else { |
331 | m_full_path.delete(); |
332 | if (!m_temp_path.renameTo(m_full_path)) { |
333 | throw new IOException("renameTo failed"); |
334 | } |
335 | } |
336 | } |
337 | private boolean isGeneratedSame() throws IOException { |
338 | Reader org; |
339 | try { |
340 | org = new BufferedReader(new FileReader(m_full_path)); |
341 | } catch (FileNotFoundException e) { |
342 | // orig did not exist |
343 | return false; |
344 | } |
345 | |
346 | Reader gen = new BufferedReader(new FileReader(m_temp_path)); |
347 | try { |
348 | int c_gen; |
349 | int c_org; |
350 | while ((c_gen = gen.read()) != -1) { |
351 | c_org = org.read(); |
352 | if (c_gen != c_org) { |
353 | return false; |
354 | } |
355 | } |
356 | return -1 == org.read(); |
357 | } finally { |
358 | try { org.close(); } catch (IOException e) { } |
359 | try { gen.close(); } catch (IOException e) { } |
360 | } |
361 | } |
362 | |
363 | Class m_class; |
364 | boolean m_quiet = false; |
365 | static TranslationContext m_trans_ctx = null; |
366 | File m_full_path = null; |
367 | File m_temp_path = null; |
368 | private int m_n_visit = 0; |
369 | |
370 | } |