Converted JetNameSuggester to Kotlin (step 1)

This commit is contained in:
Valentin Kipyatkov
2015-06-26 14:14:29 +02:00
parent 2206459874
commit 6f9e5ed386
7 changed files with 162 additions and 178 deletions
@@ -14,42 +14,37 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.kotlin.idea.core.refactoring; package org.jetbrains.kotlin.idea.core.refactoring
import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.util.text.StringUtil
import com.intellij.util.ArrayUtil; import com.intellij.util.ArrayUtil
import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.builtins.KotlinBuiltIns; import org.jetbrains.kotlin.descriptors.ClassifierDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor; import org.jetbrains.kotlin.idea.caches.resolve.*
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor; import org.jetbrains.kotlin.lexer.JetLexer
import org.jetbrains.kotlin.idea.caches.resolve.ResolvePackage; import org.jetbrains.kotlin.lexer.JetTokens
import org.jetbrains.kotlin.lexer.JetLexer; import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.lexer.JetTokens; import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.name.Name; import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.BindingContext; import org.jetbrains.kotlin.types.ErrorUtils
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode; import org.jetbrains.kotlin.types.JetType
import org.jetbrains.kotlin.types.ErrorUtils; import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.JetType; import org.jetbrains.kotlin.types.checker.JetTypeChecker
import org.jetbrains.kotlin.types.TypeUtils;
import org.jetbrains.kotlin.types.checker.JetTypeChecker;
import java.util.ArrayList; import java.util.ArrayList
import java.util.List; import java.util.regex.Matcher
import java.util.regex.Matcher; import java.util.regex.Pattern
import java.util.regex.Pattern;
public class JetNameSuggester { public object JetNameSuggester {
private JetNameSuggester() {
}
private static void addName(ArrayList<String> result, @Nullable String name, JetNameValidator validator) { private fun addName(result: ArrayList<String>, name: String?, validator: JetNameValidator) {
if (name == null) return; var name: kotlin.String? = name ?: return
if ("class".equals(name)) name = "clazz"; if ("class" == name) name = "clazz"
if (!isIdentifier(name)) return; if (!isIdentifier(name)) return
result.add(validator.validateName(name)); result.add(validator.validateName(name))
} }
/** /**
@@ -61,258 +56,247 @@ public class JetNameSuggester {
* 2. Reference expressions according to reference name camel humps * 2. Reference expressions according to reference name camel humps
* 3. Method call expression according to method callee expression * 3. Method call expression according to method callee expression
* @param expression to suggest name for variable * @param expression to suggest name for variable
* *
* @param validator to check scope for such names * @param validator to check scope for such names
* *
* @param defaultName * @param defaultName
* *
* @return possible names * @return possible names
*/ */
public static @NotNull String[] suggestNames(@NotNull JetExpression expression, @NotNull JetNameValidator validator, @Nullable String defaultName) { public fun suggestNames(expression: JetExpression, validator: JetNameValidator, defaultName: String?): Array<String> {
ArrayList<String> result = new ArrayList<String>(); val result = ArrayList<String>()
BindingContext bindingContext = ResolvePackage.analyze(expression, BodyResolveMode.FULL); val bindingContext = expression.analyze(BodyResolveMode.FULL)
JetType jetType = bindingContext.getType(expression); val jetType = bindingContext.getType(expression)
if (jetType != null) { if (jetType != null) {
addNamesForType(result, jetType, validator); addNamesForType(result, jetType, validator)
} }
addNamesForExpression(result, expression, validator); addNamesForExpression(result, expression, validator)
if (result.isEmpty()) addName(result, defaultName, validator); if (result.isEmpty()) addName(result, defaultName, validator)
return ArrayUtil.toStringArray(result); return ArrayUtil.toStringArray(result)
} }
public static @NotNull String[] suggestNames(@NotNull JetType type, @NotNull JetNameValidator validator, @Nullable String defaultName) { public fun suggestNames(type: JetType, validator: JetNameValidator, defaultName: String?): Array<String> {
ArrayList<String> result = new ArrayList<String>(); val result = ArrayList<String>()
addNamesForType(result, type, validator); addNamesForType(result, type, validator)
if (result.isEmpty()) addName(result, defaultName, validator); if (result.isEmpty()) addName(result, defaultName, validator)
return ArrayUtil.toStringArray(result); return ArrayUtil.toStringArray(result)
} }
public static @NotNull String[] suggestNamesForType(@NotNull JetType jetType, @NotNull JetNameValidator validator) { public fun suggestNamesForType(jetType: JetType, validator: JetNameValidator): Array<String> {
ArrayList<String> result = new ArrayList<String>(); val result = ArrayList<String>()
addNamesForType(result, jetType, validator); addNamesForType(result, jetType, validator)
return ArrayUtil.toStringArray(result); return ArrayUtil.toStringArray(result)
} }
public static @NotNull String[] suggestNamesForExpression(@NotNull JetExpression expression, @NotNull JetNameValidator validator) { jvmOverloads public fun suggestNamesForExpression(expression: JetExpression, validator: JetNameValidator, defaultName: String? = null): Array<String> {
return suggestNamesForExpression(expression, validator, null); val result = ArrayList<String>()
addNamesForExpression(result, expression, validator)
if (result.isEmpty()) addName(result, defaultName, validator)
return ArrayUtil.toStringArray(result)
} }
public static @NotNull String[] suggestNamesForExpression(@NotNull JetExpression expression, @NotNull JetNameValidator validator, @Nullable String defaultName) { private val COMMON_TYPE_PARAMETER_NAMES = arrayOf("T", "U", "V", "W", "X", "Y", "Z")
ArrayList<String> result = new ArrayList<String>();
addNamesForExpression(result, expression, validator);
if (result.isEmpty()) addName(result, defaultName, validator);
return ArrayUtil.toStringArray(result);
}
private static final String[] COMMON_TYPE_PARAMETER_NAMES = {"T", "U", "V", "W", "X", "Y", "Z"}; public fun suggestNamesForTypeParameters(count: Int, validator: JetNameValidator): Array<String> {
val result = ArrayList<String>()
public static @NotNull String[] suggestNamesForTypeParameters(int count, @NotNull JetNameValidator validator) { for (i in 0..count - 1) {
ArrayList<String> result = new ArrayList<String>(); result.add(validator.validateNameWithVariants(*COMMON_TYPE_PARAMETER_NAMES))
for (int i = 0; i < count; i++) {
result.add(validator.validateNameWithVariants(COMMON_TYPE_PARAMETER_NAMES));
} }
return ArrayUtil.toStringArray(result); return ArrayUtil.toStringArray(result)
} }
private static void addNamesForType(ArrayList<String> result, JetType jetType, JetNameValidator validator) { private fun addNamesForType(result: ArrayList<String>, jetType: JetType, validator: JetNameValidator) {
KotlinBuiltIns builtIns = KotlinBuiltIns.getInstance(); var jetType = jetType
JetTypeChecker typeChecker = JetTypeChecker.DEFAULT; val builtIns = KotlinBuiltIns.getInstance()
jetType = TypeUtils.makeNotNullable(jetType); // wipe out '?' val typeChecker = JetTypeChecker.DEFAULT
if (ErrorUtils.containsErrorType(jetType)) return; jetType = TypeUtils.makeNotNullable(jetType) // wipe out '?'
if (ErrorUtils.containsErrorType(jetType)) return
if (typeChecker.equalTypes(builtIns.getBooleanType(), jetType)) { if (typeChecker.equalTypes(builtIns.getBooleanType(), jetType)) {
addName(result, "b", validator); addName(result, "b", validator)
} }
else if (typeChecker.equalTypes(builtIns.getIntType(), jetType)) { else if (typeChecker.equalTypes(builtIns.getIntType(), jetType)) {
addName(result, "i", validator); addName(result, "i", validator)
} }
else if (typeChecker.equalTypes(builtIns.getByteType(), jetType)) { else if (typeChecker.equalTypes(builtIns.getByteType(), jetType)) {
addName(result, "byte", validator); addName(result, "byte", validator)
} }
else if (typeChecker.equalTypes(builtIns.getLongType(), jetType)) { else if (typeChecker.equalTypes(builtIns.getLongType(), jetType)) {
addName(result, "l", validator); addName(result, "l", validator)
} }
else if (typeChecker.equalTypes(builtIns.getFloatType(), jetType)) { else if (typeChecker.equalTypes(builtIns.getFloatType(), jetType)) {
addName(result, "fl", validator); addName(result, "fl", validator)
} }
else if (typeChecker.equalTypes(builtIns.getDoubleType(), jetType)) { else if (typeChecker.equalTypes(builtIns.getDoubleType(), jetType)) {
addName(result, "d", validator); addName(result, "d", validator)
} }
else if (typeChecker.equalTypes(builtIns.getShortType(), jetType)) { else if (typeChecker.equalTypes(builtIns.getShortType(), jetType)) {
addName(result, "sh", validator); addName(result, "sh", validator)
} }
else if (typeChecker.equalTypes(builtIns.getCharType(), jetType)) { else if (typeChecker.equalTypes(builtIns.getCharType(), jetType)) {
addName(result, "c", validator); addName(result, "c", validator)
} }
else if (typeChecker.equalTypes(builtIns.getStringType(), jetType)) { else if (typeChecker.equalTypes(builtIns.getStringType(), jetType)) {
addName(result, "s", validator); addName(result, "s", validator)
} }
else if (KotlinBuiltIns.isArray(jetType) || KotlinBuiltIns.isPrimitiveArray(jetType)) { else if (KotlinBuiltIns.isArray(jetType) || KotlinBuiltIns.isPrimitiveArray(jetType)) {
JetType elementType = KotlinBuiltIns.getInstance().getArrayElementType(jetType); val elementType = KotlinBuiltIns.getInstance().getArrayElementType(jetType)
if (typeChecker.equalTypes(builtIns.getBooleanType(), elementType)) { if (typeChecker.equalTypes(builtIns.getBooleanType(), elementType)) {
addName(result, "booleans", validator); addName(result, "booleans", validator)
} }
else if (typeChecker.equalTypes(builtIns.getIntType(), elementType)) { else if (typeChecker.equalTypes(builtIns.getIntType(), elementType)) {
addName(result, "ints", validator); addName(result, "ints", validator)
} }
else if (typeChecker.equalTypes(builtIns.getByteType(), elementType)) { else if (typeChecker.equalTypes(builtIns.getByteType(), elementType)) {
addName(result, "bytes", validator); addName(result, "bytes", validator)
} }
else if (typeChecker.equalTypes(builtIns.getLongType(), elementType)) { else if (typeChecker.equalTypes(builtIns.getLongType(), elementType)) {
addName(result, "longs", validator); addName(result, "longs", validator)
} }
else if (typeChecker.equalTypes(builtIns.getFloatType(), elementType)) { else if (typeChecker.equalTypes(builtIns.getFloatType(), elementType)) {
addName(result, "floats", validator); addName(result, "floats", validator)
} }
else if (typeChecker.equalTypes(builtIns.getDoubleType(), elementType)) { else if (typeChecker.equalTypes(builtIns.getDoubleType(), elementType)) {
addName(result, "doubles", validator); addName(result, "doubles", validator)
} }
else if (typeChecker.equalTypes(builtIns.getShortType(), elementType)) { else if (typeChecker.equalTypes(builtIns.getShortType(), elementType)) {
addName(result, "shorts", validator); addName(result, "shorts", validator)
} }
else if (typeChecker.equalTypes(builtIns.getCharType(), elementType)) { else if (typeChecker.equalTypes(builtIns.getCharType(), elementType)) {
addName(result, "chars", validator); addName(result, "chars", validator)
} }
else if (typeChecker.equalTypes(builtIns.getStringType(), elementType)) { else if (typeChecker.equalTypes(builtIns.getStringType(), elementType)) {
addName(result, "strings", validator); addName(result, "strings", validator)
} }
else { else {
ClassDescriptor classDescriptor = TypeUtils.getClassDescriptor(elementType); val classDescriptor = TypeUtils.getClassDescriptor(elementType)
if (classDescriptor != null) { if (classDescriptor != null) {
Name className = classDescriptor.getName(); val className = classDescriptor.getName()
addName(result, "arrayOf" + StringUtil.capitalize(className.asString()) + "s", validator); addName(result, "arrayOf" + StringUtil.capitalize(className.asString()) + "s", validator)
} }
} }
} }
else { else {
addForClassType(result, jetType, validator); addForClassType(result, jetType, validator)
} }
} }
private static void addForClassType(ArrayList<String> result, JetType jetType, JetNameValidator validator) { private fun addForClassType(result: ArrayList<String>, jetType: JetType, validator: JetNameValidator) {
ClassifierDescriptor descriptor = jetType.getConstructor().getDeclarationDescriptor(); val descriptor = jetType.getConstructor().getDeclarationDescriptor()
if (descriptor != null) { if (descriptor != null) {
Name className = descriptor.getName(); val className = descriptor.getName()
if (!className.isSpecial()) { if (!className.isSpecial()) {
addCamelNames(result, className.asString(), validator); addCamelNames(result, className.asString(), validator)
} }
} }
} }
private static final String[] ACCESSOR_PREFIXES = { "get", "is", "set" }; private val ACCESSOR_PREFIXES = arrayOf("get", "is", "set")
public static List<String> getCamelNames(String name, JetNameValidator validator, boolean startLowerCase) { public fun getCamelNames(name: String, validator: JetNameValidator, startLowerCase: Boolean): List<String> {
ArrayList<String> result = new ArrayList<String>(); val result = ArrayList<String>()
addCamelNames(result, name, validator, startLowerCase); addCamelNames(result, name, validator, startLowerCase)
return result; return result
} }
private static void addCamelNames(ArrayList<String> result, String name, JetNameValidator validator) { jvmOverloads private fun addCamelNames(result: ArrayList<String>, name: String, validator: JetNameValidator, startLowerCase: Boolean = true) {
addCamelNames(result, name, validator, true); if (name === "") return
} var s = deleteNonLetterFromString(name)
private static void addCamelNames(ArrayList<String> result, String name, JetNameValidator validator, boolean startLowerCase) { for (prefix in ACCESSOR_PREFIXES) {
if (name == "") return; if (!s.startsWith(prefix)) continue
String s = deleteNonLetterFromString(name);
for (String prefix : ACCESSOR_PREFIXES) { val len = prefix.length()
if (!s.startsWith(prefix)) continue;
int len = prefix.length();
if (len < s.length() && Character.isUpperCase(s.charAt(len))) { if (len < s.length() && Character.isUpperCase(s.charAt(len))) {
s = s.substring(len); s = s.substring(len)
break; break
} }
} }
boolean upperCaseLetterBefore = false; var upperCaseLetterBefore = false
for (int i = 0; i < s.length(); ++i) { for (i in 0..s.length() - 1) {
char c = s.charAt(i); val c = s.charAt(i)
boolean upperCaseLetter = Character.isUpperCase(c); val upperCaseLetter = Character.isUpperCase(c)
if (i == 0) { if (i == 0) {
addName(result, startLowerCase ? decapitalize(s) : s, validator); addName(result, if (startLowerCase) decapitalize(s) else s, validator)
} }
else { else {
if (upperCaseLetter && !upperCaseLetterBefore) { if (upperCaseLetter && !upperCaseLetterBefore) {
String substring = s.substring(i); val substring = s.substring(i)
addName(result, startLowerCase ? decapitalize(substring) : substring, validator); addName(result, if (startLowerCase) decapitalize(substring) else substring, validator)
} }
} }
upperCaseLetterBefore = upperCaseLetter; upperCaseLetterBefore = upperCaseLetter
} }
} }
private static String decapitalize(String s) { private fun decapitalize(s: String): String {
char c = s.charAt(0); var c = s.charAt(0)
if (!Character.isUpperCase(c)) return s; if (!Character.isUpperCase(c)) return s
StringBuilder builder = new StringBuilder(s.length()); val builder = StringBuilder(s.length())
boolean decapitalize = true; var decapitalize = true
for (int i = 0; i < s.length(); i++) { for (i in 0..s.length() - 1) {
c = s.charAt(i); c = s.charAt(i)
if (decapitalize) { if (decapitalize) {
if (Character.isUpperCase(c)) { if (Character.isUpperCase(c)) {
c = Character.toLowerCase(c); c = Character.toLowerCase(c)
} }
else { else {
decapitalize = false; decapitalize = false
} }
} }
builder.append(c); builder.append(c)
} }
return builder.toString(); return builder.toString()
} }
private static String deleteNonLetterFromString(String s) {
Pattern pattern = Pattern.compile("[^a-zA-Z]");
Matcher matcher = pattern.matcher(s);
return matcher.replaceAll("");
}
private static void addNamesForExpression(
final ArrayList<String> result,
@Nullable JetExpression expression,
final JetNameValidator validator) {
if (expression == null) return;
expression.accept(new JetVisitorVoid() { private fun deleteNonLetterFromString(s: String): String {
@Override val pattern = Pattern.compile("[^a-zA-Z]")
public void visitQualifiedExpression(@NotNull JetQualifiedExpression expression) { val matcher = pattern.matcher(s)
JetExpression selectorExpression = expression.getSelectorExpression(); return matcher.replaceAll("")
addNamesForExpression(result, selectorExpression, validator); }
private fun addNamesForExpression(result: ArrayList<String>, expression: JetExpression?, validator: JetNameValidator) {
if (expression == null) return
expression.accept(object : JetVisitorVoid() {
override fun visitQualifiedExpression(expression: JetQualifiedExpression) {
val selectorExpression = expression.getSelectorExpression()
addNamesForExpression(result, selectorExpression, validator)
} }
@Override override fun visitSimpleNameExpression(expression: JetSimpleNameExpression) {
public void visitSimpleNameExpression(@NotNull JetSimpleNameExpression expression) { val referenceName = expression.getReferencedName()
String referenceName = expression.getReferencedName(); if (referenceName == referenceName.toUpperCase()) {
if (referenceName.equals(referenceName.toUpperCase())) { addName(result, referenceName, validator)
addName(result, referenceName, validator);
} }
else { else {
addCamelNames(result, referenceName, validator); addCamelNames(result, referenceName, validator)
} }
} }
@Override override fun visitCallExpression(expression: JetCallExpression) {
public void visitCallExpression(@NotNull JetCallExpression expression) { addNamesForExpression(result, expression.getCalleeExpression(), validator)
addNamesForExpression(result, expression.getCalleeExpression(), validator);
} }
@Override override fun visitPostfixExpression(expression: JetPostfixExpression) {
public void visitPostfixExpression(@NotNull JetPostfixExpression expression) { addNamesForExpression(result, expression.getBaseExpression(), validator)
addNamesForExpression(result, expression.getBaseExpression(), validator);
} }
}); })
} }
public static boolean isIdentifier(@Nullable String name) {
ApplicationManager.getApplication().assertReadAccessAllowed();
if (name == null || name.isEmpty()) return false;
JetLexer lexer = new JetLexer(); public fun isIdentifier(name: String?): Boolean {
lexer.start(name, 0, name.length()); ApplicationManager.getApplication().assertReadAccessAllowed()
if (lexer.getTokenType() != JetTokens.IDENTIFIER) return false; if (name == null || name.isEmpty()) return false
lexer.advance();
return lexer.getTokenType() == null; val lexer = JetLexer()
lexer.start(name, 0, name.length())
if (lexer.getTokenType() !== JetTokens.IDENTIFIER) return false
lexer.advance()
return lexer.getTokenType() == null
} }
} }
@@ -71,7 +71,7 @@ public class ChangeFunctionLiteralSignatureFix extends ChangeFunctionSignatureFi
JetNameValidator validator = new SimpleCollectingValidator(); JetNameValidator validator = new SimpleCollectingValidator();
descriptor.clearNonReceiverParameters(); descriptor.clearNonReceiverParameters();
for (JetType type : parameterTypes) { for (JetType type : parameterTypes) {
String name = JetNameSuggester.suggestNames(type, validator, "param")[0]; String name = JetNameSuggester.INSTANCE$.suggestNames(type, validator, "param")[0];
descriptor.addParameter( descriptor.addParameter(
new JetParameterInfo(functionDescriptor, -1, name, type, null, null, JetValVar.None, null) new JetParameterInfo(functionDescriptor, -1, name, type, null, null, JetValVar.None, null)
); );
@@ -105,7 +105,7 @@ public abstract class ChangeFunctionSignatureFix extends JetIntentionAction<PsiE
return validator.validateName(argumentName.getAsName().asString()); return validator.validateName(argumentName.getAsName().asString());
} }
else if (expression != null) { else if (expression != null) {
return JetNameSuggester.suggestNames(expression, validator, "param")[0]; return JetNameSuggester.INSTANCE$.suggestNames(expression, validator, "param")[0];
} }
return validator.validateName("param"); return validator.validateName("param");
@@ -102,9 +102,9 @@ public class KotlinExtractFunctionDialog extends DialogWrapper {
} }
private boolean checkNames() { private boolean checkNames() {
if (!JetNameSuggester.isIdentifier(getFunctionName())) return false; if (!JetNameSuggester.INSTANCE$.isIdentifier(getFunctionName())) return false;
for (KotlinParameterTablePanel.ParameterInfo parameterInfo : parameterTablePanel.getParameterInfos()) { for (KotlinParameterTablePanel.ParameterInfo parameterInfo : parameterTablePanel.getParameterInfos()) {
if (!JetNameSuggester.isIdentifier(parameterInfo.getName())) return false; if (!JetNameSuggester.INSTANCE$.isIdentifier(parameterInfo.getName())) return false;
} }
return true; return true;
} }
@@ -345,7 +345,7 @@ public class KotlinParameterTablePanel extends JPanel {
} }
case PARAMETER_NAME_COLUMN: { case PARAMETER_NAME_COLUMN: {
String name = (String) aValue; String name = (String) aValue;
if (JetNameSuggester.isIdentifier(name)) { if (JetNameSuggester.INSTANCE$.isIdentifier(name)) {
info.setName(name); info.setName(name);
} }
updateSignature(); updateSignature();
@@ -197,7 +197,7 @@ public class KotlinIntroduceVariableHandler extends KotlinIntroduceHandlerBase {
calculateAnchor(commonParent, commonContainer, allReplaces), calculateAnchor(commonParent, commonContainer, allReplaces),
JetNameValidatorImpl.Target.PROPERTIES JetNameValidatorImpl.Target.PROPERTIES
); );
final String[] suggestedNames = JetNameSuggester.suggestNames(expression, validator, "value"); final String[] suggestedNames = JetNameSuggester.INSTANCE$.suggestNames(expression, validator, "value");
final Ref<JetProperty> propertyRef = new Ref<JetProperty>(); final Ref<JetProperty> propertyRef = new Ref<JetProperty>();
final ArrayList<JetExpression> references = new ArrayList<JetExpression>(); final ArrayList<JetExpression> references = new ArrayList<JetExpression>();
final Ref<JetExpression> reference = new Ref<JetExpression>(); final Ref<JetExpression> reference = new Ref<JetExpression>();
@@ -100,7 +100,7 @@ public class JetNameSuggesterTest extends LightCodeInsightFixtureTestCase {
JetRefactoringUtil.selectExpression(myFixture.getEditor(), file, new JetRefactoringUtil.SelectExpressionCallback() { JetRefactoringUtil.selectExpression(myFixture.getEditor(), file, new JetRefactoringUtil.SelectExpressionCallback() {
@Override @Override
public void run(@Nullable JetExpression expression) { public void run(@Nullable JetExpression expression) {
String[] names = JetNameSuggester.suggestNames(expression, EmptyValidator.INSTANCE$, "value"); String[] names = JetNameSuggester.INSTANCE$.suggestNames(expression, EmptyValidator.INSTANCE$, "value");
Arrays.sort(names); Arrays.sort(names);
String result = StringUtil.join(names, "\n").trim(); String result = StringUtil.join(names, "\n").trim();
assertEquals(expectedResultText, result); assertEquals(expectedResultText, result);