/******************************************************************************* * Copyright (c) 2009 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Zend Technologies *******************************************************************************/ package org.eclipse.php.internal.core.codeassist.strategies; import java.util.*; import org.eclipse.core.runtime.Platform; import org.eclipse.dltk.ast.Modifiers; import org.eclipse.dltk.core.*; import org.eclipse.dltk.core.index2.search.ISearchEngine.MatchRule; import org.eclipse.php.core.codeassist.ICompletionContext; import org.eclipse.php.core.codeassist.IElementFilter; import org.eclipse.php.core.compiler.PHPFlags; import org.eclipse.php.internal.core.PHPCoreConstants; import org.eclipse.php.internal.core.PHPCorePlugin; import org.eclipse.php.core.PHPVersion; import org.eclipse.php.internal.core.codeassist.contexts.ClassMemberContext; import org.eclipse.php.internal.core.codeassist.contexts.ClassMemberContext.Trigger; import org.eclipse.php.internal.core.codeassist.contexts.ClassObjMemberContext; import org.eclipse.php.internal.core.codeassist.contexts.ClassStaticMemberContext; import org.eclipse.php.internal.core.model.PHPModelAccess; import org.eclipse.php.internal.core.typeinference.PHPModelUtils; import org.eclipse.php.internal.core.typeinference.TraitUtils; import org.eclipse.php.internal.core.typeinference.UseTrait; /** * This strategy completes class members: $a->|, A::|, etc... * * @author michael */ public abstract class ClassMembersStrategy extends AbstractCompletionStrategy { ClassStaticMemberContext staticMemberContext; boolean isFunctionParameterContext = false; public ClassMembersStrategy(ICompletionContext context, IElementFilter elementFilter) { super(context, elementFilter); } public ClassMembersStrategy(ICompletionContext context) { super(context); if (context instanceof ClassStaticMemberContext) { staticMemberContext = (ClassStaticMemberContext) context; isFunctionParameterContext = staticMemberContext.isFunctionParameterContext(); } } protected boolean showStaticMembers(ClassMemberContext context) { return context.getTriggerType() == Trigger.CLASS || context.getPHPVersion().isGreaterThan(PHPVersion.PHP5); } protected boolean showNonStaticMembers(ClassMemberContext context) { return context.getTriggerType() == Trigger.OBJECT || isParentCall(context); } protected boolean isThisCall(ClassMemberContext context) { return ((context instanceof ClassObjMemberContext) && ((ClassObjMemberContext) context).isThis()); } protected boolean isDirectThis(ClassMemberContext context) { return ((context instanceof ClassObjMemberContext) && ((ClassObjMemberContext) context).isDirectThis()); } protected boolean isSelfCall(ClassMemberContext context) { return ((context instanceof ClassStaticMemberContext) && ((ClassStaticMemberContext) context).isSelf()); } protected boolean isDirectSelfCall(ClassMemberContext context) { return ((context instanceof ClassStaticMemberContext) && ((ClassStaticMemberContext) context).isDirectSelf()); } protected boolean isParentCall(ClassMemberContext context) { return ((context instanceof ClassStaticMemberContext) && ((ClassStaticMemberContext) context).isParent()); } protected boolean isDirectParentCall(ClassMemberContext context) { return ((context instanceof ClassStaticMemberContext) && ((ClassStaticMemberContext) context).isDirectParent()); } protected boolean showStrictOptions() { return Platform.getPreferencesService().getBoolean(PHPCorePlugin.ID, PHPCoreConstants.CODEASSIST_SHOW_STRICT_OPTIONS, false, null); } /** * Returns whether the specified member is visible in current context * * @param member * @param context * @return * @throws ModelException */ protected boolean isVisible(IMember member, ClassMemberContext context) throws ModelException { if (isThisCall(context) || isSelfCall(context)) { return true; } int flags = member.getFlags(); if (isParentCall(context)) { return !PHPFlags.isPrivate(flags); } return !PHPFlags.isPrivate(flags) && !PHPFlags.isProtected(flags); } /** * Returns whether the specified member should be filtered from the code * assist * * @param member * @param type * @param context * @return * @throws ModelException */ protected boolean isFiltered(IMember member, IType type, ClassMemberContext context) throws ModelException { /* check 0 */ int flags = member.getFlags(); if (PHPFlags.isConstant(member.getFlags())) { if (context.getTriggerType() == Trigger.CLASS) { if (PHPFlags.isPrivate(flags) && (member.getDeclaringType().equals(type) && !isTraitMember(context.getPHPVersion(), type, member))) { if (isParent(context)) { // is Parent return true; // 1:1 } else if (isSelfKeyword(context)) { return false;// 1:2 } else if (isStaticAccessFromInsideClass(context)) { return false;// 1:3 } else if (isStaticAccessFromOutsideClass(context)) { return true;// 1:4 } } else if (PHPFlags.isProtected(flags)) { if (isParent(context)) { // is Parent return false; // 2:1 } else if (isSelfKeyword(context)) { return false;// 2:2 } else if (isStaticAccessFromInsideClass(context)) { return false;// 2:3 } else if (isStaticAccessFromOutsideClass(context)) { return true;// 2:4 } } else if (PHPFlags.isPublic(flags)) { return false; } } else if (context.getTriggerType() == Trigger.OBJECT) { return true;// 17:5-7 } /* check 1 */ } else if (PHPFlags.isStatic(flags)) { /* check 2 */ if (member instanceof IField) { /* check 3 */ if (context.getTriggerType() == Trigger.CLASS && !isFunctionParameterContext) { /* check 5 */ if (PHPFlags.isPrivate(flags) && (member.getDeclaringType().equals(type) || isTraitMember(context.getPHPVersion(), type, member))) { if (isParent(context)) { // is Parent return true; // 1:1 } else if (isSelfKeyword(context)) { return false;// 1:2 } else if (isStaticAccessFromInsideClass(context)) { return false;// 1:3 } else if (isStaticAccessFromOutsideClass(context)) { return true;// 1:4 } } else if (PHPFlags.isProtected(flags)) { if (isParent(context)) { // is Parent return false; // 2:1 } else if (isSelfKeyword(context)) { return false;// 2:2 } else if (isStaticAccessFromInsideClass(context)) { return false;// 2:3 } else if (isStaticAccessFromOutsideClass(context)) { return true;// 2:4 } } else if (PHPFlags.isPublic(flags)) { return false; } } else if (context.getTriggerType() == Trigger.OBJECT) { if (PHPFlags.isPrivate(flags) && (member.getDeclaringType().equals(type) || isTraitMember(context.getPHPVersion(), type, member))) { if (isThisKeyWord(context) && showStrictOptions()) { return false; // 1:5 } else if (isIndirectThis(context)) { return true;// 1:6 } else if (isSimpleArrow(context)) { return true;// 1:7 } } else if (PHPFlags.isProtected(flags)) { if (isThisKeyWord(context) && showStrictOptions()) { return false; // 2:5 } else if (isIndirectThis(context) && isDirectThis(context)) { return true;// 2:6 } else if (isSimpleArrow(context)) { return true;// 2:7 } } else if (PHPFlags.isPublic(flags)) { if (isThisKeyWord(context) && showStrictOptions()) { return false; // 3:5 } else if (isIndirectThis(context) && showStrictOptions()) { return false;// 3:6 } else if (isSimpleArrow(context) && showStrictOptions()) { return false;// 3:7 } } } } else if (member instanceof IMethod) { if (context.getTriggerType() == Trigger.CLASS && !isFunctionParameterContext) { if (PHPFlags.isPrivate(flags) && (member.getDeclaringType().equals(type) || isTraitMember(context.getPHPVersion(), type, member))) { if (isParent(context)) { return true; // 5:1 } else if (isSelfCall(context)) { return false;// 5:2 } else if (!isThisCall(context) && !isParentCall(context) && isSelfCall(context)) { return false;// 5:3 } else if (!isThisCall(context) && !isParentCall(context) && !isSelfCall(context)) { return true;// 5:4 } } else if (PHPFlags.isProtected(flags)) { if (isParent(context)) { return false; // 6:1 } else if (isSelfCall(context)) { return false;// 6:2 } else if (!isThisCall(context) && !isParentCall(context) && isSelfCall(context)) { return false;// 6:3 } else if (!isThisCall(context) && !isParentCall(context) && !isSelfCall(context)) { return true;// 6:4 } } else if (PHPFlags.isPublic(flags)) { if (isParent(context)) { return false; // 7:1 } else if (isSelfCall(context)) { return false;// 7:2 } else if (!isThisCall(context) && !isParentCall(context) && isSelfCall(context)) { return false;// 7:3 } else if (!isThisCall(context) && !isParentCall(context)) { return false; } } else if (PHPFlags.isDefault(flags)) { if (isParent(context)) { return false; // 8:1 } else if (isSelfCall(context)) { return false;// 8:2 } else if (!isThisCall(context) && !isParentCall(context) && isSelfCall(context)) { return false;// 8:3 } else if (!isThisCall(context) && !isParentCall(context)) { return false; } } } else if (context.getTriggerType() == Trigger.OBJECT) { if (PHPFlags.isPrivate(flags) && (member.getDeclaringType().equals(type) || isTraitMember(context.getPHPVersion(), type, member))) { if (isThisKeyWord(context)) { return false; // 5:5 } else if (isIndirectThis(context)) { return false;// 5:6 } else if (isSimpleArrow(context)) { return true;// 5:7 } } else if (PHPFlags.isProtected(flags)) { if (isThisKeyWord(context)) { return false; // 6:5 } else if (isIndirectThis(context)) { return false;// 6:6 } else if (isSimpleArrow(context)) { return true;// 6:7 } } else if (PHPFlags.isPublic(flags)) { if (isThisKeyWord(context)) { return false; // 7:5 } else if (isIndirectThis(context)) { return false;// 7:6 } else if (isSimpleArrow(context)) { return false;// 7:7 } } else if (PHPFlags.isDefault(flags)) { if (isThisKeyWord(context)) { return false; // 8:5 } else if (isIndirectThis(context)) { return false;// 8:6 } else if (isSimpleArrow(context)) { return false;// 8:7 } } } } } else { // not static if (member instanceof IField) { if (context.getTriggerType() == Trigger.CLASS && !isFunctionParameterContext) { return true; // 9:1 - 12:4 } else if (context.getTriggerType() == Trigger.OBJECT) { if (PHPFlags.isPrivate(flags) && (member.getDeclaringType().equals(type) || isTraitMember(context.getPHPVersion(), type, member))) { if (isThisKeyWord(context)) { return false; // 9:5 } else if (isIndirectThis(context)) { return false;// 9:6 } else if (isSimpleArrow(context)) { return true;// 9:7 } } else if (PHPFlags.isProtected(flags)) { if (isThisKeyWord(context)) { return false; // 10:5 } else if (isIndirectThis(context)) { return false;// 10:6 } else if (isSimpleArrow(context)) { return true;// 10:7 } } else if (PHPFlags.isPublic(flags)) { if (isThisKeyWord(context)) { return false; // 11:5 } else if (isIndirectThis(context)) { return false;// 11:6 } else if (isSimpleArrow(context)) { return false;// 11:7 } } } } else if (member instanceof IMethod) { if (context.getTriggerType() == Trigger.CLASS && !isFunctionParameterContext) { if (PHPFlags.isPrivate(flags) && (member.getDeclaringType().equals(type) || isTraitMember(context.getPHPVersion(), type, member))) { if (isParent(context)) { return true; // 13:1 } else if (isSelfKeyword(context)) { return false;// 13:2 } else if (isStaticAccessFromInsideClass(context)) { return false;// 13:3 } else if (isStaticAccessFromOutsideClass(context)) { return true;// 13:4 } } else if (PHPFlags.isProtected(flags)) { if (isParent(context)) { return false; // 14:1 } else if (isSelfKeyword(context)) { return false;// 14:2 } else if (isStaticAccessFromInsideClass(context)) { return false;// 14:3 } else if (isStaticAccessFromOutsideClass(context)) { return true;// 14:4 } } else if (PHPFlags.isPublic(flags)) { if (isParent(context)) { return false; // 15:1 } else if (isSelfKeyword(context)) { return false;// 15:2 } else if (isStaticAccessFromInsideClass(context)) { return false;// 15:3 } else if (isStaticAccessFromOutsideClass(context) && showStrictOptions()) { return false;// 15:4 } } else if (PHPFlags.isDefault(flags)) { if (isParent(context)) { return false; // 16:1 } else if (isSelfKeyword(context)) { return false;// 16:2 } else if (isStaticAccessFromInsideClass(context)) { return false;// 16:3 } else if (isStaticAccessFromOutsideClass(context) && showStrictOptions()) { return false;// 16:4 } } } else if (context.getTriggerType() == Trigger.OBJECT) { if (PHPFlags.isPrivate(flags) && (member.getDeclaringType().equals(type) || isTraitMember(context.getPHPVersion(), type, member))) { if (isThisKeyWord(context)) { return false; // 13:5 } else if (isIndirectThis(context)) { return false;// 13:6 } else if (isSimpleArrow(context)) { return true;// 13:7 } } else if (PHPFlags.isProtected(flags)) { if (isThisKeyWord(context)) { return false; // 14:5 } else if (isIndirectThis(context)) { return false;// 14:6 } else if (isSimpleArrow(context)) { return true;// 14:7 } } else if (PHPFlags.isPublic(flags)) { if (isThisKeyWord(context)) { return false; // 15:5 } else if (isIndirectThis(context)) { return false;// 15:6 } else if (isSimpleArrow(context)) { return false;// 15:7 } } else if (PHPFlags.isDefault(flags)) { if (isThisKeyWord(context)) { return false; // 16:5 } else if (isIndirectThis(context)) { return false;// 16:6 } else if (isSimpleArrow(context)) { return false;// 16:7 } } } } } return true; } private boolean isTraitMember(PHPVersion phpVersion, IType type, IMember member) { return isTraitMember(phpVersion, type, member, new HashSet<IType>()); } private boolean isTraitMember(PHPVersion phpVersion, IType type, IMember member, Set<IType> typeSet) { if (phpVersion.isGreaterThan(PHPVersion.PHP5_3)) { UseTrait useTrait = TraitUtils.parse(type); String typeName = PHPModelUtils.getFullName(member.getDeclaringType()); for (String trait : useTrait.getTraits()) { if (trait.equals(typeName)) { return true; } } for (String trait : useTrait.getTraits()) { IType[] traits = PHPModelAccess.getDefault().findTraits(trait, MatchRule.EXACT, 0, 0, TraitUtils.createSearchScope(type), null); for (IType traitType : traits) { return isTraitMember(phpVersion, traitType, member, typeSet); } } } return false; } /** * is 'parent' keyword * * @param context * @return */ private boolean isParent(ClassMemberContext context) { return !isThisCall(context) && isParentCall(context) && isDirectParentCall(context); } /** * is 'self' keyword * * @param context * @return */ private boolean isSelfKeyword(ClassMemberContext context) { return !isThisCall(context) && !isParentCall(context) && isSelfCall(context) && isDirectSelfCall(context); } /** * class A { static private $var = 0; function foo() { A::$var; //has an * access to private $var field } } * * @param context * @return */ private boolean isStaticAccessFromInsideClass(ClassMemberContext context) { return !isThisCall(context) && !isParentCall(context) && isSelfCall(context) && !isDirectSelfCall(context); } /** * class A { static private $var = 0; public function foo() {} } A::$var; * //has no access to private $var field A::foo(); //has access to public * function foo * * * @param context * @return */ private boolean isStaticAccessFromOutsideClass(ClassMemberContext context) { return !isThisCall(context) && !isParentCall(context) && !isSelfCall(context) && !isDirectSelfCall(context); } /** * simple 'this' keyword case * * @param context * @return */ private boolean isThisKeyWord(ClassMemberContext context) { return isThisCall(context) && isDirectThis(context); } /** * class A { static private $var = 0; function foo() { $a = new A(); $a->| } * } access level as for 'this' keyword in this case. * * @param context * @return */ private boolean isIndirectThis(ClassMemberContext context) { return isThisCall(context) && !isDirectThis(context); } /** * case 6 simple -> case * * @param context * @return */ private boolean isSimpleArrow(ClassMemberContext context) { return !isThisCall(context) && !isDirectThis(context); } /** * Removes overridden members from the completion list * * @param members * Class/Interface members in type hierarchy order (from bottom * to up) */ protected <T extends IMember> Collection<T> removeOverriddenElements(Collection<T> members) { List<T> result = new LinkedList<T>(); List<T> newMembers = new ArrayList<T>(); newMembers.addAll(members); Collections.sort(newMembers, new Comparator<T>() { public int compare(T o1, T o2) { try { int flag1 = getFlag(o1.getFlags()); int flag2 = getFlag(o2.getFlags()); return flag2 - flag1; } catch (ModelException e) { } return 0; } private int getFlag(int flags) { if (Flags.isPublic(flags)) { return Modifiers.AccPublic; } if (Flags.isProtected(flags)) { return Modifiers.AccProtected; } if (Flags.isPrivate(flags)) { return Modifiers.AccPrivate; } return Modifiers.AccPrivate; } }); Set<String> processed = new HashSet<String>(); for (IMember member : newMembers) { if (processed.add(member.getElementName())) { result.add((T) member); } } return result; } }