您的位置:首页 > 编程语言 > Java开发

利用javadoc定制自己的接口文档(二)

2016-08-21 16:46 239 查看

前言

  上一篇我们介绍了doclet及其命令行选项,最后是自己自定义的doclet1代码,思路很简单,就是利用doclet读取代码在方法上面的注解,然后将这些注解的值写到模板中,最后输出到指定位置。在第一代doclet中,自己将html模板,java模板,自定义标签名称等都写在一起,耦合性极强。这一篇就是利用freemarker将其中的模板从中抽出来,于是有了第二代doclet——doclet2

freemarker的简介

  FreeMarker是一款模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页、电子邮件、配置文件、源代码等)的通用工具,简而言之,就是:模板+数据模型=输出

  自己之所以决定使用它,最主要的原因有以下4点:

  1.它能很方便得生成各种文本:HTML,XML,PTF,JAVA源代码等

  2.是轻量级的框架,不需要Sservlet环境,易于嵌入到产品中

  3.可以从任何源载入模板,如本地文件,数据库等

  4.可以按需生成文本:保存到本地文件;作为email发送;从web应用程序发送它返回给web浏览器

  这4点简直是量身为doclet打造的:自己所写的doclet2,并没有servlet环境;数据是从doclet中拿到的;最终不仅要生成html格式的文档,还要生成java类型的前端代码。

  (ps:在java领域,表现层技术主要有三种:jsp、freemarker、velocity。大家有兴趣可以查一查这三种的优缺点)

freemarker的使用

  freemarker的模板是.ftl文件,在文档编译器中编辑好模板,保存时以.ftl结尾即可。freemarker的语法和标签的使用,大家可以参考:http://demojava.iteye.com/blog/800204,写得很全,我就不赘述了。

  

  freemarker的数据模型使用三种基本的对象类型:scalars(标量),hashes(哈希表),sequences(序列),除此之外,还有方法和用户自定义FTL标记两种,具体可参考

  http://www.zzbaike.com/wiki/FreeMarker%E7%9A%84%E6%95%B0%E6%8D%AE%E6%A8%A1%E5%9E%8B

  

  freemarker的合并主要是以下四个步骤:

  1.创建Configuration实例,该实例负责管理FreeMarker的模板加载路径,生成模板实例。

Configuration cfg = new Configuration();


  2.使用Configuration实例来生成Template实例,在这里需要指定使用的模板文件。

//指定模板路径
File file = new File("src");
//设置要解析的模板所在的目录,并加载模板文件
cfg.setDirectoryForTemplateLoading(file);
//设置编码方式
cfg.setDefaultEncoding("UTF-8");
Template indexTemplate = cfg.getTemplate("index.ftl");


  3.填充数据模型,数据模型就是一个Map对象。

Map indexRoot = new HashMap();


  4.调用Template实例的process方法完成合并。

//将要生成的文件
File indexfile = new File(DES_DIRPATH + "2.0.html");
//得到输出流
BufferedWriter write = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(indexfile),"UTF-8"));
//合并模板和数据
indexTemplate.process(indexRoot,write);


针对每个方法,自己要生成的html文档的ftl模板(文件名为:Method.ftl)如下:

<!DOCTYPE html>
<html>
<meta charset=utf8>
<head>
<title></title>
</head>
<style type="text/css">
body{
font: 12px/1.125 Arial, Helvetica, sans-serif;
}

.wiki_title{
line-height: 37px;
border-bottom: 1px solid #e5e5e5;
margin: 16px 0 8px 0;
font-size: 20px;
color: #333;
font-family: "Microsoft Yahei";
font-weight: 300;
}

h1.wiki_title{
font-size: 24px;
}

a{
color: #3c7cb3;
text-decoration: none;
}

table{
border-collapse: collapse;
border-spacing: 0;
}

table.parameters{
border-top-width: 1px;
border-right-width: 1px;
border-bottom-width: 1px;
border-left-width: 1px;
-webkit-border-horizontal-spacing: 0px;
-webkit-border-vertical-spacing: 0px;
width: 100%;
}

th,td{
text-align: center;
font-weight: bolder;
border: 1px solid #cccccc;
height: 20px;
}

