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