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 }