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