KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > ruby > RubyIndexer


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19 package org.netbeans.modules.ruby;
20
21 import java.io.IOException JavaDoc;
22 import java.util.Collections JavaDoc;
23 import java.util.EnumSet JavaDoc;
24 import java.util.HashMap JavaDoc;
25 import java.util.HashSet JavaDoc;
26 import java.util.List JavaDoc;
27 import java.util.Map JavaDoc;
28 import java.util.Set JavaDoc;
29
30 import org.jruby.ast.ClassNode;
31 import org.jruby.ast.Node;
32 import org.netbeans.api.gsf.Element;
33 import org.netbeans.api.gsf.ElementKind;
34 import org.netbeans.api.gsf.Index;
35 import org.netbeans.api.gsf.Indexer;
36 import org.netbeans.api.gsf.Modifier;
37 import org.netbeans.api.gsf.ParserFile;
38 import org.netbeans.api.gsf.ParserResult;
39 import org.netbeans.editor.BaseDocument;
40 import org.netbeans.modules.ruby.elements.AstElement;
41 import org.netbeans.modules.ruby.elements.ClassElement;
42 import org.openide.filesystems.FileObject;
43 import org.openide.util.Exceptions;
44
45
46 /**
47  * @todo Index global variables
48  * @todo Think about searching for modules; I will now hit EVERY class that refers to it
49  * (as their parent) whereas I really only want to hit individual module entries, right?
50  * @todo Do I index anything outside of module or classnodes? How do these get deleted?
51  *
52  * @author Tor Norbye
53  */

