[mapguide-commits] r7224 - in sandbox/jng/swig-java: BuildTools/WebTools/IMake BuildTools/WebTools/IMake/Win32 Web/src/DotNetUnmanagedApi/Foundation Web/src/DotNetUnmanagedApi/Geometry Web/src/DotNetUnmanagedApi/MapGuideCommon Web/src/DotNetUnmanagedApi/PlatformBase Web/src/DotNetUnmanagedApi/Web Web/src/MapGuideApi

svn_mapguide at osgeo.org svn_mapguide at osgeo.org
Sun Nov 25 22:38:45 PST 2012


Author: jng
Date: 2012-11-25 22:38:45 -0800 (Sun, 25 Nov 2012)
New Revision: 7224

Modified:
   sandbox/jng/swig-java/BuildTools/WebTools/IMake/IMake.cpp
   sandbox/jng/swig-java/BuildTools/WebTools/IMake/Win32/IMake.exe
   sandbox/jng/swig-java/BuildTools/WebTools/IMake/stdafx.h
   sandbox/jng/swig-java/Web/src/DotNetUnmanagedApi/Foundation/FoundationApiGen.xml
   sandbox/jng/swig-java/Web/src/DotNetUnmanagedApi/Geometry/GeometryApiGen.xml
   sandbox/jng/swig-java/Web/src/DotNetUnmanagedApi/MapGuideCommon/MapGuideCommonApiGen.xml
   sandbox/jng/swig-java/Web/src/DotNetUnmanagedApi/PlatformBase/PlatformBaseApiGen.xml
   sandbox/jng/swig-java/Web/src/DotNetUnmanagedApi/Web/WebApiGen.xml
   sandbox/jng/swig-java/Web/src/MapGuideApi/MapGuideApiGen.xml
Log:
#9, #2114: This submission implements support for transplanting doxygen API documentation to JavaDoc for the Java API. We (ab)use the SWIG directives (javaclassmodifiers and javamethodmodifiers) to inject API documentation to generated java proxy class methods. 

To support this, IMake has been modified in the following fashion:
 * Support creation of a separate .i file (specified by a new DocTarget element in MapGuideApiGen.xml). This file is not created when Constants.xml is processed.
 * Modify the processing code to not discard comments when writing the main MapGuideApi.i file
 * Instead, these retained comments are matched with their respective method, and gets written out as a javamethodmodifiers directive in the DocTarget .i file
 * MapGuideApiGen.xml now includes the DocTarget .i file
 * Add new methods to convert the collected doxygen comments to the appropriate target language documentation format (currently JavaDoc)

Modified: sandbox/jng/swig-java/BuildTools/WebTools/IMake/IMake.cpp
===================================================================
--- sandbox/jng/swig-java/BuildTools/WebTools/IMake/IMake.cpp	2012-11-23 16:22:13 UTC (rev 7223)
+++ sandbox/jng/swig-java/BuildTools/WebTools/IMake/IMake.cpp	2012-11-26 06:38:45 UTC (rev 7224)
@@ -12,11 +12,12 @@
     java
 };
 
-static char version[] = "1.1";
+static char version[] = "1.2";
 
 static string module;
 static string customPath;
 static string target;
+static string docTarget;
 static string cppInline;
 static string swigInline;
 static string typedefs;
@@ -27,6 +28,7 @@
 static vector<string> headers;
 static map<string, int> rootObjectMethods;
 static FILE* outfile;
+static FILE* docOutFile;
 static char charbuf[2];
 static bool translateMode;
 static Language language;
@@ -75,6 +77,15 @@
     return attr->value;
 }
 
+string parseDocTarget(XNode* elt)
+{
+    LPXAttr attr = elt->GetAttr("path");
+    if(attr == NULL)
+        error("DocTarget element does not have a 'path' attribute");
+
+    return attr->value;
+}
+
 string parseCppInline(XNode* elt)
 {
     string text = Trim(elt->GetText());
@@ -204,6 +215,12 @@
                 error("Target is not a valid section in translation mode");
             target = parseTarget(node);
         }
