001/*
002 * Sonar, open source software quality management tool.
003 * Copyright (C) 2008-2012 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * Sonar is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 3 of the License, or (at your option) any later version.
010 *
011 * Sonar is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 * Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public
017 * License along with Sonar; if not, write to the Free Software
018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
019 */
020package org.sonar.colorizer;
021
022import java.util.Arrays;
023
024import org.sonar.channel.CodeReader;
025import org.sonar.channel.EndMatcher;
026
027public class MultilinesDocTokenizer extends Tokenizer {
028
029  private static final String COMMENT_STARTED_ON_PREVIOUS_LINE = "COMMENT_STARTED_ON_PREVIOUS_LINE";
030  private static final String COMMENT_TOKENIZER = "MULTILINE_COMMENT_TOKENIZER";
031  private final char[] startToken;
032  private final char[] endToken;
033  private final String tagBefore;
034  private final String tagAfter;
035
036  /**
037   * @deprecated endToken is hardcoded to star-slash, whatever the startToken !
038   */
039  @Deprecated
040  public MultilinesDocTokenizer(String startToken, String tagBefore, String tagAfter) {
041    this(startToken, "*/", tagBefore, tagAfter);
042  }
043
044  public MultilinesDocTokenizer(String startToken, String endToken, String tagBefore, String tagAfter) {
045    this.tagBefore = tagBefore;
046    this.tagAfter = tagAfter;
047    this.startToken = startToken.toCharArray();
048    this.endToken = endToken.toCharArray();
049  }
050
051  public boolean hasNextToken(CodeReader code, HtmlCodeBuilder codeBuilder) {
052    return code.peek() != '\n'
053        && code.peek() != '\r'
054        && (isCommentStartedOnPreviousLine(codeBuilder) || (code.peek() == startToken[0] && Arrays.equals(code.peek(startToken.length),
055            startToken)));
056  }
057
058  @Override
059  public boolean consume(CodeReader code, HtmlCodeBuilder codeBuilder) {
060    if (hasNextToken(code, codeBuilder)) {
061      codeBuilder.appendWithoutTransforming(tagBefore);
062      code.popTo(new MultilineEndTokenMatcher(codeBuilder), codeBuilder);
063      codeBuilder.appendWithoutTransforming(tagAfter);
064      return true;
065    } else {
066      return false;
067    }
068  }
069
070  private class MultilineEndTokenMatcher implements EndMatcher {
071
072    private final HtmlCodeBuilder code;
073    private final StringBuilder colorizedCode;
074    private int commentSize = 0;
075
076    public MultilineEndTokenMatcher(HtmlCodeBuilder code) {
077      this.code = code;
078      this.colorizedCode = code.getColorizedCode();
079    }
080
081    public boolean match(int endFlag) {
082      commentSize++;
083      if (commentSize >= endToken.length + startToken.length || (commentSize >= endToken.length && isCommentStartedOnPreviousLine(code))) {
084        boolean matches = true;
085        for (int i = 1; i <= endToken.length; i++) {
086          if (colorizedCode.charAt(colorizedCode.length() - i) != endToken[endToken.length - i]) {
087            matches = false;
088            break;
089          }
090        }
091        if (matches) {
092          setCommentStartedOnPreviousLine(code, Boolean.FALSE);
093          return true;
094        }
095      }
096
097      if (endFlag == '\r' || endFlag == '\n') {
098        setCommentStartedOnPreviousLine(code, Boolean.TRUE);
099        return true;
100      }
101      return false;
102    }
103  }
104
105  private boolean isCommentStartedOnPreviousLine(HtmlCodeBuilder codeBuilder) {
106    Boolean b = (Boolean) codeBuilder.getVariable(COMMENT_STARTED_ON_PREVIOUS_LINE, Boolean.FALSE);
107    return (b == Boolean.TRUE) && (getTokenizerId().equals(codeBuilder.getVariable(COMMENT_TOKENIZER)));
108  }
109
110  private void setCommentStartedOnPreviousLine(HtmlCodeBuilder codeBuilder, Boolean b) {
111    codeBuilder.setVariable(COMMENT_STARTED_ON_PREVIOUS_LINE, b);
112    codeBuilder.setVariable(COMMENT_TOKENIZER, b ? getTokenizerId() : null);
113  }
114
115  private String getTokenizerId() {
116    return getClass().getSimpleName();
117  }
118}