diff --git a/src/org/jetbrains/jet/j2k/Converter.java b/src/org/jetbrains/jet/j2k/Converter.java index a94f30c6cd2..2898e4ce555 100644 --- a/src/org/jetbrains/jet/j2k/Converter.java +++ b/src/org/jetbrains/jet/j2k/Converter.java @@ -1,8 +1,6 @@ package org.jetbrains.jet.j2k; import com.intellij.psi.*; -import com.intellij.psi.search.searches.ReferencesSearch; -import com.intellij.psi.util.PsiUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.j2k.ast.*; @@ -22,7 +20,7 @@ import static org.jetbrains.jet.j2k.visitors.TypeVisitor.*; */ public class Converter { @NotNull - private final static Set NOT_NULL_ANNOTATIONS = new HashSet() { + final static Set NOT_NULL_ANNOTATIONS = new HashSet() { { add("org.jetbrains.annotations.NotNull"); add("com.sun.istack.internal.NotNull"); @@ -326,9 +324,9 @@ public class Converter { ourMethodReturnType = method.getReturnType(); final IdentifierImpl identifier = new IdentifierImpl(method.getName()); - final Type returnType = typeToType(method.getReturnType(), isNotNull(method.getModifierList())); + final Type returnType = typeToType(method.getReturnType(), ConverterUtil.isAnnotatedAsNotNull(method.getModifierList())); final Block body = blockToBlock(method.getBody(), notEmpty); - final Element params = elementToElement(method.getParameterList()); + final Element params = createFunctionParameters(method); final List typeParameters = elementsToElementList(method.getTypeParameters()); final Set modifiers = modifiersListToModifiersSet(method.getModifierList()); @@ -361,6 +359,19 @@ public class Converter { ); } + @NotNull + private static ParameterList createFunctionParameters(@NotNull PsiMethod method) { + List result = new LinkedList(); + for (PsiParameter parameter : method.getParameterList().getParameters()) { + result.add(new Parameter( + new IdentifierImpl(parameter.getName()), + typeToType(parameter.getType(), ConverterUtil.isAnnotatedAsNotNull(parameter.getModifierList())), + ConverterUtil.isReadOnly(parameter, method.getBody()) + )); + } + return new ParameterList(result); + } + private static boolean isNotOpenMethod(@NotNull final PsiMethod method) { if (method.getParent() instanceof PsiClass) { final PsiModifierList parentModifierList = ((PsiClass) method.getParent()).getModifierList(); @@ -549,35 +560,10 @@ public class Converter { public static Parameter parameterToParameter(@NotNull PsiParameter parameter) { return new Parameter( new IdentifierImpl(parameter.getName()), - typeToType(parameter.getType(), isNotNull(parameter.getModifierList())), - isReadOnly(parameter) + typeToType(parameter.getType(), ConverterUtil.isAnnotatedAsNotNull(parameter.getModifierList())) ); } - public static boolean isNotNull(@Nullable PsiModifierList modifierList) { - if (modifierList != null) { - PsiAnnotation[] annotations = modifierList.getAnnotations(); - for (PsiAnnotation a : annotations) { - String qualifiedName = a.getQualifiedName(); - if (qualifiedName != null && NOT_NULL_ANNOTATIONS.contains(qualifiedName)) - return true; - } - } - return false; - } - - private static boolean isReadOnly(@NotNull PsiParameter parameter) { - try { - for (PsiReference r : (ReferencesSearch.search(parameter))) { - if (r instanceof PsiExpression && PsiUtil.isAccessedForWriting((PsiExpression) r)) { - return false; - } - } - } catch (IllegalArgumentException ignored) { - } - return true; - } - @NotNull public static Identifier identifierToIdentifier(@Nullable PsiIdentifier identifier) { if (identifier == null) return Identifier.EMPTY_IDENTIFIER; @@ -722,4 +708,5 @@ public class Converter { createConversionForExpression(expression, type) : ""; return new SureCallChainExpression(expressionToExpression(expression), conversion); } + } \ No newline at end of file diff --git a/src/org/jetbrains/jet/j2k/ConverterUtil.java b/src/org/jetbrains/jet/j2k/ConverterUtil.java index eb04032aa7b..a979ff61be1 100644 --- a/src/org/jetbrains/jet/j2k/ConverterUtil.java +++ b/src/org/jetbrains/jet/j2k/ConverterUtil.java @@ -2,6 +2,7 @@ package org.jetbrains.jet.j2k; import com.intellij.openapi.util.Pair; import com.intellij.psi.*; +import com.intellij.psi.util.PsiUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -9,6 +10,8 @@ import java.text.MessageFormat; import java.util.LinkedList; import java.util.List; +import static org.jetbrains.jet.j2k.Converter.NOT_NULL_ANNOTATIONS; + /** * @author ignatov */ @@ -68,4 +71,46 @@ public class ConverterUtil { final PsiType componentType = ((PsiArrayType) type).getComponentType(); return componentType.equalsToText("java.lang.String"); } + + public static int countWritingAccesses(@Nullable PsiElement element, @Nullable PsiElement container) { + int counter = 0; + if (container != null) { + ReferenceCollector visitor = new ReferenceCollector(); + container.accept(visitor); + for (PsiReferenceExpression e : visitor.getCollectedReferences()) + if (e.isReferenceTo(element) && PsiUtil.isAccessedForWriting(e)) + counter++; + } + return counter; + } + + static boolean isReadOnly(PsiElement element, PsiElement container) { + return countWritingAccesses(element, container) == 0; + } + + public static boolean isAnnotatedAsNotNull(@Nullable PsiModifierList modifierList) { + if (modifierList != null) { + PsiAnnotation[] annotations = modifierList.getAnnotations(); + for (PsiAnnotation a : annotations) { + String qualifiedName = a.getQualifiedName(); + if (qualifiedName != null && NOT_NULL_ANNOTATIONS.contains(qualifiedName)) + return true; + } + } + return false; + } + + static class ReferenceCollector extends JavaRecursiveElementVisitor { + public List getCollectedReferences() { + return myCollectedReferences; + } + + private List myCollectedReferences = new LinkedList(); + + @Override + public void visitReferenceExpression(PsiReferenceExpression expression) { + super.visitReferenceExpression(expression); + myCollectedReferences.add(expression); + } + } } diff --git a/src/org/jetbrains/jet/j2k/JavaToKotlinTranslator.java b/src/org/jetbrains/jet/j2k/JavaToKotlinTranslator.java new file mode 100644 index 00000000000..7a7b224f62c --- /dev/null +++ b/src/org/jetbrains/jet/j2k/JavaToKotlinTranslator.java @@ -0,0 +1,161 @@ +/* + * Copyright 2000-2011 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.j2k; + +import com.intellij.core.JavaCoreEnvironment; +import com.intellij.lang.java.JavaLanguage; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiFileFactory; +import com.intellij.psi.PsiJavaFile; +import com.intellij.util.Function; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jet.j2k.visitors.ClassVisitor; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; + +/** + * @author ignatov + */ +public class JavaToKotlinTranslator { + private JavaToKotlinTranslator() { + } + + @Nullable + protected static PsiFile createFile(@NonNls String name, String text) { + JavaCoreEnvironment javaCoreEnvironment = new JavaCoreEnvironment(new Disposable() { + @Override + public void dispose() { + } + }); + + javaCoreEnvironment.addToClasspath(findRtJar(true)); + javaCoreEnvironment.addToClasspath(new File("jre/lib/annotations.jar")); + + return PsiFileFactory.getInstance(javaCoreEnvironment.getProject()).createFileFromText( + name, JavaLanguage.INSTANCE, text + ); + } + + @NotNull + private static String prettify(@Nullable String code) { + if (code == null) + return ""; + return code + .trim() + .replaceAll("\r\n", "\n") + .replaceAll(" \n", "\n") + .replaceAll("\n ", "\n") + .replaceAll("\n+", "\n") + .replaceAll(" +", " ") + .trim() + ; + } + + public static File findRtJar(boolean failOnError) { + String javaHome = System.getenv("JAVA_HOME"); + File rtJar; + if (javaHome == null) { + rtJar = findActiveRtJar(failOnError); + + if (rtJar == null && failOnError) { + throw new SetupJavaCoreEnvironmentException("JAVA_HOME environment variable needs to be defined"); + } + } else { + rtJar = findRtJar(javaHome); + } + + if ((rtJar == null || !rtJar.exists()) && failOnError) { + rtJar = findActiveRtJar(failOnError); + + if ((rtJar == null || !rtJar.exists())) { + throw new SetupJavaCoreEnvironmentException("No rt.jar found under JAVA_HOME=" + javaHome); + } + } + return rtJar; + } + + private static File findRtJar(String javaHome) { + File rtJar = new File(javaHome, "jre/lib/rt.jar"); + if (rtJar.exists()) { + return rtJar; + } + + File classesJar = new File(new File(javaHome).getParentFile().getAbsolutePath(), "Classes/classes.jar"); + if (classesJar.exists()) { + return classesJar; + } + return null; + } + + public static File findActiveRtJar(boolean failOnError) { + ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); + if (systemClassLoader instanceof URLClassLoader) { + URLClassLoader loader = (URLClassLoader) systemClassLoader; + for (URL url : loader.getURLs()) { + if ("file".equals(url.getProtocol())) { + if (url.getFile().endsWith("/lib/rt.jar")) { + return new File(url.getFile()); + } + if (url.getFile().endsWith("/Classes/classes.jar")) { + return new File(url.getFile()).getAbsoluteFile(); + } + } + } + if (failOnError) { + throw new SetupJavaCoreEnvironmentException("Could not find rt.jar in system class loader: " + StringUtil.join(loader.getURLs(), new Function() { + @Override + public String fun(URL url) { + return url.toString() + "\n"; + } + }, ", ")); + } + } else if (failOnError) { + throw new SetupJavaCoreEnvironmentException("System class loader is not an URLClassLoader: " + systemClassLoader); + } + return null; + } + + private static void setClassIdentifiers(PsiElement psiFile) { + ClassVisitor c = new ClassVisitor(); + psiFile.accept(c); + Converter.clearClassIdentifiers(); + Converter.setClassIdentifiers(c.getClassIdentifiers()); + } + + static String generateKotlinCode(String arg) { + PsiFile file = createFile("test.java", arg); + if (file != null && file instanceof PsiJavaFile) { + setClassIdentifiers(file); + return prettify(Converter.fileToFile((PsiJavaFile) file).toKotlin()); + } + return ""; + } + + public static void main(String[] args) throws IOException { + if (args.length > 0) { + //noinspection UseOfSystemOutOrSystemErr + System.out.println(generateKotlinCode(args[0])); + } + } +} diff --git a/src/org/jetbrains/jet/j2k/SetupJavaCoreEnvironmentException.java b/src/org/jetbrains/jet/j2k/SetupJavaCoreEnvironmentException.java new file mode 100644 index 00000000000..bbe7e064ac8 --- /dev/null +++ b/src/org/jetbrains/jet/j2k/SetupJavaCoreEnvironmentException.java @@ -0,0 +1,9 @@ +package org.jetbrains.jet.j2k; + +/** + * @author ignatov + */ +public class SetupJavaCoreEnvironmentException extends RuntimeException { + public SetupJavaCoreEnvironmentException(String s) { + } +} diff --git a/src/org/jetbrains/jet/j2k/visitors/ElementVisitor.java b/src/org/jetbrains/jet/j2k/visitors/ElementVisitor.java index c9fd52f1240..78cccb320bc 100644 --- a/src/org/jetbrains/jet/j2k/visitors/ElementVisitor.java +++ b/src/org/jetbrains/jet/j2k/visitors/ElementVisitor.java @@ -2,22 +2,21 @@ package org.jetbrains.jet.j2k.visitors; import com.intellij.psi.*; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.jet.j2k.Converter; import org.jetbrains.jet.j2k.ast.*; import java.util.List; import static org.jetbrains.jet.j2k.Converter.*; +import static org.jetbrains.jet.j2k.ConverterUtil.*; /** * @author ignatov */ public class ElementVisitor extends JavaElementVisitor { - @Nullable + @NotNull private Element myResult = Element.EMPTY_ELEMENT; - @Nullable + @NotNull public Element getResult() { return myResult; } @@ -29,7 +28,7 @@ public class ElementVisitor extends JavaElementVisitor { myResult = new LocalVariable( new IdentifierImpl(variable.getName()), // TODO modifiersListToModifiersSet(variable.getModifierList()), - typeToType(variable.getType(), Converter.isNotNull(variable.getModifierList())), + typeToType(variable.getType(), isAnnotatedAsNotNull(variable.getModifierList())), createSureCallOnlyForChain(variable.getInitializer(), variable.getType()) ); } diff --git a/src/org/jetbrains/jet/j2k/visitors/StatementVisitor.java b/src/org/jetbrains/jet/j2k/visitors/StatementVisitor.java index 57d9069cf57..1d7e4d369d7 100644 --- a/src/org/jetbrains/jet/j2k/visitors/StatementVisitor.java +++ b/src/org/jetbrains/jet/j2k/visitors/StatementVisitor.java @@ -1,9 +1,7 @@ package org.jetbrains.jet.j2k.visitors; import com.intellij.psi.*; -import com.intellij.psi.search.searches.ReferencesSearch; import com.intellij.psi.tree.IElementType; -import com.intellij.psi.util.PsiUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.j2k.Converter; @@ -15,6 +13,7 @@ import java.util.LinkedList; import java.util.List; import static org.jetbrains.jet.j2k.Converter.*; +import static org.jetbrains.jet.j2k.ConverterUtil.countWritingAccesses; /** * @author ignatov @@ -110,9 +109,16 @@ public class StatementVisitor extends ElementVisitor { final PsiStatement initialization = statement.getInitialization(); final PsiStatement update = statement.getUpdate(); final PsiExpression condition = statement.getCondition(); + final PsiStatement body = statement.getBody(); + final PsiLocalVariable firstChild = initialization != null && initialization.getFirstChild() instanceof PsiLocalVariable ? (PsiLocalVariable) initialization.getFirstChild() : null; + int bodyWriteCount = countWritingAccesses(firstChild, body); + int conditionWriteCount = countWritingAccesses(firstChild, condition); + int updateWriteCount = countWritingAccesses(firstChild, update); + boolean onceWritableIterator = updateWriteCount == 1 && bodyWriteCount + conditionWriteCount == 0; + final IElementType operationTokenType = condition != null && condition instanceof PsiBinaryExpression ? ((PsiBinaryExpression) condition).getOperationTokenType() : null; if ( @@ -130,7 +136,7 @@ public class StatementVisitor extends ElementVisitor { && initialization.getFirstChild() instanceof PsiLocalVariable && firstChild != null && firstChild.getNameIdentifier() != null - && isOnceWritableIterator(firstChild) + && onceWritableIterator ) { final Expression end = expressionToExpression(((PsiBinaryExpression) condition).getROperand()); final Expression endExpression = operationTokenType == JavaTokenType.LT ? @@ -140,7 +146,7 @@ public class StatementVisitor extends ElementVisitor { new IdentifierImpl(firstChild.getName()), expressionToExpression(firstChild.getInitializer()), endExpression, - statementToStatement(statement.getBody()) + statementToStatement(body) ); } else { // common case: while loop instead of for loop List forStatements = new LinkedList(); @@ -148,23 +154,12 @@ public class StatementVisitor extends ElementVisitor { forStatements.add(new WhileStatement( expressionToExpression(condition), new Block( - Arrays.asList(statementToStatement(statement.getBody()), + Arrays.asList(statementToStatement(body), new Block(Arrays.asList(statementToStatement(update))))))); myResult = new Block(forStatements); } } - private static boolean isOnceWritableIterator(@Nullable PsiLocalVariable firstChild) { - int counter = 0; - if (firstChild != null) - for (PsiReference r : (ReferencesSearch.search(firstChild))) { - if (r instanceof PsiExpression) { - if (PsiUtil.isAccessedForWriting((PsiExpression) r)) - counter++; - } - } - return counter == 1; // only increment usage - } @Override public void visitForeachStatement(@NotNull PsiForeachStatement statement) {