improved PHP parser
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpeclipse / phpeditor / php / PHPCompletionProcessor.java
1 /**********************************************************************
2  Copyright (c) 2000, 2002 IBM Corp. and others.
3  All rights reserved. This program and the accompanying materials
4  are made available under the terms of the Common Public License v1.0
5  which accompanies this distribution, and is available at
6  http://www.eclipse.org/legal/cpl-v10.html
7
8  Contributors:
9  IBM Corporation - Initial implementation
10  **********************************************************************/
11 package net.sourceforge.phpeclipse.phpeditor.php;
12
13 import java.io.IOException;
14 import java.sql.ResultSet;
15 import java.util.ArrayList;
16 import java.util.Arrays;
17 import java.util.HashMap;
18 import java.util.HashSet;
19 import java.util.Iterator;
20 import java.util.List;
21 import java.util.Set;
22 import java.util.SortedMap;
23
24 import net.sourceforge.phpdt.core.ICompilationUnit;
25 import net.sourceforge.phpdt.core.IJavaElement;
26 import net.sourceforge.phpdt.core.IMethod;
27 import net.sourceforge.phpdt.core.IType;
28 import net.sourceforge.phpdt.core.JavaCore;
29 import net.sourceforge.phpdt.core.ToolFactory;
30 import net.sourceforge.phpdt.core.compiler.ITerminalSymbols;
31 import net.sourceforge.phpdt.core.compiler.InvalidInputException;
32 import net.sourceforge.phpdt.internal.compiler.DefaultErrorHandlingPolicies;
33 import net.sourceforge.phpdt.internal.compiler.impl.CompilerOptions;
34 import net.sourceforge.phpdt.internal.compiler.parser.Scanner;
35 import net.sourceforge.phpdt.internal.compiler.parser.SyntaxError;
36 import net.sourceforge.phpdt.internal.compiler.parser.UnitParser;
37 import net.sourceforge.phpdt.internal.compiler.parser.VariableInfo;
38 import net.sourceforge.phpdt.internal.compiler.problem.DefaultProblemFactory;
39 import net.sourceforge.phpdt.internal.compiler.problem.ProblemReporter;
40 import net.sourceforge.phpdt.internal.core.CompilationUnit;
41 import net.sourceforge.phpdt.internal.core.SourceMethod;
42 import net.sourceforge.phpdt.internal.core.SourceType;
43 import net.sourceforge.phpdt.internal.corext.template.php.JavaContext;
44 import net.sourceforge.phpdt.internal.corext.template.php.JavaContextType;
45 import net.sourceforge.phpdt.internal.ui.PHPUiImages;
46 import net.sourceforge.phpdt.internal.ui.text.PHPCodeReader;
47 import net.sourceforge.phpdt.internal.ui.text.java.IPHPCompletionProposal;
48 import net.sourceforge.phpdt.internal.ui.text.java.PHPCompletionProposalComparator;
49 import net.sourceforge.phpdt.internal.ui.text.template.BuiltInEngine;
50 import net.sourceforge.phpdt.internal.ui.text.template.DeclarationEngine;
51 import net.sourceforge.phpdt.internal.ui.text.template.LocalVariableProposal;
52 import net.sourceforge.phpdt.internal.ui.text.template.SQLProposal;
53 import net.sourceforge.phpdt.internal.ui.text.template.contentassist.TemplateEngine;
54 import net.sourceforge.phpdt.ui.IWorkingCopyManager;
55 import net.sourceforge.phpeclipse.PHPeclipsePlugin;
56 import net.sourceforge.phpeclipse.builder.IdentifierIndexManager;
57 import net.sourceforge.phpeclipse.internal.compiler.ast.CompilationUnitDeclaration;
58 import net.sourceforge.phpeclipse.phpeditor.PHPEditor;
59 import net.sourceforge.phpeclipse.phpeditor.PHPSyntaxRdr;
60 import net.sourceforge.phpeclipse.ui.IPreferenceConstants;
61 import net.sourceforge.phpeclipse.ui.overlaypages.ProjectPrefUtil;
62
63 import org.eclipse.core.resources.IFile;
64 import org.eclipse.core.resources.IProject;
65 import org.eclipse.jface.text.BadLocationException;
66 import org.eclipse.jface.text.IDocument;
67 import org.eclipse.jface.text.IRegion;
68 import org.eclipse.jface.text.ITextViewer;
69 import org.eclipse.jface.text.Region;
70 import org.eclipse.jface.text.TextPresentation;
71 import org.eclipse.jface.text.contentassist.ICompletionProposal;
72 import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
73 import org.eclipse.jface.text.contentassist.IContextInformation;
74 import org.eclipse.jface.text.contentassist.IContextInformationExtension;
75 import org.eclipse.jface.text.contentassist.IContextInformationPresenter;
76 import org.eclipse.jface.text.contentassist.IContextInformationValidator;
77 import org.eclipse.jface.text.templates.DocumentTemplateContext;
78 import org.eclipse.jface.text.templates.TemplateContextType;
79 import org.eclipse.swt.graphics.Image;
80 import org.eclipse.swt.graphics.Point;
81 import org.eclipse.ui.IEditorPart;
82 import org.eclipse.ui.IFileEditorInput;
83
84 import com.quantum.ExternalInterface;
85 import com.quantum.model.NotConnectedException;
86
87 /**
88  * Example PHP completion processor.
89  */
90 public class PHPCompletionProcessor implements IContentAssistProcessor {
91   /**
92    * Simple content assist tip closer. The tip is valid in a range of 5 characters around its popup location.
93    */
94   protected static class Validator implements IContextInformationValidator, IContextInformationPresenter {
95     protected int fInstallOffset;
96
97     /*
98      * @see IContextInformationValidator#isContextInformationValid(int)
99      */
100     public boolean isContextInformationValid(int offset) {
101       return Math.abs(fInstallOffset - offset) < 5;
102     }
103
104     /*
105      * @see IContextInformationValidator#install(IContextInformation, ITextViewer, int)
106      */
107     public void install(IContextInformation info, ITextViewer viewer, int offset) {
108       fInstallOffset = offset;
109     }
110
111     /*
112      * @see org.eclipse.jface.text.contentassist.IContextInformationPresenter#updatePresentation(int, TextPresentation)
113      */
114     public boolean updatePresentation(int documentPosition, TextPresentation presentation) {
115       return false;
116     }
117   };
118
119   private static class ContextInformationWrapper implements IContextInformation, IContextInformationExtension {
120     private final IContextInformation fContextInformation;
121
122     private int fPosition;
123
124     public ContextInformationWrapper(IContextInformation contextInformation) {
125       fContextInformation = contextInformation;
126     }
127
128     /*
129      * @see IContextInformation#getContextDisplayString()
130      */
131     public String getContextDisplayString() {
132       return fContextInformation.getContextDisplayString();
133     }
134
135     /*
136      * @see IContextInformation#getImage()
137      */
138     public Image getImage() {
139       return fContextInformation.getImage();
140     }
141
142     /*
143      * @see IContextInformation#getInformationDisplayString()
144      */
145     public String getInformationDisplayString() {
146       return fContextInformation.getInformationDisplayString();
147     }
148
149     /*
150      * @see IContextInformationExtension#getContextInformationPosition()
151      */
152     public int getContextInformationPosition() {
153       return fPosition;
154     }
155
156     public void setContextInformationPosition(int position) {
157       fPosition = position;
158     }
159   };
160
161   private class TableName {
162     String fTableName;
163
164     TableName() {
165       fTableName = null;
166     }
167
168     /**
169      * @return Returns the tableName.
170      */
171     public String getTableName() {
172       if (fTableName == null) {
173         return "<!--no-table-->";
174       }
175       return fTableName;
176     }
177
178     /**
179      * @param tableName
180      *          The tableName to set.
181      */
182     public void setTableName(String tableName) {
183       fTableName = tableName;
184     }
185   }
186
187   private char[] fProposalAutoActivationSet;
188
189   protected IContextInformationValidator fValidator = new Validator();
190
191   private TemplateEngine fTemplateEngine;
192
193   private PHPCompletionProposalComparator fComparator;
194
195   private int fNumberOfComputedResults = 0;
196
197   private IEditorPart fEditor;
198
199   protected IWorkingCopyManager fManager;
200
201   public PHPCompletionProcessor(IEditorPart editor) {
202     fEditor = editor;
203     fManager = PHPeclipsePlugin.getDefault().getWorkingCopyManager();
204     TemplateContextType contextType = PHPeclipsePlugin.getDefault().getTemplateContextRegistry().getContextType("php"); //$NON-NLS-1$
205     if (contextType != null)
206       fTemplateEngine = new TemplateEngine(contextType);
207     fComparator = new PHPCompletionProposalComparator();
208   }
209
210   /**
211    * Tells this processor to order the proposals alphabetically.
212    * 
213    * @param order
214    *          <code>true</code> if proposals should be ordered.
215    */
216   public void orderProposalsAlphabetically(boolean order) {
217     fComparator.setOrderAlphabetically(order);
218   }
219
220   /**
221    * Sets this processor's set of characters triggering the activation of the completion proposal computation.
222    * 
223    * @param activationSet
224    *          the activation set
225    */
226   public void setCompletionProposalAutoActivationCharacters(char[] activationSet) {
227     fProposalAutoActivationSet = activationSet;
228   }
229
230   /*
231    * (non-Javadoc) Method declared on IContentAssistProcessor
232    */
233   public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int documentOffset) {
234     int contextInformationPosition = guessContextInformationPosition(viewer, documentOffset);
235     return internalComputeCompletionProposals(viewer, documentOffset, contextInformationPosition);
236   }
237
238   private int getLastToken(List list, ITextViewer viewer, int completionPosition, JavaContext context, TableName tableName) {
239     IDocument document = viewer.getDocument();
240     int start = context.getStart();
241     int end = context.getEnd();
242     String startText;
243     int lastSignificantToken = ITerminalSymbols.TokenNameEOF;
244     try {
245       // begin search 2 lines behind of this
246       int j = start;
247       if (j != 0) {
248         char ch;
249         while (j-- > 0) {
250           ch = document.getChar(j);
251           if (ch == '\n') {
252             break;
253           }
254         }
255         while (j-- > 0) {
256           ch = document.getChar(j);
257           if (ch == '\n') {
258             break;
259           }
260         }
261       }
262       if (j != start) {
263         // scan the line for the dereferencing operator '->'
264         startText = document.get(j, start - j);
265         if (Scanner.DEBUG) {
266           System.out.println(startText);
267         }
268         int token = ITerminalSymbols.TokenNameEOF;
269         //        token = getLastSQLToken(startText);
270         tableName.setTableName(getLastSQLTableName(startText));
271         Scanner scanner = ToolFactory.createScanner(false, false, false);
272         scanner.setSource(startText.toCharArray());
273         scanner.setPHPMode(true);
274         int beforeLastToken = ITerminalSymbols.TokenNameEOF;
275         int lastToken = ITerminalSymbols.TokenNameEOF;
276         char[] ident = null;
277         try {
278           token = scanner.getNextToken();
279           lastToken = token;
280           while (token != ITerminalSymbols.TokenNameERROR && token != ITerminalSymbols.TokenNameEOF) {
281             beforeLastToken = lastToken;
282             if (token == ITerminalSymbols.TokenNameVariable) {
283               ident = scanner.getCurrentTokenSource();
284               if (ident.length == 5 && ident[0] == '$' && ident[1] == 't' && ident[2] == 'h' && ident[3] == 'i' && ident[4] == 's') {
285                 token = ITerminalSymbols.TokenNamethis_PHP_COMPLETION;
286               }
287             }
288             lastToken = token;
289             // System.out.println(scanner.toStringAction(lastToken));
290             token = scanner.getNextToken();
291           }
292         } catch (InvalidInputException e1) {
293         } catch (SyntaxError e) {
294         }
295         switch (lastToken) {
296         case ITerminalSymbols.TokenNameMINUS_GREATER:
297           // dereferencing operator '->' found
298           lastSignificantToken = ITerminalSymbols.TokenNameMINUS_GREATER;
299           if (beforeLastToken == ITerminalSymbols.TokenNameVariable) {
300             lastSignificantToken = ITerminalSymbols.TokenNameVariable;
301             list.set(0, ident);
302           } else if (beforeLastToken == ITerminalSymbols.TokenNamethis_PHP_COMPLETION) {
303             lastSignificantToken = ITerminalSymbols.TokenNamethis_PHP_COMPLETION;
304             list.set(0, ident);
305           }
306           break;
307         case ITerminalSymbols.TokenNamenew:
308           lastSignificantToken = ITerminalSymbols.TokenNamenew;
309           break;
310         }
311       }
312     } catch (BadLocationException e) {
313     }
314     return lastSignificantToken;
315   }
316
317   String getSQLTableName(String sqlText, int start) {
318     int tableNameStart = -1;
319     int currentCharacterPosition = start + 1;
320     char ch;
321     try {
322       while (true) {
323         ch = sqlText.charAt(currentCharacterPosition++);
324         if (tableNameStart == -1 && Scanner.isPHPIdentifierStart(ch)) {
325           tableNameStart = currentCharacterPosition - 1;
326         } else {
327           if (!Scanner.isPHPIdentifierPart(ch)) {
328             return sqlText.substring(tableNameStart, currentCharacterPosition - 1);
329           }
330         }
331       }
332     } catch (IndexOutOfBoundsException e) {
333       if (tableNameStart >= 0) {
334         return sqlText.substring(tableNameStart, currentCharacterPosition - 1);
335       }
336     }
337     return "";
338   }
339
340   private String getLastSQLTableName(String startText) {
341     int token;
342     // scan for sql identifiers
343     char ch = ' ';
344     int currentSQLPosition = startText.length();
345     int identEnd = -1;
346     String ident = null;
347     boolean whiteSpace = true;
348     try {
349       while (true) {
350         ch = startText.charAt(--currentSQLPosition);
351         if (ch >= 'A' && ch <= 'Z') {
352           if (identEnd < 0) {
353             identEnd = currentSQLPosition + 1;
354           }
355         } else if (ch >= 'a' && ch <= 'z') {
356           if (identEnd < 0) {
357             identEnd = currentSQLPosition + 1;
358           }
359         } else if (identEnd >= 0) {
360           ident = startText.substring(currentSQLPosition + 1, identEnd);
361           // select -- from -- where --
362           // update -- set -- where --
363           // insert into -- ( -- ) values ( -- )
364           if (ident.length() >= 4 && ident.length() <= 6) {
365             ident = ident.toLowerCase();
366             switch (ident.length()) {
367             //              case 3 :
368             //                if (ident.equals("set")) {
369             //                  // System.out.println("set");
370             //                  token = ITerminalSymbols.TokenNameSQLset;
371             //                  return token;
372             //                }
373             //                break;
374             case 4:
375               if (ident.equals("from")) {
376                 //                  System.out.println("from");
377                 token = ITerminalSymbols.TokenNameSQLfrom;
378                 return getSQLTableName(startText, identEnd);
379               } else if (ident.equals("into")) {
380                 //                System.out.println("into");
381                 token = ITerminalSymbols.TokenNameSQLinto;
382                 return getSQLTableName(startText, identEnd);
383               }
384               break;
385             //              case 5 :
386             //                if (ident.equals("where")) {
387             //                  // System.out.println("where");
388             //                  token = ITerminalSymbols.TokenNameSQLwhere;
389             //                  return token;
390             //                }
391             //                break;
392             case 6:
393               //                if (ident.equals("select")) {
394               //                  // System.out.println("select");
395               //                  token = ITerminalSymbols.TokenNameSQLselect;
396               //                  return token;
397               //                } else if (ident.equals("insert")) {
398               //                  // System.out.println("insert");
399               //                  token = ITerminalSymbols.TokenNameSQLinsert;
400               //                  return token;
401               //                } else
402               if (ident.equals("update")) {
403                 //                  System.out.println("update");
404                 token = ITerminalSymbols.TokenNameSQLupdate;
405                 return getSQLTableName(startText, identEnd);
406               }
407               //                else if (ident.equals("values")) {
408               //                  // System.out.println("values");
409               //                  token = ITerminalSymbols.TokenNameSQLvalues;
410               //                  return token;
411               //                }
412               break;
413             }
414           }
415           whiteSpace = false;
416           identEnd = -1;
417         } else if (Character.isWhitespace(ch)) {
418         } else {
419           whiteSpace = false;
420         }
421       }
422     } catch (IndexOutOfBoundsException e) {
423     }
424     return "<!--no-table-->";
425   }
426
427   /**
428    * Detect the last significant SQL token in the text before the completion
429    * 
430    * @param startText
431    */
432   private int getLastSQLToken(String startText) {
433     int token;
434     // scan for sql identifiers
435     char ch = ' ';
436     int currentSQLPosition = startText.length();
437     int identEnd = -1;
438     String ident = null;
439     boolean whiteSpace = true;
440     try {
441       while (true) {
442         ch = startText.charAt(--currentSQLPosition);
443         if (ch >= 'A' && ch <= 'Z') {
444           if (identEnd < 0) {
445             identEnd = currentSQLPosition + 1;
446           }
447         } else if (ch >= 'a' && ch <= 'z') {
448           if (identEnd < 0) {
449             identEnd = currentSQLPosition + 1;
450           }
451         } else if (identEnd >= 0) {
452           ident = startText.substring(currentSQLPosition + 1, identEnd);
453           // select -- from -- where --
454           // update -- set -- where --
455           // insert into -- ( -- ) values ( -- )
456           if (ident.length() >= 3 && ident.length() <= 6) {
457             ident = ident.toLowerCase();
458             switch (ident.length()) {
459             case 3:
460               if (ident.equals("set")) {
461                 //                  System.out.println("set");
462                 token = ITerminalSymbols.TokenNameSQLset;
463                 return token;
464               }
465               break;
466             case 4:
467               if (ident.equals("from")) {
468                 //                  System.out.println("from");
469                 token = ITerminalSymbols.TokenNameSQLfrom;
470                 //getSQLTableName();
471                 return token;
472               } else if (ident.equals("into")) {
473                 //                System.out.println("into");
474                 token = ITerminalSymbols.TokenNameSQLinto;
475                 return token;
476               }
477               break;
478             case 5:
479               if (ident.equals("where")) {
480                 //                  System.out.println("where");
481                 token = ITerminalSymbols.TokenNameSQLwhere;
482                 return token;
483               }
484               break;
485             case 6:
486               if (ident.equals("select")) {
487                 //                  System.out.println("select");
488                 token = ITerminalSymbols.TokenNameSQLselect;
489                 return token;
490               } else if (ident.equals("insert")) {
491                 //                  System.out.println("insert");
492                 token = ITerminalSymbols.TokenNameSQLinsert;
493                 return token;
494               } else if (ident.equals("update")) {
495                 //                  System.out.println("update");
496                 token = ITerminalSymbols.TokenNameSQLupdate;
497                 return token;
498               } else if (ident.equals("values")) {
499                 //                System.out.println("values");
500                 token = ITerminalSymbols.TokenNameSQLvalues;
501                 return token;
502               }
503               break;
504             }
505           }
506           whiteSpace = false;
507           identEnd = -1;
508         } else if (Character.isWhitespace(ch)) {
509         } else {
510           whiteSpace = false;
511         }
512       }
513     } catch (IndexOutOfBoundsException e) {
514     }
515     return ITerminalSymbols.TokenNameEOF;
516   }
517
518   private ICompletionProposal[] internalComputeCompletionProposals(ITextViewer viewer, int offset, int contextOffset) {
519     ICompilationUnit unit = fManager.getWorkingCopy(fEditor.getEditorInput());
520     IDocument document = viewer.getDocument();
521     IFile file = null;
522     IProject project = null;
523     if (offset > 0) {
524       PHPEditor editor = null;
525       if (fEditor != null && (fEditor instanceof PHPEditor)) {
526         editor = (PHPEditor) fEditor;
527         file = ((IFileEditorInput) editor.getEditorInput()).getFile();
528         project = file.getProject();
529       }
530     }
531
532     Point selection = viewer.getSelectedRange();
533     // remember selected text
534     String selectedText = null;
535     if (selection.y != 0) {
536       try {
537         selectedText = document.get(selection.x, selection.y);
538       } catch (BadLocationException e) {
539       }
540     }
541
542     JavaContextType phpContextType = (JavaContextType) PHPeclipsePlugin.getDefault().getTemplateContextRegistry().getContextType(
543         "php"); //$NON-NLS-1$
544     JavaContext context = (JavaContext) phpContextType.createContext(document, offset, selection.y, unit);
545     context.setVariable("selection", selectedText); //$NON-NLS-1$
546     String prefix = context.getKey();
547
548     HashMap methodVariables = null;
549     HashMap typeVariables = null;
550     HashMap unitVariables = null;
551     ICompilationUnit compilationUnit = (ICompilationUnit) context.findEnclosingElement(IJavaElement.COMPILATION_UNIT);
552     //    if (compilationUnit != null) {
553     //      unitVariables = ((CompilationUnit) compilationUnit).variables;
554     //    }
555     IType type = (IType) context.findEnclosingElement(IJavaElement.TYPE);
556     //    if (type != null) {
557     //      typeVariables = ((SourceType) type).variables;
558     //    }
559     IMethod method = (IMethod) context.findEnclosingElement(IJavaElement.METHOD);
560     //    if (method != null) {
561     //      methodVariables = ((SourceMethod) method).variables;
562     //    }
563
564     boolean emptyPrefix = prefix == null || prefix.equals("");
565     IPHPCompletionProposal[] localVariableResults = new IPHPCompletionProposal[0];
566
567     if (!emptyPrefix && prefix.length() >= 1 && prefix.charAt(0) == '$') { // php Variable ?
568       String lowerCasePrefix = prefix.toLowerCase();
569       HashSet localVariables = new HashSet();
570       if (compilationUnit != null) {
571         unitVariables = getUnitVariables(unitVariables, compilationUnit);
572         getVariableProposals(localVariables, viewer, project, context, unitVariables, lowerCasePrefix, 94);
573       }
574       if (method != null) {
575         methodVariables = getMethodVariables(methodVariables, method);
576         getVariableProposals(localVariables, viewer, project, context, methodVariables, lowerCasePrefix, 99);
577       }
578       if (!localVariables.isEmpty()) {
579         localVariableResults = (IPHPCompletionProposal[]) localVariables.toArray(new IPHPCompletionProposal[localVariables.size()]);
580       }
581     }
582
583     TableName sqlTable = new TableName();
584     ArrayList list = new ArrayList();
585     list.add(null);
586     int lastSignificantToken = getLastToken(list, viewer, offset, context, sqlTable);
587     boolean useClassMembers = (lastSignificantToken == ITerminalSymbols.TokenNameMINUS_GREATER)
588         || (lastSignificantToken == ITerminalSymbols.TokenNameVariable) || (lastSignificantToken == ITerminalSymbols.TokenNamenew)
589         || (lastSignificantToken == ITerminalSymbols.TokenNamethis_PHP_COMPLETION);
590
591     if (fTemplateEngine != null) {
592       IPHPCompletionProposal[] templateResults = new IPHPCompletionProposal[0];
593       ICompletionProposal[] results;
594       if (!emptyPrefix) {
595         fTemplateEngine.reset();
596         fTemplateEngine.complete(viewer, offset, unit);
597         templateResults = fTemplateEngine.getResults();
598       }
599       // TODO delete this
600       IPHPCompletionProposal[] identifierResults = new IPHPCompletionProposal[0];
601
602       // declarations stored in file project.index on project level
603       IPHPCompletionProposal[] declarationResults = new IPHPCompletionProposal[0];
604       if (project != null) {
605         DeclarationEngine declarationEngine;
606         JavaContextType contextType = (JavaContextType) PHPeclipsePlugin.getDefault().getTemplateContextRegistry().getContextType(
607             "php"); //$NON-NLS-1$
608         if (contextType != null) {
609           IdentifierIndexManager indexManager = PHPeclipsePlugin.getDefault().getIndexManager(project);
610           SortedMap sortedMap;
611           declarationEngine = new DeclarationEngine(project, contextType, lastSignificantToken, file);
612           if (lastSignificantToken == ITerminalSymbols.TokenNamethis_PHP_COMPLETION) {
613             // complete '$this->'
614             sortedMap = indexManager.getIdentifiers(file);
615             declarationEngine.completeObject(viewer, offset, sortedMap, unit);
616           } else {
617             String typeRef = null;
618             char[] varName = (char[]) list.get(0);
619             if (varName != null) {
620               if (method != null) {
621                 methodVariables = getMethodVariables(methodVariables, method);
622                 VariableInfo info = (VariableInfo) methodVariables.get(new String(varName));
623                 if (info != null && info.typeIdentifier != null) {
624                   typeRef = new String(info.typeIdentifier);
625                 }
626
627               }
628             }
629             if (typeRef != null) {
630               // complete '$variable->' with type information
631               sortedMap = indexManager.getIdentifiers(typeRef);
632               declarationEngine.completeObject(viewer, offset, sortedMap, unit);
633             } else {
634               // complete '$variable->' without type information
635               sortedMap = indexManager.getIdentifierMap();
636               declarationEngine.complete(viewer, offset, sortedMap, unit);
637             }
638           }
639           declarationResults = declarationEngine.getResults();
640         }
641       }
642       // built in function names from phpsyntax.xml
643       ArrayList syntaxbuffer = PHPSyntaxRdr.getSyntaxData();
644       IPHPCompletionProposal[] builtinResults = new IPHPCompletionProposal[0];
645       if ((!useClassMembers) && syntaxbuffer != null) {
646         BuiltInEngine builtinEngine;
647         String proposal;
648         JavaContextType contextType = (JavaContextType) PHPeclipsePlugin.getDefault().getTemplateContextRegistry().getContextType(
649             "php"); //$NON-NLS-1$
650         if (contextType != null) {
651           builtinEngine = new BuiltInEngine(contextType);
652           builtinEngine.complete(viewer, offset, syntaxbuffer, unit);
653           builtinResults = builtinEngine.getResults();
654         }
655       }
656       ICompletionProposal[] sqlResults = new ICompletionProposal[0];
657       if (project != null) {
658         sqlResults = getSQLProposals(viewer, project, context, prefix, sqlTable);
659       }
660       // concatenate the result arrays
661       IPHPCompletionProposal[] total;
662       total = new IPHPCompletionProposal[localVariableResults.length + templateResults.length + identifierResults.length
663           + builtinResults.length + declarationResults.length + sqlResults.length];
664       System.arraycopy(templateResults, 0, total, 0, templateResults.length);
665       System.arraycopy(identifierResults, 0, total, templateResults.length, identifierResults.length);
666       System.arraycopy(builtinResults, 0, total, templateResults.length + identifierResults.length, builtinResults.length);
667       System.arraycopy(declarationResults, 0, total, templateResults.length + identifierResults.length + builtinResults.length,
668           declarationResults.length);
669       System.arraycopy(sqlResults, 0, total, templateResults.length + identifierResults.length + builtinResults.length
670           + declarationResults.length, sqlResults.length);
671       System.arraycopy(localVariableResults, 0, total, templateResults.length + identifierResults.length + builtinResults.length
672           + declarationResults.length + sqlResults.length, localVariableResults.length);
673       results = total;
674       fNumberOfComputedResults = (results == null ? 0 : results.length);
675       /*
676        * Order here and not in result collector to make sure that the order applies to all proposals and not just those of the
677        * compilation unit.
678        */
679       return order(results);
680     }
681     return new IPHPCompletionProposal[0];
682   }
683  
684   /**
685    * @param unitVariables
686    * @param unit
687    */
688   private HashMap getUnitVariables(HashMap unitVariables, ICompilationUnit unit) {
689     if (unitVariables == null) {
690       try {
691         String unitText = unit.getSource();
692         unitVariables = new HashMap();
693         
694         ProblemReporter problemReporter = new ProblemReporter(DefaultErrorHandlingPolicies.exitAfterAllProblems(),
695             new CompilerOptions(JavaCore.getOptions()), new DefaultProblemFactory());
696         UnitParser parser = new UnitParser(problemReporter);
697         parser.compilationUnit = new CompilationUnitDeclaration(problemReporter, null, unitText.length());
698         parser.parse(unitText, unitVariables);
699         
700       } catch (Exception e) {
701         // TODO Auto-generated catch block
702         e.printStackTrace();
703         PHPeclipsePlugin.log(e);
704       }
705     }
706     return unitVariables;
707   }
708   
709   /**
710    * @param methodVariables
711    * @param method
712    */
713   private HashMap getMethodVariables(HashMap methodVariables, IMethod method) {
714     if (methodVariables == null) {
715       try {
716         String methodText = method.getSource();
717         methodVariables = new HashMap();
718         ProblemReporter problemReporter = new ProblemReporter(DefaultErrorHandlingPolicies.exitAfterAllProblems(),
719             new CompilerOptions(JavaCore.getOptions()), new DefaultProblemFactory());
720         UnitParser parser = new UnitParser(problemReporter);
721         parser.compilationUnit = new CompilationUnitDeclaration(problemReporter, null, methodText.length());
722         parser.parseFunction(methodText, methodVariables);
723       } catch (Exception e) {
724         // TODO Auto-generated catch block
725         e.printStackTrace();
726         PHPeclipsePlugin.log(e);
727       }
728     }
729     return methodVariables;
730   }
731
732   /**
733    * @param viewer
734    * @param project
735    * @param context
736    * @param prefix
737    * @return
738    */
739   private void getVariableProposals(HashSet localVariables, ITextViewer viewer, IProject project, JavaContext context,
740       HashMap variables, String prefix, int relevance) {
741 //    try {
742       int start = context.getStart();
743       int end = context.getEnd();
744       IRegion region = new Region(start, end - start);
745       //      IMethod method = (IMethod) context.findEnclosingElement(IJavaElement.METHOD);
746       //      if (method != null && (method instanceof SourceMethod) && ((SourceMethod) method).variables != null) {
747       //        HashMap map = ((SourceMethod) method).variables;
748       Set set = variables.keySet();
749       Iterator iter = set.iterator();
750       String varName;
751       boolean matchesVarName;
752       while (iter.hasNext()) {
753         varName = (String) iter.next();
754         if (varName.length() >= prefix.length()) {
755           matchesVarName = true;
756           for (int i = 0; i < prefix.length(); i++) {
757             if (prefix.charAt(i) != Character.toLowerCase(varName.charAt(i))) {
758               matchesVarName = false;
759               break;
760             }
761           }
762           if (matchesVarName) {
763             LocalVariableProposal prop;
764             //            if (varName.length == prefix.length()) {
765             //              prop = new LocalVariableProposal(new String(varName), region, viewer, relevance-10);
766             //            } else {
767             prop = new LocalVariableProposal(new String(varName), region, viewer, relevance);
768             //            }
769             localVariables.add(prop);
770           }
771         }
772       }
773       //      }
774
775       //      char[] varName;
776       //      boolean matchesVarName;
777       //      if (method != null) {
778       //        ISourceRange range = method.getSourceRange();
779       //        char[] source = method.getSource().toCharArray();
780       //        Scanner scanner = new Scanner();
781       //        scanner.setSource(source);
782       //        scanner.phpMode = true;
783       //        int token = Scanner.TokenNameWHITESPACE;
784       //        while ((token = scanner.getNextToken()) != Scanner.TokenNameEOF) {
785       //          if (token == Scanner.TokenNameVariable) {
786       //            varName = scanner.getCurrentTokenSource();
787       //            if (varName.length >= prefix.length()) {
788       //              matchesVarName = true;
789       //              for (int i = 0; i < prefix.length(); i++) {
790       //                if (prefix.charAt(i) != varName[i]) {
791       //                  matchesVarName = false;
792       //                  break;
793       //                }
794       //              }
795       //              if (matchesVarName) {
796       //                LocalVariableProposal prop = new LocalVariableProposal(new String(varName), region, viewer);
797       //                if (varName.length == prefix.length()) {
798       //                  prop.setRelevance(98);
799       //                }
800       //                localVariables.add(prop);
801       //              }
802       //            }
803       //          }
804       //        }
805       //      }
806     //    } catch (Throwable e) {
807     //      // ignore - Syntax exceptions could occur, if there are syntax errors !
808     //    }
809   }
810
811   /**
812    * @param viewer
813    * @param project
814    * @param context
815    * @param prefix
816    * @param sqlTable
817    * @param sqlResults
818    * @return
819    */
820   private ICompletionProposal[] getSQLProposals(ITextViewer viewer, IProject project, DocumentTemplateContext context,
821       String prefix, TableName sqlTable) {
822     ICompletionProposal[] sqlResults = new ICompletionProposal[0];
823     // Get The Database bookmark from the Quantum SQL plugin:
824     //    BookmarkCollection sqlBookMarks = BookmarkCollection.getInstance();
825     //    if (sqlBookMarks != null) {
826     String bookmarkString = ProjectPrefUtil.getMiscProjectsPreferenceValue(project, IPreferenceConstants.PHP_BOOKMARK_DEFAULT);
827     if (bookmarkString != null && !bookmarkString.equals("")) {
828       String[] bookmarks = ExternalInterface.getBookmarkNames();
829       boolean foundBookmark = false;
830       for (int i = 0; i < bookmarks.length; i++) {
831         if (bookmarks[i].equals(bookmarkString)) {
832           foundBookmark = true;
833         }
834       }
835       if (!foundBookmark) {
836         return sqlResults;
837       }
838       //        Bookmark bookmark = sqlBookMarks.find(bookmarkString);
839       ArrayList sqlList = new ArrayList();
840       if (!ExternalInterface.isBookmarkConnected(bookmarkString)) {
841         ExternalInterface.connectBookmark(bookmarkString, null);
842         if (!ExternalInterface.isBookmarkConnected(bookmarkString)) {
843           return sqlResults;
844         }
845       }
846       //      if (ExternalInterface.isBookmarkConnected(bookmarkString)) {
847       try {
848         //            Connection connection = bookmark.getConnection();
849         //            DatabaseMetaData metaData = connection.getMetaData();
850
851         //            if (metaData != null) {
852         int start = context.getStart();
853         int end = context.getEnd();
854         String foundSQLTableName = sqlTable.getTableName();
855         String tableName;
856         String columnName;
857         String prefixWithoutDollar = prefix;
858         boolean isDollarPrefix = false;
859         if (prefix.length() > 0 && prefix.charAt(0) == '$') {
860           prefixWithoutDollar = prefix.substring(1);
861           isDollarPrefix = true;
862         }
863         IRegion region = new Region(start, end - start);
864         ResultSet set;
865         if (!isDollarPrefix) {
866           String[] tableNames = ExternalInterface.getMatchingTableNames(null, bookmarkString, prefixWithoutDollar, null, false);
867           for (int i = 0; i < tableNames.length; i++) {
868             sqlList.add(new SQLProposal(tableNames[i], context, region, viewer, PHPUiImages.get(PHPUiImages.IMG_TABLE)));
869           }
870
871           //                set = metaData.getTables(null, null, prefixWithoutDollar + "%", null);
872           //                while (set.next()) {
873           //                  tableName = set.getString("TABLE_NAME");
874           //                  tableName = (tableName == null) ? "" : tableName.trim();
875           //                  if (tableName != null && tableName.length() > 0) {
876           //                    sqlList.add(new SQLProposal(tableName, context, region, viewer, PHPUiImages.get(PHPUiImages.IMG_TABLE)));
877           //                  }
878           //                }
879           //                set.close();
880         }
881
882         String[] columnNames = ExternalInterface.getMatchingColumnNames(null, bookmarkString, prefixWithoutDollar, null, false);
883         for (int i = 0; i < columnNames.length; i++) {
884           sqlList.add(new SQLProposal(columnNames[i], context, region, viewer, PHPUiImages.get(PHPUiImages.IMG_TABLE)));
885         }
886         //              set = metaData.getColumns(null, null, "%", prefixWithoutDollar + "%");
887         //              SQLProposal sqlProposal;
888         //              while (set.next()) {
889         //                columnName = set.getString("COLUMN_NAME");
890         //                columnName = (columnName == null) ? "" : columnName.trim();
891         //                tableName = set.getString("TABLE_NAME");
892         //                tableName = (tableName == null) ? "" : tableName.trim();
893         //                if (tableName != null && tableName.length() > 0 && columnName != null && columnName.length() > 0) {
894         //                  if (isDollarPrefix) {
895         //                    sqlProposal = new SQLProposal(tableName, "$" + columnName, context, region, viewer, PHPUiImages
896         //                        .get(PHPUiImages.IMG_COLUMN));
897         //                  } else {
898         //                    sqlProposal = new SQLProposal(tableName, columnName, context, region, viewer, PHPUiImages
899         //                        .get(PHPUiImages.IMG_COLUMN));
900         //                  }
901         //                  if (tableName.equals(foundSQLTableName)) {
902         //                    sqlProposal.setRelevance(90);
903         //                  } else if (tableName.indexOf(foundSQLTableName) >= 0) {
904         //                    sqlProposal.setRelevance(75);
905         //                  }
906         //                  sqlList.add(sqlProposal);
907         //                }
908         //              }
909         //              set.close();
910         sqlResults = new IPHPCompletionProposal[sqlList.size()];
911         for (int i = 0; i < sqlList.size(); i++) {
912           sqlResults[i] = (SQLProposal) sqlList.get(i);
913         }
914         //            }
915       } catch (NotConnectedException e) {
916         // ignore this - not mission critical
917         //        } catch (SQLException e) {
918         //          e.printStackTrace();
919       }
920       //      }
921     }
922     //    }
923     return sqlResults;
924   }
925
926   private boolean looksLikeMethod(PHPCodeReader reader) throws IOException {
927     int curr = reader.read();
928     while (curr != PHPCodeReader.EOF && Character.isWhitespace((char) curr))
929       curr = reader.read();
930
931     if (curr == PHPCodeReader.EOF)
932       return false;
933
934     return Scanner.isPHPIdentifierPart((char) curr) || Scanner.isPHPIdentifierStart((char) curr);
935   }
936
937   private int guessContextInformationPosition(ITextViewer viewer, int offset) {
938     int contextPosition = offset;
939     IDocument document = viewer.getDocument();
940     try {
941
942       PHPCodeReader reader = new PHPCodeReader();
943       reader.configureBackwardReader(document, offset, true, true);
944
945       int nestingLevel = 0;
946
947       int curr = reader.read();
948       while (curr != PHPCodeReader.EOF) {
949
950         if (')' == (char) curr)
951           ++nestingLevel;
952
953         else if ('(' == (char) curr) {
954           --nestingLevel;
955
956           if (nestingLevel < 0) {
957             int start = reader.getOffset();
958             if (looksLikeMethod(reader))
959               return start + 1;
960           }
961         }
962
963         curr = reader.read();
964       }
965     } catch (IOException e) {
966     }
967     return contextPosition;
968   }
969
970   /**
971    * @see IContentAssistProcessor#computeContextInformation(ITextViewer, int)
972    */
973   public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) {
974     int contextInformationPosition = guessContextInformationPosition(viewer, offset);
975     List result = addContextInformations(viewer, contextInformationPosition);
976     return (IContextInformation[]) result.toArray(new IContextInformation[result.size()]);
977   }
978
979   private List addContextInformations(ITextViewer viewer, int offset) {
980     ICompletionProposal[] proposals = internalComputeCompletionProposals(viewer, offset, -1);
981     List result = new ArrayList();
982     for (int i = 0; i < proposals.length; i++) {
983       IContextInformation contextInformation = proposals[i].getContextInformation();
984       if (contextInformation != null) {
985         ContextInformationWrapper wrapper = new ContextInformationWrapper(contextInformation);
986         wrapper.setContextInformationPosition(offset);
987         result.add(wrapper);
988       }
989     }
990     return result;
991   }
992
993   /**
994    * Order the given proposals.
995    */
996   private ICompletionProposal[] order(ICompletionProposal[] proposals) {
997     Arrays.sort(proposals, fComparator);
998     //    int len = proposals.length;
999     //    if (len > 10) {
1000     //      len = 10;
1001     //    }
1002     //    for (int i = 0; i < len; i++) {
1003     //      System.out.println(proposals[i].getDisplayString());
1004     //    }
1005     return proposals;
1006   }
1007
1008   /*
1009    * (non-Javadoc) Method declared on IContentAssistProcessor
1010    */
1011   public char[] getCompletionProposalAutoActivationCharacters() {
1012     return fProposalAutoActivationSet;
1013     //    return null; // new char[] { '$' };
1014   }
1015
1016   /*
1017    * (non-Javadoc) Method declared on IContentAssistProcessor
1018    */
1019   public char[] getContextInformationAutoActivationCharacters() {
1020     return new char[] {};
1021   }
1022
1023   /*
1024    * (non-Javadoc) Method declared on IContentAssistProcessor
1025    */
1026   public IContextInformationValidator getContextInformationValidator() {
1027     return fValidator;
1028   }
1029
1030   /*
1031    * (non-Javadoc) Method declared on IContentAssistProcessor
1032    */
1033   public String getErrorMessage() {
1034     return null;
1035   }
1036 }