/*
* Copyright (C) 2012-2016 NS Solutions Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.htmlhifive.tools.rhino.comment.js;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import com.htmlhifive.tools.rhino.Constants;
import com.htmlhifive.tools.rhino.comment.RelationNodeType;
import com.htmlhifive.tools.rhino.comment.TagType;
import com.htmlhifive.tools.rhino.comment.Token;
import com.htmlhifive.tools.rhino.comment.TokenType;
import com.htmlhifive.tools.rhino.comment.TokenUtil;
public class JSDocCommentNodeParser {
private RelationNodeType docType;
private List<Token> tokenList;
public JSDocCommentNodeParser(RelationNodeType docType) {
this.docType = docType;
tokenList = new ArrayList<Token>();
}
public JSDocRoot parse(String comment) {
StringTokenizer lineSeparator = new StringTokenizer(comment, "\n\r");
int currentLineNum = 0;
int currentIndexNum = 0;
tokenList = new ArrayList<Token>();
while (lineSeparator.hasMoreTokens()) {
// コメント一行分取得
String lineStr = lineSeparator.nextToken();
// 一行分をトークンに分割
StringTokenizer tokenSeparator = new StringTokenizer(lineStr);
while (tokenSeparator.hasMoreTokens()) {
// tokenを取得
String tokenStr = tokenSeparator.nextToken();
Token token = new Token(TokenUtil.resolveType(tokenStr), tokenStr);
// tokenの行番号を設定
token.setLineNum(currentLineNum);
// tokenの該当行からの文字位置を設定
token.setIndexNum(currentIndexNum);
tokenList.add(token);
currentIndexNum = currentIndexNum + tokenStr.length() + 1;
}
currentIndexNum = 0;
currentLineNum++;
}
return resolveJsDoc();
}
private JSDocRoot resolveJsDoc() {
// 解析に関係しているタグ(@~~)
JSTag currentTag = JSTag.ROOT;
// 前のTokenのタイプ
TokenType previousTokenType = TokenType.START;
JSDocRoot root = new JSDocRoot(docType);
// 一時保管ノード.このノードがツリーに追加される.
JSTagNode tempNode = root;
int currentLineNum = 0;
// 現在の説明構築用StringBuilder
StringBuilder currentDesc = new StringBuilder();
// JsDocコメントの説明部分.
String desc = null;
for (Token token : tokenList) {
if (currentDesc.length() != 0 && !TokenUtil.isSymbolType(token.getType())) {
if (token.getLineNum() != currentLineNum) {
// 行が変わっていたら改行コードを追加
currentDesc.append(Constants.LINE_SEPARATOR);
currentLineNum = token.getLineNum();
} else {
// 改行されていないToken間は空白を追加.
currentDesc.append(" ");
}
}
switch (token.getType()) {
case STRING_LITERAL:
resolveStringLiteral(token, currentTag, previousTokenType, tempNode, currentDesc);
break;
case ANNOTATION:
if (tempNode instanceof JSDocRoot) {
// 一時ノードがルートだった場合はdescはJSDocの説明部分なのでdescに格納
desc = currentDesc.toString();
} else if (tempNode instanceof JSSinglePartTagNode) {
// パートが一つ以上あるタグには値を追加.
((JSSinglePartTagNode) tempNode).setValue(StringUtils.chomp(currentDesc.toString()));
}
if (tempNode != root) {
root.addTagNode(tempNode);
}
currentDesc = new StringBuilder();
// タグの書き換え.
JSTag temp = TokenUtil.resolveTagType(token);
if (temp != null) {
currentTag = temp;
}
// 一時ノードの書き換え.
tempNode = resolveTagNode(currentTag);
break;
case TYPE:
if (currentTag == null) {
// タグの下ではなかったら説明に追加
currentDesc.append(token.getValue());
}
if (tempNode instanceof JSTypePartNode) {
// Typeを保持するノードの場合はタイプを一時nodeにセット
((JSTypePartNode) tempNode).setType(StringUtils.strip(token.getValue(), "{}"));
}
break;
case END:
if (tempNode instanceof JSSinglePartTagNode) {
// パートが一つ以上あるタグには値を追加.
((JSSinglePartTagNode) tempNode).setValue(currentDesc.toString());
}
root.addTagNode(tempNode);
break;
case START:
case SYMBOL:
default:
break;
}
previousTokenType = token.getType();
}
// ルートに説明をセット.
root.setDescription(desc);
return root;
}
/**
* token解析中の処理.現在のトークンが文字列だった場合の分岐処理.
*
* @param token 現在のトークン.
* @param currentTag 現在の係っているタグ
* @param previousTokenType ひとつ前のトークンタイプ.
* @param tempNode 一時ノード.
* @param desc 説明.
*/
private void resolveStringLiteral(Token token, JSTag currentTag, TokenType previousTokenType, JSTagNode tempNode,
StringBuilder desc) {
switch (currentTag) {
case PARAM:
case PROPERTY:
// 現在のかかわりのタグがParamかプロパティの場合はtempNodeはTypeNamePartNode
switch (previousTokenType) {
case TYPE:
// 前のトークンが型だった場合はその次のトークンは変数名.
if (tempNode instanceof JSTypeNamePartNode) {
((JSTypeNamePartNode) tempNode).setName(token.getValue());
}
return;
default:
break;
}
default:
// タグの場合はタグデスクプションに追加.
desc.append(token.getValue());
break;
}
}
private JSTagNode resolveTagNode(JSTag currentTag) {
if (ArrayUtils.contains(TagType.NO_PART_TAG.getJsTag(), currentTag)) {
return new JSNoPartTagNode(currentTag);
} else if (ArrayUtils.contains(TagType.SINGLE_PART_TAG.getJsTag(), currentTag)) {
return new JSSinglePartTagNode(currentTag);
} else if (ArrayUtils.contains(TagType.OTHER_PARAM_TAG.getJsTag(), currentTag)) {
switch (currentTag) {
case PARAM:
case PROPERTY:
return new JSTypeNamePartNode(currentTag);
case THROWS:
case RETURNS:
return new JSTypePartNode(currentTag);
default:
break;
}
// 上記以外のタグはSinglePartTagを返す
return new JSSinglePartTagNode(currentTag);
}
return null;
}
}