001: ////////////////////////////////////////////////////////////////////////////////
002: // checkstyle: Checks Java source code for adherence to a set of rules.
003: // Copyright (C) 2001-2007 Oliver Burn
004: //
005: // This library is free software; you can redistribute it and/or
006: // modify it under the terms of the GNU Lesser General Public
007: // License as published by the Free Software Foundation; either
008: // version 2.1 of the License, or (at your option) any later version.
009: //
010: // This library is distributed in the hope that it will be useful,
011: // but WITHOUT ANY WARRANTY; without even the implied warranty of
012: // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: // Lesser General Public License for more details.
014: //
015: // You should have received a copy of the GNU Lesser General Public
016: // License along with this library; if not, write to the Free Software
017: // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
018: ////////////////////////////////////////////////////////////////////////////////
019: package com.puppycrawl.tools.checkstyle.api;
020:
021: import java.util.ArrayList;
022: import java.util.Collections;
023: import java.util.List;
024: import java.util.Map;
025: import java.util.HashMap;
026: import java.util.Collection;
027: import java.util.Iterator;
028:
029: import java.util.regex.Pattern;
030:
031: import com.puppycrawl.tools.checkstyle.grammars.CommentListener;
032:
033: /**
034: * Represents the contents of a file.
035: *
036: * @author Oliver Burn
037: * @version 1.0
038: */
039: public final class FileContents implements CommentListener {
040: /**
041: * the pattern to match a single line comment containing only the comment
042: * itself -- no code.
043: */
044: private static final String MATCH_SINGLELINE_COMMENT_PAT = "^\\s*//.*$";
045: /** compiled regexp to match a single-line comment line */
046: private static final Pattern MATCH_SINGLELINE_COMMENT = Pattern
047: .compile(MATCH_SINGLELINE_COMMENT_PAT);
048:
049: /** the file name */
050: private final String mFilename;
051:
052: /** the lines */
053: private final String[] mLines;
054:
055: /** map of the Javadoc comments indexed on the last line of the comment.
056: * The hack is it assumes that there is only one Javadoc comment per line.
057: */
058: private final Map mJavadocComments = new HashMap();
059:
060: /** map of the C++ comments indexed on the first line of the comment. */
061: private final Map mCPlusPlusComments = new HashMap();
062: /**
063: * map of the C comments indexed on the first line of the comment to a
064: * list of comments on that line
065: */
066: private final Map mCComments = new HashMap();
067:
068: /**
069: * Creates a new <code>FileContents</code> instance.
070: *
071: * @param aFilename name of the file
072: * @param aLines the contents of the file
073: */
074: public FileContents(String aFilename, String[] aLines) {
075: mFilename = aFilename;
076: mLines = aLines;
077: }
078:
079: /** {@inheritDoc} */
080: public void reportSingleLineComment(String aType, int aStartLineNo,
081: int aStartColNo) {
082: reportCppComment(aStartLineNo, aStartColNo);
083: }
084:
085: /** {@inheritDoc} */
086: public void reportBlockComment(String aType, int aStartLineNo,
087: int aStartColNo, int aEndLineNo, int aEndColNo) {
088: reportCComment(aStartLineNo, aStartColNo, aEndLineNo, aEndColNo);
089: }
090:
091: /**
092: * Report the location of a C++ style comment.
093: * @param aStartLineNo the starting line number
094: * @param aStartColNo the starting column number
095: **/
096: public void reportCppComment(int aStartLineNo, int aStartColNo) {
097: final String line = mLines[aStartLineNo - 1];
098: final String[] txt = new String[] { line.substring(aStartColNo) };
099: final Comment comment = new Comment(txt, aStartColNo,
100: aStartLineNo, line.length() - 1);
101: mCPlusPlusComments.put(new Integer(aStartLineNo), comment);
102: }
103:
104: /**
105: * Returns a map of all the C++ style comments. The key is a line number,
106: * the value is the comment {@link TextBlock} at the line.
107: * @return the Map of comments
108: */
109: public Map getCppComments() {
110: return Collections.unmodifiableMap(mCPlusPlusComments);
111: }
112:
113: /**
114: * Report the location of a C-style comment.
115: * @param aStartLineNo the starting line number
116: * @param aStartColNo the starting column number
117: * @param aEndLineNo the ending line number
118: * @param aEndColNo the ending column number
119: **/
120: public void reportCComment(int aStartLineNo, int aStartColNo,
121: int aEndLineNo, int aEndColNo) {
122: final String[] cc = extractCComment(aStartLineNo, aStartColNo,
123: aEndLineNo, aEndColNo);
124: final Comment comment = new Comment(cc, aStartColNo,
125: aEndLineNo, aEndColNo);
126:
127: // save the comment
128: final Integer key = new Integer(aStartLineNo);
129: if (mCComments.containsKey(key)) {
130: final List entries = (List) mCComments.get(key);
131: entries.add(comment);
132: } else {
133: final List entries = new ArrayList();
134: entries.add(comment);
135: mCComments.put(key, entries);
136: }
137:
138: // Remember if possible Javadoc comment
139: if (mLines[aStartLineNo - 1].indexOf("/**", aStartColNo) != -1) {
140: mJavadocComments.put(new Integer(aEndLineNo - 1), comment);
141: }
142: }
143:
144: /**
145: * Returns a map of all C style comments. The key is the line number, the
146: * value is a {@link List} of C style comment {@link TextBlock}s
147: * that start at that line.
148: * @return the map of comments
149: */
150: public Map getCComments() {
151: return Collections.unmodifiableMap(mCComments);
152: }
153:
154: /**
155: * Returns the specified C comment as a String array.
156: * @return C comment as a array
157: * @param aStartLineNo the starting line number
158: * @param aStartColNo the starting column number
159: * @param aEndLineNo the ending line number
160: * @param aEndColNo the ending column number
161: **/
162: private String[] extractCComment(int aStartLineNo, int aStartColNo,
163: int aEndLineNo, int aEndColNo) {
164: String[] retVal;
165: if (aStartLineNo == aEndLineNo) {
166: retVal = new String[1];
167: retVal[0] = mLines[aStartLineNo - 1].substring(aStartColNo,
168: aEndColNo + 1);
169: } else {
170: retVal = new String[aEndLineNo - aStartLineNo + 1];
171: retVal[0] = mLines[aStartLineNo - 1].substring(aStartColNo);
172: for (int i = aStartLineNo; i < aEndLineNo; i++) {
173: retVal[i - aStartLineNo + 1] = mLines[i];
174: }
175: retVal[retVal.length - 1] = mLines[aEndLineNo - 1]
176: .substring(0, aEndColNo + 1);
177: }
178: return retVal;
179: }
180:
181: /**
182: * Returns the Javadoc comment before the specified line.
183: * A return value of <code>null</code> means there is no such comment.
184: * @return the Javadoc comment, or <code>null</code> if none
185: * @param aLineNo the line number to check before
186: **/
187: public TextBlock getJavadocBefore(int aLineNo) {
188: // Lines start at 1 to the callers perspective, so need to take off 2
189: int lineNo = aLineNo - 2;
190:
191: // skip blank lines
192: while ((lineNo > 0)
193: && (lineIsBlank(lineNo) || lineIsComment(lineNo))) {
194: lineNo--;
195: }
196:
197: return (TextBlock) mJavadocComments.get(new Integer(lineNo));
198: }
199:
200: /** @return the lines in the file */
201: public String[] getLines() {
202: return mLines;
203: }
204:
205: /** @return the name of the file */
206: public String getFilename() {
207: return mFilename;
208: }
209:
210: /**
211: * Checks if the specified line is blank.
212: * @param aLineNo the line number to check
213: * @return if the specified line consists only of tabs and spaces.
214: **/
215: public boolean lineIsBlank(int aLineNo) {
216: // possible improvement: avoid garbage creation in trim()
217: return "".equals(mLines[aLineNo].trim());
218: }
219:
220: /**
221: * Checks if the specified line is a single-line comment without code.
222: * @param aLineNo the line number to check
223: * @return if the specified line consists of only a single line comment
224: * without code.
225: **/
226: public boolean lineIsComment(int aLineNo) {
227: return MATCH_SINGLELINE_COMMENT.matcher(mLines[aLineNo])
228: .matches();
229: }
230:
231: /**
232: * Checks if the specified position intersects with a comment.
233: * @param aStartLineNo the starting line number
234: * @param aStartColNo the starting column number
235: * @param aEndLineNo the ending line number
236: * @param aEndColNo the ending column number
237: * @return true if the positions intersects with a comment.
238: **/
239: public boolean hasIntersectionWithComment(int aStartLineNo,
240: int aStartColNo, int aEndLineNo, int aEndColNo) {
241: // Check C comments (all comments should be checked)
242: final Collection values = mCComments.values();
243:
244: final Iterator it = values.iterator();
245: while (it.hasNext()) {
246: final List row = (List) it.next();
247: final Iterator rowIterator = row.iterator();
248: while (rowIterator.hasNext()) {
249: final TextBlock comment = (TextBlock) rowIterator
250: .next();
251: if (comment.intersects(aStartLineNo, aStartColNo,
252: aEndLineNo, aEndColNo)) {
253: return true;
254: }
255: }
256: }
257:
258: // Check CPP comments (line searching is possible)
259: for (int lineNumber = aStartLineNo; lineNumber <= aEndLineNo; lineNumber++) {
260: final TextBlock comment = (TextBlock) mCPlusPlusComments
261: .get(new Integer(lineNumber));
262: if ((comment != null)
263: && comment.intersects(aStartLineNo, aStartColNo,
264: aEndLineNo, aEndColNo)) {
265: return true;
266: }
267: }
268: return false;
269: }
270:
271: }
|