blob: ef26f8436b5824c8da285ca2c987d78556881fd1 [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.List;
import java.util.StringTokenizer;
import org.netbeans.modules.php.editor.api.AliasedName;
import org.netbeans.modules.php.editor.api.QualifiedName;
import org.netbeans.modules.php.editor.api.elements.TypeNameResolver;
import org.netbeans.modules.php.editor.model.NamespaceScope;
import org.netbeans.modules.php.editor.model.Scope;
import org.netbeans.modules.php.editor.model.UseScope;
import org.netbeans.modules.php.editor.model.impl.Type;
import org.netbeans.modules.php.editor.model.impl.VariousUtils;
import org.netbeans.modules.php.editor.model.nodes.NamespaceDeclarationInfo;
import org.openide.util.NbBundle;
/**
*
* @author Ondrej Brejla <obrejla@netbeans.org>
*/
public abstract class TypeNameResolverImpl implements TypeNameResolver {
public static TypeNameResolver forNull() {
return new TypeNameResolver() {
@Override
public QualifiedName resolve(final QualifiedName qualifiedName) {
return qualifiedName;
}
};
}
public static TypeNameResolver forChainOf(final List<TypeNameResolver> typeNameResolvers) {
return new TypeNameResolver() {
@Override
public QualifiedName resolve(final QualifiedName qualifiedName) {
QualifiedName result = qualifiedName;
for (TypeNameResolver nameResolver : typeNameResolvers) {
result = nameResolver.resolve(result);
}
return result;
}
};
}
public static TypeNameResolver forFullyQualifiedName(final Scope scope, final int offset) {
return new FullyQualifiedTypeNameResolver(scope, offset);
}
public static TypeNameResolver forQualifiedName(final Scope scope, final int offset) {
return new CommonQualifiedTypeNameResolver(scope, offset);
}
public static TypeNameResolver forUnqualifiedName() {
return new TypeNameResolver() {
@Override
public QualifiedName resolve(final QualifiedName qualifiedName) {
return qualifiedName.toName();
}
};
}
public static TypeNameResolver forSmartName(final Scope scope, final int offset) {
return new SmartQualifiedTypeNameResolver(scope, offset);
}
private abstract static class BaseTypeNameResolver extends TypeNameResolverImpl {
private final Scope scope;
private final int offset;
public BaseTypeNameResolver(final Scope scope, final int offset) {
this.scope = scope;
this.offset = offset;
}
protected abstract QualifiedName processFullyQualifiedName(final QualifiedName fullyQualifiedName, final NamespaceScope namespaceScope);
protected abstract QualifiedName processQualifiedName(final QualifiedName fullyQualifiedName, final NamespaceScope namespaceScope);
protected abstract QualifiedName processUnQualifiedName(final QualifiedName fullyQualifiedName, final NamespaceScope namespaceScope);
@Override
public QualifiedName resolve(final QualifiedName qualifiedName) {
QualifiedName result = qualifiedName;
if (!VariousUtils.isSpecialClassName(qualifiedName.getName()) && !Type.isPrimitive(qualifiedName.toString())) {
result = processExactType(qualifiedName);
}
return result;
}
protected int getOffset() {
return offset;
}
private QualifiedName processExactType(final QualifiedName qualifiedName) {
QualifiedName result = qualifiedName;
NamespaceScope namespaceScope = retrieveNamespaceScope();
if (namespaceScope != null) {
if (qualifiedName.getKind().isFullyQualified()) {
result = processFullyQualifiedName(qualifiedName, namespaceScope);
} else if (qualifiedName.getKind().isQualified()) {
result = processQualifiedName(qualifiedName, namespaceScope);
} else {
result = processUnQualifiedName(qualifiedName, namespaceScope);
}
}
return result;
}
private NamespaceScope retrieveNamespaceScope() {
NamespaceScope result = null;
Scope inScope = scope;
while (inScope != null && !(inScope instanceof NamespaceScope)) {
inScope = inScope.getInScope();
}
if (inScope != null) {
result = (NamespaceScope) inScope;
}
return result;
}
}
private static class FullyQualifiedTypeNameResolver extends BaseTypeNameResolver {
public FullyQualifiedTypeNameResolver(final Scope scope, final int offset) {
super(scope, offset);
}
@Override
protected QualifiedName processFullyQualifiedName(final QualifiedName fullyQualifiedName, final NamespaceScope namespaceScope) {
return fullyQualifiedName;
}
@Override
protected QualifiedName processQualifiedName(final QualifiedName qualifiedName, final NamespaceScope namespaceScope) {
return resolveFullyQualifiedName(qualifiedName, namespaceScope);
}
@Override
protected QualifiedName processUnQualifiedName(final QualifiedName unQualifiedName, final NamespaceScope namespaceScope) {
return resolveFullyQualifiedName(unQualifiedName, namespaceScope);
}
private QualifiedName resolveFullyQualifiedName(final QualifiedName qualifiedName, final NamespaceScope namespaceScope) {
QualifiedName result = qualifiedName;
String firstSegmentName = qualifiedName.getSegments().getFirst();
UseScope matchedUseScope = null;
int lastOffset = -1;
for (UseScope useElement : namespaceScope.getAllDeclaredSingleUses()) {
// trying to make a FQ from exact use element, they are FQ by default
if (useElement.getNameRange().containsInclusive(getOffset())) {
result = QualifiedName.create(true, qualifiedName.getSegments());
break;
} else if (useElement.getOffset() < getOffset()) {
AliasedName aliasName = useElement.getAliasedName();
if (aliasName != null) {
if (firstSegmentName.equals(aliasName.getAliasName())) {
matchedUseScope = useElement;
continue;
}
} else {
if (lastOffset < useElement.getOffset() && (useElement.getName().equals(firstSegmentName)
|| useElement.getName().endsWith(NamespaceDeclarationInfo.NAMESPACE_SEPARATOR + firstSegmentName))) {
matchedUseScope = useElement;
lastOffset = useElement.getOffset();
}
}
}
}
if (matchedUseScope != null) {
result = resolveForMatchedUseScope(result, matchedUseScope);
} else if (!result.getKind().isFullyQualified()) {
String fullNamespaceName = namespaceScope.getNamespaceName().toString();
if (result.getKind().isQualified()) {
fullNamespaceName += fullNamespaceName.trim().isEmpty() ? "" : NamespaceDeclarationInfo.NAMESPACE_SEPARATOR;
fullNamespaceName += result.getNamespaceName();
}
result = QualifiedName.createFullyQualified(result.getName(), fullNamespaceName);
}
return result;
}
private QualifiedName resolveForMatchedUseScope(final QualifiedName qualifiedName, final UseScope matchedUseScope) {
QualifiedName result = qualifiedName;
final ArrayList<String> segments = new ArrayList();
for (StringTokenizer st = new StringTokenizer(matchedUseScope.getName(), NamespaceDeclarationInfo.NAMESPACE_SEPARATOR); st.hasMoreTokens();) {
String token = st.nextToken();
segments.add(token);
}
final List<String> origName = result.getSegments();
for (int i = 1; i < origName.size(); i++) {
segments.add(origName.get(i));
}
return QualifiedName.create(true, segments);
}
}
private interface QualifiedTypeNameResolver {
QualifiedName resolveForUseScope(final QualifiedName fullyQualifiedName, final UseScope matchedUseScope);
}
private static class CommonQualifiedTypeNameResolver extends BaseTypeNameResolver implements QualifiedTypeNameResolver {
public CommonQualifiedTypeNameResolver(Scope scope, int offset) {
super(scope, offset);
}
@Override
protected QualifiedName processFullyQualifiedName(QualifiedName fullyQualifiedName, NamespaceScope namespaceScope) {
return new FullyQualifiedNameProcessor(this, getOffset()).process(fullyQualifiedName, namespaceScope);
}
@Override
protected QualifiedName processQualifiedName(QualifiedName qualifiedName, NamespaceScope namespaceScope) {
return resolveNonFullyQualifiedName(qualifiedName, namespaceScope);
}
@Override
protected QualifiedName processUnQualifiedName(QualifiedName unQualifiedName, NamespaceScope namespaceScope) {
return resolveNonFullyQualifiedName(unQualifiedName, namespaceScope);
}
@Override
public QualifiedName resolveForUseScope(final QualifiedName fullyQualifiedName, final UseScope matchedUseScope) {
int skipLength = FullyQualifiedNameProcessor.countSkipLength(matchedUseScope);
return QualifiedName.create(fullyQualifiedName.toString().substring(skipLength));
}
private QualifiedName resolveNonFullyQualifiedName(final QualifiedName nonFullyQualifiedName, final NamespaceScope namespaceScope) {
QualifiedName result = nonFullyQualifiedName;
UseScope matchedUseScope = getMatchedUseScopeForNonFullyQualifiedName(nonFullyQualifiedName, namespaceScope);
if (matchedUseScope == null) {
// passed qualified name is not valid, so construct QN with current NS
result = namespaceScope.getNamespaceName().append(nonFullyQualifiedName);
}
return result;
}
private UseScope getMatchedUseScopeForNonFullyQualifiedName(final QualifiedName nonFullyQualifiedName, final NamespaceScope namespaceScope) {
UseScope result = null;
String firstSegmentName = nonFullyQualifiedName.getSegments().getFirst();
int lastOffset = -1;
for (UseScope useScope : namespaceScope.getAllDeclaredSingleUses()) {
if (useScope.getOffset() < getOffset()) {
AliasedName aliasName = useScope.getAliasedName();
if (aliasName != null) {
if (firstSegmentName.equals(aliasName.getAliasName())) {
result = useScope;
continue;
}
} else {
if (lastOffset < useScope.getOffset() && useScope.getName().endsWith(firstSegmentName)) {
result = useScope;
lastOffset = useScope.getOffset();
}
}
}
}
return result;
}
}
@NbBundle.Messages({
"# {0} - Class name",
"IllegalArgument=Only fully-qualified names can be resolved by {0}"
})
private static class SmartQualifiedTypeNameResolver extends BaseTypeNameResolver implements QualifiedTypeNameResolver {
public SmartQualifiedTypeNameResolver(final Scope scope, final int offset) {
super(scope, offset);
}
@Override
public QualifiedName resolveForUseScope(final QualifiedName fullyQualifiedName, final UseScope matchedUseScope) {
QualifiedName result = fullyQualifiedName;
if (matchedUseScope != null) {
int skipLength = FullyQualifiedNameProcessor.countSkipLength(matchedUseScope);
result = QualifiedName.create(fullyQualifiedName.toString().substring(skipLength));
}
return result;
}
@Override
protected QualifiedName processFullyQualifiedName(final QualifiedName fullyQualifiedName, final NamespaceScope namespaceScope) {
return new FullyQualifiedNameProcessor(this, getOffset()).process(fullyQualifiedName, namespaceScope);
}
@Override
protected QualifiedName processQualifiedName(final QualifiedName fullyQualifiedName, final NamespaceScope namespaceScope) {
throw new IllegalArgumentException(Bundle.IllegalArgument(this.getClass().getName()));
}
@Override
protected QualifiedName processUnQualifiedName(final QualifiedName fullyQualifiedName, final NamespaceScope namespaceScope) {
throw new IllegalArgumentException(Bundle.IllegalArgument(this.getClass().getName()));
}
}
private static class FullyQualifiedNameProcessor {
private final int offset;
private final QualifiedTypeNameResolver qualifiedTypeNameResolver;
public static int countSkipLength(final UseScope matchedUseElement) {
int result = NamespaceDeclarationInfo.NAMESPACE_SEPARATOR.length();
if (matchedUseElement != null) {
List<String> segments = createSegments(matchedUseElement);
if (!segments.isEmpty()) {
result += QualifiedName.create(true, segments).toString().length();
}
}
return result;
}
private static List<String> createSegments(final UseScope matchedUseElement) {
List<String> segments = new ArrayList();
for (StringTokenizer st = new StringTokenizer(matchedUseElement.getName(), NamespaceDeclarationInfo.NAMESPACE_SEPARATOR); st.hasMoreTokens();) {
String token = st.nextToken();
if (st.hasMoreTokens()) {
segments.add(token);
}
}
return Collections.unmodifiableList(segments);
}
private static boolean isFromCurrentNamespace(final QualifiedName fullyQualifiedName, final QualifiedName namespaceName) {
return fullyQualifiedName.toString()
.substring(NamespaceDeclarationInfo.NAMESPACE_SEPARATOR.length())
.startsWith(namespaceName.toString() + NamespaceDeclarationInfo.NAMESPACE_SEPARATOR);
}
public FullyQualifiedNameProcessor(final QualifiedTypeNameResolver qualifiedTypeNameResolver, final int offset) {
this.qualifiedTypeNameResolver = qualifiedTypeNameResolver;
this.offset = offset;
}
public QualifiedName process(final QualifiedName fullyQualifiedName, final NamespaceScope namespaceScope) {
QualifiedName result = fullyQualifiedName;
if (namespaceScope != null) {
QualifiedName namespaceName = namespaceScope.getNamespaceName();
if (isFromCurrentNamespace(fullyQualifiedName, namespaceName)) {
result = resolveFromCurrentNamespace(fullyQualifiedName, namespaceName);
} else {
result = resolveFromAnotherNamespace(fullyQualifiedName, namespaceScope.getAllDeclaredSingleUses());
}
}
return result;
}
private QualifiedName resolveFromCurrentNamespace(final QualifiedName fullyQualifiedName, final QualifiedName namespaceName) {
int namespaceNameSegmentsSize = namespaceName.getSegments().size();
int qualifiedNameSegmentsSize = fullyQualifiedName.getSegments().size();
assert namespaceNameSegmentsSize < qualifiedNameSegmentsSize
: namespaceName.toString() + ":" + namespaceNameSegmentsSize + " < " + fullyQualifiedName.toString() + ":" + qualifiedNameSegmentsSize; //NOI18N
String resultName = fullyQualifiedName.toString().substring(
NamespaceDeclarationInfo.NAMESPACE_SEPARATOR.length() + namespaceName.toString().length() + NamespaceDeclarationInfo.NAMESPACE_SEPARATOR.length());
return QualifiedName.create(resultName);
}
private QualifiedName resolveFromAnotherNamespace(final QualifiedName fullyQualifiedName, final Collection<? extends UseScope> declaredUses) {
UseScope matchedUseScope = getMatchedUseScopeForFullyQualifiedName(fullyQualifiedName, declaredUses);
return qualifiedTypeNameResolver.resolveForUseScope(fullyQualifiedName, matchedUseScope);
}
private UseScope getMatchedUseScopeForFullyQualifiedName(final QualifiedName fullyQualifiedName, final Collection<? extends UseScope> declaredUses) {
UseScope result = null;
String firstSegmentName = fullyQualifiedName.getSegments().getFirst();
int lastOffset = -1;
for (UseScope useScope : declaredUses) {
if (useScope.getOffset() < offset) {
AliasedName aliasName = useScope.getAliasedName();
if (aliasName != null) {
if (firstSegmentName.equals(aliasName.getAliasName())) {
result = useScope;
continue;
}
} else {
if (lastOffset < useScope.getOffset()) {
String useElementName = useScope.getName();
String modifiedUseElementName = useElementName.startsWith(NamespaceDeclarationInfo.NAMESPACE_SEPARATOR)
? useElementName
: NamespaceDeclarationInfo.NAMESPACE_SEPARATOR + useElementName;
if (fullyQualifiedName.toString().startsWith(modifiedUseElementName)) {
lastOffset = useScope.getOffset();
result = useScope;
}
}
}
}
}
return result;
}
}
}