changes for VariableHover
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpeclipse / builder / IdentifierIndexManager.java
1 package net.sourceforge.phpeclipse.builder;
2 import java.io.BufferedInputStream;
3 import java.io.BufferedReader;
4 import java.io.FileNotFoundException;
5 import java.io.FileReader;
6 import java.io.FileWriter;
7 import java.io.IOException;
8 import java.io.InputStream;
9 import java.util.ArrayList;
10 import java.util.Collection;
11 import java.util.Comparator;
12 import java.util.HashMap;
13 import java.util.Iterator;
14 import java.util.List;
15 import java.util.SortedMap;
16 import java.util.StringTokenizer;
17 import java.util.TreeMap;
18 import net.sourceforge.phpdt.core.compiler.ITerminalSymbols;
19 import net.sourceforge.phpdt.core.compiler.InvalidInputException;
20 import net.sourceforge.phpdt.internal.compiler.parser.Scanner;
21 import net.sourceforge.phpdt.internal.compiler.parser.SyntaxError;
22 import net.sourceforge.phpdt.internal.compiler.util.Util;
23 import net.sourceforge.phpeclipse.obfuscator.PHPIdentifier;
24 import org.eclipse.core.resources.IFile;
25 import org.eclipse.core.runtime.CoreException;
26 /**
27  * Manages the identifer index information for a specific project
28  *  
29  */
30 public class IdentifierIndexManager {
31   public class LineCreator implements ITerminalSymbols {
32     private Scanner fScanner;
33     private int fToken;
34     public LineCreator() {
35       fScanner = new Scanner(true, false);
36     }
37     /**
38      * Add the information of the current identifier to the line
39      * 
40      * @param typeOfIdentifier
41      *            the type of the identifier ('c'lass, 'd'efine, 'f'unction,
42      *            'm'ethod, 'v'ariable)
43      * @param identifier
44      *            current identifier
45      * @param line
46      *            Buffer for the current index line
47      * @param phpdocOffset
48      *            the offset of the PHPdoc comment if available
49      * @param phpdocLength
50      *            the length of the PHPdoc comment if available
51      */
52     private void addIdentifierInformation(char typeOfIdentifier,
53         char[] identifier, StringBuffer line, int phpdocOffset, int phpdocLength) {
54       line.append('\t');
55       line.append(typeOfIdentifier);
56       line.append(identifier);
57       line.append("\to"); // Offset
58       line.append(fScanner.getCurrentTokenStartPosition());
59       if (phpdocOffset >= 0) {
60         line.append("\tp"); // phpdoc offset
61         line.append(phpdocOffset);
62         line.append("\tl"); // phpdoc length
63         line.append(phpdocLength);
64       }
65     }
66     /**
67      * Get the next token from input
68      */
69     private void getNextToken() {
70       try {
71         fToken = fScanner.getNextToken();
72         if (Scanner.DEBUG) {
73           int currentEndPosition = fScanner.getCurrentTokenEndPosition();
74           int currentStartPosition = fScanner.getCurrentTokenStartPosition();
75           System.out.print(currentStartPosition + "," + currentEndPosition
76               + ": ");
77           System.out.println(fScanner.toStringAction(fToken));
78         }
79         return;
80       } catch (InvalidInputException e) {
81         // ignore errors
82       }
83       fToken = TokenNameERROR;
84     }
85     private void parseDeclarations(char[] parent, StringBuffer buf,
86         boolean goBack) {
87       char[] ident;
88       char[] classVariable;
89       int counter = 0;
90       int phpdocOffset = -1;
91       int phpdocLength = -1;
92       try {
93         while (fToken != TokenNameEOF && fToken != TokenNameERROR) {
94           phpdocOffset = -1;
95           if (fToken == TokenNameCOMMENT_PHPDOC) {
96             phpdocOffset = fScanner.getCurrentTokenStartPosition();
97             phpdocLength = fScanner.getCurrentTokenEndPosition()
98                 - fScanner.getCurrentTokenStartPosition() + 1;
99             getNextToken();
100             if (fToken == TokenNameEOF || fToken == TokenNameERROR) {
101               break;
102             }
103           }
104           if (fToken == TokenNamevar || fToken == TokenNamepublic
105               || fToken == TokenNameprotected || fToken == TokenNameprivate) {
106             getNextToken();
107             if (fToken == TokenNameVariable) {
108               ident = fScanner.getCurrentIdentifierSource();
109               classVariable = new char[ident.length - 1];
110               System.arraycopy(ident, 1, classVariable, 0, ident.length - 1);
111               addIdentifierInformation('v', classVariable, buf, phpdocOffset,
112                   phpdocLength);
113               getNextToken();
114             }
115           } else if (fToken == TokenNamefunction) {
116             getNextToken();
117             if (fToken == TokenNameAND) {
118               getNextToken();
119             }
120             if (fToken == TokenNameIdentifier) {
121               ident = fScanner.getCurrentIdentifierSource();
122               if (parent != null && equalCharArrays(parent, ident)) {
123                 // constructor function
124                 addIdentifierInformation('k', ident, buf, phpdocOffset,
125                     phpdocLength);
126               } else {
127                 if (parent != null) {
128                   // class method function
129                   addIdentifierInformation('m', ident, buf, phpdocOffset,
130                       phpdocLength);
131                 } else {
132                   // nested function ?!
133                   addIdentifierInformation('f', ident, buf, phpdocOffset,
134                       phpdocLength);
135                 }
136               }
137               getNextToken();
138               parseDeclarations(null, buf, true);
139             }
140           } else if (fToken == TokenNameclass) {
141             getNextToken();
142             if (fToken == TokenNameIdentifier) {
143               ident = fScanner.getCurrentIdentifierSource();
144               addIdentifierInformation('c', ident, buf, phpdocOffset,
145                   phpdocLength);
146               getNextToken();
147               //skip tokens for classname, extends and others until we have
148               // the opening '{'
149               while (fToken != TokenNameLBRACE && fToken != TokenNameEOF
150                   && fToken != TokenNameERROR) {
151                 getNextToken();
152               }
153               parseDeclarations(ident, buf, true);
154             }
155           } else if (fToken == TokenNameIdentifier) {
156             ident = fScanner.getCurrentIdentifierSource();
157             getNextToken();
158             if (ident.length==6 && 
159                 ident[0]=='d' && 
160                 ident[1]=='e' && 
161                 ident[2]=='f' && 
162                 ident[3]=='i' && 
163                 ident[4]=='n' && 
164                 ident[5]=='e') {
165               if (fToken == TokenNameLPAREN) {
166                 getNextToken();
167                 if (fToken == TokenNameStringLiteral) {
168                   ident = fScanner.getCurrentStringLiteralSource();
169                   addIdentifierInformation('d', ident, buf, phpdocOffset,
170                       phpdocLength);
171                   getNextToken();
172                 }
173               }
174             }
175           } else if (fToken == TokenNameLBRACE) {
176             getNextToken();
177             counter++;
178           } else if (fToken == TokenNameRBRACE) {
179             getNextToken();
180             --counter;
181             if (counter == 0 && goBack) {
182               return;
183             }
184           } else {
185             getNextToken();
186           }
187         }
188       } catch (SyntaxError e) {
189         // TODO Auto-generated catch block
190         e.printStackTrace();
191       }
192     }
193     public void parseIdentifiers(char[] charArray, StringBuffer buf) {
194       char[] ident;
195       String identifier;
196       int counter = 0;
197       int phpdocOffset = -1;
198       int phpdocLength = -1;
199       fScanner.setSource(charArray);
200       fScanner.setPHPMode(false);
201       fToken = TokenNameEOF;
202       getNextToken();
203       try {
204         while (fToken != TokenNameEOF && fToken != TokenNameERROR) {
205           phpdocOffset = -1;
206           if (fToken == TokenNameCOMMENT_PHPDOC) {
207             phpdocOffset = fScanner.getCurrentTokenStartPosition();
208             phpdocLength = fScanner.getCurrentTokenEndPosition()
209                 - fScanner.getCurrentTokenStartPosition() + 1;
210             getNextToken();
211             if (fToken == TokenNameEOF || fToken == TokenNameERROR) {
212               break;
213             }
214           }
215           if (fToken == TokenNamefunction) {
216             getNextToken();
217             if (fToken == TokenNameAND) {
218               getNextToken();
219             }
220             if (fToken == TokenNameIdentifier) {
221               ident = fScanner.getCurrentIdentifierSource();
222               addIdentifierInformation('f', ident, buf, phpdocOffset,
223                   phpdocLength);
224               getNextToken();
225               parseDeclarations(null, buf, true);
226             }
227           } else if (fToken == TokenNameclass) {
228             getNextToken();
229             if (fToken == TokenNameIdentifier) {
230               ident = fScanner.getCurrentIdentifierSource();
231               addIdentifierInformation('c', ident, buf, phpdocOffset,
232                   phpdocLength);
233               getNextToken();
234               //skip fTokens for classname, extends and others until we have
235               // the opening '{'
236               while (fToken != TokenNameLBRACE && fToken != TokenNameEOF
237                   && fToken != TokenNameERROR) {
238                 getNextToken();
239               }
240               parseDeclarations(ident, buf, true);
241             }
242           } else if (fToken == TokenNameIdentifier) {
243             ident = fScanner.getCurrentIdentifierSource();
244             getNextToken();
245             if (ident.length==6 && 
246                 ident[0]=='d' && 
247                 ident[1]=='e' && 
248                 ident[2]=='f' && 
249                 ident[3]=='i' && 
250                 ident[4]=='n' && 
251                 ident[5]=='e') {
252               if (fToken == TokenNameLPAREN) {
253                 getNextToken();
254                 if (fToken == TokenNameStringLiteral) {
255                   ident = fScanner.getCurrentStringLiteralSource();
256                   addIdentifierInformation('d', ident, buf, phpdocOffset,
257                       phpdocLength);
258                   getNextToken();
259                 }
260               }
261             }
262           } else {
263             getNextToken();
264           }
265         }
266       } catch (SyntaxError e) {
267         // TODO Auto-generated catch block
268         e.printStackTrace();
269       }
270     }
271   }
272   class StringComparator implements Comparator {
273     public int compare(Object o1, Object o2) {
274       String s1 = (String) o1;
275       String s2 = (String) o2;
276       return s1.compareTo(s2);
277       //        return s1.toUpperCase().compareTo(s2.toUpperCase());
278     }
279     public boolean equals(Object o) {
280       String s = (String) o;
281       return compare(this, o) == 0;
282     }
283   }
284   private HashMap fFileMap;
285   private String fFilename;
286   private TreeMap fIndentifierMap;
287   public IdentifierIndexManager(String filename) {
288     fFilename = filename;
289     initialize();
290     readFile();
291   }
292   /**
293    * Check if 2 char arrays are equal
294    * 
295    * @param a
296    * @param b
297    * @return
298    */
299   private static boolean equalCharArrays(char[] a, char[] b) {
300     if (a.length != b.length) {
301       return false;
302     }
303     for (int i = 0; i < b.length; i++) {
304       if (a[i] != b[i]) {
305         return false;
306       }
307     }
308     return true;
309   }
310   /**
311    * Add the information for a given IFile resource
312    *  
313    */
314   public void addFile(IFile fileToParse) {
315     //    InputStream iStream;
316     LineCreator lineCreator = new LineCreator();
317     try {
318       //      iStream = fileToParse.getContents();
319       //
320       //      StringBuffer buf = new StringBuffer();
321       //      int c0;
322       //      try {
323       //        while ((c0 = iStream.read()) != (-1)) {
324       //          buf.append((char) c0);
325       //        }
326       //      } catch (IOException e) {
327       //        return;
328       //      }
329       InputStream stream = null;
330       try {
331         stream = new BufferedInputStream(fileToParse.getContents());
332         StringBuffer lineBuffer = new StringBuffer();
333         lineBuffer.append(fileToParse.getFullPath().toString());
334         int lineLength = lineBuffer.length();
335         // lineCreator.parseIdentifiers(buf.toString().toCharArray(),
336         // lineBuffer);
337         lineCreator.parseIdentifiers(Util.getInputStreamAsCharArray(stream, -1,
338             null), lineBuffer);
339         if (lineLength != lineBuffer.length()) {
340           addLine(lineBuffer.toString());
341         }
342       } catch (IOException e) {
343         return;
344       } finally {
345         try {
346           if (stream != null) {
347             stream.close();
348           }
349         } catch (IOException e) {
350         }
351       }
352     } catch (CoreException e1) {
353       // TODO Auto-generated catch block
354       e1.printStackTrace();
355     }
356   }
357   /**
358    * Adds a line of the index file for function, class, class-method and
359    * class-variable names
360    * 
361    * @param line
362    */
363   private void addLine(String line) {
364     StringTokenizer tokenizer;
365     String phpFileName = null;
366     String token;
367     String identifier = null;
368     String classname = null;
369     String offset = null;
370     PHPIdentifierLocation phpIdentifier = null;
371     boolean tokenExists = false;
372     tokenizer = new StringTokenizer(line, "\t");
373     // first token contains the filename:
374     if (tokenizer.hasMoreTokens()) {
375       phpFileName = tokenizer.nextToken();
376       //System.out.println(token);
377     } else {
378       return;
379     }
380     // all the other tokens are identifiers:
381     while (tokenizer.hasMoreTokens()) {
382       token = tokenizer.nextToken();
383       //System.out.println(token);
384       switch (token.charAt(0)) {
385         case 'c' :
386           // class name
387           identifier = token.substring(1);
388           classname = identifier;
389           phpIdentifier = new PHPIdentifierLocation(identifier,
390               PHPIdentifier.CLASS, phpFileName);
391           break;
392         case 'd' :
393           // define
394           identifier = token.substring(1);
395           phpIdentifier = new PHPIdentifierLocation(identifier,
396               PHPIdentifier.DEFINE, phpFileName);
397           break;
398         case 'f' :
399           // function name
400           identifier = token.substring(1);
401           phpIdentifier = new PHPIdentifierLocation(identifier,
402               PHPIdentifier.FUNCTION, phpFileName);
403           break;
404         case 'k' :
405           // constructor function name
406           identifier = token.substring(1);
407           phpIdentifier = new PHPIdentifierLocation(identifier,
408               PHPIdentifier.CONSTRUCTOR, phpFileName);
409           break;
410         case 'm' :
411           //method inside a class
412           identifier = token.substring(1);
413           phpIdentifier = new PHPIdentifierLocation(identifier,
414               PHPIdentifier.METHOD, phpFileName, classname);
415           break;
416         case 'v' :
417           // variable inside a class
418           identifier = token.substring(1);
419           phpIdentifier = new PHPIdentifierLocation(identifier,
420               PHPIdentifier.VARIABLE, phpFileName, classname);
421           break;
422         case 'o' :
423           // offset information
424           identifier = null;
425           if (phpIdentifier != null) {
426             offset = token.substring(1);
427             phpIdentifier.setOffset(Integer.parseInt(offset));
428           }
429           break;
430         case 'p' :
431           // PHPdoc offset information
432           identifier = null;
433           if (phpIdentifier != null) {
434             offset = token.substring(1);
435             phpIdentifier.setPHPDocOffset(Integer.parseInt(offset));
436           }
437           break;
438         case 'l' :
439           // PHPdoc length information
440           identifier = null;
441           if (phpIdentifier != null) {
442             offset = token.substring(1);
443             phpIdentifier.setPHPDocLength(Integer.parseInt(offset));
444           }
445           break;
446         default :
447           identifier = null;
448           phpIdentifier = null;
449           classname = null;
450       }
451       if (identifier != null && phpIdentifier != null) {
452         tokenExists = true;
453         ArrayList list = (ArrayList) fIndentifierMap.get(identifier);
454         if (list == null) {
455           list = new ArrayList();
456           list.add(phpIdentifier);
457           fIndentifierMap.put(identifier, list);
458         } else {
459           boolean flag = false;
460           for (int i = 0; i < list.size(); i++) {
461             if (list.get(i).equals(phpIdentifier)) {
462               flag = true;
463               break;
464             }
465           }
466           if (flag == false) {
467             list.add(phpIdentifier);
468           }
469         }
470       }
471     }
472     if (tokenExists) {
473       fFileMap.put(phpFileName, line);
474     }
475   }
476   /**
477    * Change the information for a given IFile resource
478    *  
479    */
480   public void changeFile(IFile fileToParse) {
481     removeFile(fileToParse);
482     addFile(fileToParse);
483   }
484   /**
485    * Get a list of all PHPIdentifierLocation object's associated with an
486    * identifier
487    * 
488    * @param identifier
489    * @return
490    */
491   public List getLocations(String identifier) {
492     return (List) fIndentifierMap.get(identifier);
493   }
494   /**
495    * Initialize (i.e. clear) the current index information
496    *  
497    */
498   public void initialize() {
499     fIndentifierMap = new TreeMap(new StringComparator());
500     fFileMap = new HashMap();
501   }
502   private void readFile() {
503     FileReader fileReader;
504     try {
505       fileReader = new FileReader(fFilename);
506       BufferedReader bufferedReader = new BufferedReader(fileReader);
507       String line;
508       while (bufferedReader.ready()) {
509         // all entries for one file are in a line
510         // separated by tabs !
511         line = bufferedReader.readLine();
512         addLine(line);
513       }
514       fileReader.close();
515     } catch (FileNotFoundException e) {
516       // ignore this
517       // TODO DialogBox which asks the user if she/he likes to build new index?
518     } catch (IOException e) {
519       // TODO Auto-generated catch block
520       e.printStackTrace();
521     }
522   }
523   /**
524    * Remove the information for a given IFile resource
525    *  
526    */
527   public void removeFile(IFile fileToParse) {
528     //    String line = (String)
529     // fFileMap.get(fileToParse.getLocation().toString());
530     String line = (String) fFileMap.get(fileToParse.getFullPath().toString());
531     if (line != null) {
532       removeLine(line);
533     }
534   }
535   /**
536    * Removes a line of the index file for function, class, class-method and
537    * class-variable names
538    * 
539    * @param line
540    */
541   private void removeLine(String line) {
542     StringTokenizer tokenizer;
543     String phpFileName = null;
544     String token;
545     String identifier = null;
546     String classname = null;
547     PHPIdentifier phpIdentifier = null;
548     boolean tokenExists = false;
549     tokenizer = new StringTokenizer(line, "\t");
550     // first token contains the filename:
551     if (tokenizer.hasMoreTokens()) {
552       phpFileName = tokenizer.nextToken();
553       //System.out.println(token);
554     } else {
555       return;
556     }
557     // all the other tokens are identifiers:
558     while (tokenizer.hasMoreTokens()) {
559       token = tokenizer.nextToken();
560       //System.out.println(token);
561       switch (token.charAt(0)) {
562         case 'c' :
563           // class name
564           identifier = token.substring(1);
565           classname = identifier;
566           phpIdentifier = new PHPIdentifierLocation(identifier,
567               PHPIdentifier.CLASS, phpFileName);
568           break;
569         case 'd' :
570           // define
571           identifier = token.substring(1);
572           phpIdentifier = new PHPIdentifierLocation(identifier,
573               PHPIdentifier.DEFINE, phpFileName);
574           break;
575         case 'f' :
576           // function name
577           identifier = token.substring(1);
578           phpIdentifier = new PHPIdentifierLocation(identifier,
579               PHPIdentifier.FUNCTION, phpFileName);
580           break;
581         case 'k' :
582           // constructor function name
583           identifier = token.substring(1);
584           phpIdentifier = new PHPIdentifierLocation(identifier,
585               PHPIdentifier.CONSTRUCTOR, phpFileName);
586           break;
587         case 'm' :
588           //method inside a class
589           identifier = token.substring(1);
590           phpIdentifier = new PHPIdentifierLocation(identifier,
591               PHPIdentifier.METHOD, phpFileName, classname);
592           break;
593         case 'v' :
594           // variable inside a class
595           identifier = token.substring(1);
596           phpIdentifier = new PHPIdentifierLocation(identifier,
597               PHPIdentifier.VARIABLE, phpFileName, classname);
598           break;
599         default :
600           identifier = null;
601           phpIdentifier = null;
602           classname = null;
603       }
604       if (identifier != null && phpIdentifier != null) {
605         ArrayList list = (ArrayList) fIndentifierMap.get(identifier);
606         if (list == null) {
607         } else {
608           for (int i = 0; i < list.size(); i++) {
609             if (list.get(i).equals(phpIdentifier)) {
610               list.remove(i);
611               break;
612             }
613           }
614           if (list.size() == 0) {
615             fIndentifierMap.remove(identifier);
616           }
617         }
618       }
619     }
620     fFileMap.remove(phpFileName);
621   }
622   /**
623    * Save the current index information in the projects index file
624    *  
625    */
626   public void writeFile() {
627     FileWriter fileWriter;
628     try {
629       fileWriter = new FileWriter(fFilename);
630       String line;
631       Collection collection = fFileMap.values();
632       Iterator iterator = collection.iterator();
633       while (iterator.hasNext()) {
634         line = (String) iterator.next();
635         fileWriter.write(line + '\n');
636       }
637       fileWriter.close();
638     } catch (FileNotFoundException e) {
639       // ignore exception; project is deleted by user
640     } catch (IOException e) {
641       // TODO Auto-generated catch block
642       e.printStackTrace();
643     }
644   }
645   /**
646    * @param fromKey
647    * @param toKey
648    * @return
649    */
650   public SortedMap getIdentifierMap() {
651     return fIndentifierMap;
652   }
653 }