/** * 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 java.text.MessageFormat; import java.util.ArrayList; import java.util.EnumSet; import java.util.List; import com.asakusafw.utils.java.internal.parser.javadoc.ir.IrDocBlock; import com.asakusafw.utils.java.internal.parser.javadoc.ir.IrDocComment; 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; /** * A parser for Java documentation comments. */ public final class JavadocParser extends JavadocBaseParser { /** * Creates a new instance. * @param blockParsers the block parsers * @throws IllegalArgumentException if the parameter is {@code null} */ public JavadocParser(List<? extends JavadocBlockParser> blockParsers) { super(blockParsers); } /** * Parses the documentation comments. * @param scanner the source scanner * @return the analyzed element * @throws JavadocParseException if the documentation comment is malformed * @throws IllegalArgumentException if the parameter is {@code null} */ public IrDocComment parse(JavadocScanner scanner) throws JavadocParseException { if (scanner == null) { throw new IllegalArgumentException("scanner"); //$NON-NLS-1$ } int index = scanner.getIndex(); try { JavadocInfo info = fetchJavadocInfo(scanner); List<IrDocBlock> blocks = new ArrayList<>(info.getBlocks().size()); for (JavadocBlockInfo b: info.getBlocks()) { IrDocBlock block = parseBlock(b); blocks.add(block); } IrDocComment elem = new IrDocComment(); elem.setBlocks(blocks); elem.setLocation(info.getLocation()); return elem; } catch (JavadocParseException e) { scanner.seek(index); throw e; } } /** * Parses a block. * @param scanner the source scanner * @return the analyzed block * @throws JavadocParseException if the block is malformed * @throws IllegalArgumentException if the parameter is {@code null} */ public IrDocBlock parseBlock(JavadocScanner scanner) throws JavadocParseException { if (scanner == null) { throw new IllegalArgumentException("scanner"); //$NON-NLS-1$ } // search for end of comment int eoc = JavadocScannerUtil.countUntilCommentEnd(scanner, true, 0); if (eoc >= 0) { throw new IllegalEndOfCommentException(scanner.lookahead(0).getLocation(), null); } JavadocTokenKind kind = scanner.lookahead(0).getKind(); int index = scanner.getIndex(); try { JavadocBlockInfo info; if (kind == JavadocTokenKind.AT) { info = fetchStandAloneBlock(scanner, scanner.getTokens()); } else { info = fetchSynopsisBlock(scanner); } if (info == null) { throw new IllegalArgumentException("Empty block"); } return parseBlock(info); } catch (JavadocParseException e) { scanner.seek(index); throw e; } } private static JavadocInfo fetchJavadocInfo(JavadocScanner scanner) throws IllegalDocCommentFormatException { int offset = 0; IrLocation firstLocation = scanner.lookahead(0).getLocation(); // check: "/" "*" "*" if (!hasJavadocHead(scanner, offset)) { throw new IllegalDocCommentFormatException(true, scanner.lookahead(0).getLocation(), null); } offset += 3; // avoid: "/" "*" "*" "/" if (scanner.lookahead(offset).getKind() == JavadocTokenKind.SLASH) { throw new IllegalDocCommentFormatException(true, scanner.lookahead(0).getLocation(), null); } // check content range int bodyStart = offset; offset += JavadocScannerUtil.countUntilCommentEnd(scanner, false, offset); int bodyEnd = offset; // check: "*" "/" if (!hasJavadocTail(scanner, offset)) { throw new IllegalDocCommentFormatException(false, scanner.lookahead(0).getLocation(), null); } IrLocation lastLocation = scanner.lookahead(offset + 1).getLocation(); // create content scanner int base = scanner.getIndex(); JavadocScanner bodyScanner = new DefaultJavadocScanner( scanner.getTokens().subList(base + bodyStart, base + bodyEnd), scanner.lookahead(offset).getStartPosition()); List<JavadocBlockInfo> blockScanners = splitBlocks(bodyScanner); int locStart = firstLocation.getStartPosition(); int locEnd = lastLocation.getStartPosition() + lastLocation.getLength(); IrLocation location = new IrLocation(locStart, locEnd - locStart); scanner.consume(offset); return new JavadocInfo(location, blockScanners); } private static List<JavadocBlockInfo> splitBlocks(JavadocScanner scanner) { List<JavadocBlockInfo> blocks = new ArrayList<>(); JavadocBlockInfo synopsis = fetchSynopsisBlock(scanner); if (synopsis != null) { blocks.add(synopsis); } List<JavadocToken> tokens = scanner.getTokens(); while (true) { JavadocBlockInfo info = fetchStandAloneBlock(scanner, tokens); if (info == null) { break; } blocks.add(info); } return blocks; } private static JavadocBlockInfo fetchStandAloneBlock(JavadocScanner scanner, List<JavadocToken> tokens) { JavadocToken first = scanner.lookahead(0); JavadocTokenKind kind = first.getKind(); if (kind == JavadocTokenKind.EOF) { return null; } if (kind != JavadocTokenKind.AT) { throw new AssertionError(MessageFormat.format( "AT <-> {0}({1})@{2}", //$NON-NLS-1$ first, first.getKind(), first.getLocation())); } scanner.consume(1); // fetch: tag name int tagCount = JavadocBlockParserUtil.countWhileTagName(scanner, 0); List<JavadocToken> tagNames = new ArrayList<>(tagCount); for (int i = 0; i < tagCount; i++) { tagNames.add(scanner.nextToken()); } String tagName = JavadocBlockParserUtil.buildString(tagNames); // fetch: body int bodyStart = scanner.getIndex(); int count = JavadocScannerUtil.countUntilBlockEnd(scanner, 0); int success = scanner.lookahead(count).getStartPosition(); DefaultJavadocScanner bs = new DefaultJavadocScanner( new ArrayList<>(tokens.subList(bodyStart, bodyStart + count)), success); int init = first.getStartPosition(); IrLocation location = new IrLocation(init, success - init); JavadocBlockInfo info = new JavadocBlockInfo(tagName, bs, location); scanner.consume(count); return info; } private static JavadocBlockInfo fetchSynopsisBlock(JavadocScanner scanner) { int start = scanner.getIndex(); List<JavadocToken> tokens = scanner.getTokens(); int offset = 0; // remove top "*" offset += JavadocScannerUtil.countWhile(EnumSet.of(JavadocTokenKind.ASTERISK), scanner, offset); // remove white-spaces offset += JavadocScannerUtil.countUntilNextPrintable(scanner, offset); JavadocTokenKind kind = scanner.lookahead(offset).getKind(); if (kind == JavadocTokenKind.AT || kind == JavadocTokenKind.EOF) { // no synopsis block scanner.consume(offset); return null; } else { int count = JavadocScannerUtil.countUntilBlockEnd(scanner, offset); int success = scanner.lookahead(offset + count).getStartPosition(); DefaultJavadocScanner bs = new DefaultJavadocScanner( new ArrayList<>(tokens.subList(start + offset, start + offset + count)), success); JavadocToken first = scanner.lookahead(offset); int init = first.getStartPosition(); IrLocation location = new IrLocation(init, success - init); JavadocBlockInfo info = new JavadocBlockInfo(null, bs, location); scanner.consume(offset + count); return info; } } private static boolean hasJavadocHead(JavadocScanner scanner, int start) { if (scanner.lookahead(start + 0).getKind() != JavadocTokenKind.SLASH) { return false; } if (scanner.lookahead(start + 1).getKind() != JavadocTokenKind.ASTERISK) { return false; } if (scanner.lookahead(start + 2).getKind() != JavadocTokenKind.ASTERISK) { return false; } return true; } private static boolean hasJavadocTail(JavadocScanner scanner, int start) { if (scanner.lookahead(start + 0).getKind() != JavadocTokenKind.ASTERISK) { return false; } if (scanner.lookahead(start + 1).getKind() != JavadocTokenKind.SLASH) { return false; } return true; } private static class JavadocInfo { private final IrLocation location; private final List<JavadocBlockInfo> blockScanners; JavadocInfo(IrLocation location, List<JavadocBlockInfo> blockScanners) { this.location = location; this.blockScanners = blockScanners; } public IrLocation getLocation() { return this.location; } public List<JavadocBlockInfo> getBlocks() { return this.blockScanners; } } }