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