001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.modules.php.editor.completion;
042:
043: import java.util.HashSet;
044: import java.util.LinkedList;
045: import java.util.List;
046: import java.util.Set;
047:
048: import org.netbeans.modules.gsf.api.CompletionProposal;
049: import org.netbeans.modules.php.doc.DocumentationRegistry;
050: import org.netbeans.modules.php.doc.FunctionDoc;
051: import org.netbeans.modules.php.model.Constant;
052: import org.netbeans.modules.php.model.DoStatement;
053: import org.netbeans.modules.php.model.Expression;
054: import org.netbeans.modules.php.model.ExpressionStatement;
055: import org.netbeans.modules.php.model.ForEachStatement;
056: import org.netbeans.modules.php.model.ForStatement;
057: import org.netbeans.modules.php.model.FunctionDeclaration;
058: import org.netbeans.modules.php.model.FunctionDefinition;
059: import org.netbeans.modules.php.model.IfStatement;
060: import org.netbeans.modules.php.model.ModelAccess;
061: import org.netbeans.modules.php.model.PhpModel;
062: import org.netbeans.modules.php.model.ReturnStatement;
063: import org.netbeans.modules.php.model.SourceElement;
064: import org.netbeans.modules.php.model.Statement;
065: import org.netbeans.modules.php.model.SwitchStatement;
066: import org.netbeans.modules.php.model.WhileStatement;
067: import org.openide.filesystems.FileObject;
068:
069: /**
070: * This implementation cares about any non special cases within ExpressionStatement
071: * context. ( F.e. variable , that is part of expression statment, handled
072: * in different provider ).
073: *
074: * @author ads
075: *
076: */
077: public class ExpressionContext implements CompletionResultProvider {
078:
079: /* (non-Javadoc)
080: * @see org.netbeans.modules.php.editor.completion.CompletionResultProvider#isApplicable(org.netbeans.modules.php.editor.completion.CodeCompletionContext)
081: */
082: public boolean isApplicable(CodeCompletionContext context) {
083: SourceElement e = context.getSourceElement();
084: if (e == null) {
085: return false;
086: }
087: if (!Expression.class.isAssignableFrom(e.getElementType())) {
088: return false;
089: }
090: if (isNewIncompletedStatement(e)) {
091: return false;
092: }
093: ScopeResolutionOperatorContext provider = new ScopeResolutionOperatorContext();
094: if (provider.isApplicable(context)) {
095: return false;
096: }
097: return true;
098: }
099:
100: /* (non-Javadoc)
101: * @see org.netbeans.modules.php.editor.completion.CompletionResultProvider#getProposals(org.netbeans.modules.php.editor.completion.CodeCompletionContext)
102: */
103: public List<CompletionProposal> getProposals(
104: CodeCompletionContext context) {
105: String prefix = context.getPrefix();
106: if (prefix == null || prefix.length() == 0) {
107: return null;
108: }
109: SourceElement e = context.getSourceElement();
110: assert e != null : "Current SourceElement is null";
111: if (e.getElementType().equals(Constant.class)) {
112: return createFunctionProposals(context);
113: }
114: return null;
115: }
116:
117: /**
118: * Returns <code>true</code> if the specified <code>SourceElement</code>
119: * is the {@link org.netbeans.modules.php.model.Constant} and the next
120: * element is the last element in the new incompleted
121: * {@link org.netbeans.modules.php.model.ExpressionStatement}.
122: *
123: * @param e the underlying <code>SourceElement</code>.
124: * @return <code>true</code> if parent of the specified
125: * <code>SourceElement</code> is the new incompleted
126: * {@link org.netbeans.modules.php.model.ExpressionStatement},
127: * otherwise - <code>false</code>.
128: */
129: public static boolean isNewIncompletedStatement(SourceElement e) {
130: SourceElement parent = e.getParent();
131: if (!(parent instanceof ExpressionStatement)) {
132: return false;
133: }
134: List<SourceElement> children = parent.getChildren();
135: return children.size() == 2
136: && children.get(0) instanceof Constant
137: && children.get(1) instanceof org.netbeans.modules.php.model.Error;
138: }
139:
140: /**
141: * Creates function proposals based on the specified <code>context</code>.
142: * @param context a source element that defines the proposal context.
143: * @param caretOffset
144: * @param prefix
145: * @param formatter
146: * @return
147: * @see <a href="http://www.php.net/manual/en/language.functions.php">
148: * Chapter 17. Functions</a>
149: */
150: private List<CompletionProposal> createFunctionProposals(
151: CodeCompletionContext context) {
152: List<CompletionProposal> list = new LinkedList<CompletionProposal>();
153: if (isFunctionCallContext(context.getSourceElement())) {
154: addBuiltinFunctionProposals(list, context);
155: addUserDefinedFunctionProposals(list, context);
156: }
157: return list;
158: }
159:
160: /**
161: * Adds user defined function proposals to the specified <code>list</code>.
162: * @param list a list of the proposals.
163: * @param context a source element that defines the proposal context.
164: * @param caretOffset
165: * @param prefix
166: * @param formatter
167: * @see <a href=
168: * "http://www.php.net/manual/en/language.functions.php#functions.user-defined"
169: * >User-defined functions</a> section of the Chapter 17. Functions of
170: * the PHP Manual.
171: * @todo Example 17.2. Conditional functions
172: * @todo Example 17.3. Functions within functions
173: * @todo Example 17.4. Recursive functions
174: */
175: public static void addUserDefinedFunctionProposals(
176: List<CompletionProposal> list, CodeCompletionContext context) {
177: // TODO: It seems model should be synchronized once. It is not required here.
178: FileObject fileObject = context.getCompilationInfo()
179: .getFileObject();
180: PhpModel model = ModelAccess.getAccess().getModel(
181: ModelAccess.getModelOrigin(fileObject));
182: model.writeLock();
183: try {
184: model.sync();
185: List<FunctionDefinition> fds = model
186: .getStatements(FunctionDefinition.class);
187: String prefix = context.getPrefix();
188: // TODO: Example 17.2. Conditional functions
189: // TODO: Example 17.3. Functions within functions
190: for (FunctionDefinition fd : fds) {
191: FunctionDeclaration decl = fd.getDeclaration();
192: if (isMatchedFunction(decl.getName(), prefix)) {
193: list
194: .add(new UserDefinedMethodItem(decl,
195: context.getInsertOffset(), context
196: .getFormatter()));
197: }
198: }
199: } finally {
200: model.writeUnlock();
201: }
202:
203: }
204:
205: public static void addBuiltinFunctionProposals(
206: List<CompletionProposal> list, CodeCompletionContext context) {
207: String prefix = context.getPrefix();
208: if (prefix == null) {
209: return; // we won't return a list of all built-in functions.
210: }
211: char firstLetter = prefix.charAt(0);
212: List<FunctionDoc> docs = DocumentationRegistry.getInstance()
213: .getFunctionByName(firstLetter);
214: if (docs.isEmpty()) {
215: return;
216: }
217: for (FunctionDoc doc : docs) {
218: if (doc.getOwnerName() == null
219: && isMatchedFunction(doc.getName(), prefix)) {
220: list.add(new BuiltinMethodItem(doc, context
221: .getInsertOffset(), context.getFormatter()));
222: }
223: }
224: }
225:
226: /**
227: * Matches given PHP function name with the specified prefix accordig to the
228: * PHP language rules that are established by a note in the
229: * <a href=
230: * "http://www.php.net/manual/en/language.functions.php#functions.user-defined">
231: * User-defined functions</a> section of the <i>Chapter 17. Functions</i> of
232: * the PHP Manual:
233: * <p>
234: * "Note: Function names are <u>case-insensitive</u>, though it is usually
235: * good form to call functions as they appear in their declaration."
236: * </p>
237: *
238: * @param fName a PHP function name.
239: * @param prefix the prefix string, empty string or <code>null</code>.
240: * @return <code>true</code> if <code>fName</code> is matched with
241: * <code>prefix</code> or <code>prefix</code> is empty string or
242: * <code>null</code>, otherwise <code>false</code>.
243: */
244: private static boolean isMatchedFunction(String fName, String prefix) {
245: // TODO: declare this method as public and move it into the Php class.
246: if (prefix == null || prefix.trim().length() == 0) {
247: return true;
248: }
249: return fName.toLowerCase().startsWith(prefix.toLowerCase());
250: }
251:
252: private boolean isFunctionCallContext(SourceElement element) {
253: SourceElement parent = element.getParent();
254: while (parent != null) {
255: if (FUCTION_CALL_CONTEXTS.contains(parent.getElementType())) {
256: return true;
257: }
258: parent = parent.getParent();
259: }
260: return false;
261: }
262:
263: /**
264: * The set of contexts where a function call is applicable.
265: */
266: private static Set<Class<? extends Statement>> FUCTION_CALL_CONTEXTS;
267: static {
268: FUCTION_CALL_CONTEXTS = new HashSet<Class<? extends Statement>>();
269: FUCTION_CALL_CONTEXTS.add(ExpressionStatement.class);
270: FUCTION_CALL_CONTEXTS.add(ReturnStatement.class);
271: FUCTION_CALL_CONTEXTS.add(DoStatement.class);
272: FUCTION_CALL_CONTEXTS.add(ForEachStatement.class);
273: FUCTION_CALL_CONTEXTS.add(ForStatement.class);
274: FUCTION_CALL_CONTEXTS.add(IfStatement.class);
275: FUCTION_CALL_CONTEXTS.add(SwitchStatement.class);
276: FUCTION_CALL_CONTEXTS.add(WhileStatement.class);
277: }
278:
279: }
|