001 /*
002 @license.text@
003 */
004 package biz.hammurapi.codegen;
005
006 import java.io.File;
007 import java.io.IOException;
008 import java.io.StringReader;
009 import java.util.HashMap;
010 import java.util.Iterator;
011 import java.util.Map;
012 import java.util.Properties;
013
014 import javax.xml.parsers.DocumentBuilderFactory;
015 import javax.xml.parsers.FactoryConfigurationError;
016 import javax.xml.parsers.ParserConfigurationException;
017 import javax.xml.transform.TransformerException;
018
019 import org.apache.bcel.classfile.JavaClass;
020 import org.apache.bcel.generic.ClassGen;
021 import org.w3c.dom.DOMException;
022 import org.w3c.dom.Document;
023 import org.w3c.dom.Element;
024 import org.w3c.dom.Node;
025
026 import biz.hammurapi.codegen.JavaLexer;
027 import biz.hammurapi.codegen.JavaRecognizer;
028 import biz.hammurapi.codegen.JavaTokenTypes;
029
030 import antlr.RecognitionException;
031 import antlr.TokenStreamException;
032 import antlr.collections.AST;
033 import biz.hammurapi.xml.dom.DOMUtils;
034
035
036 /**
037 * Stores generated class files in a specified directory and creates HTML documenation.
038 * @author Pavel Vlasov
039 * @version $Revision: 1.4 $
040 */
041 public class XmlDocConsumer implements DocumentingConsumer {
042 private File classDir;
043 private File docDir;
044 private Document indexDocument;
045 private Element indexRoot;
046
047 final Map documentMap=new HashMap();
048
049 /**
050 * Construcotor
051 * @param classDir Output directory for generated classes. Mandatory.
052 * @param docDir Output directory for HTML documentation. Optional (can be null).
053 * @param indexName Name of index file.
054 * @throws GenerationException
055 */
056 public XmlDocConsumer(File classDir, File docDir, String indexName) throws GenerationException {
057 if (classDir==null) {
058 throw new GenerationException("Class directory is null");
059 }
060 if (!classDir.isDirectory()) {
061 throw new GenerationException(classDir.getAbsolutePath()+" is not a directory");
062 }
063 if (docDir!=null && !docDir.isDirectory()) {
064 throw new GenerationException(docDir.getAbsolutePath()+" is not a directory");
065 }
066 this.classDir=classDir;
067 this.docDir=docDir;
068 this.indexName=indexName;
069
070 if (docDir!=null) {
071 indexDocument=newDocument();
072 indexRoot=indexDocument.createElement("classes");
073 indexDocument.appendChild(indexRoot);
074 }
075 }
076
077 /**
078 * @throws GenerationException
079 */
080 private Document newDocument() {
081 try {
082 return DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
083 } catch (ParserConfigurationException e) {
084 throw new DocumentingException("Cannot create index document: "+e, e);
085 } catch (FactoryConfigurationError e) {
086 throw new DocumentingException("Cannot create index document: "+e, e);
087 }
088 }
089
090 private GenerationListener listener;
091 private String indexName;
092
093 /**
094 * Writes file to disk and also adds it to documentation
095 * @param javaClass Class to save and document
096 * @throws GenerationException If generated file could not be saved
097 */
098 public void consume(JavaClass javaClass) throws GenerationException {
099 try {
100 javaClass.dump(new File(classDir, javaClass.getClassName().replace('.', File.separatorChar)+".class"));
101 } catch (IOException e) {
102 throw new GenerationException("Cannot save generated file: "+e, e);
103 }
104 }
105
106 private void saveDocument(String className) {
107 Node classRoot = (Node) documentMap.get(className);
108 if (classRoot!=null) {
109 try {
110 Iterator it=documentMap.keySet().iterator();
111 while (it.hasNext()) {
112 Element ref=classRoot.getOwnerDocument().createElement("ref");
113 classRoot.appendChild(ref);
114 ref.appendChild(ref.getOwnerDocument().createTextNode((String) it.next()));
115 }
116 File file = new File(docDir, className.replace('.', File.separatorChar)+".xml");
117 file.getParentFile().mkdirs();
118 DOMUtils.serialize(classRoot.getOwnerDocument(), file);
119 indexRoot.appendChild(indexDocument.importNode(classRoot, true));
120 } catch (IOException e) {
121 throw new DocumentingException("Cannot save documentation for class "+className+": "+e, e);
122 } catch (TransformerException e) {
123 throw new DocumentingException("Cannot save documentation for class "+className+": "+e, e);
124 }
125 }
126 }
127
128 /**
129 * @return Generation listener which documents methods and classes
130 */
131 public GenerationListener getListener() {
132 if (listener==null && docDir!=null) {
133 listener = new GenerationListener() {
134
135 public void onClass(ClassGen classGen, String description) {
136 String className=classGen.getClassName();
137 int idx=className.lastIndexOf('.');
138 String pkg = className.substring(0, idx);
139
140 Document classDocument=newDocument();
141 Element classRoot=classDocument.createElement("class");
142 classRoot.setAttribute("fcn", className);
143 classDocument.appendChild(classRoot);
144 if (idx==-1) {
145 classRoot.setAttribute("name", className);
146 } else {
147 classRoot.setAttribute("package", pkg);
148 StringBuffer sb=new StringBuffer("..");
149 for (int i=pkg.indexOf('.'); i!=-1; i=pkg.indexOf(".", i+1)) {
150 sb.append("/..");
151 }
152 classRoot.setAttribute("upPath", sb.toString());
153 classRoot.setAttribute("name", className.substring(idx+1));
154 }
155 if (description!=null) {
156 classRoot.setAttribute("description", description);
157 }
158
159 if (classGen.isInterface()) {
160 classRoot.setAttribute("interface", "yes");
161 }
162
163 String superclass = classGen.getSuperclassName();
164 if (superclass!=null) {
165 addSuper(classRoot, "extends", superclass);
166 }
167
168 String[] interfaces = classGen.getInterfaceNames();
169 for (int i=0; i<interfaces.length; i++) {
170 addSuper(classRoot, "implements", interfaces[i]);
171 }
172
173 documentMap.put(className, classRoot);
174 }
175
176 private void addSuper(Element classRoot, String elementName, String className) {
177 Element classElement=classRoot.getOwnerDocument().createElement(elementName);
178 classRoot.appendChild(classElement);
179 classElement.setAttribute("fcn", className);
180 int idx=className.lastIndexOf('.');
181 String pkg = className.substring(0, idx);
182 if (idx==-1) {
183 classElement.setAttribute("name", className);
184 } else {
185 classElement.setAttribute("package", pkg);
186 classElement.setAttribute("name", className.substring(idx+1));
187 }
188 }
189
190 public void onMethod(String className, String signature, String description, Properties attributes) {
191 //signature="public static java.lang.Object[] getObject(int a, java.lang.Object, BigDecimal[][] b) throws abc, java.lang.Pizdec";
192 Element classElement=selectClass(className);
193 Document ownerDocument = classElement.getOwnerDocument();
194 Element methodElement=ownerDocument.createElement("method");
195 classElement.appendChild(methodElement);
196 methodElement.setAttribute("signature", signature);
197 if (description!=null) {
198 methodElement.setAttribute("description", description);
199 }
200
201 try {
202 JavaLexer lexer=new JavaLexer(new StringReader(signature));
203 JavaRecognizer parser=new JavaRecognizer(lexer);
204 //System.out.println("Parsing: "+signature);
205 parser.signature();
206 AST ast=parser.getAST();
207 //AstUtil.dumpAll(ast, parser.getTokenNames());
208 if (ast.getType()==JavaTokenTypes.MODIFIERS) {
209 for (AST modifier=ast.getFirstChild(); modifier!=null; modifier=modifier.getNextSibling()) {
210 Element me=ownerDocument.createElement("modifier");
211 me.appendChild(ownerDocument.createTextNode(modifier.getText()));
212 methodElement.appendChild(me);
213 }
214 ast=ast.getNextSibling();
215 }
216
217 Element rte=ownerDocument.createElement("return");
218 methodElement.appendChild(rte);
219 type(ast, rte);
220 ast=ast.getNextSibling();
221 methodElement.setAttribute("name", ast.getText());
222 ast=ast.getNextSibling();
223 for (AST parameter=ast.getFirstChild(); parameter!=null; parameter=parameter.getNextSibling()) {
224 Element pe=ownerDocument.createElement("parameter");
225 methodElement.appendChild(pe);
226 Element pte=ownerDocument.createElement("type");
227 pe.appendChild(pte);
228 type(parameter.getFirstChild().getFirstChild(), pte);
229 AST nameNode = parameter.getFirstChild().getNextSibling();
230 if (nameNode!=null) {
231 pe.setAttribute("name", nameNode.getText());
232 }
233 }
234 ast=ast.getNextSibling();
235 if (ast!=null) {
236 for (AST tc=ast.getFirstChild(); tc!=null; tc=tc.getNextSibling()) {
237 Element te=ownerDocument.createElement("throws");
238 type(tc, te);
239 methodElement.appendChild(te);
240 }
241 }
242 } catch (TokenStreamException e) {
243 System.err.println("WARN: Signature could not be parsed: "+signature+" ("+e+")");
244 } catch (RecognitionException e) {
245 System.err.println("WARN: Signature could not be parsed: "+signature+" ("+e+")");
246 }
247
248 if (attributes!=null) {
249 Iterator it=attributes.keySet().iterator();
250 while (it.hasNext()) {
251 Element attributeElement=ownerDocument.createElement("attribute");
252 methodElement.appendChild(attributeElement);
253 String key=(String) it.next();
254 attributeElement.setAttribute("name", key);
255 attributeElement.appendChild(ownerDocument.createTextNode(attributes.getProperty(key)));
256 }
257 }
258 }
259
260 public void onField(String className, String declaration, String description, Properties attributes) {
261 Element classElement=selectClass(className);
262 Document ownerDocument = classElement.getOwnerDocument();
263 Element fieldElement=ownerDocument.createElement("field");
264 classElement.appendChild(fieldElement);
265 fieldElement.setAttribute("declaration", declaration);
266 if (description!=null) {
267 fieldElement.setAttribute("description", description);
268 }
269
270 try {
271 for (AST ast=ClassGeneratorBase.field(declaration); ast!=null; ast=ast.getNextSibling()) {
272 if (ast.getType()==JavaTokenTypes.VARIABLE_DEF) {
273 for (AST node=ast.getFirstChild(); node!=null; node=node.getNextSibling()) {
274 switch (node.getType()) {
275 case JavaTokenTypes.MODIFIERS:
276 for (AST child=node.getFirstChild(); child!=null; child=child.getNextSibling()) {
277 Element me=ownerDocument.createElement("modifier");
278 me.appendChild(ownerDocument.createTextNode(child.getText()));
279 fieldElement.appendChild(me);
280 }
281 break;
282 case JavaTokenTypes.IDENT:
283 fieldElement.setAttribute("name", node.getText());
284 break;
285 case JavaTokenTypes.TYPE:
286 fieldElement.setAttribute("type", ClassGeneratorBase.toString(node.getFirstChild()));
287 break;
288 default:
289 System.err.println("WARN: Bad field declaration '"+declaration+"', unexpected node: "+node);
290 return;
291 }
292 }
293 } else {
294 System.err.println("WARN: Invalid node type "+ast.getType()+" in declaration '"+declaration+"'");
295 }
296 }
297
298 if (attributes!=null) {
299 Iterator it=attributes.keySet().iterator();
300 while (it.hasNext()) {
301 Element attributeElement=ownerDocument.createElement("attribute");
302 fieldElement.appendChild(attributeElement);
303 String key=(String) it.next();
304 attributeElement.setAttribute("name", key);
305 attributeElement.appendChild(ownerDocument.createTextNode(attributes.getProperty(key)));
306 }
307 }
308 } catch (GenerationException e) {
309 System.err.println("WARN: Invalid field declaration: '"+declaration+"', exception: "+e);
310 } catch (DOMException e) {
311 System.err.println("WARN: Invalid field declaration: '"+declaration+"', exception: "+e);
312 }
313 }
314
315 private void type(AST ast, Element rte) {
316 while (ast.getType()==JavaTokenTypes.ARRAY_DECLARATOR) {
317 rte.setAttribute("dimensions", rte.getAttribute("dimensions")+"[]");
318 ast=ast.getFirstChild();
319 }
320
321 if (ast.getType()==JavaTokenTypes.DOT) {
322 String pkg = toString(ast.getFirstChild());
323 rte.setAttribute("package", pkg);
324 String name = ast.getFirstChild().getNextSibling().getText();
325 rte.setAttribute("name", name);
326 rte.setAttribute("fcn", pkg+"."+name);
327 } else {
328 rte.setAttribute("name", ast.getText());
329 rte.setAttribute("fcn", ast.getText());
330 }
331 }
332
333 private String toString(AST node) {
334 if (node.getType()==JavaTokenTypes.DOT) {
335 return toString(node.getFirstChild()) + "." + toString(node.getFirstChild().getNextSibling());
336 }
337
338 return node.getText();
339 }
340
341 private Element selectClass(String className) {
342 return (Element) documentMap.get(className);
343 }
344 };
345 }
346 return listener;
347 }
348
349 /**
350 * Closes all files.
351 *
352 */
353 public void close() {
354 Iterator it=documentMap.keySet().iterator();
355 while (it.hasNext()) {
356 saveDocument((String) it.next());
357 }
358
359 if (indexDocument!=null) {
360 try {
361 DOMUtils.serialize(indexDocument, new File(docDir, indexName));
362 } catch (IOException e) {
363 throw new DocumentingException("Cannot close index.html: "+e, e);
364 } catch (TransformerException e) {
365 throw new DocumentingException("Cannot close index.html: "+e, e);
366 }
367 }
368 }
369
370 }