/**
* Copyright 2011-2017 Asakusa Framework Team.
*
* 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.asakusafw.utils.java.parser.javadoc;
import static com.asakusafw.utils.java.internal.parser.javadoc.ir.JavadocTokenKind.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.asakusafw.utils.java.internal.parser.javadoc.ir.IrBasicTypeKind;
import com.asakusafw.utils.java.internal.parser.javadoc.ir.IrDocArrayType;
import com.asakusafw.utils.java.internal.parser.javadoc.ir.IrDocBasicType;
import com.asakusafw.utils.java.internal.parser.javadoc.ir.IrDocElement;
import com.asakusafw.utils.java.internal.parser.javadoc.ir.IrDocField;
import com.asakusafw.utils.java.internal.parser.javadoc.ir.IrDocFragment;
import com.asakusafw.utils.java.internal.parser.javadoc.ir.IrDocMethod;
import com.asakusafw.utils.java.internal.parser.javadoc.ir.IrDocMethodParameter;
import com.asakusafw.utils.java.internal.parser.javadoc.ir.IrDocName;
import com.asakusafw.utils.java.internal.parser.javadoc.ir.IrDocNamedType;
import com.asakusafw.utils.java.internal.parser.javadoc.ir.IrDocQualifiedName;
import com.asakusafw.utils.java.internal.parser.javadoc.ir.IrDocSimpleName;
import com.asakusafw.utils.java.internal.parser.javadoc.ir.IrDocText;
import com.asakusafw.utils.java.internal.parser.javadoc.ir.IrDocType;
import com.asakusafw.utils.java.internal.parser.javadoc.ir.IrLocation;
import com.asakusafw.utils.java.internal.parser.javadoc.ir.JavadocToken;
import com.asakusafw.utils.java.internal.parser.javadoc.ir.JavadocTokenKind;
/**
* Utilities for {@link JavadocBlockParser}.
*/
public final class JavadocBlockParserUtil {
/**
* The white-space token kinds.
*/
public static final Set<JavadocTokenKind> S_WHITE;
static {
EnumSet<JavadocTokenKind> set = EnumSet.noneOf(JavadocTokenKind.class);
set.add(WHITE_SPACES);
set.add(LINE_BREAK);
set.add(EOF);
S_WHITE = Collections.unmodifiableSet(set);
}
private static final Set<JavadocTokenKind> S_TEXT_DELIM;
static {
EnumSet<JavadocTokenKind> set = EnumSet.noneOf(JavadocTokenKind.class);
set.add(LINE_BREAK);
set.add(LEFT_BRACE);
S_TEXT_DELIM = Collections.unmodifiableSet(set);
}
private static final Set<JavadocTokenKind> S_TAG_NAME_DELIM;
static {
EnumSet<JavadocTokenKind> set = EnumSet.noneOf(JavadocTokenKind.class);
set.add(WHITE_SPACES);
set.add(LINE_BREAK);
set.add(AT);
set.add(RIGHT_BRACE);
S_TAG_NAME_DELIM = Collections.unmodifiableSet(set);
}
private static final Set<JavadocTokenKind> S_INLINE_BLOCK_DELIM;
static {
EnumSet<JavadocTokenKind> set = EnumSet.noneOf(JavadocTokenKind.class);
set.add(LEFT_BRACE);
set.add(RIGHT_BRACE);
S_INLINE_BLOCK_DELIM = Collections.unmodifiableSet(set);
}
private static final Map<String, IrBasicTypeKind> BASIC_TYPE_NAMES;
static {
Map<String, IrBasicTypeKind> map = new HashMap<>();
for (IrBasicTypeKind k : IrBasicTypeKind.values()) {
map.put(k.getSymbol().intern(), k);
}
BASIC_TYPE_NAMES = Collections.unmodifiableMap(map);
}
private JavadocBlockParserUtil() {
return;
}
/**
* Sets the location information for the target element.
* This will do nothing if some locations are unknown.
* @param <T> the element type
* @param elem the target element
* @param start the first token
* @param stop the last token
* @return the target element (location may be set)
*/
public static <T extends IrDocElement> T setLocation(T elem, JavadocToken start, JavadocToken stop) {
int s = start.getStartPosition();
int e = stop.getStartPosition() + stop.getText().length();
IrLocation location = new IrLocation(s, e - s);
elem.setLocation(location);
return elem;
}
/**
* Sets the location information for the target element.
* This will do nothing if some locations are unknown.
* @param <T> the element type
* @param elem the target element
* @param start the first location
* @param stop the last location
* @return the target element (location may be set)
*/
public static <T extends IrDocElement> T setLocation(T elem, IrLocation start, IrLocation stop) {
if (start == null || stop == null) {
return elem;
}
int s = start.getStartPosition();
int e = stop.getStartPosition() + stop.getLength();
IrLocation location = new IrLocation(s, e - s);
elem.setLocation(location);
return elem;
}
/**
* Consumes tokens from the scanner and returns the corresponded plain text.
* Tokens will be removed from the scanner only if this operation was successfully completed.
* If the next plain text will be empty, this returns {@code null} (operation failed).
* @param scanner the target scanner
* @param trimHead {@code true} to trim leading white-spaces
* @param trimTail {@code true} to trim trailing while-spaces
* @return the consumed plain text, or {@code null} if it will be {@code empty}
* @throws IllegalArgumentException if the parameter is {@code null}
*/
public static IrDocText fetchText(JavadocScanner scanner, boolean trimHead, boolean trimTail) {
if (scanner == null) {
throw new IllegalArgumentException("scanner"); //$NON-NLS-1$
}
int offset = 0;
while (true) {
offset += JavadocScannerUtil.countUntil(S_TEXT_DELIM, scanner, offset);
JavadocTokenKind kind = scanner.lookahead(offset).getKind();
if (kind == LEFT_BRACE) {
JavadocToken la = scanner.lookahead(offset + 1);
if (la.getKind() == JavadocTokenKind.AT) {
break;
}
offset++;
} else {
break;
}
}
return consumeAsText(scanner, offset, trimHead, trimTail);
}
/**
* Consumes tokens from the scanner and returns the corresponded inline block.
* This will ignore successive while space tokens, and tokens are removed from the scanner only if this operation
* was successfully completed.
* @param scanner the target scanner
* @return the next inline block if this operation was succeeded, or {@code null}
* @throws IllegalArgumentException if the parameter is {@code null}
*/
public static JavadocBlockInfo fetchBlockInfo(JavadocScanner scanner) {
if (scanner == null) {
throw new IllegalArgumentException("scanner"); //$NON-NLS-1$
}
int offset = 0;
offset += JavadocScannerUtil.countUntilNextPrintable(scanner, offset);
// block start: {
JavadocToken head = scanner.lookahead(offset);
if (head.getKind() != LEFT_BRACE) {
return null;
}
// tag start: @
offset++;
JavadocToken at = scanner.lookahead(offset);
if (at.getKind() != AT) {
return null;
}
// tag name
offset++;
int nameCount = countWhileTagName(scanner, offset);
String tagName = buildString(JavadocScannerUtil.lookaheadTokens(scanner, offset, nameCount));
offset += nameCount;
// find for block end
int blockEnd = JavadocScannerUtil.countUntil(S_INLINE_BLOCK_DELIM, scanner, offset);
JavadocToken token = scanner.lookahead(offset + blockEnd);
JavadocTokenKind kind = token.getKind();
if (kind == LEFT_BRACE) {
blockEnd = 0;
}
boolean legalBlock = (kind == JavadocTokenKind.RIGHT_BRACE);
JavadocToken tail = scanner.lookahead(offset + blockEnd);
int startIndex = scanner.getIndex() + offset;
int stopIndex = startIndex + blockEnd;
int startPos = head.getStartPosition();
int endPos = tail.getStartPosition();
if (legalBlock) {
endPos += tail.getText().length();
}
IrLocation blockLocation = new IrLocation(startPos, endPos - startPos);
DefaultJavadocScanner blockScanner = new DefaultJavadocScanner(new ArrayList<>(
scanner.getTokens().subList(startIndex, stopIndex)), endPos);
scanner.consume(offset + blockEnd);
if (legalBlock) {
scanner.consume(1);
}
return new JavadocBlockInfo(tagName, blockScanner, blockLocation);
}
/**
* Returns the number of tokens in the current block body.
* @param scanner the target scanner
* @param start the starting offset
* @return the number of tokens
*/
public static int countWhileTagName(JavadocScanner scanner, int start) {
return JavadocScannerUtil.countUntil(S_TAG_NAME_DELIM, scanner, start);
}
/**
* Consumes tokens from the scanner and returns the corresponded simple name.
* This will ignore successive while space tokens, and tokens are removed from the scanner only if this operation
* was successfully completed.
* @param scanner the target scanner
* @param follow the acceptable token kinds after this operation was finished, or {@code null} to accept anything
* @return the next element if this operation was succeeded, or {@code null}
* @throws IllegalArgumentException if {@code scanner} is {@code null}
*/
public static IrDocSimpleName fetchSimpleName(JavadocScanner scanner, Set<JavadocTokenKind> follow) {
DefaultJavadocTokenStream stream = new DefaultJavadocTokenStream(
scanner);
stream.mark();
IrDocSimpleName elem = fetchSimpleName(stream);
if (elem == null) {
return null;
}
if (!follows(stream, follow)) {
stream.rewind();
return null;
} else {
stream.discard();
return elem;
}
}
/**
* Consumes tokens from the scanner and returns the corresponded (simple or qualified) name.
* This will ignore successive while space tokens, and tokens are removed from the scanner only if this operation
* was successfully completed.
* @param scanner the target scanner
* @param follow the acceptable token kinds after this operation was finished, or {@code null} to accept anything
* @return the next element if this operation was succeeded, or {@code null}
* @throws IllegalArgumentException if {@code scanner} is {@code null}
*/
public static IrDocName fetchName(JavadocScanner scanner, Set<JavadocTokenKind> follow) {
DefaultJavadocTokenStream stream = new DefaultJavadocTokenStream(
scanner);
stream.mark();
IrDocName elem = fetchName(stream);
if (elem == null) {
return null;
}
if (!follows(stream, follow)) {
stream.rewind();
return null;
} else {
stream.discard();
return elem;
}
}
/**
* Consumes tokens from the scanner and returns the corresponded basic type.
* This will ignore successive while space tokens, and tokens are removed from the scanner only if this operation
* was successfully completed.
* @param scanner the target scanner
* @param follow the acceptable token kinds after this operation was finished, or {@code null} to accept anything
* @return the next element if this operation was succeeded, or {@code null}
* @throws IllegalArgumentException if {@code scanner} is {@code null}
*/
public static IrDocBasicType fetchBasicType(JavadocScanner scanner, Set<JavadocTokenKind> follow) {
JavadocTokenStream stream = new DefaultJavadocTokenStream(scanner);
stream.mark();
IrDocBasicType elem = fetchBasicType(stream);
if (!follows(stream, follow)) {
stream.rewind();
return null;
} else {
stream.discard();
return elem;
}
}
/**
* Consumes tokens from the scanner and returns the corresponded primitive type.
* This will ignore successive while space tokens, and tokens are removed from the scanner only if this operation
* was successfully completed.
* @param scanner the target scanner
* @param follow the acceptable token kinds after this operation was finished, or {@code null} to accept anything
* @return the next element if this operation was succeeded, or {@code null}
* @throws IllegalArgumentException if {@code scanner} is {@code null}
*/
public static IrDocBasicType fetchPrimitiveType(JavadocScanner scanner, Set<JavadocTokenKind> follow) {
JavadocTokenStream stream = new DefaultJavadocTokenStream(scanner);
stream.mark();
IrDocBasicType elem = fetchBasicType(stream);
if (elem.getTypeKind() == IrBasicTypeKind.VOID) {
stream.rewind();
return null;
}
if (!follows(stream, follow)) {
stream.rewind();
return null;
} else {
stream.discard();
return elem;
}
}
/**
* Consumes tokens from the scanner and returns the corresponded named type.
* This will ignore successive while space tokens, and tokens are removed from the scanner only if this operation
* was successfully completed.
* @param scanner the target scanner
* @param follow the acceptable token kinds after this operation was finished, or {@code null} to accept anything
* @return the next element if this operation was succeeded, or {@code null}
* @throws IllegalArgumentException if {@code scanner} is {@code null}
*/
public static IrDocNamedType fetchNamedType(JavadocScanner scanner, Set<JavadocTokenKind> follow) {
JavadocTokenStream stream = new DefaultJavadocTokenStream(scanner);
stream.mark();
IrDocNamedType elem = fetchNamedType(stream);
if (!follows(stream, follow)) {
stream.rewind();
return null;
} else {
stream.discard();
return elem;
}
}
/**
* Consumes tokens from the scanner and returns the corresponded type.
* This will ignore successive while space tokens, and tokens are removed from the scanner only if this operation
* was successfully completed.
* @param scanner the target scanner
* @param follow the acceptable token kinds after this operation was finished, or {@code null} to accept anything
* @return the next element if this operation was succeeded, or {@code null}
* @throws IllegalArgumentException if {@code scanner} is {@code null}
*/
public static IrDocType fetchType(JavadocScanner scanner, Set<JavadocTokenKind> follow) {
JavadocTokenStream stream = new DefaultJavadocTokenStream(scanner);
stream.mark();
IrDocType elem = fetchType(stream);
if (!follows(stream, follow)) {
stream.rewind();
return null;
} else {
stream.discard();
return elem;
}
}
/**
* Consumes tokens from the scanner and returns the corresponded field.
* This will ignore successive while space tokens, and tokens are removed from the scanner only if this operation
* was successfully completed.
* @param scanner the target scanner
* @param follow the acceptable token kinds after this operation was finished, or {@code null} to accept anything
* @return the next element if this operation was succeeded, or {@code null}
* @throws IllegalArgumentException if {@code scanner} is {@code null}
*/
public static IrDocField fetchField(JavadocScanner scanner, Set<JavadocTokenKind> follow) {
JavadocTokenStream stream = new DefaultJavadocTokenStream(scanner);
stream.mark();
IrDocField elem = fetchField(stream);
if (!follows(stream, follow)) {
stream.rewind();
return null;
} else {
stream.discard();
return elem;
}
}
/**
* Consumes tokens from the scanner and returns the corresponded method or constructor.
* This will ignore successive while space tokens, and tokens are removed from the scanner only if this operation
* was successfully completed.
* @param scanner the target scanner
* @param follow the acceptable token kinds after this operation was finished, or {@code null} to accept anything
* @return the next element if this operation was succeeded, or {@code null}
* @throws IllegalArgumentException if {@code scanner} is {@code null}
*/
public static IrDocMethod fetchMethod(JavadocScanner scanner, Set<JavadocTokenKind> follow) {
JavadocTokenStream stream = new DefaultJavadocTokenStream(scanner);
stream.mark();
IrDocMethod elem = fetchMethod(stream);
if (!follows(stream, follow)) {
stream.rewind();
return null;
} else {
stream.discard();
return elem;
}
}
/**
* Consumes tokens from the scanner and returns the corresponded link target.
* The link target means one of type, field, method, or constructor.
* This will ignore successive while space tokens, and tokens are removed from the scanner only if this operation
* was successfully completed.
* @param scanner the target scanner
* @param follow the acceptable token kinds after this operation was finished, or {@code null} to accept anything
* @return the next element if this operation was succeeded, or {@code null}
* @throws IllegalArgumentException if {@code scanner} is {@code null}
*/
public static IrDocFragment fetchLinkTarget(JavadocScanner scanner, Set<JavadocTokenKind> follow) {
IrDocMethod method = fetchMethod(scanner, follow);
if (method != null) {
return method;
}
IrDocField field = fetchField(scanner, follow);
if (field != null) {
return field;
}
IrDocNamedType type = fetchNamedType(scanner, follow);
if (type != null) {
return type;
}
return null;
}
private static IrDocMethod fetchMethod(JavadocTokenStream stream) {
stream.mark();
IrDocField field = fetchField(stream);
if (field == null) {
stream.rewind();
return null;
}
if (consumeIfMatch(stream, LEFT_PAREN) == null) {
stream.rewind();
return null;
}
JavadocToken delim;
List<IrDocMethodParameter> parameters;
IrDocMethodParameter first = fetchMethodParameter(stream);
if (first == null) {
delim = consumeIfMatch(stream, RIGHT_PAREN);
if (delim == null) {
stream.rewind();
return null;
}
parameters = Collections.emptyList();
} else {
parameters = new ArrayList<>();
parameters.add(first);
while (true) {
delim = stream.nextToken();
if (delim.getKind() == RIGHT_PAREN) {
break;
} else if (delim.getKind() == COMMA) {
IrDocMethodParameter p = fetchMethodParameter(stream);
if (p == null) {
stream.rewind();
return null;
}
parameters.add(p);
} else {
stream.rewind();
return null;
}
}
}
stream.discard();
IrDocMethod elem = new IrDocMethod();
elem.setDeclaringType(field.getDeclaringType());
elem.setName(field.getName());
elem.setParameters(parameters);
setLocation(elem, field.getLocation(), delim.getLocation());
return elem;
}
private static IrDocMethodParameter fetchMethodParameter(JavadocTokenStream stream) {
stream.mark();
IrDocType type = fetchType(stream);
if (type == null) {
stream.rewind();
return null;
} else {
stream.discard();
}
IrLocation delim = type.getLocation();
boolean varargs;
if (consumeIfMatch(stream, DOT) != null) {
if ((stream.lookahead(0).getKind() != DOT) || (stream.lookahead(1).getKind() != DOT)) {
stream.rewind();
return null;
}
stream.nextToken();
JavadocToken lastDot = stream.nextToken();
delim = lastDot.getLocation();
varargs = true;
} else {
varargs = false;
}
IrDocSimpleName name = fetchSimpleName(stream);
if (name != null) {
delim = name.getLocation();
}
IrDocMethodParameter elem = new IrDocMethodParameter();
elem.setType(type);
elem.setVariableArity(varargs);
elem.setName(name);
setLocation(elem, type.getLocation(), delim);
return elem;
}
private static IrDocField fetchField(JavadocTokenStream stream) {
stream.mark();
IrDocNamedType decl = fetchNamedType(stream);
JavadocToken sharp = consumeIfMatch(stream, SHARP);
if (sharp == null) {
stream.rewind();
return null;
}
IrDocSimpleName name = fetchSimpleName(stream);
if (name == null) {
stream.rewind();
return null;
}
IrDocField elem = new IrDocField();
elem.setDeclaringType(decl);
elem.setName(name);
setLocation(elem, decl == null ? sharp.getLocation() : decl.getLocation(), name.getLocation());
return elem;
}
private static IrDocNamedType fetchNamedType(JavadocTokenStream stream) {
IrDocName name = fetchName(stream);
if (name == null) {
return null;
}
IrDocNamedType elem = new IrDocNamedType(name);
setLocation(elem, name.getLocation(), name.getLocation());
return elem;
}
private static IrDocType fetchType(JavadocTokenStream stream) {
stream.mark();
IrDocType elem = fetchBasicType(stream);
if (elem == null) {
IrDocName name = fetchName(stream);
if (name == null) {
stream.rewind();
return null;
}
elem = new IrDocNamedType(name);
elem.setLocation(name.getLocation());
}
while (true) {
stream.mark();
if (consumeIfMatch(stream, JavadocTokenKind.LEFT_BRACKET) == null) {
stream.rewind();
break;
}
JavadocToken stop = consumeIfMatch(stream,
JavadocTokenKind.RIGHT_BRACKET);
if (stop == null) {
stream.rewind();
break;
} else {
stream.discard();
IrDocArrayType t = new IrDocArrayType(elem);
setLocation(t, elem.getLocation(), stop.getLocation());
elem = t;
}
}
stream.discard();
return elem;
}
private static IrDocName fetchName(JavadocTokenStream stream) {
IrDocName name = fetchSimpleName(stream);
if (name == null) {
return null;
}
while (true) {
stream.mark();
if (consumeIfMatch(stream, JavadocTokenKind.DOT) == null) {
stream.rewind();
break;
}
IrDocSimpleName simple = fetchSimpleName(stream);
if (simple == null) {
stream.rewind();
break;
} else {
IrDocQualifiedName qualified = new IrDocQualifiedName(name, simple);
setLocation(qualified, name.getLocation(), simple.getLocation());
name = qualified;
stream.discard();
}
}
return name;
}
private static IrDocBasicType fetchBasicType(JavadocTokenStream stream) {
JavadocToken token = stream.peek();
if (token.getKind() == JavadocTokenKind.IDENTIFIER) {
if (BASIC_TYPE_NAMES.containsKey(token.getText())) {
stream.nextToken();
IrBasicTypeKind k = BASIC_TYPE_NAMES.get(token.getText());
IrDocBasicType elem = new IrDocBasicType(k);
setLocation(elem, token, token);
return elem;
}
}
return null;
}
private static IrDocSimpleName fetchSimpleName(JavadocTokenStream stream) {
JavadocToken token = consumeIfMatch(stream, JavadocTokenKind.IDENTIFIER);
if (token != null) {
IrDocSimpleName name = new IrDocSimpleName(token.getText());
setLocation(name, token, token);
return name;
} else {
return null;
}
}
private static boolean follows(JavadocTokenStream stream,
Collection<JavadocTokenKind> set) {
if (set == null) {
return true;
}
JavadocTokenKind kind = stream.lookahead(0).getKind();
return set.contains(kind);
}
private static JavadocToken consumeIfMatch(JavadocTokenStream stream, JavadocTokenKind kind) {
JavadocToken token = stream.peek();
if (token.getKind() == kind) {
return stream.nextToken();
}
return null;
}
private static IrDocText consumeAsText(JavadocScanner scanner, int count, boolean trimHead, boolean trimTail) {
assert scanner != null;
assert count >= 0;
int mark = scanner.getIndex();
List<JavadocToken> tokens = consumeTokens(scanner, count, trimHead, trimTail);
IrDocText elem = buildText(tokens);
if (elem == null) {
scanner.seek(mark);
return null;
} else {
return elem;
}
}
private static IrDocText buildText(List<? extends JavadocToken> tokens) {
assert tokens != null;
if (tokens.isEmpty()) {
return null;
}
String text = buildString(tokens);
IrDocText elem = new IrDocText(text);
JavadocToken start = tokens.get(0);
JavadocToken stop = tokens.get(tokens.size() - 1);
setLocation(elem, start, stop);
return elem;
}
/**
* Concatenates the token images.
* @param tokens the target tokens
* @return the concatenated string
*/
public static String buildString(List<? extends JavadocToken> tokens) {
if (tokens == null) {
throw new IllegalArgumentException("tokens"); //$NON-NLS-1$
}
if (tokens.isEmpty()) {
return ""; //$NON-NLS-1$
}
StringBuilder buf = new StringBuilder();
for (JavadocToken t : tokens) {
buf.append(t.getText());
}
return buf.toString();
}
private static List<JavadocToken> consumeTokens(JavadocScanner scanner,
int count, boolean trimHead, boolean trimTail) {
if (count == 0) {
return Collections.emptyList();
}
int rest = count;
if (trimHead) {
int offset = JavadocScannerUtil.countWhile(S_WHITE, scanner, 0);
scanner.consume(offset);
rest -= offset;
if (rest == 0) {
return Collections.emptyList();
}
}
List<JavadocToken> tokens = new ArrayList<>(rest);
for (int i = 0; i < rest; i++) {
JavadocToken t = scanner.nextToken();
if (t.getKind() == JavadocTokenKind.EOF) {
break;
}
tokens.add(t);
}
if (trimTail) {
int lastWs = tokens.size();
while (lastWs >= 0) {
JavadocToken t = tokens.get(lastWs - 1);
if (t.getKind() != WHITE_SPACES) {
break;
}
lastWs--;
}
if (lastWs != tokens.size()) {
tokens = tokens.subList(0, lastWs);
}
}
return tokens;
}
}