001: package tide.syntaxtree;
002:
003: import javaparser.*;
004: import tide.editor.*;
005: import tide.sources.*;
006: import snow.concurrent.*;
007: import java.util.*;
008: import java.io.*;
009: import java.awt.*;
010: import java.awt.event.*;
011: import javax.swing.*;
012: import javax.swing.text.*;
013: import javax.swing.tree.*;
014:
015: /** Scans tree parts and look for problems !
016: * Important: only detect problems not detected by the compiler (1.6)
017: * for example public classes names should be the name of the file => already checked by the compiler
018: */
019: public final class ProblemsSearch {
020: // TODO: explain and allow to turn on/off
021:
022: public static boolean detectMethodNameCase = false;
023: public static boolean detectClassNameCase = false;
024: public static boolean detectPagkageNameCase = false;
025:
026: public static boolean detectWrongPagkageName = false;
027: public static boolean detectFalsePublicName = true;
028:
029: public static boolean detectEmptyPackageImport = false;
030: public static boolean detectNoPublicTopLevelType = false;
031: public static boolean detectEmptyClass = false;
032:
033: public static boolean detectMethodsWithSameNameAsClass = true;
034: public static boolean detectFieldWithSameNameAsClass = true;
035:
036: public static boolean detectMultiplePublicTopLevelTypes = true;
037: public static boolean detectUnusefulSemicolons = true;
038:
039: private ProblemsSearch() {
040: }
041:
042: /** checks
043: 1) public type name according to file name
044: 2) package name according to file structure
045: 3) package name lowercase
046:
047: remark: some checks and warnings are already made and added in the SimplifiedSyntaxTree.
048:
049: @param javaName is aprioriJavaName, based on the filename
050: */
051: public static void analyseFile(SimplifiedSyntaxTree2 st,
052: String javaName) {
053: // 1
054: //
055: if (st.publicTopLevelType != null) {
056: String topLevelTypeName = st.publicTopLevelType
057: .getTypeRelativeName();
058: String javaFilePartName = st.aprioriJavaName;
059: int posPt = javaFilePartName.lastIndexOf('.');
060: if (posPt >= 0) {
061: javaFilePartName = javaFilePartName
062: .substring(posPt + 1);
063: }
064:
065: if (detectFalsePublicName
066: && !javaFilePartName.equals(topLevelTypeName)) {
067: WarningNode wn = new WarningNode(
068: "Name error, type name is " + topLevelTypeName
069: + " but file name is "
070: + javaFilePartName, 1);
071: wn.takeBoundsFrom(st.publicTopLevelType);
072: st.addWarning(wn);
073: }
074: } else if (!(javaName.equals("package-info") || javaName
075: .endsWith(".package-info"))) {
076: if (detectNoPublicTopLevelType) {
077: WarningNode wn = new WarningNode(
078: "no public top level type", 2);
079: wn.takeBoundsFrom(st.packageNode);
080: st.addWarning(wn);
081: }
082: }
083:
084: // 2
085: //
086: String packageName = javaName;
087: int posPt = packageName.lastIndexOf('.');
088: if (posPt > 0) {
089: packageName = packageName.substring(0, posPt);
090: } else {
091: // unnamed scope in the root
092: packageName = "";
093: }
094:
095: if (detectWrongPagkageName
096: && !st.packageNode.packageName.equals(packageName)) {
097: WarningNode wn = new WarningNode("Wrong package name\""
098: + st.packageNode.packageName + "\", should be \""
099: + packageName + "\"", 1);
100: wn.takeBoundsFrom(st.packageNode);
101: st.addWarning(wn);
102: }
103:
104: if (detectPagkageNameCase
105: && !packageName.toLowerCase().equals(packageName)) {
106: WarningNode wn = new WarningNode(
107: "Package names should be lowercase: " + packageName,
108: 2);
109: wn.takeBoundsFrom(st.packageNode);
110: st.addWarning(wn);
111: }
112:
113: // imports.
114: for (int i = 0; i < st.importsNode.getChildCount(); i++) {
115: ImportNode in = (ImportNode) st.importsNode
116: .getChildNodeAt(i);
117: String iname = in.getImportName();
118: if (iname.endsWith(".*")) {
119: iname = iname.substring(0, iname.length() - 2);
120: }
121: FileItem ti = MainEditorFrame.instance.sourcesTreePanel
122: .getTreeModel().getPackage(iname);
123:
124: if (ti != null) {
125: if (!ti.isJavaFile()) {
126: if (detectEmptyPackageImport && isEmptyPackage(ti)) {
127: // sometimes NOT compiling when compiled separately !
128: WarningNode wn = new WarningNode(
129: "Importing empty package " + iname, 1);
130: wn.takeBoundsFrom(in);
131: st.addWarning(wn);
132: }
133: }
134: }
135:
136: }
137: }
138:
139: /** The compiler is (sometimes) not happy when importing empty packages.
140: */
141: public static boolean isEmptyPackage(FileItem ti) {
142: for (int i = 0; i < ti.getChildCount(); i++) {
143: FileItem tic = (FileItem) ti.getChildAt(i);
144: if (tic.isJavaFile())
145: return false;
146: }
147: return true;
148: }
149:
150: /** class or interface problem analysis.
151: * Only called when the checkbox "enable parser warnings" is on.
152: */
153: public static void analyseClass(SimplifiedSyntaxTree2 st,
154: ClassNode cn) {
155: String className = cn.name;
156:
157: if (detectClassNameCase
158: && Character.isLowerCase(className.charAt(0))) {
159: WarningNode wn = new WarningNode("class " + className
160: + " should not start with a lowercase letter", 1);
161: wn.takeBoundsFrom(cn);
162: st.addWarning(wn);
163: }
164:
165: // detect empty classes
166: //
167: if (detectEmptyClass && cn.getChildCount() == 0) {
168: if (!cn.classOrInterface.equals("interface")) // let empty interfaces, they are often used as markers.
169: {
170: WarningNode wn = new WarningNode("empty class "
171: + className, 2);
172: wn.takeBoundsFrom(cn);
173: st.addWarning(wn);
174: // new Throwable().printStackTrace();
175: }
176: }
177:
178: // collect all childs
179: //
180: Collection<ParserTreeNode> allMethodsAndFields = TreeUtils
181: .getAllMethodsAndFieldsForType(cn);
182:
183: // look for methods or fields with the same name as the constructor
184: //
185: for (ParserTreeNode pt : allMethodsAndFields) {
186: if (pt instanceof MethodNode) {
187: MethodNode mn = (MethodNode) pt;
188: if (detectMethodsWithSameNameAsClass
189: && mn.name.equals(className)) {
190: WarningNode wn = new WarningNode(
191: "Method should not have the same name as the class: "
192: + className, 1);
193: wn.takeBoundsFrom(mn);
194: st.addWarning(wn);
195: }
196: } else if (pt instanceof FieldNode) {
197: FieldNode mn = (FieldNode) pt;
198: if (detectFieldWithSameNameAsClass
199: && mn.name.equals(className)) {
200: WarningNode wn = new WarningNode(
201: "Field with same name as class found: "
202: + className, 1);
203: wn.takeBoundsFrom(mn);
204: st.addWarning(wn);
205: }
206: }
207: }
208:
209: // look for methods and fields with uppercase letter
210: //
211: if (detectMethodNameCase) {
212: for (ParserTreeNode pt : allMethodsAndFields) {
213: if (pt instanceof MethodNode) {
214: MethodNode mn = (MethodNode) pt;
215:
216: if (Character.isUpperCase(mn.name.charAt(0))) {
217: WarningNode wn = new WarningNode(
218: "Method name starts with an uppercase: "
219: + mn.name, 2);
220: wn.takeBoundsFrom(mn);
221: st.addWarning(wn);
222: }
223: }
224: /* else if(pt instanceof FieldNode)
225: {
226: FieldNode mn = (FieldNode) pt;
227:
228: boolean finalStatic = ArrayUtils.contains(mn.modifiers, JavaParserConstants.STATIC)
229: && ArrayUtils.contains(mn.modifiers, JavaParserConstants.FINAL);
230: if(!finalStatic)
231: {
232: if(Character.isUpperCase( mn.name.charAt(0) ))
233: {
234: WarningNode wn = new WarningNode("only static final fields should be uppercased");
235: wn.takeBoundsFrom( mn );
236: st.addWarning(wn);
237: }
238: }
239: }*/
240:
241: }
242: }
243:
244: // Help GC !
245: allMethodsAndFields.clear();
246: }
247:
248: /* Very dangerous: 010 is 8, because integers starting with 0 are represented as octals !
249: *
250: public static void searchOctalRelpresentations()
251: {
252: //[^0-9a-z.,\"\'_\[\\:#]0\d+[^#.,:a-z\'\"]
253: // + ignore if in litteral
254: // + ignore if in comments
255: // + ignore if of type \de[-+]
256: }*/
257:
258: }
|