001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.modules.java.hints;
042:
043: import com.sun.source.tree.AssignmentTree;
044: import com.sun.source.tree.ExpressionTree;
045: import com.sun.source.tree.IdentifierTree;
046: import com.sun.source.tree.MemberSelectTree;
047: import com.sun.source.tree.MethodInvocationTree;
048: import com.sun.source.tree.Tree.Kind;
049: import com.sun.source.tree.VariableTree;
050: import com.sun.source.util.TreePath;
051: import java.io.IOException;
052: import java.util.ArrayList;
053: import java.util.Arrays;
054: import java.util.Collections;
055: import java.util.EnumSet;
056: import java.util.HashSet;
057: import java.util.List;
058: import java.util.Set;
059: import java.util.logging.Level;
060: import java.util.logging.Logger;
061: import javax.lang.model.element.Element;
062: import javax.lang.model.element.ElementKind;
063: import javax.lang.model.element.ExecutableElement;
064: import org.netbeans.api.java.source.CompilationInfo;
065: import org.netbeans.modules.java.editor.semantic.Utilities;
066: import org.netbeans.modules.java.hints.spi.AbstractHint;
067: import org.netbeans.spi.editor.hints.ErrorDescription;
068: import org.netbeans.spi.editor.hints.ErrorDescriptionFactory;
069: import org.openide.util.Exceptions;
070: import org.openide.util.NbBundle;
071:
072: /**
073: *
074: * @author Jan Lahoda
075: */
076: public class SuspiciousNamesCombination extends AbstractHint {
077:
078: /** Creates a new instance of SuspiciousNamesCombination */
079: public SuspiciousNamesCombination() {
080: super (false, false, AbstractHint.HintSeverity.WARNING);
081: }
082:
083: public Set<Kind> getTreeKinds() {
084: return EnumSet.of(Kind.METHOD_INVOCATION, Kind.ASSIGNMENT,
085: Kind.VARIABLE);
086: }
087:
088: public List<ErrorDescription> run(CompilationInfo info,
089: TreePath treePath) {
090: switch (treePath.getLeaf().getKind()) {
091: case METHOD_INVOCATION:
092: return handleMethodInvocation(info, treePath);
093: case ASSIGNMENT:
094: return handleAssignment(info, treePath);
095: case VARIABLE:
096: return handleVariable(info, treePath);
097: default:
098: return null;
099: }
100: }
101:
102: public void cancel() {
103: // XXX implement me
104: }
105:
106: private List<ErrorDescription> handleMethodInvocation(
107: CompilationInfo info, TreePath treePath) {
108: Element el = info.getTrees().getElement(treePath);
109:
110: if (el == null
111: || (el.getKind() != ElementKind.CONSTRUCTOR && el
112: .getKind() != ElementKind.METHOD)) {
113: return null;
114: }
115:
116: MethodInvocationTree mit = (MethodInvocationTree) treePath
117: .getLeaf();
118: ExecutableElement ee = (ExecutableElement) el;
119:
120: if (ee.getParameters().size() != mit.getArguments().size()) {
121: //should not happen?
122: return null;
123: }
124:
125: List<ErrorDescription> result = new ArrayList<ErrorDescription>();
126:
127: for (int cntr = 0; cntr < ee.getParameters().size(); cntr++) {
128: String declarationName = ee.getParameters().get(cntr)
129: .getSimpleName().toString();
130: ExpressionTree arg = mit.getArguments().get(cntr);
131: String actualName = getName(arg);
132:
133: if (isConflicting(declarationName, actualName)) {
134: long start = info.getTrees().getSourcePositions()
135: .getStartPosition(info.getCompilationUnit(),
136: arg);
137: long end = info.getTrees().getSourcePositions()
138: .getEndPosition(info.getCompilationUnit(), arg);
139:
140: if (start != (-1) && end != (-1)) {
141: result.add(ErrorDescriptionFactory
142: .createErrorDescription(getSeverity()
143: .toEditorSeverity(),
144: "Suspicious names combination",
145: info.getFileObject(), (int) start,
146: (int) end));
147: }
148: }
149: }
150:
151: return result;
152: }
153:
154: private List<ErrorDescription> handleAssignment(
155: CompilationInfo info, TreePath treePath) {
156: AssignmentTree at = (AssignmentTree) treePath.getLeaf();
157:
158: String declarationName = getName(at.getVariable());
159: String actualName = getName(at.getExpression());
160:
161: if (isConflicting(declarationName, actualName)) {
162: long start = info.getTrees().getSourcePositions()
163: .getStartPosition(info.getCompilationUnit(),
164: at.getVariable());
165: long end = info.getTrees().getSourcePositions()
166: .getEndPosition(info.getCompilationUnit(),
167: at.getVariable());
168:
169: if (start != (-1) && end != (-1)) {
170: return Collections
171: .singletonList(ErrorDescriptionFactory
172: .createErrorDescription(getSeverity()
173: .toEditorSeverity(),
174: "Suspicious names combination",
175: info.getFileObject(),
176: (int) start, (int) end));
177: }
178: }
179:
180: return null;
181: }
182:
183: private List<ErrorDescription> handleVariable(CompilationInfo info,
184: TreePath treePath) {
185: VariableTree vt = (VariableTree) treePath.getLeaf();
186:
187: if (vt.getName() == null)
188: return null;
189:
190: String declarationName = vt.getName().toString();
191: String actualName = getName(vt.getInitializer());
192:
193: if (isConflicting(declarationName, actualName)) {
194: int[] span = info.getTreeUtilities().findNameSpan(vt);
195:
196: if (span != null) {
197: String description = NbBundle.getMessage(
198: SuspiciousNamesCombination.class,
199: "HINT_SuspiciousNamesCombination");
200:
201: return Collections
202: .singletonList(ErrorDescriptionFactory
203: .createErrorDescription(getSeverity()
204: .toEditorSeverity(),
205: description, info
206: .getFileObject(),
207: span[0], span[1]));
208: }
209: }
210:
211: return null;
212: }
213:
214: static String getName(ExpressionTree et) {
215: if (et == null)
216: return null;
217:
218: switch (et.getKind()) {
219: case IDENTIFIER:
220: return ((IdentifierTree) et).getName().toString();
221: case METHOD_INVOCATION:
222: return getName(((MethodInvocationTree) et)
223: .getMethodSelect());
224: case MEMBER_SELECT:
225: return ((MemberSelectTree) et).getIdentifier().toString();
226: default:
227: return null;
228: }
229: }
230:
231: private boolean isConflicting(String declarationName,
232: String actualName) {
233: if (declarationName == null || actualName == null)
234: return false;
235:
236: int declarationCat = findCategory(declarationName);
237: int actualCat = findCategory(actualName);
238:
239: return declarationCat != actualCat && declarationCat != (-1)
240: && actualCat != (-1);
241: }
242:
243: private int findCategory(String name) {
244: Set<String> broken = breakName(name);
245: int index = 0;
246:
247: for (List<String> names : NAME_CATEGORIES) {
248: Set<String> copy = new HashSet<String>(names);
249:
250: copy.retainAll(broken);
251:
252: if (!copy.isEmpty()) {
253: return index;
254: }
255:
256: index++;
257: }
258:
259: return -1;
260: }
261:
262: static Set<String> breakName(String name) {
263: Set<String> result = new HashSet<String>();
264: int wordStartOffset = 0;
265: int index = 0;
266:
267: while (index < name.length()) {
268: if (Character.isUpperCase(name.charAt(index))) {
269: //starting new word:
270: if (wordStartOffset < index) {
271: result.add(name.substring(wordStartOffset, index)
272: .toLowerCase());
273: }
274: wordStartOffset = index;
275: }
276:
277: if (name.charAt(index) == '-') {
278: //starting new word:
279: if (wordStartOffset < index) {
280: result.add(name.substring(wordStartOffset, index)
281: .toLowerCase());
282: }
283: wordStartOffset = index + 1;
284: }
285:
286: index++;
287: }
288:
289: if (wordStartOffset < index) {
290: result.add(name.substring(wordStartOffset, index)
291: .toLowerCase());
292: }
293:
294: return result;
295: }
296:
297: private List<List<String>> NAME_CATEGORIES = Arrays.asList(Arrays
298: .asList("x", "width"), //NOI18N
299: Arrays.asList("y", "height") //NOI18N
300: );
301:
302: public String getId() {
303: return SuspiciousNamesCombination.class.getName();
304: }
305:
306: public String getDisplayName() {
307: return NbBundle.getMessage(SuspiciousNamesCombination.class,
308: "DN_SuspiciousNamesCombination");
309: }
310:
311: public String getDescription() {
312: return NbBundle.getMessage(SuspiciousNamesCombination.class,
313: "DESC_SuspiciousNamesCombination");
314: }
315:
316: }
|