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 }