.code_type{
text-transform: uppercase;
margin-bottom: 5px;
display: inline-block;*
display: inline;*
zoom: 1;
background: #b4e3b4;
border-radius: 2px;
color: #008200;
padding: 2px 8px;
}
a:hover{
text-decoration: underline;
}
</style>
<body>
<h1 class="wiki_title">
<span class="mw-headline">${uri}</span>
</h1>
<p>${describe}</p>
<h2 class="wiki_title">
<span class="mw-headline">URL</span>
</h2>
<p>
<span style="font-weight:600">
<a rel="nofollow" class="external free" href="">${uri}</a>
</span>
</p>
<h2 class="wiki_title">
<span class="mw-headline" >支持格式</span>
</h2>
<p>
<span style="text-transform:uppercase;font-weight:600">${support!"JSON"}</span>
</p>

<h2 class="wiki_title">
<span class="mw-headline" >HTTP请求方式</span>
</h2>
<p>
<span style="text-transform:uppercase;font-weight:600">${requesttype!"POST"}</span>
</p>

<h2 class="wiki_title">
<span class="mw-headline" >请求参数</span>
</h2>

<table border="1" cellspacing="0" cellpadding="0" width="100%" class="parameters" style="border-color: #CCCCCC;">

<tbody>
<tr>
<th width="10%" style="text-align:center;font-weight:bolder;border:1px solid #cccccc">名称</th>
<th width="5%" style="text-align:center;font-weight:bolder;border:1px solid #cccccc">必选</th>
<th width="10%" style="text-align:center;font-weight:bolder;border:1px solid #cccccc">类型及范围</th>
<th width="75%" style="text-align:center;font-weight:bolder;border:1px solid #cccccc">说明</th>
</tr>
<#list param as item>
<tr>
<td style="text-align:center;font-weight:bolder;border:1px solid #cccccc">${item.name}</td>
<td style="text-align:center;border:1px solid #cccccc">${item.select}</td>
<td style="text-align:left;padding-left:5px;border:1px solid #cccccc">${item.type}</td>
<td style="text-align:left;padding-left:5px;border:1px solid #cccccc">${item.explain}</td>
</tr>
</#list>

</tbody>
</table>

<h2 class="wiki_title">
<span class="mw-headline">返回结果</span>
</h2>

<div class="code_type" style="text-transform:uppercase;margin-bottom:5px;">JSON示例</div>
<pre>
${returnjson}
</pre>

<h2 class="wiki_title">
<span class="mw-headline">返回字段说明</span>
</h2>

<table border="1" cellspacing="0" cellpadding="0" width="100%" class="parameters" style="border-color: #CCCCCC;">

<tbody>
<tr>
<th width="25%" style="text-align:left;padding-left:5px;font-weight:bolder;border:1px solid #cccccc">返回值字段</th>
<th width="15%" style="text-align:left;padding-left:5px;font-weight:bolder;border:1px solid #cccccc">字段类型</th>
<th width="60%" style="text-align:left;padding-left:5px;font-weight:bolder;border:1px solid #cccccc">字段说明</th>
</tr>

<#list returnparam as item>
<tr>
<td style="text-align:left;padding-left:5px;font-weight:bolder;border:1px solid #cccccc">${item.name}</td>
<td style="text-align:left;padding-left:5px;border:1px solid #cccccc">${item.type}</td>
<td style="text-align:left;padding-left:5px;border:1px solid #cccccc">${item.explain}</td>
</tr>
</#list>
</tbody>
</table>

<h2 class="wiki_title">
<span class="mw-headline" >注意事项</span>
</h2>

<p>${notice!"无"}</p>
</body>
</html>


  

  要生成的java代码的ftl文档(名为javaTemplate.ftl)如下:

package ${basepath}.request;
import java.util.HashMap;
import java.util.Map;
import com.android.volley.Response.ErrorListener;
import com.android.volley.Response.Listener;
import ${basepath}.LlptHttpJsonRequest;
import ${basepath}.response.getTagListResponse;

public class ${methodname}Request extends LlptHttpJsonRequest<${methodname}Response> {
private static final String APIPATH = "${uri}";

<#list paramlist as param>
private String ${param.name};
public String get${param.name?cap_first}() {return ${param.name};}
public void set${param.name?cap_first}(String ${param.name}) {this.${param.name} = ${param.name};}

</#list>

public ${methodname}Request(Listener<${methodname}Response> listener, ErrorListener errorListener) {
super(Method.POST, APIPATH, listener, errorListener);
}
public ${methodname}Request(int method, String partUrl, Listener<${methodname}Response> listener, ErrorListener errorListener) {
super(method, partUrl, listener, errorListener);
}
public Class<${methodname}Response> getResponseClass() {return ${methodname}Response.class;}

public String GetApiPath() {return APIPATH;}

public Map<String, String> GetParameters() {
Map<String, String> map = new HashMap<String, String>();
<#list paramlist as param>
map.put("${param.name}",${param.name});
</#list>
return map;
}
}


