summaryrefslogtreecommitdiff
path: root/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/GroovyNameSuggestionUtil.java
blob: b1856c124e0ed7de1c28931fb73e2509c800a596 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
/*
 * Copyright 2000-2014 JetBrains s.r.o.
 *
 * 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 org.jetbrains.plugins.groovy.refactoring;

import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTypesUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.ArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression;
import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyNamesUtil;
import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;

/**
 * @author ilyas
 */
public class GroovyNameSuggestionUtil {

  public static final Logger LOG = Logger.getInstance("org.jetbrains.plugins.groovy.refactoring.GroovyNameSuggestionUtil");

  private GroovyNameSuggestionUtil() {
  }

  public static String[] suggestVariableNames(GrExpression expr, NameValidator validator) {
    return suggestVariableNames(expr, validator, false);
  }

  public static String[] suggestVariableNames(@NotNull GrExpression expr, NameValidator validator, boolean forStaticVariable) {
    Set<String> possibleNames = new LinkedHashSet<String>();
    PsiType type = expr.getType();
    generateNameByExpr(expr, possibleNames, validator, forStaticVariable);
    if (type != null && !PsiType.VOID.equals(type)) {
      generateVariableNameByTypeInner(type, possibleNames,validator);
    }

    possibleNames.remove("");
    if (possibleNames.isEmpty()) {
      possibleNames.add(validator.validateName("var", true));
    }
    return ArrayUtil.toStringArray(possibleNames);
  }

  public static String[] suggestVariableNameByType(PsiType type, NameValidator validator) {
    if (type == null) return ArrayUtil.EMPTY_STRING_ARRAY;
    Set<String> possibleNames = new LinkedHashSet<String>();
    generateVariableNameByTypeInner(type, possibleNames, validator);
    return ArrayUtil.toStringArray(possibleNames);
  }

  private static void generateVariableNameByTypeInner(PsiType type, Set<String> possibleNames, NameValidator validator) {
    String unboxed = PsiTypesUtil.unboxIfPossible(type.getCanonicalText());
    if (unboxed != null && !unboxed.equals(type.getCanonicalText())) {
      String name = generateNameForBuiltInType(unboxed);
      name = validator.validateName(name, true);
      if (GroovyNamesUtil.isIdentifier(name)) {
        possibleNames.add(name);
      }
    }
    else if (type instanceof PsiIntersectionType) {
      for (PsiType psiType : ((PsiIntersectionType)type).getConjuncts()) {
        generateByType(psiType, possibleNames, validator);
      }
    }
    else {
      generateByType(type, possibleNames, validator);
    }
  }

  private static void generateNameByExpr(GrExpression expr, Set<String> possibleNames, NameValidator validator, boolean forStaticVariable) {
    if (expr instanceof GrReferenceExpression && ((GrReferenceExpression) expr).getReferenceName() != null) {
      if (PsiUtil.isThisReference(expr)) {
        possibleNames.add(validator.validateName("thisInstance", true));
      }
      if (PsiUtil.isSuperReference(expr)) {
        possibleNames.add(validator.validateName("superInstance", true));
      }
      GrReferenceExpression refExpr = (GrReferenceExpression) expr;
      String name = refExpr.getReferenceName();
      if (name != null && name.toUpperCase().equals(name)) {
        possibleNames.add(validator.validateName(name.toLowerCase(), true));
      } else {
        generateCamelNames(possibleNames, validator, name);
      }
      if (expr.getText().equals(name)) {
        possibleNames.remove(name);
      }
    }
    if (expr instanceof GrMethodCallExpression) {
      generateNameByExpr(((GrMethodCallExpression) expr).getInvokedExpression(), possibleNames, validator, forStaticVariable);
    }
    if (expr instanceof GrLiteral) {
      final Object value = ((GrLiteral)expr).getValue();
      if (value instanceof String) {
        generateNameByString(possibleNames, (String)value, validator, forStaticVariable, expr.getProject());
      }
    }
  }

  private static void generateNameByString(Set<String> possibleNames,
                                           String value,
                                           NameValidator validator,
                                           boolean forStaticVariable,
                                           Project project) {
    if (!PsiNameHelper.getInstance(project).isIdentifier(value)) return;
    if (forStaticVariable) {
      StringBuilder buffer = new StringBuilder(value.length() + 10);
      char[] chars = new char[value.length()];
      value.getChars(0, value.length(), chars, 0);
      boolean wasLow = Character.isLowerCase(chars[0]);

      buffer.append(Character.toUpperCase(chars[0]));
      for (int i = 1; i < chars.length; i++) {
        if (Character.isUpperCase(chars[i])) {
          if (wasLow) {
            buffer.append('_');
            wasLow = false;
          }
        }
        else {
          wasLow = true;
        }

        buffer.append(Character.toUpperCase(chars[i]));
      }
      possibleNames.add(validator.validateName(buffer.toString(), true));
    }
    else {
      possibleNames.add(validator.validateName(value, true));
    }
  }

  private static void generateByType(PsiType type, Set<String> possibleNames, NameValidator validator) {
    String typeName = type.getPresentableText();
    generateNamesForCollectionType(type, possibleNames, validator);
    generateNamesForArrayType(type, possibleNames, validator);
    generateNamesForExceptions(type, possibleNames, validator);
    typeName = cleanTypeName(typeName);
    if (typeName.equals("String")) {
      possibleNames.add(validator.validateName("s", true));
    }
    if (typeName.equals("Closure")) {
      possibleNames.add(validator.validateName("cl", true));
    }
    if (typeName.toUpperCase().equals(typeName)) {
      possibleNames.add(validator.validateName(GroovyNamesUtil.deleteNonLetterFromString(typeName.toLowerCase()), true));
    } else if (!typeName.equals(typeName.toLowerCase())) {
      generateCamelNames(possibleNames, validator, typeName);
      possibleNames.remove(typeName);
    }
  }

