blob: 1459c077c0263624138a15ea6671b2199e80aaa8 [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.csl;
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 java.util.logging.Logger;
import javax.swing.ImageIcon;
import org.netbeans.modules.csl.api.ElementHandle;
import org.netbeans.modules.csl.api.ElementKind;
import org.netbeans.modules.csl.api.HtmlFormatter;
import org.netbeans.modules.csl.api.Modifier;
import org.netbeans.modules.csl.api.OffsetRange;
import org.netbeans.modules.csl.api.StructureItem;
import org.netbeans.modules.php.editor.CodeUtils;
import org.netbeans.modules.php.editor.api.AliasedName;
import org.netbeans.modules.php.editor.api.NameKind;
import org.netbeans.modules.php.editor.api.QualifiedName;
import org.netbeans.modules.php.editor.api.elements.ElementFilter;
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.TypeResolver;
import org.netbeans.modules.php.editor.model.ClassConstantElement;
import org.netbeans.modules.php.editor.model.ClassScope;
import org.netbeans.modules.php.editor.model.ConstantElement;
import org.netbeans.modules.php.editor.model.FieldElement;
import org.netbeans.modules.php.editor.model.FileScope;
import org.netbeans.modules.php.editor.model.FunctionScope;
import org.netbeans.modules.php.editor.model.InterfaceScope;
import org.netbeans.modules.php.editor.model.MethodScope;
import org.netbeans.modules.php.editor.model.Model;
import org.netbeans.modules.php.editor.model.ModelElement;
import org.netbeans.modules.php.editor.model.ModelUtils;
import org.netbeans.modules.php.editor.model.NamespaceScope;
import org.netbeans.modules.php.editor.model.Scope;
import org.netbeans.modules.php.editor.model.TraitScope;
import org.netbeans.modules.php.editor.model.TypeScope;
import org.netbeans.modules.php.editor.model.UseScope;
import org.netbeans.modules.php.editor.model.impl.VariousUtils;
import org.openide.util.ImageUtilities;
/**
*
* @author Ondrej Brejla <obrejla@netbeans.org>
*/
public final class NavigatorScanner {
private static final Logger LOGGER = Logger.getLogger(NavigatorScanner.class.getName());
private static final String FONT_GRAY_COLOR = "<font color=\"#999999\">"; //NOI18N
private static final String CLOSE_FONT = "</font>"; //NOI18N
private static ImageIcon interfaceIcon = null;
private static ImageIcon traitIcon = null;
private static boolean isLogged = false;
private final FileScope fileScope;
private final Set<TypeElement> deprecatedTypes;
public static NavigatorScanner create(Model model, boolean resolveDeprecatedElements) {
return new NavigatorScanner(model, resolveDeprecatedElements);
}
private NavigatorScanner(Model model, boolean resolveDeprecatedElements) {
fileScope = model.getFileScope();
if (resolveDeprecatedElements) {
if (!isLogged) {
LOGGER.info("Resolving of deprecated elements in Navigator scanner - IDE will be possibly slow!");
isLogged = true;
}
deprecatedTypes = ElementFilter.forDeprecated(true).filter(model.getIndexScope().getIndex().getTypes(NameKind.empty()));
} else {
deprecatedTypes = Collections.<TypeElement>emptySet();
}
}
public List<? extends StructureItem> scan() {
final List<StructureItem> items = new ArrayList<>();
processNamespaces(items, fileScope.getDeclaredNamespaces());
return items;
}
private void processNamespaces(List<StructureItem> items, Collection<? extends NamespaceScope> declaredNamespaces) {
for (NamespaceScope nameScope : declaredNamespaces) {
List<StructureItem> namespaceChildren = nameScope.isDefaultNamespace() ? items : new ArrayList<StructureItem>();
if (!nameScope.isDefaultNamespace()) {
items.add(new PHPNamespaceStructureItem(nameScope, namespaceChildren));
}
Collection<? extends UseScope> declaredUses = nameScope.getAllDeclaredSingleUses();
for (UseScope useElement : declaredUses) {
namespaceChildren.add(new PHPUseStructureItem(useElement));
}
Collection<? extends FunctionScope> declaredFunctions = nameScope.getDeclaredFunctions();
for (FunctionScope fnc : declaredFunctions) {
if (fnc.isAnonymous()) {
continue;
}
List<StructureItem> variables = new ArrayList<>();
namespaceChildren.add(new PHPFunctionStructureItem(fnc, variables));
}
Collection<? extends ConstantElement> declaredConstants = nameScope.getDeclaredConstants();
for (ConstantElement constant : declaredConstants) {
namespaceChildren.add(new PHPConstantStructureItem(constant, "const"));
}
processTypes(items, namespaceChildren, nameScope.getDeclaredTypes());
}
}
private void processTypes(List<StructureItem> items, List<StructureItem> namespaceChildren, Collection<? extends TypeScope> declaredTypes) {
for (TypeScope type : declaredTypes) {
List<StructureItem> children = new ArrayList<>();
if (type instanceof ClassScope) {
namespaceChildren.add(new PHPClassStructureItem((ClassScope) type, children));
} else if (type instanceof InterfaceScope) {
namespaceChildren.add(new PHPInterfaceStructureItem((InterfaceScope) type, children));
} else if (type instanceof TraitScope) {
namespaceChildren.add(new PHPTraitStructureItem((TraitScope) type, children));
}
Collection<? extends MethodScope> declaredMethods = type.getDeclaredMethods();
for (MethodScope method : declaredMethods) {
// The method name doesn't have to be always defined during parsing.
// For example when user writes in a php doc @method and parsing is
// started when there is no name yet.
if (method.getName() != null && !method.getName().isEmpty()) {
List<StructureItem> variables = new ArrayList<>();
if (method.isConstructor()) {
children.add(new PHPConstructorStructureItem(method, variables));
} else {
children.add(new PHPMethodStructureItem(method, variables));
}
}
}
Collection<? extends ClassConstantElement> declaredClsConstants = type.getDeclaredConstants();
for (ClassConstantElement classConstant : declaredClsConstants) {
children.add(new PHPConstantStructureItem(classConstant, "con")); //NOI18N
}
if (type instanceof ClassScope) {
ClassScope cls = (ClassScope) type;
Collection<? extends FieldElement> declaredFields = cls.getDeclaredFields();
for (FieldElement field : declaredFields) {
children.add(new PHPFieldStructureItem(field));
}
}
if (type instanceof TraitScope) {
TraitScope trait = (TraitScope) type;
Collection<? extends FieldElement> declaredFields = trait.getDeclaredFields();
for (FieldElement field : declaredFields) {
children.add(new PHPFieldStructureItem(field));
}
}
}
}
private boolean isDeprecatedType(String type, ModelElement modelElement) {
boolean result = false;
String typeName = CodeUtils.removeNullableTypePrefix(type);
QualifiedName fullyQualifiedName = VariousUtils.getFullyQualifiedName(QualifiedName.create(typeName), modelElement.getOffset(), modelElement.getInScope());
for (TypeElement typeElement : deprecatedTypes) {
if (typeElement.getFullyQualifiedName().equals(fullyQualifiedName)) {
result = true;
break;
}
}
return result;
}
private abstract class PHPStructureItem implements StructureItem {
private final ModelElement modelElement;
private final List<? extends StructureItem> children;
private final String sortPrefix;
public PHPStructureItem(ModelElement elementHandle, List<? extends StructureItem> children, String sortPrefix) {
this.modelElement = elementHandle;
this.sortPrefix = sortPrefix;
if (children != null) {
this.children = children;
} else {
this.children = Collections.emptyList();
}
}
@Override
public boolean equals(Object obj) {
boolean thesame = false;
if (obj instanceof PHPStructureItem) {
PHPStructureItem item = (PHPStructureItem) obj;
if (item.getName() != null && this.getName() != null) {
thesame = item.modelElement.getName().equals(modelElement.getName()) && item.modelElement.getOffset() == modelElement.getOffset();
}
}
return thesame;
}
@Override
public int hashCode() {
//int hashCode = super.hashCode();
int hashCode = 11;
if (getName() != null) {
hashCode = 31 * getName().hashCode() + hashCode;
}
hashCode = (int) (31 * getPosition() + hashCode);
return hashCode;
}
@Override
public String getName() {
return modelElement.getName();
}
@Override
public String getSortText() {
return sortPrefix + modelElement.getName();
}
@Override
public ElementHandle getElementHandle() {
return modelElement.getPHPElement();
}
public ModelElement getModelElement() {
return modelElement;
}
@Override
public ElementKind getKind() {
return modelElement.getPHPElement().getKind();
}
@Override
public Set<Modifier> getModifiers() {
return modelElement.getPHPElement().getModifiers();
}
@Override
public boolean isLeaf() {
return (children.isEmpty());
}
@Override
public List<? extends StructureItem> getNestedItems() {
return children;
}
@Override
public long getPosition() {
return modelElement.getOffset();
}
@Override
public long getEndPosition() {
if (modelElement instanceof Scope) {
final OffsetRange blockRange = ((Scope) modelElement).getBlockRange();
if (blockRange != null) {
return blockRange.getEnd();
}
}
return modelElement.getNameRange().getEnd();
}
@Override
public ImageIcon getCustomIcon() {
return null;
}
protected void appendInterfaces(Collection<? extends InterfaceScope> interfaes, HtmlFormatter formatter) {
boolean first = true;
for (InterfaceScope interfaceScope : interfaes) {
if (interfaceScope != null) {
if (!first) {
formatter.appendText(", "); //NOI18N
} else {
first = false;
}
appendName(interfaceScope, formatter);
}
}
}
protected void appendUsedTraits(Collection<? extends TraitScope> usedTraits, HtmlFormatter formatter) {
boolean first = true;
for (TraitScope traitScope : usedTraits) {
if (!first) {
formatter.appendText(", "); //NOI18N
} else {
first = false;
}
appendName(traitScope, formatter);
}
}
protected void appendFunctionDescription(FunctionScope function, HtmlFormatter formatter) {
formatter.reset();
if (function == null) {
return;
}
if (function.isDeprecated()) {
formatter.deprecated(true);
}
formatter.appendText(function.getName());
if (function.isDeprecated()) {
formatter.deprecated(false);
}
formatter.appendText("("); //NOI18N
List<? extends ParameterElement> parameters = function.getParameters();
if (parameters != null && parameters.size() > 0) {
processParameters(function, formatter, parameters);
}
formatter.appendText(")"); //NOI18N
Collection<? extends String> returnTypes = function.getReturnTypeNames();
if (!returnTypes.isEmpty()) {
processReturnTypes(function, formatter, returnTypes);
}
}
private void processParameters(FunctionScope function, HtmlFormatter formatter, List<? extends ParameterElement> parameters) {
boolean first = true;
for (ParameterElement formalParameter : parameters) {
String name = formalParameter.getName();
Set<TypeResolver> types = formalParameter.getTypes();
if (name != null) {
if (!first) {
formatter.appendText(", "); //NOI18N
}
if (!types.isEmpty()) {
formatter.appendHtml(FONT_GRAY_COLOR);
int i = 0;
for (TypeResolver typeResolver : types) {
i++;
if (typeResolver.isResolved()) {
QualifiedName typeName = typeResolver.getTypeName(false);
if (typeName != null) {
if (i > 1) {
formatter.appendText("|"); //NOI18N
}
if (typeResolver.isNullableType()) {
formatter.appendText(CodeUtils.NULLABLE_TYPE_PREFIX);
}
processTypeName(typeName.toString(), function, formatter);
}
}
}
formatter.appendText(" "); //NOI18N
formatter.appendHtml(CLOSE_FONT);
}
formatter.appendText(name);
first = false;
}
}
}
private void processReturnTypes(FunctionScope function, HtmlFormatter formatter, Collection<? extends String> returnTypes) {
formatter.appendHtml(FONT_GRAY_COLOR + ":"); //NOI18N
// ignore duplicate types e.g. ?MyClass, MyClass
HashSet<String> ignoredTypes = new HashSet<>();
returnTypes.stream().filter(returnType -> CodeUtils.isNullableType(returnType)).forEach(returnType -> {
ignoredTypes.add(returnType.substring(1));
});
int i = 0;
for (String type : returnTypes) {
if (!ignoredTypes.contains(type)) {
i++;
if (i > 1) {
formatter.appendText(", "); //NOI18N
}
processTypeName(type, function, formatter);
}
}
formatter.appendHtml(CLOSE_FONT);
}
}
private void processTypeName(String type, ModelElement element, HtmlFormatter formatter) {
if (CodeUtils.isNullableType(type)) {
formatter.appendText(CodeUtils.NULLABLE_TYPE_PREFIX);
}
String typeName = CodeUtils.removeNullableTypePrefix(type);
boolean deprecatedType = isDeprecatedType(typeName, element);
if (deprecatedType) {
formatter.deprecated(true);
}
formatter.appendText(typeName);
if (deprecatedType) {
formatter.deprecated(false);
}
}
protected void appendName(ModelElement modelElement, HtmlFormatter formatter) {
String name = modelElement.getName();
if (CodeUtils.isSyntheticTypeName(name)) {
name = "{}"; // NOI18N
}
if (modelElement.isDeprecated()) {
formatter.deprecated(true);
formatter.appendText(name);
formatter.deprecated(false);
} else {
formatter.appendText(name);
}
}
private class PHPFieldStructureItem extends PHPSimpleStructureItem {
public PHPFieldStructureItem(FieldElement elementHandle) {
super(elementHandle, "field"); //NOI18N
}
public FieldElement getField() {
return (FieldElement) getElementHandle();
}
@Override
public String getHtml(HtmlFormatter formatter) {
FieldElement field = getField();
if (field.isDeprecated()) {
formatter.deprecated(true);
}
formatter.appendText(field.getName());
if (field.isDeprecated()) {
formatter.deprecated(false);
}
Collection<? extends String> types = field.getDefaultTypeNames();
if (!types.isEmpty()) {
formatter.appendHtml(FONT_GRAY_COLOR + ":"); //NOI18N
int i = 0;
for (String type : types) {
i++;
if (i > 1) {
formatter.appendText(", "); //NOI18N
}
processTypeName(type, field, formatter);
}
formatter.appendHtml(CLOSE_FONT);
}
return formatter.getText();
}
}
private class PHPSimpleStructureItem extends PHPStructureItem {
private String simpleText;
public PHPSimpleStructureItem(ModelElement elementHandle, String prefix) {
super(elementHandle, null, prefix);
this.simpleText = elementHandle.getName();
}
@Override
public String getHtml(HtmlFormatter formatter) {
formatter.appendText(simpleText);
return formatter.getText();
}
}
private class PHPNamespaceStructureItem extends PHPStructureItem {
public PHPNamespaceStructureItem(NamespaceScope elementHandle, List<? extends StructureItem> children) {
super(elementHandle, children, "namespace"); //NOI18N
}
public NamespaceScope getNamespaceScope() {
return (NamespaceScope) getModelElement();
}
@Override
public String getHtml(HtmlFormatter formatter) {
formatter.reset();
if (getNamespaceScope().isDeprecated()) {
formatter.deprecated(true);
}
formatter.appendText(getName());
if (getNamespaceScope().isDeprecated()) {
formatter.deprecated(false);
}
return formatter.getText();
}
@Override
public ElementKind getKind() {
return ElementKind.MODULE;
}
}
private class PHPUseStructureItem extends PHPStructureItem {
public PHPUseStructureItem(UseScope elementHandle) {
super(elementHandle, null, "aaaa_use"); //NOI18N
}
@Override
public String getHtml(HtmlFormatter formatter) {
formatter.reset();
UseScope useElement = (UseScope) getElementHandle();
String name = getName();
boolean deprecatedType = isDeprecatedType(name, useElement);
if (deprecatedType) {
formatter.deprecated(true);
}
formatter.appendText(name);
final AliasedName aliasedName = useElement.getAliasedName();
if (aliasedName != null) {
formatter.appendText(" as "); //NOI18N
formatter.appendText(aliasedName.getAliasName());
}
if (deprecatedType) {
formatter.deprecated(false);
}
return formatter.getText();
}
@Override
public ElementKind getKind() {
return ElementKind.RULE;
}
}
private class PHPClassStructureItem extends PHPStructureItem {
private final String superClassName;
private final Collection<? extends InterfaceScope> interfaces;
private final Collection<? extends TraitScope> usedTraits;
public PHPClassStructureItem(ClassScope elementHandle, List<? extends StructureItem> children) {
super(elementHandle, children, "cl"); //NOI18N
superClassName = ModelUtils.getFirst(getClassScope().getSuperClassNames());
interfaces = getClassScope().getSuperInterfaceScopes();
usedTraits = getClassScope().getTraits();
}
private ClassScope getClassScope() {
return (ClassScope) getModelElement();
}
@Override
public String getHtml(HtmlFormatter formatter) {
formatter.reset();
appendName(getClassScope(), formatter);
if (superClassName != null) {
formatter.appendHtml(FONT_GRAY_COLOR + "::"); //NOI18N
formatter.appendText(superClassName);
formatter.appendHtml(CLOSE_FONT);
}
if (interfaces != null && interfaces.size() > 0) {
formatter.appendHtml(FONT_GRAY_COLOR + ":"); //NOI18N
appendInterfaces(interfaces, formatter);
formatter.appendHtml(CLOSE_FONT);
}
if (usedTraits != null && usedTraits.size() > 0) {
formatter.appendHtml(FONT_GRAY_COLOR + "#"); //NOI18N
appendUsedTraits(usedTraits, formatter);
formatter.appendHtml(CLOSE_FONT);
}
return formatter.getText();
}
}
private class PHPConstantStructureItem extends PHPStructureItem {
public PHPConstantStructureItem(ConstantElement elementHandle, String prefix) {
super(elementHandle, null, prefix);
}
public ConstantElement getConstant() {
return (ConstantElement) getModelElement();
}
@Override
public String getHtml(HtmlFormatter formatter) {
formatter.reset();
if (getConstant().isDeprecated()) {
formatter.deprecated(true);
}
formatter.appendText(getName());
if (getConstant().isDeprecated()) {
formatter.deprecated(false);
}
final ConstantElement constant = getConstant();
String value = constant.getValue();
if (value != null) {
formatter.appendText(" "); //NOI18N
formatter.appendHtml(FONT_GRAY_COLOR); //NOI18N
formatter.appendText(value);
formatter.appendHtml(CLOSE_FONT);
}
return formatter.getText();
}
}
private class PHPFunctionStructureItem extends PHPStructureItem {
public PHPFunctionStructureItem(FunctionScope elementHandle, List<? extends StructureItem> children) {
super(elementHandle, children, "fn"); //NOI18N
}
public FunctionScope getFunctionScope() {
return (FunctionScope) getModelElement();
}
@Override
public String getHtml(HtmlFormatter formatter) {
formatter.reset();
appendFunctionDescription(getFunctionScope(), formatter);
return formatter.getText();
}
}
private class PHPMethodStructureItem extends PHPStructureItem {
public PHPMethodStructureItem(MethodScope elementHandle, List<? extends StructureItem> children) {
super(elementHandle, children, "fn"); //NOI18N
}
public MethodScope getMethodScope() {
return (MethodScope) getModelElement();
}
@Override
public String getHtml(HtmlFormatter formatter) {
formatter.reset();
appendFunctionDescription(getMethodScope(), formatter);
return formatter.getText();
}
}
private class PHPInterfaceStructureItem extends PHPStructureItem {
private static final String PHP_INTERFACE_ICON = "org/netbeans/modules/php/editor/resources/interface.png"; //NOI18N
private final Collection<? extends InterfaceScope> interfaces;
public PHPInterfaceStructureItem(InterfaceScope elementHandle, List<? extends StructureItem> children) {
super(elementHandle, children, "cl"); //NOI18N
interfaces = getInterfaceScope().getSuperInterfaceScopes();
}
@Override
public ImageIcon getCustomIcon() {
if (interfaceIcon == null) {
interfaceIcon = new ImageIcon(ImageUtilities.loadImage(PHP_INTERFACE_ICON));
}
return interfaceIcon;
}
private InterfaceScope getInterfaceScope() {
return (InterfaceScope) getModelElement();
}
@Override
public String getHtml(HtmlFormatter formatter) {
formatter.reset();
appendName(getInterfaceScope(), formatter);
if (interfaces != null && interfaces.size() > 0) {
formatter.appendHtml(FONT_GRAY_COLOR + "::"); //NOI18N
appendInterfaces(interfaces, formatter);
formatter.appendHtml(CLOSE_FONT);
}
return formatter.getText();
}
}
private class PHPTraitStructureItem extends PHPStructureItem {
private static final String PHP_TRAIT_ICON = "org/netbeans/modules/php/editor/resources/trait.png"; //NOI18N
private final Collection<? extends TraitScope> usedTraits;
public PHPTraitStructureItem(ModelElement elementHandle, List<? extends StructureItem> children) {
super(elementHandle, children, "cl"); //NOI18N
usedTraits = getTraitScope().getTraits();
}
@Override
public ImageIcon getCustomIcon() {
if (traitIcon == null) {
traitIcon = new ImageIcon(ImageUtilities.loadImage(PHP_TRAIT_ICON));
}
return traitIcon;
}
private TraitScope getTraitScope() {
return (TraitScope) getModelElement();
}
@Override
public String getHtml(HtmlFormatter formatter) {
formatter.reset();
appendName(getTraitScope(), formatter);
if (usedTraits != null && usedTraits.size() > 0) {
formatter.appendHtml(FONT_GRAY_COLOR + "#"); //NOI18N
appendUsedTraits(usedTraits, formatter);
formatter.appendHtml(CLOSE_FONT);
}
return formatter.getText();
}
}
private class PHPConstructorStructureItem extends PHPStructureItem {
public PHPConstructorStructureItem(MethodScope elementHandle, List<? extends StructureItem> children) {
super(elementHandle, children, "con"); //NOI18N
}
@Override
public ElementKind getKind() {
return ElementKind.CONSTRUCTOR;
}
public MethodScope getMethodScope() {
return (MethodScope) getModelElement();
}
@Override
public String getHtml(HtmlFormatter formatter) {
formatter.reset();
appendFunctionDescription(getMethodScope(), formatter);
return formatter.getText();
}
}
}