+        else if(node->name == "DocTarget")
+        {
+            if(translateMode)
+                error("DocTarget is not a valid section in translation mode");
+            docTarget = parseDocTarget(node);
+        }
         else if(node->name == "CppInline")
         {
             if(translateMode)
@@ -336,6 +353,7 @@
                         break;
 
                     case '/':
+                        /*
                         if(!translateMode)
                         {
                             if(i < (int)input.length() - 1 && input[i + 1] == '/')
@@ -343,7 +361,7 @@
                             else
                                 tokens.push_back("/");
                         }
-                        else
+                        else*/
                         {
                             if(input[i + 1] == '/')
                             {
@@ -512,6 +530,236 @@
     return itMethod != rootObjectMethods.end();
 }
 
+string doxygenToJavaDoc(const string& commentStr)
+{
+    // Doxygen documentation translation overview:
+    //
+    // What sorcery allows us to transplant doxygen documentation to our target language of choice?
+    //
+    // The answer lies in the swig directives %javamethodmodifiers and %typemap(javaclassmodifiers)
+    // (and its csharp equvialents %csmethodmodifiers and %typemap(csclassmodifiers))
+    //
+    // The official SWIG documentation says that these directives are used to modify the visibility modifier for
+    // <class declaration|class method name>. Although this directive is mainly used to modify
+    // class/method visibility, the content itself does not have to be public/protected/abstract/static/const/etc
+    // SWIG itself does not do any validation of the content in this directive.
+    //
+    // Given this directive
+    //
+    // %javamethodmodifiers MgMyClass::MyMethod() <content>
+    //
+    // SWIG generates this (Java):
+    //
+    // public class MgMyClass {
+    //    ...
+    //    <content> MyMethod() {
+    //    ...
+    //    }
+    //    ...
+    // }
+    //
+    // <content> should generally be public/protected/abstract/static/const/etc, but SWIG does not validate or enforce this
+    //
+    // Therefore we (ab)use these SWIG directives to prepend arbitrary content to the generated proxy class declaration/methods. In our
+    // case, the content being our doxygen commentary (collected by IMake) translated to the target-specific documentation format
+    //
+    // In fact, this technique is documented as a way of transplanting documentation:
+    //   http://www.swig.org/Doc1.3/Java.html#javadoc_comments
+
+    //Re-tokenize the method declaration
+    //
+    //We want the bits between '(' and ')' (parentheses included) and the first token before the '('
+    std::vector<std::string> elems;
+    std::stringstream ss(commentStr);
+    std::string item;
+    while(std::getline(ss, item, '\n')) {
+        elems.push_back(item);
+    }
+    int i = 0;
+    
+    bool isDeprecated = false;
+    std::vector<std::string> descriptionParts;
+    std::vector<std::string> paramParts;
+    std::vector<std::string> returnParts;
+
+    while(i < elems.size()) {
+        if (elems[i].find("<!-- Syntax in .Net, Java, and PHP -->") != std::string::npos ||
+            elems[i].find("<!-- Example (PHP) -->") != std::string::npos ||
+            elems[i].find("<!-- Example (Java) -->") != std::string::npos ||
+            elems[i].find("<!-- Example (DotNet) -->") != std::string::npos) {
+            i++;
+            continue;
+        }
+
+        if (elems[i].find("\\brief") != std::string::npos) {
+            i++;
+            //Keep going until we find the next doxygen directive or end of comments
+            while (i < elems.size() && elems[i].find("\\") == std::string::npos) {
+                if (elems[i].find("<!-- Syntax in .Net, Java, and PHP -->") != std::string::npos ||
+                    elems[i].find("<!-- Example (PHP) -->") != std::string::npos ||
+                    elems[i].find("<!-- Example (Java) -->") != std::string::npos ||
+                    elems[i].find("<!-- Example (DotNet) -->") != std::string::npos) {
+                    i++;
+                    continue;
+                }
+                std::string token = elems[i].substr(3);
+                if (!token.empty())
+                    descriptionParts.push_back(token);
+                i++;
+            }
+            
+            continue;
+        }
+        else if (elems[i].find("\\param") != std::string::npos) {
+            std::string paramPart = elems[i].substr(elems[i].find("\\param") + 6);
+            paramPart.append(" ");
+            i++;
+            //Keep going until we find the next doxygen directive or end of comments
+            while (i < elems.size() && elems[i].find("\\") == std::string::npos) {
+                std::string token = elems[i].substr(3);
+                if (!token.empty()) {
+                    paramPart.append(token);
+                    paramPart.append(" ");
+                }
+                i++;
+            }
+            paramParts.push_back(paramPart);
+            continue;
+        }
+        else if (elems[i].find("\\return") != std::string::npos) {
+            i++;
+            //Keep going until we find the next doxygen directive or end of comments
+            while (i < elems.size() && elems[i].find("\\") == std::string::npos) {
+                if (elems[i].find("<!-- Syntax in .Net, Java, and PHP -->") != std::string::npos ||
+                    elems[i].find("<!-- Example (PHP) -->") != std::string::npos ||
+                    elems[i].find("<!-- Example (Java) -->") != std::string::npos ||
+                    elems[i].find("<!-- Example (DotNet) -->") != std::string::npos) {
+                    i++;
+                    continue;
+                }
+                std::string token = elems[i].substr(3);
+                if (!token.empty())
+                    returnParts.push_back(token);
+                i++;
+            }
+            continue;
+        }
+        else if (elems[i].find("\\deprecated") != std::string::npos) {
+            i++;
+            isDeprecated = true;
+            continue;
+        }
+        i++;
+    }
+
+    // ---------------------- JAVADOC START ------------------------ //
+    std::string javaDoc = "\n/**\n";
+
+    if (descriptionParts.size() > 0) {
+        for (int i = 0; i < descriptionParts.size(); i++) {
+            javaDoc.append(" *");
+            javaDoc.append(descriptionParts[i]);    
+            javaDoc.append("\n");
+        }
+        javaDoc.append(" *\n");
+    } else {
+        javaDoc.append(" * TODO: Missing API Documentation here\n");
+    }
+
+    if (paramParts.size() > 0) {
+        for (int i = 0; i < paramParts.size(); i++) {
+            javaDoc.append(" * @param ");
+            javaDoc.append(paramParts[i]);
+            javaDoc.append("\n");
+        }
+    }
+
+    if (returnParts.size() > 0) {
+        javaDoc.append(" * @return ");
+        for (int i = 0; i < returnParts.size(); i++) {
+            javaDoc.append(returnParts[i]);
+            if (i < returnParts.size() - 1)
+                javaDoc.append("\n * ");
+        }
+        javaDoc.append("\n");
+    }
+
+    // ---------------------- JAVADOC END ------------------------ //
+    javaDoc.append(" */\n");
+    if (isDeprecated)
+        javaDoc.append("@Deprecated\n");
+    return javaDoc;
+}
+
+string doxygenToCsharpDoc(const string& commentStr)
+{
+    return commentStr;
+}
+
+void outputMethodDoc(const string& className, const string& methodDecl, const string& commentStr)
+{
+    //Nothing for PHP
+    if (language == php)
+        return;
+
+    //Skip destructors
+    if (methodDecl.find("~") != string::npos)
+        return;
+
+    string convertedDoc;
+    string swigMethodDecl;
+    swigMethodDecl = className;
+    swigMethodDecl += "::";
+    
+    //Re-tokenize the method declaration
+    //
+    //We want the bits between '(' and ')' (parentheses included) and the first token before the '('
+    std::vector<std::string> elems;
+    std::stringstream ss(methodDecl);
+    std::string item;
+    while(std::getline(ss, item, ' ')) {
+        elems.push_back(item);
+    }
+    std::string methodName;
+    for (int i = 0; i < elems.size(); i++) {
+        if (elems[i] == "" || elems[i] == "virtual")
+            continue;
+
+        if (elems[i] == "(") {
+            if (i > 0) //Should be
+                methodName += elems[i-1];
+            methodName += elems[i];
+            int j = i;
+            //Process parameters between the ( and )
+            while(j < elems.size()) {
+                j++;
+                if (elems[j] != ")") {
+                    methodName += " ";
+                    methodName += elems[j];
+                } else {
+                    methodName += " ";
+                    methodName += elems[j];
+                    break;
+                }
+            }
+            break;
+        }
+    }
+
+    if (methodName.empty())
+        return;
+
+    swigMethodDecl += methodName;
+
+    if (language == java) {
+        convertedDoc = doxygenToJavaDoc(commentStr);
+        fprintf(docOutFile, "\n%%javamethodmodifiers %s %%{%s public%%}\n", swigMethodDecl.c_str(), convertedDoc.c_str());
+    } else if(language == csharp) {
+        convertedDoc = doxygenToCsharpDoc(commentStr);
+        fprintf(docOutFile, "\n%%csmethodmodifiers %s %%{%s public%%}\n", swigMethodDecl.c_str(), convertedDoc.c_str());
+    }
+}
+
 void processExternalApiSection(string& className, vector<string>& tokens, int begin, int end)
 {
     //until we find a problem with that, we output whatever we find in this section. In the
@@ -524,6 +772,8 @@
 
     FILE* propertyFile = NULL;
 
+    string commentStr;
+    string methodDecl;
     for(int i = begin; i <= end; i++)
     {
         assignmentAdded = false;
@@ -532,6 +782,15 @@
         if(token == "")
             continue;
 
+        //pickup the doc comments for the class, if any.
+        //all contiguous doc comment will be considered part of the class comment
+        if(strncmp(token.c_str(), "///", 3) == 0)
+        {
+            commentStr.append(token);
+            commentStr.append("\n");
+            continue;
+        }
+
         if(token[0] == '_' || isalpha(token[0]))
         {
             if(typeReplacements.find(token) != typeReplacements.end())
@@ -692,8 +951,15 @@
             destructor = true;
         }
 
+        methodDecl.append(" ");
+        methodDecl.append(token);
         if(token[0] == ';' || assignmentAdded)
         {
+            if (!translateMode) {
+                outputMethodDoc(className, methodDecl, commentStr);
+            }
+            commentStr.clear();
+            methodDecl.clear();
             if(nesting == 0)
             {
                 fprintf(outfile, "\n   ");
@@ -764,9 +1030,12 @@
 
         //in translation mode, filters out clases which don't belong to the class list
         bool ignore = translateMode && classes.find(className) == classes.end();
-
+        //printf("Processing header: %s\n", className.c_str());
         if(!ignore)
         {
+            //TODO: The transplanted comments are meaningless both java/csharp contexts because the the documentation
+            //format is incompatible with javadoc/.net XML documentation
+
             if(translateMode)
             {
                 if(language == java)
@@ -784,11 +1053,9 @@
                         fprintf(outfile, "package %s;\n\n", package.c_str());
                 }
 
-                //TODO: The transplanted comments are meaningless both java/csharp contexts because the the documentation
-                //format is incompatible with javadoc/.net XML documentation
-
-                //in translate mode, pickup the doc comments for the class, if any.
+                //pickup the doc comments for the class, if any.
                 //all contiguous doc comment will be considered part of the class comment
+                string commentStr;
                 int commentStart = i - 2;
                 for(; commentStart >= 0; )
                 {
@@ -802,10 +1069,15 @@
                     for(commentStart = commentStart < 0? commentStart+1: commentStart; commentStart <= i - 2; commentStart++)
                     {
                         const char* thisTok = tokens[commentStart].c_str();
-                        fprintf(outfile, "%s\n", tokens[commentStart].c_str());
+                        commentStr.append(thisTok);
+                        commentStr.append("\n");
                     }
                 }
 
+                if (!commentStr.empty()) 
+                {
+                    fprintf(outfile, "%s", commentStr.c_str());
+                }
             }
 
             //output the class header
@@ -868,11 +1140,12 @@
         if(!ignore)
         {
             fprintf(outfile, "\n}");
-            if(!translateMode)
+            if(!translateMode) {
                 fprintf(outfile, ";\n\n");
-            else
+            }
+            else {
                 fprintf(outfile, "\n\n");
-
+            }
         }
 
     }
@@ -899,6 +1172,9 @@
         outfile = fopen(target.c_str(), "w");
         if(outfile == NULL)
             error(string("Cannot create file ") + target);
+        docOutFile = fopen(docTarget.c_str(), "w");
+        if(docOutFile == NULL)
+            error(string("Cannot create file ") + docTarget);
     }
 
     time_t now = time(NULL);
@@ -912,6 +1188,15 @@
     //write the module
     fprintf(outfile, "%%module %s\n", module.c_str());
 
+    if(!translateMode)
+    {
+        //write the banner
+        fprintf(docOutFile, "//======================================================\n");
+        fprintf(docOutFile, "// Generated with IMake version %s\n", version);
+        fprintf(docOutFile, "// %s\n", asctime(localtime(&now)));
+        fprintf(docOutFile, "//\n");
+    }
+
     if(!translateMode || language != java)
     {
         //write the C++ inline code
@@ -929,7 +1214,12 @@
         processHeaderFile(*it);
 
     if(!translateMode || language != java)
+    {
         fclose(outfile);
+        if (docOutFile != NULL) {
+            fclose(docOutFile);
+        }
+    }
 }
 
 void createNativeFile()
@@ -937,6 +1227,9 @@
     if(target.length() == 0)
         error("Target section is missing");
 
+    if (!translateMode && docTarget.length() == 0)
+        error("DocTarget section is missing");
+
     if(language == unknown)
         error("Unknown language");
 
@@ -945,6 +1238,11 @@
         outfile = fopen(target.c_str(), "w");
         if(outfile == NULL)
             error(string("Cannot create file ") + target);
+        if (!translateMode) {
+            docOutFile = fopen(docTarget.c_str(), "w");
+            if (docOutFile == NULL)
+                error(string("Cannot create file ") + docTarget);
+        }
     }
 
     if(language == php)
@@ -981,6 +1279,8 @@
 
     if(language != java)
         fclose(outfile);
+    if (docOutFile != NULL)
+        fclose(docOutFile);
 }
 
 void createInterfaceFile(char* paramFile)

Modified: sandbox/jng/swig-java/BuildTools/WebTools/IMake/Win32/IMake.exe
===================================================================
(Binary files differ)

Modified: sandbox/jng/swig-java/BuildTools/WebTools/IMake/stdafx.h
===================================================================
--- sandbox/jng/swig-java/BuildTools/WebTools/IMake/stdafx.h	2012-11-23 16:22:13 UTC (rev 7223)
+++ sandbox/jng/swig-java/BuildTools/WebTools/IMake/stdafx.h	2012-11-26 06:38:45 UTC (rev 7224)
@@ -7,6 +7,7 @@
 #define _IMAKE_STD_H_
 
 #include <string>
+#include <iostream>
 #include <vector>
 #include <algorithm>
 #include <deque>

Modified: sandbox/jng/swig-java/Web/src/DotNetUnmanagedApi/Foundation/FoundationApiGen.xml
===================================================================
--- sandbox/jng/swig-java/Web/src/DotNetUnmanagedApi/Foundation/FoundationApiGen.xml	2012-11-23 16:22:13 UTC (rev 7223)
+++ sandbox/jng/swig-java/Web/src/DotNetUnmanagedApi/Foundation/FoundationApiGen.xml	2012-11-26 06:38:45 UTC (rev 7224)
@@ -14,6 +14,7 @@
    Target section.
 -->
 <Target path="./FoundationApi.i" />
+<DocTarget path="./FoundationApi_Doc.i" />
 
 <!--
    C++ inline section.

Modified: sandbox/jng/swig-java/Web/src/DotNetUnmanagedApi/Geometry/GeometryApiGen.xml
===================================================================
--- sandbox/jng/swig-java/Web/src/DotNetUnmanagedApi/Geometry/GeometryApiGen.xml	2012-11-23 16:22:13 UTC (rev 7223)
+++ sandbox/jng/swig-java/Web/src/DotNetUnmanagedApi/Geometry/GeometryApiGen.xml	2012-11-26 06:38:45 UTC (rev 7224)
@@ -14,6 +14,7 @@
    Target section.
 -->
 <Target path="./GeometryApi.i" />
+<DocTarget path="./GeometryApi_Doc.i" />
 
 <!--
    C++ inline section.

Modified: sandbox/jng/swig-java/Web/src/DotNetUnmanagedApi/MapGuideCommon/MapGuideCommonApiGen.xml
===================================================================
--- sandbox/jng/swig-java/Web/src/DotNetUnmanagedApi/MapGuideCommon/MapGuideCommonApiGen.xml	2012-11-23 16:22:13 UTC (rev 7223)
+++ sandbox/jng/swig-java/Web/src/DotNetUnmanagedApi/MapGuideCommon/MapGuideCommonApiGen.xml	2012-11-26 06:38:45 UTC (rev 7224)
@@ -14,6 +14,7 @@
    Target section.
 -->
 <Target path="./MapGuideCommonApi.i" />
+<DocTarget path="./MapGuideCommonApi_Doc.i" />
 
 <!--
    C++ inline section.

Modified: sandbox/jng/swig-java/Web/src/DotNetUnmanagedApi/PlatformBase/PlatformBaseApiGen.xml
===================================================================
--- sandbox/jng/swig-java/Web/src/DotNetUnmanagedApi/PlatformBase/PlatformBaseApiGen.xml	2012-11-23 16:22:13 UTC (rev 7223)
+++ sandbox/jng/swig-java/Web/src/DotNetUnmanagedApi/PlatformBase/PlatformBaseApiGen.xml	2012-11-26 06:38:45 UTC (rev 7224)
@@ -14,6 +14,7 @@
    Target section.
 -->
 <Target path="./PlatformBaseApi.i" />
+<DocTarget path="./PlatformBaseApi_Doc.i" />
 
 <!--
    C++ inline section.

Modified: sandbox/jng/swig-java/Web/src/DotNetUnmanagedApi/Web/WebApiGen.xml
===================================================================
--- sandbox/jng/swig-java/Web/src/DotNetUnmanagedApi/Web/WebApiGen.xml	2012-11-23 16:22:13 UTC (rev 7223)
+++ sandbox/jng/swig-java/Web/src/DotNetUnmanagedApi/Web/WebApiGen.xml	2012-11-26 06:38:45 UTC (rev 7224)
@@ -14,6 +14,7 @@
    Target section.
 -->
 <Target path="./MapGuideApi.i" />
+<DocTarget path="./MapGuideApi_Doc.i" />
 
 <!--
    C++ inline section.

Modified: sandbox/jng/swig-java/Web/src/MapGuideApi/MapGuideApiGen.xml
===================================================================
--- sandbox/jng/swig-java/Web/src/MapGuideApi/MapGuideApiGen.xml	2012-11-23 16:22:13 UTC (rev 7223)
+++ sandbox/jng/swig-java/Web/src/MapGuideApi/MapGuideApiGen.xml	2012-11-26 06:38:45 UTC (rev 7224)
@@ -9,6 +9,7 @@
    Target section.
 -->
 <Target path="./MapGuideApi.i" />
+<DocTarget path="./MapGuideApi_Doc.i" />
 
 <!--
    C++ inline section.
@@ -51,6 +52,7 @@
    Swig inline section.
 -->
 <SwigInline>
+  %include "MapGuideApi_Doc.i" //translated doxygen fragments
   %include "language.i"   //typemaps specific for each language
   %include "../../../Common/Foundation/System/FoundationClassId.h"
   %include "../../../Common/Geometry/GeometryClassId.h"



More information about the mapguide-commits mailing list