  private static void generateNamesForExceptions(PsiType type, Set<String> possibleNames, NameValidator validator) {
    if (InheritanceUtil.isInheritor(type, CommonClassNames.JAVA_LANG_ERROR)) {
      possibleNames.add(validator.validateName("error", true));
    }
    else if (InheritanceUtil.isInheritor(type, CommonClassNames.JAVA_LANG_EXCEPTION)) {
      possibleNames.add(validator.validateName("e", true));
    }
  }

  private static void generateNamesForArrayType(PsiType type, Set<String> possibleNames, NameValidator validator) {
    int arrayDim = type.getArrayDimensions();
    if (arrayDim == 0) return;
    PsiType deepType = type.getDeepComponentType();
    String candidateName = cleanTypeName(deepType.getPresentableText());
    if (deepType instanceof PsiClassType) {
      PsiClass clazz = ((PsiClassType) deepType).resolve();
      if (clazz == null) return;
      candidateName = GroovyNamesUtil.fromLowerLetter(clazz.getName());
    }
    candidateName = StringUtil.pluralize(GroovyNamesUtil.fromLowerLetter(candidateName));
    generateCamelNames(possibleNames, validator, candidateName);

    ArrayList<String> camelizedName = GroovyNamesUtil.camelizeString(candidateName);
    candidateName = camelizedName.get(camelizedName.size() - 1);
    candidateName = "arrayOf" + fromUpperLetter(candidateName);
    possibleNames.add(validator.validateName(candidateName, true));
  }

  private static void generateNamesForCollectionType(PsiType type, Set<String> possibleNames, NameValidator validator) {
    PsiType componentType = getCollectionComponentType(type, validator.getProject());
    if (!(type instanceof PsiClassType) || componentType == null) return;
    PsiClass clazz = ((PsiClassType) type).resolve();
    if (clazz == null) return;
    String collectionName = clazz.getName();
    assert collectionName != null;

    String componentName = cleanTypeName(componentType.getPresentableText());
    if (componentType instanceof PsiClassType) {
      PsiClassType classType = (PsiClassType) componentType;
      PsiClass psiClass = classType.resolve();
      if (psiClass == null) return;
      componentName = psiClass.getName();
    }

    assert componentName != null;
    String candidateName = StringUtil.pluralize(GroovyNamesUtil.fromLowerLetter(componentName));
    generateCamelNames(possibleNames, validator, candidateName);

    ArrayList<String> camelizedName = GroovyNamesUtil.camelizeString(candidateName);
    candidateName = camelizedName.get(camelizedName.size() - 1);
    candidateName = collectionName.toLowerCase() + "Of" + fromUpperLetter(candidateName);
    possibleNames.add(validator.validateName(candidateName, true));
  }

  @NotNull
  private static String cleanTypeName(@NotNull String typeName) {
    if (typeName.contains(".")) {
      typeName = typeName.substring(typeName.lastIndexOf(".") + 1);
    }
    if (typeName.contains("<")) {
      typeName = typeName.substring(0, typeName.indexOf("<"));
    }
    return typeName;
  }

  private static void generateCamelNames(Set<String> possibleNames, NameValidator validator, String typeName) {
    ArrayList<String> camelTokens = GroovyNamesUtil.camelizeString(typeName);
    Collections.reverse(camelTokens);
    if (!camelTokens.isEmpty()) {
      String possibleName = "";
      for (String camelToken : camelTokens) {
        possibleName = camelToken + fromUpperLetter(possibleName);
        String candidate = validator.validateName(possibleName, true);
        // todo generify
        if (candidate.equals("class")) {
          candidate = validator.validateName("clazz", true);
        }
        if (!possibleNames.contains(candidate) && GroovyNamesUtil.isIdentifier(candidate)) {
          possibleNames.add(candidate);
        }
      }
    }
  }

  private static String generateNameForBuiltInType(String unboxed) {
    return unboxed.toLowerCase().substring(0, 1);
  }

  private static String fromUpperLetter(String str) {
    if (str.isEmpty()) return "";
    if (str.length() == 1) return str.toUpperCase();
    return str.substring(0, 1).toUpperCase() + str.substring(1);
  }

  @Nullable
  private static PsiType getCollectionComponentType(PsiType type, Project project) {
    if (!(type instanceof PsiClassType)) return null;
    PsiClassType classType = (PsiClassType) type;
    PsiClassType.ClassResolveResult result = classType.resolveGenerics();
    PsiClass clazz = result.getElement();
    if (clazz == null) return null;
    JavaPsiFacade facade = JavaPsiFacade.getInstance(project);
    @SuppressWarnings({"ConstantConditions"}) PsiClass collectionClass = facade.findClass("java.util.Collection", type.getResolveScope());
    if (collectionClass == null || collectionClass.getTypeParameters().length != 1) return null;
    PsiSubstitutor substitutor = TypeConversionUtil.getClassSubstitutor(collectionClass, clazz, result.getSubstitutor());

    if (substitutor == null) return null;
    PsiType componentType = substitutor.substitute(collectionClass.getTypeParameters()[0]);
    return componentType instanceof PsiIntersectionType ? null : componentType;
  }
}