自己所写的doclet2代码(265行)如下:

import com.sun.javadoc.*;
import com.sun.tools.doclets.formats.html.ConfigurationImpl;
import freemarker.template.Configuration;
import freemarker.template.Template;

import java.io.*;
import java.lang.System;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* @mytag class mytag
*/
public class doclet2 {
//类相关的注解
private static String CLASS_DESCRIBE = "description";   //类的描述,如人脉,用户,消息
private static String CLASS_URI = "uri";   //类的访问路径
//方法相关的注解
private static String METHOD_URI = "uri";   //方法访问路径
private static String METHOD_TYPE = "type"; //方法类别
private static String METHOD_DESCRIBE = "description"; //方法描述
private static String METHOD_PATH = "path"; //UE中如何找到接口
private static String METHOD_SUPPORT = "datatype";   //支持格式(json)
private static String METHOD_REQUESTTYPE = "method";   //请求方式(get/post)
private static String METHOD_PARAM = "param";   //传入参数描述,描述格式为:名称~必选~类型~说明(若为非必选,须说明什么情况下不选)
private static String METHOD_NOTICE = "notice"; //注意事项
private static String METHOD_RETURNJSON = "returnjson"; //返回的json格式
private static String METHOD_RETURNPARAM = "returnparam";   //输出参数描述,描述格式为:名称~类型~字段说明

private static String DIRPATH="D:/api2/";

private static String BASEPATH = "com.bluemobi.bluecollar.network";

public static boolean start(RootDoc root) {

try {
doc(root.classes());
} catch (Exception e) {
e.printStackTrace();
}
return true;
}

private static String doc_class(ClassDoc classDoc, String TAG)
{
System.out.print(classDoc.containingPackage());
Tag[] Class_Describe = classDoc.tags(TAG);
if (Class_Describe.length != 0)
{
return Class_Describe[0].text();
}else {
return null;
}

}

private static String docMethod(MethodDoc methodDoc, String TAG)
{
Tag[] mcts = methodDoc.tags(TAG);
if (mcts.length != 0)
return mcts[0].text();
else
return null;
}

private static ArrayList<String> docMethodList(MethodDoc methodDoc, String TAG)
{
ArrayList<String> params = new ArrayList<String>();
Tag[] mcts = methodDoc.tags(TAG);
for (int i=0; i < mcts.length ; i++)
{
params.add(mcts[i].text());
}
return params;
}
/**
* @mytag doc mytag
* @param classDocs
*/
private static void doc(ClassDoc[] classDocs) throws Exception {
BufferedWriter write=null;
for (int i = 0; i < classDocs.length; i++) {

String class_describe=doc_class(classDocs[i], CLASS_DESCRIBE);
String class_uri=doc_class(classDocs[i], CLASS_URI);

StringBuffer destdirname= new StringBuffer();
destdirname.append(DIRPATH).append(doc_class(classDocs[i], CLASS_URI));

MethodDoc[] methods = classDocs[i].methods();

//创建configuration对象
Configuration cfg = new Configuration();
//指定模板路径
File file = new File(".");
//            System.out.println(file.getAbsolutePath());
//设置要解析的模板所在的目录,并加载模板文件
cfg.setDirectoryForTemplateLoading(file);
//设置编码方式
cfg.setDefaultEncoding("UTF-8");
Template indexdivTemplate = cfg.getTemplate("indexdiv.ftl");

Map indexRoot = new HashMap();
indexRoot.put("description",class_describe);
List<Map> readlist = new ArrayList<>();
List<Map> writelist = new ArrayList<>();
indexRoot.put("readlist",readlist);
indexRoot.put("writelist",writelist);

for(int j = 0; j< methods.length; j++){

String method_uri=docMethod(methods[j], METHOD_URI);
String method_path=docMethod(methods[j],METHOD_PATH);
String method_type=docMethod(methods[j], METHOD_TYPE);

//获取uri后即可得函数名称,API路径,构造函数,
String uri = class_uri+"/"+method_uri;

//获取模板
Template javaTemplate = cfg.getTemplate("javaTemplate.ftl");
Template methodTemplate = cfg.getTemplate("Method.ftl");

//封装方法的数据对象
Map MethodRoot = new HashMap();
Map JavaRoot = new HashMap();

//                System.out.println(method_type);
//首页数据的写入
if (method_type.equals("write")) {

Map writediv = new HashMap();
writediv.put("uri",uri);
writediv.put("methodpath",method_path);
writelist.add(writediv);

}
else if (method_type.equals("read"))
{
Map readdiv = new HashMap();
readdiv.put("uri",uri);
readdiv.put("methodpath", method_path);
readlist.add(readdiv);

}

//如果方法的uri类似于history/personal,则将personal作为方法名称
String javaname = method_uri.substring(method_uri.lastIndexOf('/')+1,method_uri.length());
JavaRoot.put("methodname",javaname);
JavaRoot.put("basepath", BASEPATH);
JavaRoot.put("uri",uri);

MethodRoot.put("uri", uri);
MethodRoot.put("describe", docMethod(methods[j], METHOD_DESCRIBE));
MethodRoot.put("support", docMethod(methods[j], METHOD_SUPPORT));
MethodRoot.put("requesttype", docMethod(methods[j], METHOD_REQUESTTYPE));
MethodRoot.put("returnjson", docMethod(methods[j], METHOD_RETURNJSON));
MethodRoot.put("notice", docMethod(methods[j], METHOD_NOTICE));

List<Map> paramlist = new ArrayList<Map>();
List<Map> javaparamlist = new ArrayList<Map>();
MethodRoot.put("param", paramlist);
JavaRoot.put("paramlist",javaparamlist);
ArrayList<String> params = docMethodList(methods[j], METHOD_PARAM);//list中每一项对应一个请求参数的数据
for (int k=0; k<params.size();k++)
{
String data = params.get(k);   //获取每一个参数数据
String[] options = data.split("~");    //拆分需要的数据

Map javaparam = new HashMap();
javaparam.put("name",options[0]);

Map param = new HashMap();
param.put("name",options[0]);
param.put("select",options[1]);
param.put("type",options[2]);
param.put("explain",options[3]);

paramlist.add(param);
javaparamlist.add(javaparam);
}

List<Map> returnparamlist = new ArrayList<Map>();
MethodRoot.put("returnparam", returnparamlist);
ArrayList<String> returnparams = docMethodList(methods[j], METHOD_RETURNPARAM);//list中每一项对应一个返回参数的数据
for (int k=0; k<returnparams.size();k++)
{
String data = returnparams.get(k);   //获取每一个参数数据
String[] options = data.split("~");    //拆分需要的数据
Map returnparam = new HashMap();
returnparam.put("name",options[0]);
returnparam.put("type",options[1]);
returnparam.put("explain",options[2]);

returnparamlist.add(returnparam);
}

//每个方法对应的html文件
StringBuffer filenamepath = new StringBuffer();
filenamepath.append(destdirname.toString());
filenamepath.append("/");
filenamepath.append(method_uri);
filenamepath.append(".html");
//                System.out.println(filenamepath.toString());

//每个方法对应的java文件
StringBuffer javapath = new StringBuffer();
javapath.append(destdirname.toString());
javapath.append("/");
javapath.append(method_uri);
javapath.append("Request.java");

//输出html文件
File writefile = new File(filenamepath.toString());
String dirMethodPath = writefile.getParent();
File dirHtmlFile = new File(dirMethodPath);
if (!dirHtmlFile.exists()){
dirHtmlFile.mkdirs();
}
write = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(writefile),"UTF-8"));
methodTemplate.process(MethodRoot, write);
write.flush();

//输出java文件
File javafile = new File(javapath.toString());
String dirJavaPath = writefile.getParent();
File dirJavaFile = new File(dirJavaPath);
if (!dirJavaFile.exists()){
dirJavaFile.mkdirs();
}
write = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(javafile),"UTF-8"));
javaTemplate.process(JavaRoot,write);
write.flush();
}

StringBuffer indexnamepath = new StringBuffer();
indexnamepath.append(DIRPATH);
indexnamepath.append(class_uri);
indexnamepath.append(".html");

File indexfile = new File(indexnamepath.toString());
String dirIndexPath = indexfile.getParent();
File dirIndexFile = new File(dirIndexPath);
if (!dirIndexFile.exists())
dirIndexFile.mkdirs();
write = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(indexfile),"UTF-8"));
indexdivTemplate.process(indexRoot, write);
write.flush();
}
if (write != null)
write.close();

}

public static int optionLength(String option) {
// Construct temporary configuration for check
return (ConfigurationImpl.getInstance()).optionLength(option);
}
public static boolean validOptions(String options[][], DocErrorReporter reporter) {
// Construct temporary configuration for check
return (ConfigurationImpl.getInstance()).validOptions(options, reporter);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息