diff --git a/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/java/JavaBridgeConfiguration.java b/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/java/JavaBridgeConfiguration.java index 7e30f64a42d..36dfb60a8e9 100644 --- a/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/java/JavaBridgeConfiguration.java +++ b/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/java/JavaBridgeConfiguration.java @@ -26,6 +26,7 @@ import org.jetbrains.jet.lang.psi.JetPsiFactory; import org.jetbrains.jet.lang.resolve.BindingTrace; import org.jetbrains.jet.lang.resolve.DescriptorUtils; import org.jetbrains.jet.lang.resolve.FqName; +import org.jetbrains.jet.lang.resolve.ImportPath; import org.jetbrains.jet.lang.resolve.scopes.WritableScope; import java.util.Collection; @@ -36,7 +37,7 @@ import java.util.Collections; */ public class JavaBridgeConfiguration implements ModuleConfiguration { - public static final String[] DEFAULT_JAVA_IMPORTS = new String[] { "java.lang.*" }; + public static final ImportPath[] DEFAULT_JAVA_IMPORTS = new ImportPath[] { new ImportPath("java.lang.*") }; public static ModuleConfiguration createJavaBridgeConfiguration(@NotNull Project project, @NotNull BindingTrace trace, ModuleConfiguration delegateConfiguration) { return new JavaBridgeConfiguration(project, trace, delegateConfiguration); @@ -54,8 +55,8 @@ public class JavaBridgeConfiguration implements ModuleConfiguration { @Override public void addDefaultImports(@NotNull WritableScope rootScope, @NotNull Collection directives) { - for (String importFQN : DEFAULT_JAVA_IMPORTS) { - directives.add(JetPsiFactory.createImportDirective(project, importFQN)); + for (ImportPath importPath : DEFAULT_JAVA_IMPORTS) { + directives.add(JetPsiFactory.createImportDirective(project, importPath)); } delegateConfiguration.addDefaultImports(rootScope, directives); } diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/DefaultModuleConfiguration.java b/compiler/frontend/src/org/jetbrains/jet/lang/DefaultModuleConfiguration.java index 82bf46581b5..0dee399ee07 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/DefaultModuleConfiguration.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/DefaultModuleConfiguration.java @@ -22,6 +22,7 @@ import org.jetbrains.jet.lang.descriptors.NamespaceDescriptor; import org.jetbrains.jet.lang.psi.JetImportDirective; import org.jetbrains.jet.lang.psi.JetPsiFactory; import org.jetbrains.jet.lang.resolve.BindingTrace; +import org.jetbrains.jet.lang.resolve.ImportPath; import org.jetbrains.jet.lang.resolve.scopes.WritableScope; import java.util.Collection; @@ -30,7 +31,8 @@ import java.util.Collection; * @author svtk */ public class DefaultModuleConfiguration implements ModuleConfiguration { - public static final String[] DEFAULT_JET_IMPORTS = new String[] { "kotlin.*", "kotlin.io.*" }; + public static final ImportPath[] DEFAULT_JET_IMPORTS = new ImportPath[] { + new ImportPath("kotlin.*"), new ImportPath("kotlin.io.*") }; private Project project; @@ -44,7 +46,7 @@ public class DefaultModuleConfiguration implements ModuleConfiguration { @Override public void addDefaultImports(@NotNull WritableScope rootScope, @NotNull Collection directives) { - for (String defaultJetImport : DEFAULT_JET_IMPORTS) { + for (ImportPath defaultJetImport : DEFAULT_JET_IMPORTS) { directives.add(JetPsiFactory.createImportDirective(project, defaultJetImport)); } } @@ -52,5 +54,4 @@ public class DefaultModuleConfiguration implements ModuleConfiguration { @Override public void extendNamespaceScope(@NotNull BindingTrace trace, @NotNull NamespaceDescriptor namespaceDescriptor, @NotNull WritableScope namespaceMemberScope) { } - } diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiFactory.java b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiFactory.java index 2b3b3eb4df5..467123f6b50 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiFactory.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiFactory.java @@ -24,7 +24,7 @@ import com.intellij.psi.PsiFileFactory; import com.intellij.util.LocalTimeCounter; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.jet.lang.resolve.FqName; +import org.jetbrains.jet.lang.resolve.ImportPath; import org.jetbrains.jet.lexer.JetKeywordToken; import org.jetbrains.jet.lexer.JetTokens; import org.jetbrains.jet.plugin.JetFileType; @@ -140,21 +140,35 @@ public class JetPsiFactory { return function.getValueParameters().get(0); } -// public static JetNamespace createNamespace(Project project, String text) { -// JetFile file = createFile(project, text); -// return file.getRootNamespace(); -// } - - public static JetImportDirective createImportDirective(Project project, @NotNull String classPath) { - if (classPath.isEmpty()) { - throw new IllegalArgumentException("import path must not be empty"); - } - JetFile namespace = createFile(project, "import " + classPath); - return namespace.getImportDirectives().iterator().next(); + @NotNull + public static JetImportDirective createImportDirective(Project project, @NotNull String path) { + return createImportDirective(project, new ImportPath(path)); } - public static JetImportDirective createImportDirective(Project project, @NotNull FqName fqName) { - return createImportDirective(project, fqName.getFqName()); + @NotNull + public static JetImportDirective createImportDirective(Project project, @NotNull ImportPath importPath) { + return createImportDirective(project, importPath, null); + } + + @NotNull + public static JetImportDirective createImportDirective(Project project, @NotNull ImportPath importPath, @Nullable String aliasName) { + if (importPath.fqnPart().isRoot()) { + throw new IllegalArgumentException("import path must not be empty"); + } + + StringBuilder importDirectiveBuilder = new StringBuilder("import "); + importDirectiveBuilder.append(importPath.getPathStr()); + + if (aliasName != null) { + if (aliasName.isEmpty()) { + throw new IllegalArgumentException("Alias must not be empty"); + } + + importDirectiveBuilder.append(" as ").append(aliasName); + } + + JetFile namespace = createFile(project, importDirectiveBuilder.toString()); + return namespace.getImportDirectives().iterator().next(); } public static PsiElement createPrimaryConstructor(Project project) { diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiUtil.java b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiUtil.java index 531ffb2286a..f0d77b6c27e 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiUtil.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiUtil.java @@ -23,8 +23,8 @@ import com.intellij.psi.util.PsiTreeUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.lang.resolve.FqName; +import org.jetbrains.jet.lang.resolve.ImportPath; import org.jetbrains.jet.lexer.JetTokens; -import org.jetbrains.jet.util.QualifiedNamesUtil; import java.util.Collection; import java.util.HashSet; @@ -194,14 +194,14 @@ public class JetPsiUtil { } @Nullable @JetElement.IfNotParsed - public static String getImportPath(JetImportDirective importDirective) { + public static ImportPath getImportPath(JetImportDirective importDirective) { final JetExpression importedReference = importDirective.getImportedReference(); if (importedReference == null) { return null; } final String text = importedReference.getText(); - return text.replaceAll(" ", "") + (importDirective.isAllUnder() ? ".*" : ""); + return new ImportPath(text.replaceAll(" ", "") + (importDirective.isAllUnder() ? ".*" : "")); } @NotNull diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/ImportPath.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/ImportPath.java new file mode 100644 index 00000000000..3ef25b43833 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/ImportPath.java @@ -0,0 +1,70 @@ +/* + * Copyright 2010-2012 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.jet.lang.resolve; + +import org.jetbrains.annotations.NotNull; + +/** + * @author Nikolay Krasko + */ +public final class ImportPath { + final @NotNull FqName fqName; + final boolean isAllUnder; + + public ImportPath(@NotNull FqName fqName, boolean isAllUnder) { + this.fqName = fqName; + this.isAllUnder = isAllUnder; + } + + public ImportPath(@NotNull String pathStr) { + if (pathStr.endsWith(".*")) { + this.isAllUnder = true; + this.fqName = new FqName(pathStr.substring(0, pathStr.length() - 2)); + } + else { + this.isAllUnder = false; + this.fqName = new FqName(pathStr); + } + } + + public String getPathStr() { + return fqName.getFqName() + (isAllUnder ? ".*" : ""); + } + + @NotNull + public FqName fqnPart() { + return fqName; + } + + public boolean isAllUnder() { + return isAllUnder; + } + + @Override + public int hashCode() { + return 31 * fqName.hashCode() + (isAllUnder ? 1 : 0); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof ImportPath)) return false; + + ImportPath other = (ImportPath) obj; + return fqName.equals(other.fqName) && (isAllUnder == other.isAllUnder); + } +} diff --git a/compiler/frontend/src/org/jetbrains/jet/util/QualifiedNamesUtil.java b/compiler/frontend/src/org/jetbrains/jet/util/QualifiedNamesUtil.java index c490792ccd9..bd80da0be3a 100644 --- a/compiler/frontend/src/org/jetbrains/jet/util/QualifiedNamesUtil.java +++ b/compiler/frontend/src/org/jetbrains/jet/util/QualifiedNamesUtil.java @@ -19,14 +19,18 @@ package org.jetbrains.jet.util; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.lang.resolve.FqName; +import org.jetbrains.jet.lang.resolve.ImportPath; /** - * Common methods for working with qualified names strings. + * Common methods for working with qualified names. * * @author Nikolay Krasko */ public final class QualifiedNamesUtil { + private QualifiedNamesUtil() { + } + public static boolean isSubpackageOf(@NotNull final FqName subpackageName, @NotNull FqName packageName) { return subpackageName.equals(packageName) || (subpackageName.getFqName().startsWith(packageName.getFqName()) && subpackageName.getFqName().charAt(packageName.getFqName().length()) == '.'); @@ -45,6 +49,10 @@ public final class QualifiedNamesUtil { return fqn.indexOf('.') < 0; } + public static boolean isOneSegmentFQN(@NotNull FqName fqn) { + return isOneSegmentFQN(fqn.getFqName()); + } + @NotNull public static String fqnToShortName(@NotNull FqName fqn) { return getLastSegment(fqn); @@ -62,18 +70,18 @@ public final class QualifiedNamesUtil { } @NotNull - public static String withoutLastSegment(@NotNull String fqn) { - final int lastDotIndex = fqn.lastIndexOf('.'); - if (lastDotIndex > 0) { - return fqn.substring(0, lastDotIndex); - } - - return ""; + public static FqName withoutLastSegment(@NotNull FqName fqName) { + return fqName.parent(); } @NotNull - public static FqName withoutLastSegment(@NotNull FqName fqName) { - return fqName.parent(); + public static FqName withoutFirstSegment(@NotNull FqName fqName) { + if (fqName.isRoot() || fqName.parent().isRoot()) { + return FqName.ROOT; + } + + String fqNameStr = fqName.getFqName(); + return new FqName(fqNameStr.substring(fqNameStr.indexOf('.'), fqNameStr.length())); } @NotNull @@ -121,18 +129,19 @@ public final class QualifiedNamesUtil { return null; } - /** - * Check that given fqn could be imported with import. - * - * @param importPath path from the import. Could contain .* part - * @param fqn or another import directive - */ - public static boolean isImported(@NotNull String importPath, @NotNull String fqn) { - if (importPath.endsWith("*")) { - // TODO: import path is not valid FQN - return withoutLastSegment(importPath).equals(withoutLastSegment(fqn)); + public static boolean isImported(@NotNull ImportPath alreadyImported, @NotNull FqName fqName) { + if (alreadyImported.isAllUnder()) { + return alreadyImported.fqnPart().equals(fqName.parent()); } - return importPath.equals(fqn); + return alreadyImported.fqnPart().equals(fqName); + } + + public static boolean isImported(@NotNull ImportPath alreadyImported, @NotNull ImportPath newImport) { + if (newImport.isAllUnder()) { + return alreadyImported.equals(newImport); + } + + return isImported(alreadyImported, newImport.fqnPart()); } } diff --git a/idea/src/org/jetbrains/jet/plugin/actions/JetAddImportAction.java b/idea/src/org/jetbrains/jet/plugin/actions/JetAddImportAction.java index c528f2b1c2b..55060242854 100644 --- a/idea/src/org/jetbrains/jet/plugin/actions/JetAddImportAction.java +++ b/idea/src/org/jetbrains/jet/plugin/actions/JetAddImportAction.java @@ -33,7 +33,8 @@ import com.intellij.psi.PsiFile; import com.intellij.util.PlatformIcons; import org.jetbrains.annotations.NotNull; import org.jetbrains.jet.lang.psi.JetFile; -import org.jetbrains.jet.plugin.quickfix.ImportClassHelper; +import org.jetbrains.jet.lang.resolve.FqName; +import org.jetbrains.jet.plugin.quickfix.ImportInsertHelper; import javax.swing.*; import java.util.List; @@ -49,7 +50,7 @@ public class JetAddImportAction implements QuestionAction { private final Project myProject; private final Editor myEditor; private final PsiElement myElement; - private final List possibleImports; + private final List possibleImports; /** * @param project Project where action takes place. @@ -61,7 +62,7 @@ public class JetAddImportAction implements QuestionAction { @NotNull Project project, @NotNull Editor editor, @NotNull PsiElement element, - @NotNull Iterable imports + @NotNull Iterable imports ) { myProject = project; myEditor = editor; @@ -90,14 +91,14 @@ public class JetAddImportAction implements QuestionAction { } protected BaseListPopupStep getImportSelectionPopup() { - return new BaseListPopupStep(QuickFixBundle.message("class.to.import.chooser.title"), possibleImports) { + return new BaseListPopupStep(QuickFixBundle.message("class.to.import.chooser.title"), possibleImports) { @Override public boolean isAutoSelectionEnabled() { return false; } @Override - public PopupStep onChosen(String selectedValue, boolean finalChoice) { + public PopupStep onChosen(FqName selectedValue, boolean finalChoice) { if (selectedValue == null) { return FINAL_CHOICE; } @@ -107,7 +108,7 @@ public class JetAddImportAction implements QuestionAction { return FINAL_CHOICE; } - List toExclude = AddImportAction.getAllExcludableStrings(selectedValue); + List toExclude = AddImportAction.getAllExcludableStrings(selectedValue.getFqName()); return new BaseListPopupStep(null, toExclude) { @NotNull @@ -128,24 +129,25 @@ public class JetAddImportAction implements QuestionAction { } @Override - public boolean hasSubstep(String selectedValue) { + public boolean hasSubstep(FqName selectedValue) { return true; } @NotNull @Override - public String getTextFor(String value) { - return value; + public String getTextFor(FqName value) { + return value.getFqName(); } @Override - public Icon getIconFor(String aValue) { + public Icon getIconFor(FqName aValue) { + // TODO: change icon return PlatformIcons.CLASS_ICON; } }; } - protected static void addImport(final PsiElement element, final Project project, final String selectedImport) { + protected static void addImport(final PsiElement element, final Project project, final FqName selectedImport) { PsiDocumentManager.getInstance(project).commitAllDocuments(); CommandProcessor.getInstance().executeCommand(project, new Runnable() { @@ -154,13 +156,10 @@ public class JetAddImportAction implements QuestionAction { ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override public void run() { - // TODO: See {@link com.intellij.codeInsight.daemon.impl.actions.AddImportAction#_addImport} for more ideas. - // TODO: Optimize imports PsiFile file = element.getContainingFile(); if (!(file instanceof JetFile)) return; - ImportClassHelper.addImportDirective( - selectedImport, - (JetFile)file + ImportInsertHelper.addImportDirective(selectedImport, + (JetFile) file ); } }); diff --git a/idea/src/org/jetbrains/jet/plugin/caches/JetShortNamesCache.java b/idea/src/org/jetbrains/jet/plugin/caches/JetShortNamesCache.java index e544f7d3be6..0f73ae28653 100644 --- a/idea/src/org/jetbrains/jet/plugin/caches/JetShortNamesCache.java +++ b/idea/src/org/jetbrains/jet/plugin/caches/JetShortNamesCache.java @@ -36,10 +36,7 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.asJava.JavaElementFinder; import org.jetbrains.jet.lang.descriptors.*; import org.jetbrains.jet.lang.psi.*; -import org.jetbrains.jet.lang.resolve.BindingContext; -import org.jetbrains.jet.lang.resolve.BindingTraceContext; -import org.jetbrains.jet.lang.resolve.FqName; -import org.jetbrains.jet.lang.resolve.ImportsResolver; +import org.jetbrains.jet.lang.resolve.*; import org.jetbrains.jet.lang.resolve.scopes.JetScope; import org.jetbrains.jet.lang.types.JetType; import org.jetbrains.jet.lang.types.expressions.ExpressionTypingUtils; @@ -177,7 +174,7 @@ public class JetShortNamesCache extends PsiShortNamesCache { for (PsiMethod method : topLevelFunctionPrototypes) { FqName functionFQN = JetFromJavaDescriptorHelper.getJetTopLevelDeclarationFQN(method); if (functionFQN != null) { - JetImportDirective importDirective = JetPsiFactory.createImportDirective(project, functionFQN); + JetImportDirective importDirective = JetPsiFactory.createImportDirective(project, new ImportPath(functionFQN, false)); Collection declarationDescriptors = ImportsResolver.analyseImportReference(importDirective, jetScope, new BindingTraceContext()); for (DeclarationDescriptor declarationDescriptor : declarationDescriptors) { if (declarationDescriptor instanceof FunctionDescriptor) { diff --git a/idea/src/org/jetbrains/jet/plugin/codeInsight/OverrideImplementMethodsHandler.java b/idea/src/org/jetbrains/jet/plugin/codeInsight/OverrideImplementMethodsHandler.java index a12824792dc..e291fd34a7f 100644 --- a/idea/src/org/jetbrains/jet/plugin/codeInsight/OverrideImplementMethodsHandler.java +++ b/idea/src/org/jetbrains/jet/plugin/codeInsight/OverrideImplementMethodsHandler.java @@ -34,7 +34,7 @@ import org.jetbrains.jet.lang.resolve.BindingContext; import org.jetbrains.jet.lang.types.JetType; import org.jetbrains.jet.lang.types.lang.JetStandardLibrary; import org.jetbrains.jet.plugin.compiler.WholeProjectAnalyzerFacade; -import org.jetbrains.jet.plugin.quickfix.ImportClassHelper; +import org.jetbrains.jet.plugin.quickfix.ImportInsertHelper; import java.util.ArrayList; import java.util.Collections; @@ -93,7 +93,7 @@ public abstract class OverrideImplementMethodsHandler implements LanguageCodeIns bodyBuilder.append("val "); } bodyBuilder.append(descriptor.getName()).append(":").append(descriptor.getType()); - ImportClassHelper.addImportDirectiveIfNeeded(descriptor.getType(), file); + ImportInsertHelper.addImportDirectiveIfNeeded(descriptor.getType(), file); String initializer = defaultInitializer(descriptor.getType(), JetStandardLibrary.getInstance()); if (initializer != null) { bodyBuilder.append("=").append(initializer); @@ -118,14 +118,14 @@ public abstract class OverrideImplementMethodsHandler implements LanguageCodeIns bodyBuilder.append(":"); bodyBuilder.append(parameterDescriptor.getType().toString()); - ImportClassHelper.addImportDirectiveIfNeeded(parameterDescriptor.getType(), file); + ImportInsertHelper.addImportDirectiveIfNeeded(parameterDescriptor.getType(), file); } bodyBuilder.append(")"); final JetType returnType = descriptor.getReturnType(); final JetStandardLibrary stdlib = JetStandardLibrary.getInstance(); if (!returnType.equals(stdlib.getTuple0Type())) { bodyBuilder.append(":").append(returnType.toString()); - ImportClassHelper.addImportDirectiveIfNeeded(returnType, file); + ImportInsertHelper.addImportDirectiveIfNeeded(returnType, file); } bodyBuilder.append("{").append("throw UnsupportedOperationException()").append("}"); diff --git a/idea/src/org/jetbrains/jet/plugin/completion/handlers/JetFunctionInsertHandler.java b/idea/src/org/jetbrains/jet/plugin/completion/handlers/JetFunctionInsertHandler.java index 5e78526d427..9ee37e78cc6 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/handlers/JetFunctionInsertHandler.java +++ b/idea/src/org/jetbrains/jet/plugin/completion/handlers/JetFunctionInsertHandler.java @@ -31,12 +31,11 @@ import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor; import org.jetbrains.jet.lang.descriptors.SimpleFunctionDescriptor; import org.jetbrains.jet.lang.psi.JetFile; import org.jetbrains.jet.lang.psi.JetImportDirective; -import org.jetbrains.jet.lang.psi.JetPsiUtil; import org.jetbrains.jet.lang.psi.JetQualifiedExpression; import org.jetbrains.jet.lang.resolve.DescriptorUtils; import org.jetbrains.jet.lang.resolve.FqName; import org.jetbrains.jet.plugin.completion.JetLookupObject; -import org.jetbrains.jet.plugin.quickfix.ImportClassHelper; +import org.jetbrains.jet.plugin.quickfix.ImportInsertHelper; /** * Performs number of code modification after insertion jet function: @@ -135,7 +134,7 @@ public class JetFunctionInsertHandler implements InsertHandler { @Override public void run() { final FqName fqn = DescriptorUtils.getFQName(functionDescriptor).toSafe(); - ImportClassHelper.addImportDirective(fqn.getFqName(), file); + ImportInsertHelper.addImportDirective(fqn, file); } }); } diff --git a/idea/src/org/jetbrains/jet/plugin/completion/handlers/JetJavaClassInsertHandler.java b/idea/src/org/jetbrains/jet/plugin/completion/handlers/JetJavaClassInsertHandler.java index c879ab166ba..cbbe27a7d26 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/handlers/JetJavaClassInsertHandler.java +++ b/idea/src/org/jetbrains/jet/plugin/completion/handlers/JetJavaClassInsertHandler.java @@ -20,7 +20,8 @@ import com.intellij.codeInsight.completion.InsertHandler; import com.intellij.codeInsight.completion.InsertionContext; import com.intellij.codeInsight.completion.JavaPsiClassReferenceElement; import org.jetbrains.jet.lang.psi.JetFile; -import org.jetbrains.jet.plugin.quickfix.ImportClassHelper; +import org.jetbrains.jet.lang.resolve.FqName; +import org.jetbrains.jet.plugin.quickfix.ImportInsertHelper; /** * Handler for inserting java class completion. @@ -34,7 +35,7 @@ public class JetJavaClassInsertHandler implements InsertHandler usedQualifiedNames = extractUsedQualifiedNames(jetFile); + final JetFile jetFile = (JetFile) file; + final Set usedQualifiedNames = extractUsedQualifiedNames(jetFile); + final List sortedDirectives = jetFile.getImportDirectives(); + Collections.sort(sortedDirectives, new Comparator() { + @Override + public int compare(JetImportDirective directive1, JetImportDirective directive2) { + ImportPath firstPath = JetPsiUtil.getImportPath(directive1); + ImportPath secondPath = JetPsiUtil.getImportPath(directive2); + + if (firstPath == null || secondPath == null) { + return firstPath == null && secondPath == null ? 0 : + firstPath == null ? -1 : + 1; + } + + // import bla.bla.bla.* should be before import bla.bla.bla.something + if (firstPath.isAllUnder() && !secondPath.isAllUnder() && firstPath.fqnPart().equals(secondPath.fqnPart().parent())) { + return -1; + } + + if (!firstPath.isAllUnder() && secondPath.isAllUnder() && secondPath.fqnPart().equals(firstPath.fqnPart().parent())) { + return 1; + } + + return firstPath.getPathStr().compareTo(secondPath.getPathStr()); + } + }); + + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + // Remove imports + List imports = jetFile.getImportDirectives(); + if (!imports.isEmpty()) { + jetFile.deleteChildRange(imports.get(0), imports.get(imports.size() - 1)); + } + + // Insert back only necessary imports in correct order + for (JetImportDirective anImport : sortedDirectives) { + ImportPath importPath = JetPsiUtil.getImportPath(anImport); + if (importPath == null) { + continue; + } + + if (isUseful(importPath, anImport.getAliasName(), usedQualifiedNames)) { + ImportInsertHelper.addImportDirective(importPath, anImport.getAliasName(), jetFile); + } + } + } + }); } }; - } - public static Set extractUsedQualifiedNames(JetFile jetFile) { + public static boolean isUseful(ImportPath importPath, @Nullable String aliasName, Collection usedNames) { + if (aliasName != null) { + // TODO: Add better analysis for aliases + return true; + } - final Set usedQualifiedNames = new HashSet(); + for (FqName usedName : usedNames) { + if (QualifiedNamesUtil.isImported(importPath, usedName)) { + return true; + } + } + + return false; + } + + public static Set extractUsedQualifiedNames(JetFile jetFile) { + + final Set usedQualifiedNames = new HashSet(); jetFile.accept(new JetVisitorVoid() { @Override public void visitElement(PsiElement element) { @@ -83,7 +146,7 @@ public class JetImportOptimizer implements ImportOptimizer { } for (PsiElement psiReference : references) { - String fqName = getElementFQName(psiReference); + FqName fqName = getElementFQName(psiReference); if (fqName != null) { usedQualifiedNames.add(fqName); } @@ -101,7 +164,7 @@ public class JetImportOptimizer implements ImportOptimizer { @Nullable - public static String getElementFQName(PsiElement element) { + public static FqName getElementFQName(PsiElement element) { if (element instanceof JetClassOrObject) { return JetPsiUtil.getFQName((JetClassOrObject) element); } @@ -109,7 +172,10 @@ public class JetImportOptimizer implements ImportOptimizer { return JetPsiUtil.getFQName((JetNamedFunction) element); } if (element instanceof PsiClass) { - return ((PsiClass) element).getQualifiedName(); + String qualifiedName = ((PsiClass) element).getQualifiedName(); + if (qualifiedName != null) { + return new FqName(qualifiedName); + } } if (element instanceof PsiMethod) { PsiMethod method = (PsiMethod) element; @@ -117,15 +183,14 @@ public class JetImportOptimizer implements ImportOptimizer { PsiClass containingClass = method.getContainingClass(); if (containingClass != null) { - String classFQN = containingClass.getQualifiedName(); - - if (classFQN != null) { - if (QualifiedNamesUtil.fqnToShortName(classFQN).equals(JvmAbi.PACKAGE_CLASS)) { - String classParentFQN = QualifiedNamesUtil.withoutLastSegment(classFQN); - return QualifiedNamesUtil.combine(classParentFQN, method.getName()); + String classFQNStr = containingClass.getQualifiedName(); + if (classFQNStr != null) { + FqName classFQN = new FqName(classFQNStr); + if (classFQN.shortName().equals(JvmAbi.PACKAGE_CLASS)) { + return QualifiedNamesUtil.combine(classFQN.parent(), method.getName()); } else { - return QualifiedNamesUtil.combine(containingClass.getQualifiedName(), method.getName()); + return QualifiedNamesUtil.combine(classFQN, method.getName()); } } } diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/AddReturnTypeFix.java b/idea/src/org/jetbrains/jet/plugin/quickfix/AddReturnTypeFix.java index b4acf923204..006cb2c3878 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/AddReturnTypeFix.java +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/AddReturnTypeFix.java @@ -68,7 +68,7 @@ public class AddReturnTypeFix extends JetIntentionAction { assert element instanceof JetFunction; newElement = addFunctionType(project, (JetFunction) element, type); } - ImportClassHelper.addImportDirectiveIfNeeded(type, (JetFile) file); + ImportInsertHelper.addImportDirectiveIfNeeded(type, (JetFile) file); element.replace(newElement); } diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/ImportClassAndFunFix.java b/idea/src/org/jetbrains/jet/plugin/quickfix/ImportClassAndFunFix.java index e4c69793f5d..55b6e81f435 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/ImportClassAndFunFix.java +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/ImportClassAndFunFix.java @@ -225,13 +225,7 @@ public class ImportClassAndFunFix extends JetHintAction @NotNull private JetAddImportAction createAction(@NotNull Project project, @NotNull Editor editor) { - Collection suggesionStrings = Collections2.transform(suggestions, new Function() { - @Override - public String apply(FqName fqName) { - return fqName.getFqName(); - } - }); - return new JetAddImportAction(project, editor, element, suggesionStrings); + return new JetAddImportAction(project, editor, element, suggestions); } @Nullable diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/ImportClassHelper.java b/idea/src/org/jetbrains/jet/plugin/quickfix/ImportInsertHelper.java similarity index 62% rename from idea/src/org/jetbrains/jet/plugin/quickfix/ImportClassHelper.java rename to idea/src/org/jetbrains/jet/plugin/quickfix/ImportInsertHelper.java index b944596cacd..5663e88c382 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/ImportClassHelper.java +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/ImportInsertHelper.java @@ -18,10 +18,12 @@ package org.jetbrains.jet.plugin.quickfix; import com.intellij.psi.PsiElement; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.lang.DefaultModuleConfiguration; import org.jetbrains.jet.lang.psi.*; import org.jetbrains.jet.lang.resolve.BindingContext; import org.jetbrains.jet.lang.resolve.FqName; +import org.jetbrains.jet.lang.resolve.ImportPath; import org.jetbrains.jet.lang.resolve.java.AnalyzerFacadeForJVM; import org.jetbrains.jet.lang.resolve.java.JavaBridgeConfiguration; import org.jetbrains.jet.lang.resolve.java.JavaDescriptorResolver; @@ -35,7 +37,10 @@ import java.util.List; /** * @author svtk */ -public class ImportClassHelper { +public class ImportInsertHelper { + private ImportInsertHelper() { + } + /** * Add import directive corresponding to a type to file when it is needed. * @@ -51,36 +56,42 @@ public class ImportClassHelper { if (element != null && element.getContainingFile() == file) { //declaration is in the same file, so no import is needed return; } - addImportDirective(JetPluginUtil.computeTypeFullName(type).getFqName(), file); + addImportDirective(JetPluginUtil.computeTypeFullName(type), file); } /** * Add import directive into the PSI tree for the given namespace. * - * @param importString full name of the import. Can contain .* if necessary. + * @param importFqn full name of the import * @param file File where directive should be added. */ - public static void addImportDirective(@NotNull String importString, @NotNull JetFile file) { + public static void addImportDirective(@NotNull FqName importFqn, @NotNull JetFile file) { + addImportDirective(new ImportPath(importFqn, false), null, file); + } - // TODO: hack - if (importString.startsWith(JavaDescriptorResolver.JAVA_ROOT + ".")) { - importString = importString.substring((JavaDescriptorResolver.JAVA_ROOT + ".").length()); + public static void addImportDirective(@NotNull ImportPath importPath, @Nullable String aliasName, @NotNull JetFile file) { + + if (QualifiedNamesUtil.getFirstSegment(importPath.fqnPart().getFqName()).equals(JavaDescriptorResolver.JAVA_ROOT)) { + FqName withoutJavaRoot = QualifiedNamesUtil.withoutFirstSegment(importPath.fqnPart()); + importPath = new ImportPath(withoutJavaRoot, importPath.isAllUnder()); } - if (isImportedByDefault(importString, JetPsiUtil.getFQName(file))) { + if (isImportedByDefault(importPath, aliasName, JetPsiUtil.getFQName(file))) { return; } - List importDirectives = file.getImportDirectives(); + JetImportDirective newDirective = JetPsiFactory.createImportDirective(file.getProject(), importPath, aliasName); - JetImportDirective newDirective = JetPsiFactory.createImportDirective(file.getProject(), importString); + List importDirectives = file.getImportDirectives(); if (!importDirectives.isEmpty()) { // Check if import is already present for (JetImportDirective directive : importDirectives) { - String importPath = JetPsiUtil.getImportPath(directive); - if (importPath != null && QualifiedNamesUtil.isImported(importPath, importString)) { + ImportPath existentImportPath = JetPsiUtil.getImportPath(directive); + if (existentImportPath != null && + directive.getAliasName() == null && + QualifiedNamesUtil.isImported(existentImportPath, importPath)) { return; } } @@ -101,25 +112,40 @@ public class ImportClassHelper { } } - // Check that import is useless - private static boolean isImportedByDefault(@NotNull String importString, @NotNull FqName filePackageFqn) { - if (QualifiedNamesUtil.isOneSegmentFQN(importString) || - filePackageFqn.getFqName().equals(QualifiedNamesUtil.withoutLastSegment(importString))) { - + /** + * Check that import is useless. + */ + private static boolean isImportedByDefault(@NotNull ImportPath importPath, String aliasName, @NotNull FqName filePackageFqn) { + if (importPath.fqnPart().isRoot()) { return true; } - for (String defaultJetImport : DefaultModuleConfiguration.DEFAULT_JET_IMPORTS) { - if (QualifiedNamesUtil.isImported(defaultJetImport, importString)) { + if (aliasName != null) { + return false; + } + + // Single element import without .* and alias is useless + if (!importPath.isAllUnder() && QualifiedNamesUtil.isOneSegmentFQN(importPath.fqnPart())) { + return true; + } + + // There's no need to import a declaration from the package of current file + if (!importPath.isAllUnder() && filePackageFqn.equals(importPath.fqnPart().parent())) { + return true; + } + + for (ImportPath defaultJetImport : DefaultModuleConfiguration.DEFAULT_JET_IMPORTS) { + if (QualifiedNamesUtil.isImported(defaultJetImport, importPath)) { return true; } } - for (String defaultJavaImport : JavaBridgeConfiguration.DEFAULT_JAVA_IMPORTS) { - if (QualifiedNamesUtil.isImported(defaultJavaImport + ".*", importString)) { + for (ImportPath defaultJavaImport : JavaBridgeConfiguration.DEFAULT_JAVA_IMPORTS) { + if (QualifiedNamesUtil.isImported(defaultJavaImport, importPath)) { return true; } } + return false; } } diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/RemovePartsFromPropertyFix.java b/idea/src/org/jetbrains/jet/plugin/quickfix/RemovePartsFromPropertyFix.java index 534efa00fbb..886ea3ea8ed 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/RemovePartsFromPropertyFix.java +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/RemovePartsFromPropertyFix.java @@ -119,7 +119,7 @@ public class RemovePartsFromPropertyFix extends JetIntentionAction } } if (needImport) { - ImportClassHelper.addImportDirectiveIfNeeded(type, (JetFile)file); + ImportInsertHelper.addImportDirectiveIfNeeded(type, (JetFile) file); } element.replace(newElement); } diff --git a/idea/tests/org/jetbrains/jet/plugin/quickfix/ImportClassHelperTest.java b/idea/tests/org/jetbrains/jet/plugin/quickfix/ImportClassHelperTest.java index 85d470797bb..44247ff867a 100644 --- a/idea/tests/org/jetbrains/jet/plugin/quickfix/ImportClassHelperTest.java +++ b/idea/tests/org/jetbrains/jet/plugin/quickfix/ImportClassHelperTest.java @@ -19,6 +19,7 @@ package org.jetbrains.jet.plugin.quickfix; import com.intellij.codeInsight.daemon.LightDaemonAnalyzerTestCase; import com.intellij.openapi.application.ApplicationManager; import org.jetbrains.jet.lang.psi.JetFile; +import org.jetbrains.jet.lang.resolve.FqName; import org.jetbrains.jet.plugin.PluginTestCaseBase; import java.io.IOException; @@ -28,19 +29,19 @@ import java.io.IOException; */ public class ImportClassHelperTest extends LightDaemonAnalyzerTestCase { public void testDoNotImportIfGeneralExist() { - testImportInFile("jettesting.data.testFunction"); + doFileTest("jettesting.data.testFunction"); } public void testDoNotImportIfGeneralSpaceExist() { - testImportInFile("jettesting.data.testFunction"); + doFileTest("jettesting.data.testFunction"); } public void testNoDefaultImport() { - testImportInFile("kotlin.io.println"); + doFileTest("kotlin.io.println"); } public void testImportBeforeObject() { - testImportInFile("java.util.HashSet"); + doFileTest("java.util.HashSet"); } public void testInsertInEmptyFile() throws IOException { @@ -48,7 +49,7 @@ public class ImportClassHelperTest extends LightDaemonAnalyzerTestCase { ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override public void run() { - ImportClassHelper.addImportDirective("java.util.ArrayList", (JetFile) getFile()); + ImportInsertHelper.addImportDirective(new FqName("java.util.ArrayList"), (JetFile) getFile()); } }); @@ -60,19 +61,19 @@ public class ImportClassHelperTest extends LightDaemonAnalyzerTestCase { ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override public void run() { - ImportClassHelper.addImportDirective("java.util.ArrayList", (JetFile) getFile()); + ImportInsertHelper.addImportDirective(new FqName("java.util.ArrayList"), (JetFile) getFile()); } }); checkResultByText("package some\n\nimport java.util.ArrayList"); } - public void testImportInFile(final String importString) { + public void doFileTest(final String importString) { configureByFile(getTestName(false) + ".kt"); ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override public void run() { - ImportClassHelper.addImportDirective(importString, (JetFile) getFile()); + ImportInsertHelper.addImportDirective(new FqName(importString), (JetFile) getFile()); } }); checkResultByFile(getTestName(false) + ".kt.after");