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; } }