001: /* Soot - a J*va Optimization Framework
002: * Copyright (C) 1997-1999 Raja Vallee-Rai
003: *
004: * This library is free software; you can redistribute it and/or
005: * modify it under the terms of the GNU Lesser General Public
006: * License as published by the Free Software Foundation; either
007: * version 2.1 of the License, or (at your option) any later version.
008: *
009: * This library is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012: * Lesser General Public License for more details.
013: *
014: * You should have received a copy of the GNU Lesser General Public
015: * License along with this library; if not, write to the
016: * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
017: * Boston, MA 02111-1307, USA.
018: */
019:
020: package soot.toolkits.astmetrics;
021:
022: import soot.G;
023: import soot.options.*;
024: import polyglot.ast.*;
025: import polyglot.visit.NodeVisitor;
026:
027: import java.util.*;
028: import java.io.*;
029:
030: /**
031: * @author Michael Batchelder
032: *
033: * Created on 5-Mar-2006
034: */
035: public class IdentifiersMetric extends ASTMetric {
036:
037: double nameComplexity = 0;
038: double nameCount = 0;
039:
040: int dictionarySize = 0;
041: ArrayList<String> dictionary;
042:
043: //cache of names so no recomputation
044: HashMap<String, Double> names;
045:
046: /**
047: * @param astNode
048: *
049: * This metric will take a measure of the "complexity" of each identifier used
050: * within the program. An identifier's complexity is computed as follows:
051: *
052: * First the alpha tokens are parsed by splitting on non-alphas and capitals:
053: *
054: * example identifier: getASTNode alpha tokens: get, AST, Node
055: * example identifier: ___Junk$$name alpha tokens: Junk, name)
056: *
057: * The alpha tokens are then counted and a 'token complexity' is formed by the ratio
058: * of total tokens to the number of tokens found in the dictionary:
059: *
060: * example identifier: getASTNode Total: 3, Found: 2, Complexity: 1.5
061: *
062: * Then the 'character complexity' is computed, which is a ratio of total number of
063: * characters to the number of non-complex characters. Non-complex characters are
064: * those which are NOT part of a multiple string of non-alphas.
065: *
066: * example identifier: ___Junk$$name complex char strings: '___', '$$'
067: * number of non-complex (Junk + name): 8, total: 13, Complexity: 1.625
068: *
069: * Finally, the total identifier complexity is the sum of the token and character
070: * complexities multipled by the 'importance' of an identifier:
071: *
072: * Multipliers are as follows:
073: *
074: * Class multiplier = 3;
075: * Method multiplier = 4;
076: * Field multiplier = 2;
077: * Formal multiplier = 1.5;
078: * Local multiplier = 1;
079: *
080: */
081: public IdentifiersMetric(Node astNode) {
082: super (astNode);
083:
084: initializeDictionary();
085: }
086:
087: private void initializeDictionary() {
088: String line;
089: BufferedReader br;
090: dictionary = new ArrayList<String>();
091: names = new HashMap<String, Double>();
092:
093: InputStream is = ClassLoader
094: .getSystemResourceAsStream("mydict.txt");
095: if (is != null) {
096: br = new BufferedReader(new InputStreamReader(is));
097:
098: try {
099: while ((line = br.readLine()) != null)
100: addWord(line);
101: } catch (IOException ioexc) {
102: }
103: }
104:
105: is = ClassLoader
106: .getSystemResourceAsStream("soot/toolkits/astmetrics/dict.txt");
107: if (is != null) {
108: br = new BufferedReader(new InputStreamReader(is));
109:
110: try {
111: while ((line = br.readLine()) != null)
112: addWord(line.trim().toLowerCase());
113: } catch (IOException ioexc) {
114: }
115: }
116:
117: if ((dictionarySize = dictionary.size()) == 0)
118: G.v().out.println("Error reading in dictionary file(s)");
119: else if (Options.v().verbose())
120: G.v().out.println("Read " + dictionarySize
121: + " words in from dictionary file(s)");
122: }
123:
124: private void addWord(String word) {
125: if (dictionarySize == 0
126: || word.compareTo(dictionary.get(dictionarySize - 1)) > 0) {
127: dictionary.add(word);
128: } else {
129: int i = 0;
130: while (i < dictionarySize
131: && word.compareTo(dictionary.get(i)) > 0)
132: i++;
133:
134: if (word.compareTo(dictionary.get(i)) == 0)
135: return;
136:
137: dictionary.add(i, word);
138: }
139:
140: dictionarySize++;
141: }
142:
143: /* (non-Javadoc)
144: * @see soot.toolkits.astmetrics.ASTMetric#reset()
145: */
146: public void reset() {
147: nameComplexity = 0;
148: nameCount = 0;
149: }
150:
151: /* (non-Javadoc)
152: * @see soot.toolkits.astmetrics.ASTMetric#addMetrics(soot.toolkits.astmetrics.ClassData)
153: */
154: public void addMetrics(ClassData data) {
155: data.addMetric(new MetricData("NameComplexity", new Double(
156: nameComplexity)));
157: data.addMetric(new MetricData("NameCount",
158: new Double(nameCount)));
159: }
160:
161: public NodeVisitor enter(Node parent, Node n) {
162: double multiplier = 1;
163: String name = null;
164: if (n instanceof ClassDecl) {
165: name = ((ClassDecl) n).name();
166: multiplier = 3;
167: nameCount++;
168: } else if (n instanceof MethodDecl) {
169: name = ((MethodDecl) n).name();
170: multiplier = 4;
171: nameCount++;
172: } else if (n instanceof FieldDecl) {
173: name = ((FieldDecl) n).name();
174: multiplier = 2;
175: nameCount++;
176: } else if (n instanceof Formal) { // this is locals and formals
177: name = ((Formal) n).name();
178: multiplier = 1.5;
179: nameCount++;
180: } else if (n instanceof LocalDecl) { // this is locals and formals
181: name = ((LocalDecl) n).name();
182: nameCount++;
183: }
184:
185: if (name != null) {
186: nameComplexity += (multiplier * computeNameComplexity(name));
187: }
188: return enter(n);
189: }
190:
191: private double computeNameComplexity(String name) {
192: if (names.containsKey(name))
193: return names.get(name).doubleValue();
194:
195: ArrayList<String> strings = new ArrayList<String>();
196:
197: // throw out non-alpha characters
198: String tmp = "";
199: for (int i = 0; i < name.length(); i++) {
200: char c = name.charAt(i);
201: if ((c > 64 && c < 91) || (c > 96 && c < 123)) {
202: tmp += c;
203: } else if (tmp.length() > 0) {
204: strings.add(tmp);
205: tmp = "";
206: }
207: }
208: if (tmp.length() > 0)
209: strings.add(tmp);
210:
211: ArrayList<String> tokens = new ArrayList<String>();
212: for (int i = 0; i < strings.size(); i++) {
213: tmp = strings.get(i);
214: while (tmp.length() > 0) {
215: int caps = countCaps(tmp);
216: if (caps == 0) {
217: int idx = findCap(tmp);
218: if (idx > 0) {
219: tokens.add(tmp.substring(0, idx));
220: tmp = tmp.substring(idx, tmp.length());
221: } else {
222: tokens.add(tmp.substring(0, tmp.length()));
223: break;
224: }
225: } else if (caps == 1) {
226: int idx = findCap(tmp.substring(1)) + 1;
227: if (idx > 0) {
228: tokens.add(tmp.substring(0, idx));
229: tmp = tmp.substring(idx, tmp.length());
230: } else {
231: tokens.add(tmp.substring(0, tmp.length()));
232: break;
233: }
234: } else {
235: if (caps < tmp.length()) {
236: // count seq of capitals as one token
237: tokens.add(tmp.substring(0, caps - 1)
238: .toLowerCase());
239: tmp = tmp.substring(caps);
240: } else {
241: tokens
242: .add(tmp.substring(0, caps)
243: .toLowerCase());
244: break;
245: }
246: }
247: }
248: }
249:
250: double words = 0;
251: double complexity = 0;
252: for (int i = 0; i < tokens.size(); i++)
253: if (dictionary.contains(tokens.get(i)))
254: words++;
255:
256: if (words > 0)
257: complexity = (tokens.size()) / words;
258:
259: names.put(name, new Double(complexity
260: + computeCharComplexity(name)));
261:
262: return complexity;
263: }
264:
265: private double computeCharComplexity(String name) {
266: int count = 0, index = 0, last = 0, lng = name.length();
267: while (index < lng) {
268: char c = name.charAt(index);
269: if ((c < 65 || c > 90) && (c < 97 || c > 122)) {
270: last++;
271: } else {
272: if (last > 1)
273: count += last;
274: last = 0;
275: }
276: index++;
277: }
278:
279: double complexity = lng - count;
280:
281: if (complexity > 0)
282: return ((lng) / complexity);
283: else
284: return lng;
285: }
286:
287: /*
288: * @author Michael Batchelder
289: *
290: * Created on 6-Mar-2006
291: *
292: * @param name string to parse
293: * @return number of leading capital letters
294: */
295: private int countCaps(String name) {
296: int caps = 0;
297: while (caps < name.length()) {
298: char c = name.charAt(caps);
299: if (c > 64 && c < 91)
300: caps++;
301: else
302: break;
303: }
304:
305: return caps;
306: }
307:
308: /*
309: * @author Michael Batchelder
310: *
311: * Created on 6-Mar-2006
312: *
313: * @param name string to parse
314: * @return index of first capital letter
315: */
316: private int findCap(String name) {
317: int idx = 0;
318: while (idx < name.length()) {
319: char c = name.charAt(idx);
320: if (c > 64 && c < 91)
321: return idx;
322: else
323: idx++;
324: }
325:
326: return -1;
327: }
328: }
|