001 /* 002 * Sonar, open source software quality management tool. 003 * Copyright (C) 2009 SonarSource SA 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 */ 020 package org.sonar.squid.text; 021 022 import java.io.File; 023 import java.io.FileNotFoundException; 024 import java.io.IOException; 025 import java.util.ArrayList; 026 import java.util.Collection; 027 import java.util.List; 028 029 import org.apache.commons.io.FileUtils; 030 import org.apache.commons.io.LineIterator; 031 import org.apache.commons.lang.StringUtils; 032 import org.sonar.squid.api.AnalysisException; 033 import org.sonar.squid.api.CodeScanner; 034 import org.sonar.squid.api.CodeVisitor; 035 import org.sonar.squid.api.SquidConfiguration; 036 import org.sonar.squid.api.SourceFile; 037 import org.sonar.squid.api.SourceCode; 038 import org.sonar.squid.measures.Metric; 039 040 public abstract class AbstractTextScanner implements CodeScanner { 041 042 private static final char TAB_CHAR = '\u0009'; 043 private SquidConfiguration conf; 044 045 public AbstractTextScanner(SquidConfiguration conf) { 046 this.conf = conf; 047 } 048 049 public void scanCode(SourceCode project, Collection<File> filesToAnalyse) { 050 for (File textFile : filesToAnalyse) { 051 project.addChild(analyseFile(textFile)); 052 } 053 } 054 055 protected SourceCode analyseFile(File fileToAnalyse) { 056 LineIterator lineIterator = null; 057 try { 058 SourceFile resource = new SourceFile(fileToAnalyse.getName()); 059 AnalysisState state = new AnalysisState(); 060 lineIterator = FileUtils.lineIterator(fileToAnalyse, conf.getCharset().name()); 061 while (lineIterator.hasNext()) { 062 analyseLine(lineIterator.nextLine(), state); 063 } 064 resource.setMeasure(Metric.LINES, state.lines); 065 resource.setMeasure(Metric.LINES_OF_CODE, state.loc); 066 resource.setMeasure(Metric.BLANK_LINES, state.blankLines); 067 resource.setMeasure(Metric.COMMENT_LINES, state.commentLines); 068 return resource; 069 } catch (FileNotFoundException e) { 070 throw new AnalysisException("Unable to read file : " + fileToAnalyse.getName(), e); 071 } catch (IOException e) { 072 throw new AnalysisException("En error occured when analysing file : " + fileToAnalyse.getName(), e); 073 } finally { 074 LineIterator.closeQuietly(lineIterator); 075 } 076 } 077 078 private void analyseLine(String line, AnalysisState state) { 079 state.newLine(); 080 state.stopSingleLineComment(); 081 StringBuilder lineWithoutComment = new StringBuilder(); 082 if (StringUtils.isBlank(line)) { 083 state.incrementBlankLines(); 084 return; 085 } 086 for (int i = 0; i < line.length(); i++) { 087 if (!state.multiLinesCommentStarted && multiLinesCommentStart(line, i)) { 088 state.startMultiLinesComment(); 089 } else if (state.multiLinesCommentStarted && multiLinesCommentStop(line, i)) { 090 state.stopMultiLinesComment(); 091 i = i + 2; 092 } else if (singleLinesCommentStart(line, i)) { 093 state.startSingleLineComment(); 094 state.stopMultiLinesComment(); 095 break; 096 } else if (!state.multiLinesCommentStarted && !state.singleLinesCommentStarted && !isBlankCharacter(line.charAt(i))) { 097 lineWithoutComment.append(line.charAt(i)); 098 } 099 } 100 if (lineWithoutComment.toString().trim().length() > 0) { 101 state.incrementLoc(); 102 } 103 } 104 105 private boolean isBlankCharacter(char character) { 106 return Character.isWhitespace(character) || character == TAB_CHAR; 107 } 108 109 private boolean substringEquals(String line, int index, String substring) { 110 if (line.length() < index + substring.length()) { 111 return false; 112 } 113 return line.substring(index, index + substring.length()).equals(substring); 114 } 115 116 private boolean singleLinesCommentStart(String line, int i) { 117 for (SingleLineCommentDelimiter singleLines : getSingleLineCommentDelimiter()) { 118 String startWith = singleLines.getStartWith(); 119 if (substringEquals(line, i, startWith)) { 120 return true; 121 } 122 } 123 return false; 124 } 125 126 private boolean multiLinesCommentStart(String line, int i) { 127 for (MultiLinesCommentDelimiters multiLines : getMultiLinesCommentDelimiters()) { 128 String startWith = multiLines.getStartWith(); 129 if (substringEquals(line, i, startWith)) { 130 return true; 131 } 132 } 133 return false; 134 } 135 136 private boolean multiLinesCommentStop(String line, int i) { 137 for (MultiLinesCommentDelimiters multiLines : getMultiLinesCommentDelimiters()) { 138 String endWith = multiLines.getEndWith(); 139 if (substringEquals(line, i, endWith)) { 140 return true; 141 } 142 } 143 return false; 144 } 145 146 protected final char chartAt(String line, int index) { 147 if (index < line.length()) { 148 return line.charAt(index); 149 } 150 return ' '; 151 } 152 153 protected abstract List<MultiLinesCommentDelimiters> getMultiLinesCommentDelimiters(); 154 155 protected abstract List<SingleLineCommentDelimiter> getSingleLineCommentDelimiter(); 156 157 protected class AnalysisState { 158 159 int lines = 0; 160 int loc = 0; 161 int blankLines = 0; 162 int commentLines = 0; 163 boolean multiLinesCommentStarted = false; 164 boolean singleLinesCommentStarted = false; 165 166 public void newLine() { 167 lines++; 168 if (multiLinesCommentStarted) { 169 commentLines++; 170 } 171 } 172 173 public void incrementLoc() { 174 loc++; 175 } 176 177 public void startSingleLineComment() { 178 commentLines++; 179 singleLinesCommentStarted = true; 180 } 181 182 public void startMultiLinesComment() { 183 commentLines++; 184 multiLinesCommentStarted = true; 185 } 186 187 public void stopMultiLinesComment() { 188 multiLinesCommentStarted = false; 189 } 190 191 public void stopSingleLineComment() { 192 singleLinesCommentStarted = false; 193 } 194 195 public void incrementBlankLines() { 196 blankLines++; 197 } 198 } 199 protected class MultiLinesCommentDelimiters { 200 201 private final String startWith; 202 private final String endWith; 203 204 protected MultiLinesCommentDelimiters(String startWith, String endWith) { 205 this.startWith = startWith; 206 this.endWith = endWith; 207 } 208 209 protected String getStartWith() { 210 return startWith; 211 } 212 213 protected String getEndWith() { 214 return endWith; 215 } 216 } 217 protected class SingleLineCommentDelimiter { 218 219 private final String startWith; 220 221 protected SingleLineCommentDelimiter(String startWith) { 222 this.startWith = startWith; 223 } 224 225 protected String getStartWith() { 226 return startWith; 227 } 228 } 229 230 public void accept(CodeVisitor visitor) { 231 } 232 233 public Collection<Class<? extends CodeVisitor>> getVisitors() { 234 return new ArrayList<Class<? extends CodeVisitor>>(); 235 } 236 }