package com.baselet.plugin.refactoring;
import java.util.ArrayList;
import java.util.List;
/**
* Parser for javadoc comments.
*
* <p> Used to for refactorings in comments. It is implemented as PEG parser</p>
*<p>Placed here since unit tests are not available in the eclipse plugin project.
*</p>
*/
public class JavaDocParser {
private final String source;
private int idx;
private int sourceEnd;
public JavaDocParser(String source) {
this(source, 0, source.length());
}
public JavaDocParser(String source, int sourceStart, int sourceEnd) {
this.source = source;
idx = sourceStart;
this.sourceEnd = sourceEnd;
}
public static abstract class JavaDocNodeBase {}
public static class JavaDocCommentNode extends JavaDocNodeBase {
public List<JavaDocNodeBase> children = new ArrayList<JavaDocNodeBase>();
public <T extends JavaDocNodeBase> List<T> ofType(Class<T> clazz) {
ArrayList<T> result = new ArrayList<T>();
for (JavaDocNodeBase child : children) {
if (clazz.isInstance(child))
result.add(clazz.cast(child));
}
return result;
}
}
public static class HtmlTagAttr {
public SourceString key;
public SourceString value;
public int start;
public int end;
public HtmlTagAttr(SourceString key, SourceString value, int start, int end) {
this.key = key;
this.value = value;
this.start = start;
this.end = end;
}
@Override
public String toString() {
return "HtmlTagAttr [key=" + key + ", value=" + value + ", start=" + start + ", end=" + end + "]";
}
public int length() {
return end - start;
}
}
public static class SourceString {
public String source;
public int start;
public int end;
public SourceString(String source, int start, int end) {
super();
this.source = source;
this.start = start;
this.end = end;
}
public String getValue() {
return source.substring(start, end);
}
public int length() {
return end - start;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + end;
result = prime * result + start;
result = prime * result + ((source == null) ? 0 : source.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof SourceString))
return false;
SourceString other = (SourceString) obj;
if (end != other.end)
return false;
if (start != other.start)
return false;
if (source == null) {
if (other.source != null)
return false;
}
else if (!source.equals(other.source))
return false;
return true;
}
@Override
public String toString() {
return "SourceString [value=" + getValue() + ", start=" + start + ", end=" + end + "]";
}
}
public static class HtmlTagStartNode extends JavaDocNodeBase {
public SourceString tagName;
public List<HtmlTagAttr> attrs = new ArrayList<JavaDocParser.HtmlTagAttr>();
public int start;
public int end;
public HtmlTagStartNode() {}
public HtmlTagStartNode(SourceString tagName, int start, int end) {
this.tagName = tagName;
this.start = start;
this.end = end;
}
public HtmlTagStartNode with(SourceString key, SourceString value, int start, int end) {
attrs.add(new HtmlTagAttr(key, value, start, end));
return this;
}
@Override
public String toString() {
return "HtmlTagStartNode [tagName=" + tagName + ", attrs=" + attrs + ", start=" + start + ", end=" + end + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((attrs == null) ? 0 : attrs.hashCode());
result = prime * result + end;
result = prime * result + start;
result = prime * result + ((tagName == null) ? 0 : tagName.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof HtmlTagStartNode))
return false;
HtmlTagStartNode other = (HtmlTagStartNode) obj;
if (attrs == null) {
if (other.attrs != null)
return false;
}
else if (!attrs.equals(other.attrs))
return false;
if (end != other.end)
return false;
if (start != other.start)
return false;
if (tagName == null) {
if (other.tagName != null)
return false;
}
else if (!tagName.equals(other.tagName))
return false;
return true;
}
public HtmlTagAttr getAttr(String key) {
for (HtmlTagAttr attr : attrs) {
if (attr.key.getValue().equals(key))
return attr;
}
return null;
}
public boolean hasAttr(String key) {
return getAttr(key) != null;
}
public int length() {
return end - start;
}
}
/**
* <pre>
* comment = (
* htmlTagStart
* | anyChar
* )*
* </pre>
*/
public JavaDocCommentNode comment() {
JavaDocCommentNode result = new JavaDocCommentNode();
while (!isEof()) {
int start = idx;
HtmlTagStartNode node = htmlTagStart();
if (node != null) {
result.children.add(node);
continue;
}
idx = start;
nextCp();
}
return result;
}
/**
* <pre>
* "<" ident w* attrs "/"? ">"
* </pre>
*/
public HtmlTagStartNode htmlTagStart() {
HtmlTagStartNode result = new HtmlTagStartNode();
result.start = idx;
if (!consume('<')) {
return null;
}
result.tagName = ident();
if (result.tagName == null) {
return null;
}
optWhitespace();
result.attrs = attrs();
consume('/');
if (!consume('>')) {
return null;
}
result.end = idx;
return result;
}
/**
* <pre>
* attrs = (ident w* "=" w* "\"" !"\"" "\"" w*)*
* </pre>
*/
public List<HtmlTagAttr> attrs() {
List<HtmlTagAttr> result = new ArrayList<JavaDocParser.HtmlTagAttr>();
while (true) {
int start = idx;
SourceString key = ident();
if (key == null) {
idx = start;
return result;
}
optWhitespace();
if (!consume('=')) {
idx = start;
return result;
}
optWhitespace();
if (!consume('"')) {
idx = start;
return result;
}
int valueStart = idx;
while (!isEof() && cp() != '"') {
nextCp();
}
SourceString value = new SourceString(source, valueStart, idx);
if (!consume('"')) {
idx = start;
return result;
}
optWhitespace();
result.add(new HtmlTagAttr(key, value, start, idx));
}
}
public void optWhitespace() {
while (!isEof() && (Character.isWhitespace(cp()) || cp() == '\n' || cp() == '\r')) {
nextCp();
}
}
public SourceString ident() {
if (isEof() || !Character.isJavaIdentifierStart(cp())) {
return null;
}
int start = idx;
nextCp();
while (!isEof() && Character.isJavaIdentifierPart(cp())) {
nextCp();
}
return new SourceString(source, start, idx);
}
private boolean isEof() {
return idx >= sourceEnd;
}
private void nextCp() {
idx += Character.charCount(cp());
}
private int cp() {
return source.codePointAt(idx);
}
private boolean consume(char ch) {
if (isEof()) {
return false;
}
if (cp() != ch) {
return false;
}
nextCp();
return true;
}
}