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