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:
042: package org.netbeans.modules.php.editor.completion;
043:
044: import java.util.HashSet;
045: import java.util.List;
046: import java.util.Set;
047: import org.netbeans.modules.gsf.api.CompletionProposal;
048: import java.util.logging.Level;
049: import java.util.logging.Logger;
050: import org.netbeans.modules.gsf.api.CompletionProposal;
051: import org.netbeans.modules.languages.php.lang.Operators;
052: import org.netbeans.modules.languages.php.lang.SpecialKeywords;
053: import org.netbeans.modules.php.editor.TokenUtils;
054: import org.netbeans.modules.php.model.AttributesDeclaration;
055: import org.netbeans.modules.php.model.ClassDefinition;
056: import org.netbeans.modules.php.model.ClassFunctionDefinition;
057: import org.netbeans.modules.php.model.ClassMemberReference;
058: import org.netbeans.modules.php.model.Constant;
059: import org.netbeans.modules.php.model.Modifier;
060: import org.netbeans.modules.php.model.ObjectDefinition;
061: import org.netbeans.modules.php.model.PhpModel;
062: import org.netbeans.modules.php.model.SourceElement;
063:
064: /**
065: * Implementation of the <code>CompletionResultProvider</code> for the
066: * Scope Resolution Operator (::) context.
067: *
068: * <p><b>Note that this implementation is not synchronized.</b></p>
069: *
070: * @see PHP Manual / Example 19.12. :: from outside the class definition
071: * @see PHP Manual / Example 19.13. :: from inside the class definition
072: *
073: * @author Victor G. Vasilyev
074: */
075: public class ScopeResolutionOperatorContext extends
076: MemberAccessExpressionScope implements CompletionResultProvider {
077:
078: private static final Logger LOG = Logger
079: .getLogger(ScopeResolutionOperatorContext.class.getName());
080:
081: /**
082: * This contains a member access expression if it is defined by the context,
083: * oherwise (i.e. an incomplete expression is defined) - <code>null</code>.
084: */
085: private Constant expression;
086:
087: private static final Set<ExpectedToken> PREV_TOKENS = new HashSet<ExpectedToken>();
088: static {
089: PREV_TOKENS.add(new ExpectedToken(
090: TokenUtils.PHPTokenName.OPERATOR.value(),
091: Operators.SCOPE_RESOLUTION.value()));
092: }
093:
094: /**
095: * Returns <code>true</code> iif the specified <code>context</code>
096: * is applicable for completing the Scope Resolution Operator (::),
097: * i.e. caret is located after this operator.
098: *
099: * E.g. see PHP Manual / Example 19.12. :: from outside the class definition
100: * <p><code>
101: * <b><?php</b>
102: * class MyClass {
103: * const CONST_VALUE = 'A constant value';
104: * }
105: *
106: * echo MyClass::<span style="color: rgb(255, 0, 0);"><blink>|</blink></span>CONST_VALUE;
107:
108: * ... <b>?></b>
109: * </code></p>
110: *
111: * @param context the <code>CodeCompletionContext</code>.
112: * @return
113: */
114: public boolean isApplicable(CodeCompletionContext context) {
115: init(context);
116: SourceElement e = myContext.getSourceElement();
117: if (e instanceof Constant) {
118: if (!isScopeResolutionExpression(e)) {
119: expression = (Constant) e;
120: LOG.log(Level.INFO, "{0} provider is NOT applicable."
121: + " expression.getText() =[{1}]", new Object[] {
122: "ScopeResolutionOperatorContext",
123: expression.getText() });
124: return false;
125: }
126: // i.e. constant expr like this: SomeClass::x
127: referencedClass = getReferencedClass((Constant) e);
128: } else if (isApplicableIncompleteExpression()) {
129: referencedClass = getReferencedClass(context);
130: } else {
131: referencedClass = null;
132: }
133: // This provider is applicble iif it possible to find referencedClass.
134: if (referencedClass == null) {
135: LOG
136: .log(
137: Level.INFO,
138: "{0} provider is NOT applicable. referencedClass =[{1}]",
139: new Object[] {
140: "ScopeResolutionOperatorContext",
141: referencedClass });
142: return false;
143: }
144: LOG
145: .log(
146: Level.INFO,
147: "{0} provider is applicable. referencedClass.getName() =[{1}]",
148: new Object[] {
149: "ScopeResolutionOperatorContext",
150: referencedClass.getName() });
151: return true;
152: }
153:
154: @SuppressWarnings("unchecked")
155: public List<CompletionProposal> getProposals(
156: CodeCompletionContext context) {
157: assert context == myContext;
158: assert referencedClass != null;
159:
160: // PHP 5.3.0: Should the static access mode be processed differently?
161: if (isLocalReference()) {
162: // Should the self access mode be processed differently?
163:
164: // Example 19.13. :: from inside the class definition
165: addConstants();
166: // This also applies to Constructors and Destructors, Overloading,
167: // and Magic method definitions.
168: // (private|protected|public) (static|default), i.e. ANY
169: addMethods(null);
170: // (private|protected|public) (static|default), i.e. ANY
171: addProperties(null);
172: } else {
173: if (isParentAccess()) { // i.e. parent::xxx
174: // all constants
175: addConstants();
176: // !private=(protected|public|default) (static|default) methods
177: addMethods(new Filter<ClassFunctionDefinition>() {
178:
179: @Override
180: public boolean isApplicable(
181: ClassFunctionDefinition fd) {
182: List<Modifier> modifiers = fd.getModifiers();
183: int actualFlags = Modifier.toFlags(modifiers);
184: if ((actualFlags & Modifier.PRIVATE.flag()) != 0) {
185: return false;
186: }
187: return true;
188: }
189: });
190: // !private=(protected|public) static properties
191: addProperties(new Filter<AttributesDeclaration>() {
192:
193: @Override
194: public boolean isApplicable(AttributesDeclaration ad) {
195: List<Modifier> modifiers = ad.getModifiers();
196: int actualFlags = Modifier.toFlags(modifiers);
197: if ((actualFlags & Modifier.PRIVATE.flag()) != 0) {
198: return false;
199: }
200: if ((actualFlags & Modifier.STATIC.flag()) == 0) {
201: return false;
202: }
203: return true;
204: }
205: });
206:
207: } else {
208: // Example 19.12. :: from outside the class definition
209: // <ClassName>::<PublicStaticClassMemberOrConstant>
210: addConstants();
211: // Wow! It is possible to call a non-static method via ::
212: // from outside the class definition. So, all public methods of
213: // the specified class should be collected.
214: addMethods(new Filter<ClassFunctionDefinition>() {
215:
216: @Override
217: public boolean isApplicable(
218: ClassFunctionDefinition fd) {
219: List<Modifier> modifiers = fd.getModifiers();
220: int actualFlags = Modifier.toFlags(modifiers);
221: int logicalFlags = Modifier
222: .toLogicalFlags(actualFlags);
223: int expectedFlags = Modifier.PUBLIC.flag();
224: if ((logicalFlags & expectedFlags) != expectedFlags) {
225: return false;
226: }
227: return true;
228: }
229: });
230: addProperties(new Filter<AttributesDeclaration>() {
231:
232: @Override
233: public boolean isApplicable(AttributesDeclaration ad) {
234: List<Modifier> modifiers = ad.getModifiers();
235: int actualFlags = Modifier.toFlags(modifiers);
236: int logicalFlags = Modifier
237: .toLogicalFlags(actualFlags);
238: int expectedFlags = Modifier.STATIC.flag()
239: | Modifier.PUBLIC.flag();
240: if (logicalFlags != expectedFlags) {
241: return false;
242: }
243: return true;
244: }
245: });
246: }
247: }
248:
249: return proposals;
250: }
251:
252: /**
253: * Retirns <code>true</code> if the member access expression defines access
254: * to the parent's member via the special keyword - <code>parent</code>.
255: * @return <code>true</code> if expression in the context is like this:
256: * <code>parent::someMember</code>, otherwise <code>false</code>.
257: */
258: private boolean isParentAccess() {
259: if (expression != null) {
260: ClassMemberReference<SourceElement> ref = expression
261: .getClassConstant();
262: if (ref == null) {
263: return false;
264: }
265: String name = ref.getObjectName();
266: if (SpecialKeywords.PARENT.value().equals(name)) {
267: return true;
268: }
269: }
270: // try to find the "parent" word in the incomplete expression
271: SourceElement e = myContext.getSourceElement();
272: if (e != null) {
273: String text = e.getText();
274: return text.startsWith(SpecialKeywords.PARENT.value());
275: }
276: return false;
277: }
278:
279: /**
280: * Retirns <code>true</code> if the member access expression refers to the
281: * class where it is placed itself.
282: * <p>
283: * <b>Note:</b> This method returns <code>false</code> for the expression
284: * like this:
285: * <code>parent::someMemeber</code>
286: * </p>
287: *
288: * @return <code>true</code> if the context is located inside the referred
289: * class, otherwise <code>false</code>.
290: */
291: private boolean isLocalReference() {
292: SourceElement e = myContext.getSourceElement();
293: while (e != null) {
294: if (e == referencedClass) {
295: return true;
296: }
297: e = e.getParent();
298: }
299: return false;
300: }
301:
302: private static ClassDefinition getReferencedClass(
303: CodeCompletionContext context) {
304: // Context description:
305: // SomeClassIdentifier::
306: // i.e. Identifier of the member is not presented.
307: // In this case the PHP Model doesn't interpret the expression as
308: // the scope resolution expression.
309: String referencedClassName = getReferencedClassName(context);
310: PhpModel model = context.getSourceElement().getModel();
311: return findClassDefinition(model, referencedClassName);
312: }
313:
314: private static String getReferencedClassName(
315: CodeCompletionContext context) {
316: // TODO implement me!
317: SourceElement e = context.getSourceElement();
318: String text = e.getText();
319: int opIndex = text.lastIndexOf(Operators.SCOPE_RESOLUTION
320: .value());
321: String className = null;
322: if (opIndex > -1) {
323: className = text.substring(0, opIndex);
324: }
325: return className;
326: }
327:
328: private boolean isScopeResolutionExpression(SourceElement e) {
329: String text = e.getText();
330: if (text != null
331: && text.contains(Operators.SCOPE_RESOLUTION.value())) {
332: return true;
333: }
334: return false;
335: }
336:
337: // TODO: move to PHPModelUtil
338: private static ClassDefinition getReferencedClass(Constant c) {
339: ClassMemberReference<SourceElement> classReference = c
340: .getClassConstant();
341: if (classReference == null) {
342: return null;
343: }
344: return findClassDefinition(c.getModel(), classReference);
345: }
346:
347: private static ClassDefinition findClassDefinition(PhpModel model,
348: ClassMemberReference<SourceElement> reference) {
349: assert model != null;
350: assert reference != null;
351: ObjectDefinition od = reference.getObject();
352: if (od instanceof ClassDefinition) {
353: return (ClassDefinition) od;
354: }
355: return null;
356: }
357:
358: private static ClassDefinition findClassDefinition(PhpModel model,
359: String className) {
360: List<ClassDefinition> cdList = model
361: .getStatements(ClassDefinition.class);
362: for (ClassDefinition cd : cdList) {
363: if (cd.getName().equals(className)) {
364: return cd;
365: }
366: }
367: return null;
368: }
369:
370: @Override
371: protected int calcInsertOffset() {
372: if (prefix == null || getOperator().equals(prefix)) {
373: return myContext.getCaretOffset();
374: }
375: return myContext.getCaretOffset() - prefix.length();
376: }
377:
378: @Override
379: protected String getOperator() {
380: return Operators.SCOPE_RESOLUTION.value();
381: }
382:
383: }
|