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    }