/*
* Copyright 2013-2017 consulo.io
*
* Licensed 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 consulo.csharp.ide.refactoring.util;
import gnu.trove.THashSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import consulo.annotations.RequiredReadAction;
import consulo.csharp.ide.codeStyle.CSharpCodeGenerationSettings;
import consulo.csharp.lang.lexer.CSharpLexer;
import consulo.csharp.lang.psi.CSharpFieldDeclaration;
import consulo.csharp.lang.psi.CSharpPropertyDeclaration;
import consulo.csharp.lang.psi.CSharpReferenceExpression;
import consulo.csharp.lang.psi.CSharpTokenSets;
import consulo.csharp.lang.psi.CSharpTokens;
import consulo.csharp.lang.psi.CSharpTypeRefPresentationUtil;
import consulo.csharp.lang.psi.impl.CSharpTypeUtil;
import consulo.csharp.lang.psi.impl.source.CSharpForeachStatementImpl;
import consulo.csharp.lang.psi.impl.source.CSharpMethodCallExpressionImpl;
import consulo.csharp.lang.psi.impl.source.resolve.type.CSharpTypeRefByQName;
import consulo.dotnet.DotNetTypes;
import consulo.dotnet.psi.DotNetExpression;
import consulo.dotnet.psi.DotNetGenericParameter;
import consulo.dotnet.psi.DotNetGenericParameterListOwner;
import consulo.dotnet.psi.DotNetModifier;
import consulo.dotnet.psi.DotNetVariable;
import consulo.dotnet.resolve.DotNetTypeRef;
import consulo.dotnet.resolve.DotNetTypeResolveResult;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
/**
* @author Fedor.Korotkov
* <p/>
* from google-dart
*/
public class CSharpNameSuggesterUtil
{
private CSharpNameSuggesterUtil()
{
}
private static String deleteNonLetterFromString(@NotNull final String string)
{
Pattern pattern = Pattern.compile("[^a-zA-Z_]+");
Matcher matcher = pattern.matcher(string);
return matcher.replaceAll("_");
}
@NotNull
@RequiredReadAction
public static Collection<String> getSuggestedVariableNames(final DotNetVariable variable)
{
PsiElement parent = variable.getParent();
Collection<String> suggestedNames = getSuggestedNames(variable.toTypeRef(true), variable);
if(parent instanceof CSharpForeachStatementImpl)
{
DotNetExpression iterableExpression = ((CSharpForeachStatementImpl) parent).getIterableExpression();
if(iterableExpression != null)
{
suggestedNames = getSuggestedNames(iterableExpression, suggestedNames, variable);
}
}
DotNetExpression initializer = variable.getInitializer();
if(initializer != null)
{
suggestedNames.addAll(getSuggestedNames(initializer, suggestedNames, variable));
}
if(variable instanceof CSharpPropertyDeclaration)
{
suggestedNames = ContainerUtil.map(suggestedNames, new Function<String, String>()
{
@Override
public String fun(String variableName)
{
return StringUtil.capitalize(variableName);
}
});
}
CSharpCodeGenerationSettings settings = CSharpCodeGenerationSettings.getInstance(variable.getProject());
boolean isStatic = variable.hasModifier(DotNetModifier.STATIC);
final String prefix;
final String suffix;
if(variable instanceof CSharpPropertyDeclaration)
{
prefix = isStatic ? settings.STATIC_PROPERTY_PREFIX : settings.PROPERTY_PREFIX;
suffix = isStatic ? settings.STATIC_PROPERTY_SUFFIX : settings.PROPERTY_PREFIX;
}
else if(variable instanceof CSharpFieldDeclaration)
{
prefix = isStatic ? settings.STATIC_FIELD_PREFIX : settings.FIELD_PREFIX;
suffix = isStatic ? settings.STATIC_FIELD_SUFFIX : settings.FIELD_SUFFIX;
}
else
{
prefix = null;
suffix = null;
}
return ContainerUtil.map(suggestedNames, new Function<String, String>()
{
@Override
public String fun(String name)
{
if(prefix == null && suffix == null)
{
return name;
}
if(StringUtil.isEmpty(prefix))
{
return name + StringUtil.notNullize(suffix);
}
return StringUtil.notNullize(prefix) + StringUtil.capitalize(name) + StringUtil.notNullize(suffix);
}
});
}
@NotNull
@RequiredReadAction
public static Set<String> getSuggestedNames(final DotNetTypeRef typeRef, PsiElement scope)
{
Collection<String> candidates = new LinkedHashSet<String>();
DotNetTypeResolveResult resolveResult = CSharpTypeUtil.findTypeRefFromExtends(typeRef, new CSharpTypeRefByQName(scope, DotNetTypes.System.Collections.Generic.IEnumerable$1), scope);
if(resolveResult != null)
{
PsiElement element = resolveResult.getElement();
if(element instanceof DotNetGenericParameterListOwner)
{
DotNetGenericParameter genericParameter = ((DotNetGenericParameterListOwner) element).getGenericParameters()[0];
DotNetTypeRef insideTypeRef = resolveResult.getGenericExtractor().extract(genericParameter);
if(insideTypeRef != null)
{
candidates.addAll(generateNames(StringUtil.pluralize(CSharpTypeRefPresentationUtil.buildShortText(insideTypeRef, scope))));
}
}
}
candidates.addAll(generateNames(CSharpTypeRefPresentationUtil.buildShortText(typeRef, scope)));
final Set<String> usedNames = CSharpRefactoringUtil.collectUsedNames(scope, scope);
final List<String> result = new ArrayList<String>();
for(String candidate : candidates)
{
int index = 0;
String suffix = "";
while(usedNames.contains(candidate + suffix))
{
suffix = Integer.toString(++index);
}
result.add(candidate + suffix);
}
if(result.isEmpty())
{
result.add("o"); // never empty
}
for(ListIterator<String> iterator = result.listIterator(); iterator.hasNext(); )
{
String next = iterator.next();
if(isKeyword(next))
{
iterator.set(String.valueOf(next.charAt(0)));
}
}
return new TreeSet<String>(result);
}
@NotNull
@RequiredReadAction
public static Collection<String> getSuggestedNames(final DotNetExpression expression)
{
return getSuggestedNames(expression, null, null);
}
@NotNull
@RequiredReadAction
private static Set<String> getSuggestedNames(@NotNull DotNetExpression expression, @Nullable Collection<String> additionalUsedNames, @Nullable PsiElement toSkip)
{
Set<String> candidates = new LinkedHashSet<String>();
String text = expression.getText();
if(expression.getParent() instanceof CSharpForeachStatementImpl)
{
text = StringUtil.unpluralize(expression.getText());
}
else if(expression instanceof CSharpReferenceExpression)
{
PsiElement resolvedElement = ((CSharpReferenceExpression) expression).resolve();
String name = null;
if(resolvedElement instanceof PsiNamedElement)
{
name = ((PsiNamedElement) resolvedElement).getName();
}
if(name != null && !name.equals(StringUtil.decapitalize(name)))
{
candidates.add(StringUtil.decapitalize(name));
}
}
else if(expression instanceof CSharpMethodCallExpressionImpl)
{
final PsiElement callee = ((CSharpMethodCallExpressionImpl) expression).getCallExpression();
text = callee.getText();
}
if(text != null)
{
candidates.addAll(generateNames(text));
}
final Set<String> usedNames = CSharpRefactoringUtil.collectUsedNames(expression, toSkip);
if(additionalUsedNames != null && !additionalUsedNames.isEmpty())
{
usedNames.addAll(additionalUsedNames);
}
final List<String> result = new ArrayList<String>();
for(String candidate : candidates)
{
int index = 0;
String suffix = "";
while(usedNames.contains(candidate + suffix))
{
suffix = Integer.toString(++index);
}
result.add(candidate + suffix);
}
if(result.isEmpty())
{
result.add("o"); // never empty
}
for(ListIterator<String> iterator = result.listIterator(); iterator.hasNext(); )
{
String next = iterator.next();
if(isKeyword(next))
{
iterator.set(String.valueOf(next.charAt(0)));
}
}
return new THashSet<String>(result);
}
public static boolean isKeyword(String text)
{
return wantOnlyThisTokens(CSharpTokenSets.KEYWORDS, text);
}
public static boolean isIdentifier(String text)
{
return wantOnlyThisTokens(TokenSet.create(CSharpTokens.IDENTIFIER), text);
}
private static boolean wantOnlyThisTokens(@NotNull TokenSet tokenSet, @NotNull CharSequence text)
{
try
{
CSharpLexer lexer = new CSharpLexer();
lexer.start(text);
if(lexer.getTokenEnd() != text.length())
{
return false;
}
IElementType tokenType = lexer.getTokenType();
return tokenSet.contains(tokenType);
}
catch(Exception ignored)
{
return false;
}
}
@NotNull
public static Collection<String> generateNames(@NotNull String name)
{
name = StringUtil.decapitalize(deleteNonLetterFromString(StringUtil.unquoteString(name.replace('.', '_'))));
if(name.startsWith("get"))
{
name = name.substring(3);
}
else if(name.startsWith("is"))
{
name = name.substring(2);
}
while(name.startsWith("_"))
{
name = name.substring(1);
}
while(name.endsWith("_"))
{
name = name.substring(0, name.length() - 1);
}
final int length = name.length();
final Collection<String> possibleNames = new LinkedHashSet<String>();
for(int i = 0; i < length; i++)
{
if(Character.isLetter(name.charAt(i)) && (i == 0 || name.charAt(i - 1) == '_' || (Character.isLowerCase(name.charAt(i - 1)) && Character.isUpperCase(name.charAt(i)))))
{
final String candidate = StringUtil.decapitalize(name.substring(i));
if(candidate.length() < 25)
{
possibleNames.add(candidate);
}
}
}
// prefer shorter names
ArrayList<String> reversed = new ArrayList<String>(possibleNames);
Collections.reverse(reversed);
return ContainerUtil.map(reversed, new Function<String, String>()
{
@Override
public String fun(String name)
{
if(name.indexOf('_') == -1)
{
return name;
}
name = StringUtil.capitalizeWords(name, "_", true, true);
return StringUtil.decapitalize(name.replaceAll("_", ""));
}
});
}
}