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