001: /*
002: * Hammurapi
003: * Automated Java code review system.
004: * Copyright (C) 2004 Hammurapi Group
005: *
006: * This program is free software; you can redistribute it and/or modify
007: * it under the terms of the GNU General Public License as published by
008: * the Free Software Foundation; either version 2 of the License, or
009: * (at your option) any later version.
010: *
011: * This program 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
014: * GNU General Public License for more details.
015: *
016: * You should have received a copy of the GNU General Public License
017: * along with this program; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: *
020: * URL: http://www.hammurapi.org
021: * e-Mail: support@hammurapi.biz
022: */
023: package org.hammurapi.inspectors;
024:
025: import java.io.File;
026:
027: import org.hammurapi.InspectorBase;
028:
029: import com.pavelvlasov.config.ConfigurationException;
030: import com.pavelvlasov.config.Parameterizable;
031: import com.pavelvlasov.jsel.Class;
032: import com.pavelvlasov.jsel.CompilationUnit;
033: import com.pavelvlasov.jsel.Interface;
034: import com.pavelvlasov.jsel.LanguageElement;
035: import com.pavelvlasov.jsel.Package;
036: import com.pavelvlasov.jsel.impl.JavaTokenTypes;
037: import com.pavelvlasov.jsel.impl.Token;
038: import com.pavelvlasov.review.SimpleSourceMarker;
039:
040: /**
041: * <p>
042: * Hammurapi inspector for checking the indentation of the source code.
043: * </p>
044: * <p>
045: * The parameter <i>standard-indentation-level </i> specifies the default
046: * indentation for blocks in curly braces. There must be no difference in the
047: * indentation of statements without sorrounding curly braces. Examples (with
048: * <i>standard-indentation-level </i>= 2):
049: * </p>
050: *
051: * <pre>
052: * if (current.getType() == JavaTokenTypes.LCURLY) {
053: * requiredColumn += indentLevel;
054: * }
055: *
056: * while (parent != null && parent instanceof Class) {
057: * lassLevel++;
058: * parent = parent.getParent();
059: * }
060: * </pre>
061: *
062: * <p>
063: * If large expressions (e.g. in if-Statements) are longer than a single line it
064: * is recommended to use a different indentation. This is useful for
065: * distinguishing these expressions from the following Java blocks. In the same
066: * way the indentation of long assignment values, parameter, lists, long chains
067: * of method calls and throws-statement should be handled. This different
068: * indentation is specified by the parameter <i>expression-indentation-level
069: * </i>. Examples (with <i>expression-indentation-level </i>= 4):
070: * </p>
071: *
072: * <pre>
073: * if (current.getType() == JavaTokenTypes.LPAREN && parenthesisLevel++ == 0) {
074: * requiredColumn += exprIndentLevel;
075: * }
076: *
077: * check((Token) aClass.getAst().getFirstToken(), getClassLevel(aClass),
078: * (Token) aClass.getAst().getLastToken());
079: * </pre>
080: *
081: * @author Jochen Skulj
082: * @version $Revision: 1.4 $
083: */
084: public class IndentationRule extends InspectorBase implements
085: Parameterizable {
086:
087: /**
088: * parameter name for the standard indentation
089: */
090: public static final String PARAMETER_INDENT = "standard-indentation-level";
091:
092: /**
093: * parameter name for the expression indentation
094: */
095: public static final String PARAMETER_EXPR_INDENT = "expression-indentation-level";
096:
097: /**
098: * standard indentation
099: */
100: private int indentLevel;
101:
102: /**
103: * expression indentation
104: */
105: private int exprIndentLevel;
106:
107: /**
108: * source marker for reporting violating tokens
109: */
110: private SimpleSourceMarker sourceMarker;
111:
112: /**
113: * constructor
114: */
115: public IndentationRule() {
116: indentLevel = 2;
117: exprIndentLevel = 4;
118: }
119:
120: /**
121: * checks indentation of tokens of a class
122: *
123: * @param aClass
124: * class to inspect
125: */
126: public void visit(Class aClass) {
127: initSourceMarker(aClass);
128: check((Token) aClass.getAst().getFirstToken(),
129: getClassLevel(aClass), (Token) aClass.getAst()
130: .getLastToken());
131: }
132:
133: /**
134: * checks indentation of tokens of an interface
135: *
136: * @param anInterface
137: * interface to inspect
138: */
139: public void visit(Interface anInterface) {
140: check((Token) anInterface.getAst().getFirstToken(),
141: getClassLevel(anInterface), (Token) anInterface
142: .getAst().getLastToken());
143: }
144:
145: /**
146: * initializes the source marker for the current language element
147: *
148: * @param anElement
149: * current language element
150: */
151: protected void initSourceMarker(LanguageElement anElement) {
152: sourceMarker = new SimpleSourceMarker(anElement);
153: CompilationUnit unit = anElement.getCompilationUnit();
154: Package pack = unit.getPackage();
155: if (pack.getName().length() == 0) {
156: sourceMarker.setSourceURL(unit.getName());
157: } else {
158: sourceMarker.setSourceURL(pack.getName().replace('.',
159: File.separatorChar)
160: + File.separator + unit.getName());
161: }
162: }
163:
164: /**
165: * counts the parent classes
166: *
167: * @param anElement
168: * current class or interface
169: * @return number of parent classes
170: */
171: protected int getClassLevel(LanguageElement anElement) {
172: int classLevel = 0;
173: LanguageElement parent = anElement.getParent();
174: while (parent != null && parent instanceof Class) {
175: classLevel++;
176: parent = parent.getParent();
177: }
178: return classLevel;
179: }
180:
181: /**
182: * checks the indentation of a set of tokens
183: *
184: * @param firstToken
185: * first token to inspect
186: * @param classLevel
187: * number of surrounding classes (if current language element is an
188: * inner class or interface
189: * @param lastToken
190: * last token to inspect
191: */
192: protected void check(Token firstToken, int classLevel,
193: Token lastToken) {
194: Token current = firstToken;
195: if (current != null) {
196: int line = current.getLine();
197: int requiredColumn = 1 + (classLevel * indentLevel);
198: int parenthesisLevel = 0;
199: boolean assignment = false;
200: do {
201: // check, if a RCURLY affects the required indentation
202: if (current.getType() == JavaTokenTypes.RCURLY) {
203: requiredColumn -= indentLevel;
204: }
205: // check, if the begin of an assignment affects the possible indentation
206: if (current.getType() == JavaTokenTypes.ASSIGN
207: && !assignment) {
208: assignment = true;
209: requiredColumn += exprIndentLevel;
210: }
211: // check, if the begin of an assignment affects the possible indentation
212: if (current.getType() == JavaTokenTypes.SEMI
213: && assignment) {
214: assignment = false;
215: requiredColumn -= exprIndentLevel;
216: }
217: // if a new line is reached check indentation
218: if (current.getLine() > line) {
219: // continue with standard indentation checks if the current line
220: // does not begin with a throws literal or a method call outside of an
221: // assignment
222: if (current.getType() != JavaTokenTypes.LITERAL_throws
223: && current.getType() != JavaTokenTypes.DOT) {
224: if (current.getColumn() != requiredColumn) {
225: sourceMarker.setLine(current.getLine());
226: sourceMarker.setColumn(current.getColumn());
227: context.reportViolation(sourceMarker);
228: }
229: } else {
230: // special check for throws literals and a method calls
231: int specialRequiredColumn = assignment ? requiredColumn
232: : requiredColumn + exprIndentLevel;
233: if (current.getColumn() != specialRequiredColumn) {
234: sourceMarker.setLine(current.getLine());
235: sourceMarker.setColumn(current.getColumn());
236: context.reportViolation(sourceMarker);
237: }
238: }
239: line = current.getLine();
240: }
241: // check, if a RCURLY affects the required indentation
242: if (current.getType() == JavaTokenTypes.LCURLY) {
243: requiredColumn += indentLevel;
244: }
245: // check, if an expressions in parenthesises affects the required
246: // indentation
247: if (current.getType() == JavaTokenTypes.LPAREN
248: && parenthesisLevel++ == 0) {
249: requiredColumn += exprIndentLevel;
250: }
251: if (current.getType() == JavaTokenTypes.RPAREN
252: && --parenthesisLevel == 0) {
253: requiredColumn -= exprIndentLevel;
254: }
255: current = current.getNextNonWhiteSpaceToken();
256: } while (current != null && current != lastToken);
257: }
258: }
259:
260: /**
261: * sets parameters for the inspector
262: *
263: * @param aName
264: * parameter name
265: * @param aParameter
266: * parameter value
267: */
268: public boolean setParameter(String aName, Object aValue)
269: throws ConfigurationException {
270: boolean parameterSupported = false;
271: if (aName.equals(PARAMETER_INDENT)) {
272: parameterSupported = true;
273: indentLevel = ((Integer) aValue).intValue();
274: }
275: if (aName.equals(PARAMETER_EXPR_INDENT)) {
276: parameterSupported = true;
277: exprIndentLevel = ((Integer) aValue).intValue();
278: }
279: if (!parameterSupported) {
280: throw new ConfigurationException("Parameter '" + aName
281: + "' is not supported by " + getClass().getName());
282: }
283: return true;
284: }
285: }
|