54 public class RubyIndexer implements Indexer {
55     private static final boolean INDEX_UNDOCUMENTED = Boolean.getBoolean("ruby.index.undocumented");
56     private static final boolean PREINDEXING = Boolean.getBoolean("gsf.preindexing");
57
58     // Class/Module Document
59
static final String JavaDoc FIELD_EXTENDS_NAME = "extends"; //NOI18N
60
static final String JavaDoc FIELD_FQN_NAME = "fqn"; //NOI18N
61
static final String JavaDoc FIELD_IN = "in"; //NOI18N
62
static final String JavaDoc FIELD_FILENAME = "source"; // NOI18N
63
static final String JavaDoc FIELD_CLASS_NAME = "class"; //NOI18N
64
static final String JavaDoc FIELD_CASE_INSENSITIVE_CLASS_NAME = "class-ig"; //NOI18N
65
static final String JavaDoc FIELD_REQUIRE = "require"; //NOI18N
66
static final String JavaDoc FIELD_REQUIRES = "requires"; //NOI18N
67
static final String JavaDoc FIELD_INCLUDES = "includes"; //NOI18N
68

69     /** Attributes: "i" -> private, "o" -> protected, ", "s" - static/notinstance, "d" - documented */
70     static final String JavaDoc FIELD_METHOD_NAME = "method"; //NOI18N
71

72     /** Attributes: "i" -> private, "o" -> protected, ", "s" - static/notinstance, "d" - documented */
73     static final String JavaDoc FIELD_FIELD_NAME = "field"; //NOI18N
74
static final String JavaDoc FIELD_ATTRIBUTE_NAME = "attribute"; //NOI18N
75
static final String JavaDoc FIELD_CONSTANT_NAME = "constant"; //NOI18N
76

77     /** Attributes: "c" -> class, "m" -> module, "d" -> documented, "d(nnn)" documented with n characters */
78     static final String JavaDoc FIELD_CLASS_ATTRS = "attrs"; //NOI18N
79
// TODO: Add class info to tell whether methods are static
80

81     // Method Document
82
//static final String FIELD_PARAMS = "params"; //NOI18N
83
//static final String FIELD_RDOC = "rdoc"; //NOI18N
84
//
85
/** Attributes: "i" -> private, "o" -> protected, ", "s" - static/notinstance, "D" - documented */
86     //static final String FIELD_METHOD_ATTRS = "mattrs"; //NOI18N
87
// TODO: Return types
88

89     // No point doing case insensitive indexing of method names since in Ruby
90
// they all tend to be fully lowercase anyway
91
//static final String FIELD_CASE_INSENSITIVE_METHOD_NAME = "method-ig"; //NOI18N
92
public RubyIndexer() {
93     }
94
95     public void updateIndex(Index index, ParserResult result)
96         throws IOException JavaDoc {
97         Node root = AstUtilities.getRoot(result);
98         RubyParseResult r = (RubyParseResult)result;
99
100         if (root == null) {
101             return;
102         }
103
104         // Don't update the index when we've sanitized the source
105
// (modified it on parse error in order to try to get a valid parse result
106
// but possibly removing valid structure from the program)
107
if (r.isSanitizedSource()) {
108             return;
109         }
110
111         TreeAnalyzer analyzer = new TreeAnalyzer(index, r);
112         analyzer.analyze();
113     }
114
115     public boolean isIndexable(ParserFile file) {
116         //return file.getExtension().equalsIgnoreCase("rb");
117
return file.getNameExt().endsWith(".rb");
118     }
119
120     private static String JavaDoc getModifierString(Set JavaDoc<Modifier> modifiers) {
121         if (modifiers.contains(Modifier.STATIC)) {
122             if (modifiers.contains(Modifier.PRIVATE)) {
123                 return "si";
124             } else if (modifiers.contains(Modifier.PROTECTED)) {
125                 return "so";
126             } else {
127                 return "s";
128             }
129         } else if (modifiers.contains(Modifier.PRIVATE)) {
130             return "i";
131         } else if (modifiers.contains(Modifier.PROTECTED)) {
132             return "o";
133         } else {
134             return null;
135         }
136     }
137
138     public static Set JavaDoc<Modifier> getModifiersFromString(String JavaDoc modifierString, int startIndex) {
139         Set JavaDoc<Modifier> modifiers = Collections.emptySet();
140         Modifier access = Modifier.PUBLIC;
141         boolean instance = true;
142
143         for (int i = startIndex + 1; i < modifierString.length(); i++) {
144             char c = modifierString.charAt(i);
145
146             switch (c) {
147             case 'i':
148                 access = Modifier.PRIVATE;
149
150                 break;
151
152             case 'o':
153                 access = Modifier.PROTECTED;
154
155                 break;
156
157             case 's':
158                 instance = false;
159
160                 break;
161             }
162         }
163
164         if (access != Modifier.PUBLIC) {
165             if (instance) {
166                 modifiers = EnumSet.of(access);
167             } else {
168                 modifiers = EnumSet.of(access, Modifier.STATIC);
169             }
170         } else if (!instance) {
171             modifiers = EnumSet.of(Modifier.STATIC);
172         }
173
174         return modifiers;
175     }
176
177     private static class TreeAnalyzer {
178         private ParserFile file;
179         private String JavaDoc url;
180         private String JavaDoc requires;
181         private RubyParseResult result;
182         private BaseDocument doc;
183         private Index index;
184
185         private TreeAnalyzer(Index index, RubyParseResult result) {
186             this.index = index;
187             this.result = result;
188             this.file = result.getFile();
189
190             FileObject fo = file.getFileObject();
191
192             if (fo != null) {
193                 this.doc = AstUtilities.getBaseDocument(fo, true);
194             }
195
196             try {
197                 url = file.getFileObject().getURL().toExternalForm();
198                 
199                 if (PREINDEXING) {
200                     // Make relative URLs for preindexed data structures
201
url = RubyIndex.getPreindexUrl(url);
202                 }
203             } catch (IOException JavaDoc ioe) {
204                 Exceptions.printStackTrace(ioe);
205             }
206         }
207
208         public void analyze() throws IOException JavaDoc {
209             // Delete old contents of this file - iff we're dealing with a user source file
210
if (!file.isPlatform()) {
211                 Set JavaDoc<Map JavaDoc<String JavaDoc, String JavaDoc>> indexedList = Collections.emptySet();
212                 Set JavaDoc<Map JavaDoc<String JavaDoc, String JavaDoc>> notIndexedList = Collections.emptySet();
213                 Map JavaDoc<String JavaDoc, String JavaDoc> toDelete = new HashMap JavaDoc<String JavaDoc,String JavaDoc>();
214                 toDelete.put(FIELD_FILENAME, url);
215                 try {
216                     index.gsfStore(indexedList, notIndexedList, toDelete);
217                 } catch (IOException JavaDoc ioe) {
218                     Exceptions.printStackTrace(ioe);
219                 }
220             }
221             
222             //Node root = result.getRootNode();
223

224             // Compute the requires for this file first such that
225
// each class or module recorded in the index for this
226
// file can reference their includes
227
Set JavaDoc<String JavaDoc> requireSet = result.getRequires();
228
229             if ((requireSet != null) && (requireSet.size() > 0)) {
230                 StringBuilder JavaDoc sb = new StringBuilder JavaDoc(20 * requireSet.size());
231
232                 for (String JavaDoc s : requireSet) {
233                     if (sb.length() > 0) {
234                         sb.append(","); // NOI18N
235
}
236
237                     sb.append(s);
238                 }
239
240                 requires = sb.toString();
241             }
242
243             StructureAnalyzer.analyze(result);
244
245             List JavaDoc<?extends Element> structure = result.getStructure();
246
247             if ((structure == null) || (structure.size() == 0)) {
248                 return;
249             }
250
251             for (Element o : structure) {
252                 // Todo: Iterate over the structure and index them
253
// fields, classes, etc.
254
AstElement jn = (AstElement)o;
255                 analyze(jn);
256             }
257         }
258
259         private void analyze(AstElement element) {
260             switch (element.getKind()) {
261             case MODULE:
262             case CLASS:
263                 analyzeClassOrModule(element);
264
265                 break;
266
267             case CONSTRUCTOR:
268             case METHOD:
269             case FIELD:
270             case ATTRIBUTE:
271             case CONSTANT:
272
273                 // Methods, fields, attributes or constants outside of an explicit
274
// class or module: Added to Object/Kernel
275

276                 // TODO - index us!
277
break;
278             }
279         }
280
281         private void analyzeClassOrModule(AstElement element) {
282             // Add a document
283
Set JavaDoc<Map JavaDoc<String JavaDoc, String JavaDoc>> indexedList = new HashSet JavaDoc<Map JavaDoc<String JavaDoc, String JavaDoc>>();
284             Set JavaDoc<Map JavaDoc<String JavaDoc, String JavaDoc>> notIndexedList = new HashSet JavaDoc<Map JavaDoc<String JavaDoc, String JavaDoc>>();
285
286             // Add indexed info
287
Map JavaDoc<String JavaDoc, String JavaDoc> indexed = new HashMap JavaDoc<String JavaDoc, String JavaDoc>();
288             indexedList.add(indexed);
289
290             Map JavaDoc<String JavaDoc, String JavaDoc> notIndexed = new HashMap JavaDoc<String JavaDoc, String JavaDoc>();
291             notIndexedList.add(notIndexed);
292
293             String JavaDoc attributes;
294             String JavaDoc fqn;
295
296             Node node = element.getNode();
297
298             if (element.getKind() == ElementKind.CLASS) {
299                 ClassElement classElement = (ClassElement)element;
300                 fqn = classElement.getFqn();
301
302                 ClassNode clz = (ClassNode)node;
303                 Node superNode = clz.getSuperNode();
304                 String JavaDoc superClass = null;
305
306                 if (superNode != null) {
307                     superClass = AstUtilities.getSuperclass(clz);
308                 }
309
310                 if (superClass != null) {
311                     notIndexed.put(FIELD_EXTENDS_NAME, superClass);
312                 }
313
314                 Set JavaDoc<String JavaDoc> includes = classElement.getIncludes();
315
316                 if ((includes != null) && (includes.size() > 0)) {
317                     StringBuilder JavaDoc sb = new StringBuilder JavaDoc();
318
319                     for (String JavaDoc include : includes) {
320                         if (sb.length() > 0) {
321                             sb.append(",");
322                         }
323
324                         sb.append(include);
325                     }
326
327                     notIndexed.put(FIELD_INCLUDES, sb.toString());
328                 }
329
330                 attributes = "c";
331             } else {
332                 assert element.getKind() == ElementKind.MODULE;
333
334                 ClassElement moduleElement = (ClassElement)element;
335                 fqn = moduleElement.getFqn();
336
337                 attributes = "m";
338             }
339
340             String JavaDoc name = element.getName();
341
342             String JavaDoc in;
343             int classIndex = fqn.lastIndexOf("::");
344
345             if (classIndex != -1) {
346                 in = fqn.substring(0, classIndex);
347             } else {
348                 in = null;
349             }
350
351             boolean isDocumented = isDocumented(node);
352             int documentSize = getDocumentSize(node);
353
354             if (documentSize > 0) {
355                 attributes = attributes + "d(" + documentSize + ")";
356             }
357
358             notIndexed.put(FIELD_CLASS_ATTRS, attributes);
359
360             /* Don't prune modules without documentation because
361              * this may be an existing module that we're defining
362              * new (documented) classes for*/

363             if (file.isPlatform() && (element.getKind() == ElementKind.CLASS) &&
364                     !INDEX_UNDOCUMENTED && !isDocumented) {
365                 // XXX No, I might still want to recurse into the children -
366
// I may have classes with documentation in an undocumented
367
// module!!
368
return;
369             }
370
371             indexed.put(FIELD_FQN_NAME, fqn);
372             indexed.put(FIELD_CASE_INSENSITIVE_CLASS_NAME, name.toLowerCase());
373             indexed.put(FIELD_CLASS_NAME, name);
374
375             if (in != null) {
376                 notIndexed.put(FIELD_IN, in);
377             }
378
379             addRequire(indexed);
380
381             // TODO:
382
//addIncluded(indexed);
383
if (requires != null) {
384                 notIndexed.put(FIELD_REQUIRES, requires);
385             }
386
387             // Indexed so we can locate these documents when deleting/updating
388
indexed.put(FIELD_FILENAME, url);
389
390             // Add the fields, etc.. Recursively add the children classes or modules if any
391
for (AstElement child : element.getChildren()) {
392                 switch (child.getKind()) {
393                 case CLASS:
394                 case MODULE: {
395                     analyzeClassOrModule(child);
396
397                     break;
398                 }
399
400                 case CONSTRUCTOR:
401                 case METHOD: {
402                     Map JavaDoc<String JavaDoc, String JavaDoc> ru;
403                     ru = new HashMap JavaDoc<String JavaDoc, String JavaDoc>();
404                     indexedList.add(ru);
405
406                     Node childNode = child.getNode();
407                     String JavaDoc signature = AstUtilities.getDefSignature(childNode);
408
409                     String JavaDoc modifierString = getModifierString(child.getModifiers());
410
411                     boolean methodIsDocumented = isDocumented(childNode);
412
413                     if (methodIsDocumented) {
414                         if (modifierString == null) {
415                             modifierString = "d";
416                         } else {
417                             modifierString = modifierString + "d";
418                         }
419                     }
420
421                     if (modifierString != null) {
422                         signature = signature + ":" + modifierString;
423                     }
424
425                     ru.put(FIELD_METHOD_NAME, signature);
426
427                     // Storing a lowercase method name is kinda pointless in
428
// Ruby because the convention is to use all lowercase characters
429
// (using _ to separate words rather than camel case) so we're
430
// bloating the database for very little practical use here...
431
//ru.put(FIELD_CASE_INSENSITIVE_METHOD_NAME, name.toLowerCase());
432
if (child.getName().equals("initialize")) {
433                         // Create static method alias "new"; rdoc also seems to do this
434
Map JavaDoc<String JavaDoc, String JavaDoc> ru2;
435                         ru2 = new HashMap JavaDoc<String JavaDoc, String JavaDoc>();
436                         indexedList.add(ru2);
437
438                         // Change signature
439
signature = signature.replaceFirst("initialize", "new"); // NOI18N
440
// Make it static
441

442                         signature = signature + "s";
443                         ru2.put(FIELD_METHOD_NAME, signature);
444                     }
445
446                     break;
447                 }
448
449                 case FIELD: {
450                     Map JavaDoc<String JavaDoc, String JavaDoc> ru;
451                     ru = new HashMap JavaDoc<String JavaDoc, String JavaDoc>();
452                     indexedList.add(ru);
453
454                     String JavaDoc signature = child.getName();
455                     String JavaDoc modifierString = getModifierString(child.getModifiers());
456
457                     if (modifierString != null) {
458                         signature = signature + ":" + modifierString;
459                     }
460
461                     // TODO - gather documentation on fields? naeh
462
ru.put(FIELD_FIELD_NAME, signature);
463
464                     break;
465                 }
466
467                 case ATTRIBUTE: {
468                     Map JavaDoc<String JavaDoc, String JavaDoc> ru;
469                     ru = new HashMap JavaDoc<String JavaDoc, String JavaDoc>();
470                     indexedList.add(ru);
471
472                     ru.put(FIELD_ATTRIBUTE_NAME, child.getName());
473
474                     break;
475                 }
476
477                 case CONSTANT: {
478                     Map JavaDoc<String JavaDoc, String JavaDoc> ru;
479                     ru = new HashMap JavaDoc<String JavaDoc, String JavaDoc>();
480                     indexedList.add(ru);
481
482                     // TODO - add the RHS on the right
483
ru.put(FIELD_CONSTANT_NAME, child.getName());
484
485                     break;
486                 }
487                 }
488             }
489
490             try {
491                 Map JavaDoc<String JavaDoc, String JavaDoc> toDelete = Collections.emptyMap();
492                 index.gsfStore(indexedList, notIndexedList, toDelete);
493             } catch (IOException JavaDoc ioe) {
494                 Exceptions.printStackTrace(ioe);
495             }
496         }
497
498         private int getDocumentSize(Node node) {
499             if (doc != null) {
500                 List JavaDoc<String JavaDoc> comments = AstUtilities.gatherDocumentation(doc, node);
501
502                 if ((comments != null) && (comments.size() > 0)) {
503                     int size = 0;
504
505                     for (String JavaDoc line : comments) {
506                         size += line.length();
507                     }
508
509                     return size;
510                 }
511             }
512
513             return 0;
514         }
515
516         private boolean isDocumented(Node node) {
517             if (doc != null) {
518                 List JavaDoc<String JavaDoc> comments = AstUtilities.gatherDocumentation(doc, node);
519
520                 if ((comments != null) && (comments.size() > 0)) {
521                     return true;
522                 }
523             }
524
525             return false;
526         }
527
528         private void addRequire(Map JavaDoc<String JavaDoc, String JavaDoc> ru) {
529             // Don't generate "require" clauses for anything in generated ruby;
530
// these classes are all built in and do not require any includes
531
// (besides, the file names are bogus - they are just derived from
532
// the class name by the stub generator)
533
FileObject fo = file.getFileObject();
534             String JavaDoc folder = fo.getParent().getNameExt();
535
536             if (folder.equals("rubystubs") && fo.getName().startsWith("stub_")) {
537                 return;
538             }
539
540             // Index for require-completion
541
String JavaDoc relative = file.getRelativePath();
542
543             if (relative != null) {
544                 if (relative.endsWith(".rb")) { // NOI18N
545
relative = relative.substring(0, relative.length() - 3);
546                     ru.put(FIELD_REQUIRE, relative);
547                 }
548             }
549         }
550     }
551 }
552
Popular Tags