blob: ab1106c0bd9eb6127ce5869e59fdd08f37adb784 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.php.editor.elements;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.netbeans.modules.parsing.spi.indexing.support.IndexResult;
import org.netbeans.modules.php.api.PhpVersion;
import org.netbeans.modules.php.editor.api.ElementQuery;
import org.netbeans.modules.php.editor.api.NameKind;
import org.netbeans.modules.php.editor.api.NameKind.Exact;
import org.netbeans.modules.php.editor.api.PhpElementKind;
import org.netbeans.modules.php.editor.api.PhpModifiers;
import org.netbeans.modules.php.editor.api.elements.BaseFunctionElement.PrintAs;
import org.netbeans.modules.php.editor.api.elements.MethodElement;
import org.netbeans.modules.php.editor.api.elements.ParameterElement;
import org.netbeans.modules.php.editor.api.elements.TypeElement;
import org.netbeans.modules.php.editor.api.elements.TypeNameResolver;
import org.netbeans.modules.php.editor.api.elements.TypeResolver;
import org.netbeans.modules.php.editor.index.PHPIndexer;
import org.netbeans.modules.php.editor.index.Signature;
import org.netbeans.modules.php.editor.model.impl.VariousUtils;
import org.netbeans.modules.php.editor.model.nodes.MethodDeclarationInfo;
import org.netbeans.modules.php.editor.parser.astnodes.BodyDeclaration.Modifier;
import org.netbeans.modules.php.editor.parser.astnodes.MethodDeclaration;
import org.openide.util.Parameters;
/**
* @author Radek Matous
*/
public final class MethodElementImpl extends PhpElementImpl implements MethodElement {
public static final String IDX_FIELD = PHPIndexer.FIELD_METHOD;
public static final String IDX_CONSTRUCTOR_FIELD = PHPIndexer.FIELD_CONSTRUCTOR;
private final PhpModifiers modifiers;
private final TypeElement enclosingType;
private final BaseFunctionElementSupport functionSupport;
private final boolean isMagic;
private MethodElementImpl(
final TypeElement enclosingType,
final String methodName,
final boolean isMagic,
final int offset,
final int flags,
final String fileUrl,
final ElementQuery elementQuery,
final BaseFunctionElementSupport.Parameters parameters,
final BaseFunctionElementSupport.ReturnTypes returnTypes,
final boolean isDeprecated) {
super(methodName, enclosingType.getName(), fileUrl, offset, elementQuery, isDeprecated);
final boolean isFromInterface = enclosingType.isInterface();
this.modifiers = PhpModifiers.fromBitMask((isFromInterface) ? (flags | Modifier.ABSTRACT | Modifier.PUBLIC) : flags);
this.isMagic = isMagic;
this.enclosingType = enclosingType;
this.functionSupport = new BaseFunctionElementSupport(parameters, returnTypes);
}
public static Set<MethodElement> getMagicMethods(final TypeElement type) {
Set<MethodElement> retval = new HashSet<>();
retval.add(createMagicMethod(type, "__callStatic", Modifier.PUBLIC | Modifier.STATIC, "$name", "$arguments")); //NOI18N
retval.add(createMagicMethod(type, "__set_state", Modifier.PUBLIC | Modifier.STATIC, "$array")); //NOI18N
retval.add(createMagicMethod(type, "__call", Modifier.PUBLIC, "$name", "$arguments")); //NOI18N
retval.add(createMagicMethod(type, "__clone", Modifier.PUBLIC)); //NOI18N
retval.add(createMagicMethod(type, "__construct", Modifier.PUBLIC)); //NOI18N
retval.add(createMagicMethod(type, "__destruct", Modifier.PUBLIC)); //NOI18N
retval.add(createMagicMethod(type, "__invoke", Modifier.PUBLIC)); //NOI18N
retval.add(createMagicMethod(type, "__get", Modifier.PUBLIC, "$name")); //NOI18N
retval.add(createMagicMethod(type, "__set", Modifier.PUBLIC, "$name", "$value")); //NOI18N
retval.add(createMagicMethod(type, "__isset", Modifier.PUBLIC, "$name")); //NOI18N
retval.add(createMagicMethod(type, "__unset", Modifier.PUBLIC, "$name")); //NOI18N
retval.add(createMagicMethod(type, "__sleep", Modifier.PUBLIC)); //NOI18N
retval.add(createMagicMethod(type, "__wakeup", Modifier.PUBLIC)); //NOI18N
retval.add(createMagicMethod(type, "__toString", Modifier.PUBLIC)); //NOI18N
// PHP 7.4 New custom object serialization mechanism
// https://wiki.php.net/rfc/custom_object_serialization
retval.add(createMagicMethod(type, "__serialize", Modifier.PUBLIC)); //NOI18N
retval.add(createMagicMethod(type, "__unserialize", Modifier.PUBLIC, "array $data")); //NOI18N
return retval;
}
public static MethodElement createMagicMethod(final TypeElement type, String methodName, int flags, String... arguments) {
MethodElement retval = new MethodElementImpl(
type,
methodName,
true,
0,
flags,
type.getFilenameUrl(),
null,
BaseFunctionElementSupport.ParametersImpl.create(fromParameterNames(arguments)),
BaseFunctionElementSupport.ReturnTypes.NONE,
type.isDeprecated());
return retval;
}
private static List<ParameterElement> fromParameterNames(String... names) {
List<ParameterElement> retval = new ArrayList<>();
for (String parameterName : names) {
retval.add(new ParameterElementImpl(parameterName, null, 0, Collections.<TypeResolver>emptySet(), true, true, false, false));
}
return retval;
}
public static Set<MethodElement> fromSignature(final TypeElement type,
final IndexQueryImpl indexQuery, final IndexResult indexResult) {
return fromSignature(type, NameKind.empty(), indexQuery, indexResult);
}
public static Set<MethodElement> fromSignature(final TypeElement type, final NameKind query,
final IndexQueryImpl indexQuery, final IndexResult indexResult) {
final String[] values = indexResult.getValues(IDX_FIELD);
final Set<MethodElement> retval = values.length > 0
? new HashSet<MethodElement>() : Collections.<MethodElement>emptySet();
for (String val : values) {
final MethodElement method = fromSignature(type, query, indexQuery, Signature.get(val));
if (method != null) {
retval.add(method);
}
}
return retval;
}
private static MethodElement fromSignature(final TypeElement type, final NameKind query,
final IndexQueryImpl indexScopeQuery, final Signature sig) {
Parameters.notNull("NameKind query: can't be null", query);
final MethodSignatureParser signParser = new MethodSignatureParser(sig);
MethodElement retval = null;
if (matchesQuery(query, signParser)) {
retval = new MethodElementImpl(type, signParser.getMethodName(), false,
signParser.getOffset(), signParser.getFlags(), signParser.getFileUrl(),
indexScopeQuery, new ParametersFromSignature(signParser), new ReturnTypesFromSignature(signParser), signParser.isDeprecated());
}
return retval;
}
public static MethodElement fromNode(final TypeElement type, final MethodDeclaration node, final ElementQuery.File fileQuery) {
Parameters.notNull("type", type);
Parameters.notNull("node", node);
Parameters.notNull("fileQuery", fileQuery);
MethodDeclarationInfo info = MethodDeclarationInfo.create(fileQuery.getResult().getProgram(), node, type.isInterface());
return new MethodElementImpl(
type,
info.getName(),
false,
info.getRange().getStart(),
info.getAccessModifiers().toFlags(),
fileQuery.getURL().toExternalForm(),
fileQuery,
BaseFunctionElementSupport.ParametersImpl.create(info.getParameters()),
BaseFunctionElementSupport.ReturnTypesImpl.create(
TypeResolverImpl.parseTypes(VariousUtils.getReturnType(fileQuery.getResult().getProgram(), node.getFunction()))),
VariousUtils.isDeprecatedFromPHPDoc(fileQuery.getResult().getProgram(), node.getFunction()));
}
private static boolean matchesQuery(final NameKind query, MethodSignatureParser signParser) {
Parameters.notNull("NameKind query: can't be null", query);
return (query instanceof NameKind.Empty)
|| query.matchesName(MethodElement.KIND, signParser.getMethodName());
}
public static Set<MethodElement> fromConstructorSignature(final TypeElement type,
final IndexQueryImpl indexQuery, final IndexResult indexResult) {
final String[] values = indexResult.getValues(MethodElementImpl.IDX_CONSTRUCTOR_FIELD);
final Set<MethodElement> retval = new HashSet<>();
for (String val : values) {
retval.add(fromConstructorSignature(type, indexQuery, indexResult, Signature.get(val)));
}
return retval;
}
public static MethodElement fromConstructorSignature(final TypeElement type,
final IndexQueryImpl indexScopeQuery, final IndexResult indexResult, final Signature sig) {
final MethodSignatureParser signParser = new MethodSignatureParser(sig);
final MethodElement retval = new MethodElementImpl(type, MethodElementImpl.CONSTRUCTOR_NAME, false,
signParser.getOffset(), signParser.getFlags(), indexResult.getUrl().toString(),
indexScopeQuery, new ParametersFromSignature(signParser), new ReturnTypesFromSignature(signParser), signParser.isDeprecated());
return retval;
}
@Override
public List<ParameterElement> getParameters() {
return this.functionSupport.getParameters();
}
@Override
public Collection<TypeResolver> getReturnTypes() {
return this.functionSupport.getReturnTypes();
}
@Override
public String asString(PrintAs as) {
return asString(as, TypeNameResolverImpl.forNull());
}
@Override
public String asString(PrintAs as, TypeNameResolver typeNameResolver) {
return this.functionSupport.asString(as, this, typeNameResolver);
}
@Override
public String asString(PrintAs as, TypeNameResolver typeNameResolver, PhpVersion phpVersion) {
return this.functionSupport.asString(as, this, typeNameResolver, phpVersion);
}
@Override
public PhpElementKind getPhpElementKind() {
return MethodElement.KIND;
}
@Override
public PhpModifiers getPhpModifiers() {
return modifiers;
}
@Override
public TypeElement getType() {
return enclosingType;
}
@Override
public boolean isConstructor() {
final Exact exactName = NameKind.exact(getName());
return exactName.matchesName(getPhpElementKind(), CONSTRUCTOR_NAME)
|| exactName.matchesName(getPhpElementKind(), getType().getName());
}
@Override
public String getSignature() {
StringBuilder sb = new StringBuilder();
sb.append(getName().toLowerCase()).append(Separator.SEMICOLON); //NOI18N
sb.append(getName()).append(Separator.SEMICOLON); //NOI18N
sb.append(getSignatureLastPart());
checkSignature(sb);
return sb.toString();
}
public String getConstructorSignature() {
StringBuilder sb = new StringBuilder();
sb.append(getType().getName().toLowerCase()).append(Separator.SEMICOLON); //NOI18N
sb.append(getType().getName()).append(Separator.SEMICOLON); //NOI18N
sb.append(getSignatureLastPart());
checkConstructorSignature(sb);
return sb.toString();
}
private String getSignatureLastPart() {
StringBuilder sb = new StringBuilder();
sb.append(getOffset()).append(Separator.SEMICOLON); //NOI18N
List<ParameterElement> parameterList = getParameters();
for (int idx = 0; idx < parameterList.size(); idx++) {
ParameterElementImpl parameter = (ParameterElementImpl) parameterList.get(idx);
if (idx > 0) {
sb.append(Separator.COMMA); //NOI18N
}
sb.append(parameter.getSignature());
}
sb.append(Separator.SEMICOLON); //NOI18N
for (TypeResolver typeResolver : getReturnTypes()) {
TypeResolverImpl resolverImpl = (TypeResolverImpl) typeResolver;
sb.append(resolverImpl.getSignature());
}
sb.append(Separator.SEMICOLON); //NOI18N
sb.append(getPhpModifiers().toFlags()).append(Separator.SEMICOLON);
sb.append(isDeprecated() ? 1 : 0).append(Separator.SEMICOLON);
sb.append(getFilenameUrl()).append(Separator.SEMICOLON);
return sb.toString();
}
@Override
public boolean isStatic() {
return getPhpModifiers().isStatic();
}
@Override
public boolean isPublic() {
return getPhpModifiers().isPublic();
}
@Override
public boolean isProtected() {
return getPhpModifiers().isProtected();
}
@Override
public boolean isPrivate() {
return getPhpModifiers().isPrivate();
}
@Override
public boolean isFinal() {
return getPhpModifiers().isFinal();
}
@Override
public boolean isAbstract() {
return getPhpModifiers().isAbstract();
}
@Override
public boolean isMagic() {
return isMagic;
}
private static class MethodSignatureParser {
private final Signature signature;
MethodSignatureParser(Signature signature) {
this.signature = signature;
}
String getMethodName() {
return signature.string(1);
}
int getOffset() {
return signature.integer(2);
}
List<ParameterElement> getParameters() {
return ParameterElementImpl.parseParameters(signature.string(3));
}
int getFlags() {
return signature.integer(5);
}
Set<TypeResolver> getReturnTypes() {
return TypeResolverImpl.parseTypes(signature.string(4));
}
boolean isDeprecated() {
return signature.integer(6) == 1;
}
String getFileUrl() {
return signature.string(7);
}
}
private void checkSignature(StringBuilder sb) {
boolean checkEnabled = false;
assert checkEnabled = true;
if (checkEnabled) {
String retval = sb.toString();
MethodSignatureParser parser = new MethodSignatureParser(Signature.get(retval));
assert getName().equals(parser.getMethodName());
assert getOffset() == parser.getOffset();
assert getPhpModifiers().toFlags() == parser.getFlags();
assert getParameters().size() == parser.getParameters().size();
assert getReturnTypes().size() == parser.getReturnTypes().size();
}
}
private void checkConstructorSignature(StringBuilder sb) {
boolean checkEnabled = false;
assert checkEnabled = true;
if (checkEnabled) {
String retval = sb.toString();
MethodSignatureParser parser = new MethodSignatureParser(Signature.get(retval));
assert getName().equals(CONSTRUCTOR_NAME);
assert getOffset() == parser.getOffset();
assert getPhpModifiers().toFlags() == parser.getFlags();
assert getParameters().size() == parser.getParameters().size();
}
}
private static final class ParametersFromSignature implements BaseFunctionElementSupport.Parameters {
private final MethodSignatureParser methodSignatureParser;
//@GuardedBy("this")
private List<ParameterElement> retrievedParameters = null;
public ParametersFromSignature(MethodSignatureParser methodSignatureParser) {
this.methodSignatureParser = methodSignatureParser;
}
@Override
public synchronized List<ParameterElement> getParameters() {
if (retrievedParameters == null) {
retrievedParameters = methodSignatureParser.getParameters();
}
return retrievedParameters;
}
}
private static final class ReturnTypesFromSignature implements BaseFunctionElementSupport.ReturnTypes {
private final MethodSignatureParser methodSignatureParser;
//@GuardedBy("this")
private Set<TypeResolver> retrievedReturnTypes = null;
public ReturnTypesFromSignature(MethodSignatureParser methodSignatureParser) {
this.methodSignatureParser = methodSignatureParser;
}
@Override
public synchronized Set<TypeResolver> getReturnTypes() {
if (retrievedReturnTypes == null) {
retrievedReturnTypes = methodSignatureParser.getReturnTypes();
}
return retrievedReturnTypes;
}
}
}