diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/compiled/ClassFileStubBuilder.java b/java/java-psi-impl/src/com/intellij/psi/impl/compiled/ClassFileStubBuilder.java index 27151412e95c..ece69327e408 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/compiled/ClassFileStubBuilder.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/compiled/ClassFileStubBuilder.java @@ -38,7 +38,7 @@ import static com.intellij.psi.compiled.ClassFileDecompilers.Full; public class ClassFileStubBuilder implements BinaryFileStubBuilder { private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.compiled.ClassFileStubBuilder"); - public static final int STUB_VERSION = 14; + public static final int STUB_VERSION = 15; @Override public boolean acceptsFile(@NotNull VirtualFile file) { diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/compiled/StubBuildingVisitor.java b/java/java-psi-impl/src/com/intellij/psi/impl/compiled/StubBuildingVisitor.java index 868b71ece175..54b6475f2409 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/compiled/StubBuildingVisitor.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/compiled/StubBuildingVisitor.java @@ -43,6 +43,7 @@ import java.text.CharacterIterator; import java.text.StringCharacterIterator; import java.util.List; import java.util.Map; +import java.util.Set; import static com.intellij.openapi.util.Pair.pair; import static com.intellij.psi.CommonClassNames.JAVA_LANG_ANNOTATION_ANNOTATION; @@ -400,7 +401,7 @@ public class StubBuildingVisitor extends ClassVisitor { int localVarIgnoreCount = isStatic ? 0 : isEnumConstructor ? 3 : 1; int paramIgnoreCount = isEnumConstructor ? 2 : isInnerClassConstructor ? 1 : 0; - return new ParameterAnnotationCollectingVisitor(stub, modList, localVarIgnoreCount, paramIgnoreCount, paramCount, paramStubs, myMapping); + return new MethodAnnotationCollectingVisitor(stub, modList, localVarIgnoreCount, paramIgnoreCount, paramCount, paramStubs, myMapping); } private MethodInfo parseMethodSignature(String signature, String[] exceptions) throws ClsFormatException { @@ -558,6 +559,7 @@ public class StubBuildingVisitor extends ClassVisitor { private static class FieldAnnotationCollectingVisitor extends FieldVisitor { private final PsiModifierListStub myModList; private final Function myMapping; + private Set myFilter = null; private FieldAnnotationCollectingVisitor(PsiModifierListStub modList, Function mapping) { super(ASM_API); @@ -570,13 +572,27 @@ public class StubBuildingVisitor extends ClassVisitor { return new AnnotationTextCollector(desc, myMapping, new Consumer() { @Override public void consume(String text) { + if (myFilter == null) myFilter = ContainerUtil.newTroveSet(); + myFilter.add(text); new PsiAnnotationStubImpl(myModList, text); } }); } + + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, final TypePath typePath, String desc, boolean visible) { + return new AnnotationTextCollector(desc, myMapping, new Consumer() { + @Override + public void consume(String text) { + if (typePath == null && (myFilter == null || !myFilter.contains(text))) { + new PsiAnnotationStubImpl(myModList, text); + } + } + }); + } } - private static class ParameterAnnotationCollectingVisitor extends MethodVisitor { + private static class MethodAnnotationCollectingVisitor extends MethodVisitor { private final PsiMethodStub myOwner; private final PsiModifierListStub myModList; private final int myIgnoreCount; @@ -586,14 +602,15 @@ public class StubBuildingVisitor extends ClassVisitor { private final Function myMapping; private int myUsedParamSize = 0; private int myUsedParamCount = 0; + private List> myFilters; - private ParameterAnnotationCollectingVisitor(PsiMethodStub owner, - PsiModifierListStub modList, - int ignoreCount, - int paramIgnoreCount, - int paramCount, - PsiParameterStubImpl[] paramStubs, - Function mapping) { + private MethodAnnotationCollectingVisitor(PsiMethodStub owner, + PsiModifierListStub modList, + int ignoreCount, + int paramIgnoreCount, + int paramCount, + PsiParameterStubImpl[] paramStubs, + Function mapping) { super(ASM_API); myOwner = owner; myModList = modList; @@ -609,11 +626,44 @@ public class StubBuildingVisitor extends ClassVisitor { return new AnnotationTextCollector(desc, myMapping, new Consumer() { @Override public void consume(String text) { + filter(0, text); new PsiAnnotationStubImpl(myModList, text); } }); } + @Override + @Nullable + public AnnotationVisitor visitParameterAnnotation(final int parameter, String desc, boolean visible) { + return parameter < myParamIgnoreCount ? null : new AnnotationTextCollector(desc, myMapping, new Consumer() { + @Override + public void consume(String text) { + int idx = parameter - myParamIgnoreCount; + filter(idx + 1, text); + new PsiAnnotationStubImpl(myOwner.findParameter(idx).getModList(), text); + } + }); + } + + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, final TypePath typePath, String desc, boolean visible) { + final TypeReference ref = new TypeReference(typeRef); + return new AnnotationTextCollector(desc, myMapping, new Consumer() { + @Override + public void consume(String text) { + if (ref.getSort() == TypeReference.METHOD_RETURN && typePath == null && !filtered(0, text)) { + new PsiAnnotationStubImpl(myModList, text); + } + else if (ref.getSort() == TypeReference.METHOD_FORMAL_PARAMETER && typePath == null) { + int idx = ref.getFormalParameterIndex() - myParamIgnoreCount; + if (!filtered(idx + 1, text)) { + new PsiAnnotationStubImpl(myOwner.findParameter(idx).getModList(), text); + } + } + } + }); + } + @Override public AnnotationVisitor visitAnnotationDefault() { return new AnnotationTextCollector(null, myMapping, new Consumer() { @@ -643,15 +693,18 @@ public class StubBuildingVisitor extends ClassVisitor { } } - @Override - @Nullable - public AnnotationVisitor visitParameterAnnotation(final int parameter, String desc, boolean visible) { - return parameter < myParamIgnoreCount ? null : new AnnotationTextCollector(desc, myMapping, new Consumer() { - @Override - public void consume(String text) { - new PsiAnnotationStubImpl(myOwner.findParameter(parameter - myParamIgnoreCount).getModList(), text); - } - }); + private void filter(int index, String text) { + if (myFilters == null) { + myFilters = ContainerUtil.newArrayListWithCapacity(myParamCount + 1); + for (int i = 0; i < myParamCount + 1; i++) myFilters.add(null); + } + Set filter = myFilters.get(index); + if (filter == null) myFilters.set(index, (filter = ContainerUtil.newTroveSet())); + filter.add(text); + } + + private boolean filtered(int index, String text) { + return myFilters != null && myFilters.get(index) != null && myFilters.get(index).contains(text); } } diff --git a/java/java-tests/testData/psi/cls/mirror/TypeAnnotations.txt b/java/java-tests/testData/psi/cls/mirror/TypeAnnotations.txt new file mode 100644 index 000000000000..e02b74ec07c5 --- /dev/null +++ b/java/java-tests/testData/psi/cls/mirror/TypeAnnotations.txt @@ -0,0 +1,29 @@ + + // IntelliJ API Decompiler stub source generated from a class file + // Implementation of methods is not available + +package pkg; + +class TypeAnnotations { + @pkg.TypeAnnotations.TA("field type") + private java.lang.String f1; + @pkg.TypeAnnotations.MixA("field and type") + private java.lang.String f2; + + TypeAnnotations() { /* compiled code */ } + + @pkg.TypeAnnotations.TA("return type") + int m1() { /* compiled code */ } + + void m2(@pkg.TypeAnnotations.TA("parameter") int i) { /* compiled code */ } + + @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.TYPE_USE}) + static @interface MixA { + java.lang.String value(); + } + + @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE_USE}) + static @interface TA { + java.lang.String value(); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/psi/cls/mirror/pkg/TypeAnnotations$MixA.class b/java/java-tests/testData/psi/cls/mirror/pkg/TypeAnnotations$MixA.class new file mode 100644 index 000000000000..2795bce3a3ce Binary files /dev/null and b/java/java-tests/testData/psi/cls/mirror/pkg/TypeAnnotations$MixA.class differ diff --git a/java/java-tests/testData/psi/cls/mirror/pkg/TypeAnnotations$TA.class b/java/java-tests/testData/psi/cls/mirror/pkg/TypeAnnotations$TA.class new file mode 100644 index 000000000000..d02171e1070d Binary files /dev/null and b/java/java-tests/testData/psi/cls/mirror/pkg/TypeAnnotations$TA.class differ diff --git a/java/java-tests/testData/psi/cls/mirror/pkg/TypeAnnotations.class b/java/java-tests/testData/psi/cls/mirror/pkg/TypeAnnotations.class new file mode 100644 index 000000000000..c20fd2a9e0c2 Binary files /dev/null and b/java/java-tests/testData/psi/cls/mirror/pkg/TypeAnnotations.class differ diff --git a/java/java-tests/testData/psi/cls/mirror/src/pkg/TypeAnnotations.java b/java/java-tests/testData/psi/cls/mirror/src/pkg/TypeAnnotations.java new file mode 100644 index 000000000000..9eaf6efd5b7f --- /dev/null +++ b/java/java-tests/testData/psi/cls/mirror/src/pkg/TypeAnnotations.java @@ -0,0 +1,21 @@ +package pkg; + +import java.lang.annotation.*; +import java.util.*; + +class TypeAnnotations { + @Target(ElementType.TYPE_USE) + @interface TA { String value(); } + + @Target({ElementType.FIELD, ElementType.TYPE_USE}) + @interface MixA { String value(); } + + private @TA("field type") String f1; + private @MixA("field and type") String f2; + + @TA("return type") int m1() { + return 42; + } + + void m2(@TA("parameter") int i) { } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/psi/ClsMirrorBuildingTest.java b/java/java-tests/testSrc/com/intellij/psi/ClsMirrorBuildingTest.java index 183c12d7253c..cc8c103cc311 100644 --- a/java/java-tests/testSrc/com/intellij/psi/ClsMirrorBuildingTest.java +++ b/java/java-tests/testSrc/com/intellij/psi/ClsMirrorBuildingTest.java @@ -64,6 +64,7 @@ public class ClsMirrorBuildingTest extends LightIdeaTestCase { public void testBounds() { doTest(); } public void testGrEnum() { doTest(); } public void testSuspiciousParameterNames() { doTest(); } + public void testTypeAnnotations() { doTest(); } public void testTextPsiMismatch() { CommonCodeStyleSettings.IndentOptions options = diff --git a/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/ClassWriter.java b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/ClassWriter.java index cded67e52a38..23867987c30c 100644 --- a/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/ClassWriter.java +++ b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/ClassWriter.java @@ -23,10 +23,7 @@ import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; import org.jetbrains.java.decompiler.main.rels.ClassWrapper; import org.jetbrains.java.decompiler.main.rels.MethodWrapper; import org.jetbrains.java.decompiler.modules.decompiler.ExprProcessor; -import org.jetbrains.java.decompiler.modules.decompiler.exps.AnnotationExprent; -import org.jetbrains.java.decompiler.modules.decompiler.exps.ConstExprent; -import org.jetbrains.java.decompiler.modules.decompiler.exps.Exprent; -import org.jetbrains.java.decompiler.modules.decompiler.exps.NewExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.*; import org.jetbrains.java.decompiler.modules.decompiler.stats.RootStatement; import org.jetbrains.java.decompiler.modules.decompiler.vars.VarTypeProcessor; import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionPair; @@ -43,9 +40,7 @@ import org.jetbrains.java.decompiler.struct.gen.VarType; import org.jetbrains.java.decompiler.struct.gen.generics.*; import org.jetbrains.java.decompiler.util.InterpreterUtil; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; public class ClassWriter { private final PoolInterceptor interceptor; @@ -311,7 +306,7 @@ public class ClassWriter { appendComment(buffer, "synthetic class", indent); } - appendAnnotations(buffer, cl, indent); + appendAnnotations(buffer, indent, cl, -1); buffer.appendIndent(indent); @@ -407,7 +402,7 @@ public class ClassWriter { appendComment(buffer, "synthetic field", indent); } - appendAnnotations(buffer, fd, indent); + appendAnnotations(buffer, indent, fd, TypeAnnotation.FIELD); buffer.appendIndent(indent); @@ -630,7 +625,7 @@ public class ClassWriter { appendComment(buffer, "bridge method", indent); } - appendAnnotations(buffer, mt, indent); + appendAnnotations(buffer, indent, mt, TypeAnnotation.METHOD_RETURN_TYPE); buffer.appendIndent(indent); @@ -816,7 +811,7 @@ public class ClassWriter { StructAnnDefaultAttribute attr = (StructAnnDefaultAttribute)mt.getAttributes().getWithKey("AnnotationDefault"); if (attr != null) { buffer.append(" default "); - buffer.append(attr.getDefaultValue().toJava(0, new BytecodeMappingTracer())); // dummy tracer + buffer.append(attr.getDefaultValue().toJava(0, BytecodeMappingTracer.DUMMY)); } } @@ -951,26 +946,30 @@ public class ClassWriter { private static final String[] ANNOTATION_ATTRIBUTES = { StructGeneralAttribute.ATTRIBUTE_RUNTIME_VISIBLE_ANNOTATIONS, StructGeneralAttribute.ATTRIBUTE_RUNTIME_INVISIBLE_ANNOTATIONS}; + private static final String[] PARAMETER_ANNOTATION_ATTRIBUTES = { + StructGeneralAttribute.ATTRIBUTE_RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS, StructGeneralAttribute.ATTRIBUTE_RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS}; + private static final String[] TYPE_ANNOTATION_ATTRIBUTES = { + StructGeneralAttribute.ATTRIBUTE_RUNTIME_VISIBLE_TYPE_ANNOTATIONS, StructGeneralAttribute.ATTRIBUTE_RUNTIME_INVISIBLE_TYPE_ANNOTATIONS}; - private static void appendAnnotations(TextBuffer buffer, StructMember mb, int indent) { - BytecodeMappingTracer tracer_dummy = new BytecodeMappingTracer(); // FIXME: replace with a real one + private static void appendAnnotations(TextBuffer buffer, int indent, StructMember mb, int targetType) { + Set filter = new HashSet<>(); for (String name : ANNOTATION_ATTRIBUTES) { StructAnnotationAttribute attribute = (StructAnnotationAttribute)mb.getAttributes().getWithKey(name); if (attribute != null) { for (AnnotationExprent annotation : attribute.getAnnotations()) { - buffer.append(annotation.toJava(indent, tracer_dummy)).appendLineSeparator(); + String text = annotation.toJava(indent, BytecodeMappingTracer.DUMMY).toString(); + filter.add(text); + buffer.append(text).appendLineSeparator(); } } } + + appendTypeAnnotations(buffer, indent, mb, targetType, -1, filter); } - private static final String[] PARAMETER_ANNOTATION_ATTRIBUTES = { - StructGeneralAttribute.ATTRIBUTE_RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS, - StructGeneralAttribute.ATTRIBUTE_RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS}; - private static void appendParameterAnnotations(TextBuffer buffer, StructMethod mt, int param) { - BytecodeMappingTracer tracer_dummy = new BytecodeMappingTracer(); // FIXME: replace with a real one + Set filter = new HashSet<>(); for (String name : PARAMETER_ANNOTATION_ATTRIBUTES) { StructAnnotationParameterAttribute attribute = (StructAnnotationParameterAttribute)mt.getAttributes().getWithKey(name); @@ -978,7 +977,33 @@ public class ClassWriter { List> annotations = attribute.getParamAnnotations(); if (param < annotations.size()) { for (AnnotationExprent annotation : annotations.get(param)) { - buffer.append(annotation.toJava(0, tracer_dummy)).append(' '); + String text = annotation.toJava(-1, BytecodeMappingTracer.DUMMY).toString(); + filter.add(text); + buffer.append(text).append(' '); + } + } + } + } + + appendTypeAnnotations(buffer, -1, mt, TypeAnnotation.METHOD_PARAMETER, param, filter); + } + + private static void appendTypeAnnotations(TextBuffer buffer, int indent, StructMember mb, int targetType, int index, Set filter) { + for (String name : TYPE_ANNOTATION_ATTRIBUTES) { + StructTypeAnnotationAttribute attribute = (StructTypeAnnotationAttribute)mb.getAttributes().getWithKey(name); + if (attribute != null) { + for (TypeAnnotation annotation : attribute.getAnnotations()) { + if (annotation.isTopLevel() && annotation.getTargetType() == targetType && (index < 0 || annotation.getIndex() == index)) { + String text = annotation.getAnnotation().toJava(indent, BytecodeMappingTracer.DUMMY).toString(); + if (!filter.contains(text)) { + buffer.append(text); + if (indent < 0) { + buffer.append(' '); + } + else { + buffer.appendLineSeparator(); + } + } } } } diff --git a/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/modules/decompiler/exps/AnnotationExprent.java b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/modules/decompiler/exps/AnnotationExprent.java index 9b797a99e9e7..c17d921d494b 100644 --- a/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/modules/decompiler/exps/AnnotationExprent.java +++ b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/modules/decompiler/exps/AnnotationExprent.java @@ -47,22 +47,31 @@ public class AnnotationExprent extends Exprent { buffer.append('@'); buffer.append(DecompilerContext.getImportCollector().getShortName(ExprProcessor.buildJavaClassName(className))); - if (!parNames.isEmpty()) { + int type = getAnnotationType(); + + if (type != ANNOTATION_MARKER) { buffer.append('('); - if (getAnnotationType() == ANNOTATION_SINGLE_ELEMENT) { - buffer.append(parValues.get(0).toJava(indent + 1, tracer)); - } - else { - for (int i = 0; i < parNames.size(); i++) { + + boolean oneLiner = type == ANNOTATION_SINGLE_ELEMENT || indent < 0; + + for (int i = 0; i < parNames.size(); i++) { + if (!oneLiner) { buffer.appendLineSeparator().appendIndent(indent + 1); + } + + if (type != ANNOTATION_SINGLE_ELEMENT) { buffer.append(parNames.get(i)); buffer.append(" = "); - buffer.append(parValues.get(i).toJava(0, tracer)); - - if (i < parNames.size() - 1) { - buffer.append(','); - } } + + buffer.append(parValues.get(i).toJava(0, tracer)); + + if (i < parNames.size() - 1) { + buffer.append(','); + } + } + + if (!oneLiner) { buffer.appendLineSeparator().appendIndent(indent); } diff --git a/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/modules/decompiler/exps/TypeAnnotation.java b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/modules/decompiler/exps/TypeAnnotation.java new file mode 100644 index 000000000000..93ab87b3f676 --- /dev/null +++ b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/modules/decompiler/exps/TypeAnnotation.java @@ -0,0 +1,67 @@ +/* + * Copyright 2000-2016 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.java.decompiler.modules.decompiler.exps; + +public class TypeAnnotation { + public static final int CLASS_TYPE_PARAMETER = 0x00; + public static final int METHOD_TYPE_PARAMETER = 0x01; + public static final int SUPER_TYPE_REFERENCE = 0x10; + public static final int CLASS_TYPE_PARAMETER_BOUND = 0x11; + public static final int METHOD_TYPE_PARAMETER_BOUND = 0x12; + public static final int FIELD = 0x13; + public static final int METHOD_RETURN_TYPE = 0x14; + public static final int METHOD_RECEIVER = 0x15; + public static final int METHOD_PARAMETER = 0x16; + public static final int THROWS_REFERENCE = 0x17; + public static final int LOCAL_VARIABLE = 0x40; + public static final int RESOURCE_VARIABLE = 0x41; + public static final int CATCH_CLAUSE = 0x42; + public static final int EXPR_INSTANCEOF = 0x43; + public static final int EXPR_NEW = 0x44; + public static final int EXPR_CONSTRUCTOR_REF = 0x45; + public static final int EXPR_METHOD_REF = 0x46; + public static final int TYPE_ARG_CAST = 0x47; + public static final int TYPE_ARG_CONSTRUCTOR_CALL = 0x48; + public static final int TYPE_ARG_METHOD_CALL = 0x49; + public static final int TYPE_ARG_CONSTRUCTOR_REF = 0x4A; + public static final int TYPE_ARG_METHOD_REF = 0x4B; + + private final int target; + private final byte[] path; + private final AnnotationExprent annotation; + + public TypeAnnotation(int target, byte[] path, AnnotationExprent annotation) { + this.target = target; + this.path = path; + this.annotation = annotation; + } + + public int getTargetType() { + return target >> 24; + } + + public int getIndex() { + return target & 0x0FFFF; + } + + public boolean isTopLevel() { + return path == null; + } + + public AnnotationExprent getAnnotation() { + return annotation; + } +} \ No newline at end of file diff --git a/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/struct/attr/StructAnnotationAttribute.java b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/struct/attr/StructAnnotationAttribute.java index 73af88dad74a..13804279e24a 100644 --- a/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/struct/attr/StructAnnotationAttribute.java +++ b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/struct/attr/StructAnnotationAttribute.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2016 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. @@ -29,7 +29,6 @@ import java.util.Collections; import java.util.List; public class StructAnnotationAttribute extends StructGeneralAttribute { - private List annotations; @Override @@ -143,7 +142,7 @@ public class StructAnnotationAttribute extends StructGeneralAttribute { newType = new VarType(elementType.type, 1, elementType.value); } - NewExprent newExpr = new NewExprent(newType, Collections.emptyList(), null); + NewExprent newExpr = new NewExprent(newType, Collections.emptyList(), null); newExpr.setDirectArrayInit(true); newExpr.setLstArrayElements(elements); return newExpr; @@ -181,4 +180,4 @@ public class StructAnnotationAttribute extends StructGeneralAttribute { public List getAnnotations() { return annotations; } -} +} \ No newline at end of file diff --git a/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/struct/attr/StructAnnotationTypeAttribute.java b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/struct/attr/StructAnnotationTypeAttribute.java deleted file mode 100644 index 59324e35654d..000000000000 --- a/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/struct/attr/StructAnnotationTypeAttribute.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * 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.java.decompiler.struct.attr; - -import org.jetbrains.java.decompiler.modules.decompiler.exps.AnnotationExprent; -import org.jetbrains.java.decompiler.struct.consts.ConstantPool; - -import java.io.DataInputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public class StructAnnotationTypeAttribute extends StructGeneralAttribute { - - private static final int ANNOTATION_TARGET_TYPE_GENERIC_CLASS = 0x00; - private static final int ANNOTATION_TARGET_TYPE_GENERIC_METHOD = 0x01; - private static final int ANNOTATION_TARGET_TYPE_EXTENDS_IMPLEMENTS = 0x10; - private static final int ANNOTATION_TARGET_TYPE_GENERIC_CLASS_BOUND = 0x11; - private static final int ANNOTATION_TARGET_TYPE_GENERIC_METHOD_BOUND = 0x12; - private static final int ANNOTATION_TARGET_TYPE_FIELD = 0x13; - private static final int ANNOTATION_TARGET_TYPE_RETURN = 0x14; - private static final int ANNOTATION_TARGET_TYPE_RECEIVER = 0x15; - private static final int ANNOTATION_TARGET_TYPE_FORMAL = 0x16; - private static final int ANNOTATION_TARGET_TYPE_THROWS = 0x17; - private static final int ANNOTATION_TARGET_TYPE_LOCAL_VARIABLE = 0x40; - private static final int ANNOTATION_TARGET_TYPE_RESOURCE_VARIABLE = 0x41; - private static final int ANNOTATION_TARGET_TYPE_EXCEPTION = 0x42; - private static final int ANNOTATION_TARGET_TYPE_INSTANCEOF = 0x43; - private static final int ANNOTATION_TARGET_TYPE_NEW = 0x44; - private static final int ANNOTATION_TARGET_TYPE_DOUBLE_COLON_NEW = 0x45; - private static final int ANNOTATION_TARGET_TYPE_DOUBLE_COLON_ID = 0x46; - private static final int ANNOTATION_TARGET_TYPE_CAST = 0x47; - private static final int ANNOTATION_TARGET_TYPE_INVOCATION_CONSTRUCTOR = 0x48; - private static final int ANNOTATION_TARGET_TYPE_INVOCATION_METHOD = 0x49; - private static final int ANNOTATION_TARGET_TYPE_GENERIC_DOUBLE_COLON_NEW = 0x4A; - private static final int ANNOTATION_TARGET_TYPE_GENERIC_DOUBLE_COLON_ID = 0x4B; - - private static final int ANNOTATION_TARGET_UNION_TYPE_PARAMETER = 1; - private static final int ANNOTATION_TARGET_UNION_SUPERTYPE = 2; - private static final int ANNOTATION_TARGET_UNION_TYPE_PARAMETER_BOUND = 3; - private static final int ANNOTATION_TARGET_UNION_EMPTY = 4; - private static final int ANNOTATION_TARGET_UNION_FORMAL_PARAMETER = 5; - private static final int ANNOTATION_TARGET_UNION_THROWS = 6; - private static final int ANNOTATION_TARGET_UNION_LOCAL_VAR = 7; - private static final int ANNOTATION_TARGET_UNION_CATCH = 8; - private static final int ANNOTATION_TARGET_UNION_OFFSET = 9; - private static final int ANNOTATION_TARGET_UNION_TYPE_ARGUMENT = 10; - - @SuppressWarnings("FieldCanBeLocal") private List locations; - @SuppressWarnings("FieldCanBeLocal") private List annotations; - - @Override - public void initContent(ConstantPool pool) throws IOException { - DataInputStream data = stream(); - - int len = data.readUnsignedByte(); - if (len > 0) { - locations = new ArrayList(len); - annotations = new ArrayList(len); - for (int i = 0; i < len; i++) { - locations.add(parseAnnotationLocation(data)); - annotations.add(StructAnnotationAttribute.parseAnnotation(data, pool)); - } - } - else { - locations = Collections.emptyList(); - annotations = Collections.emptyList(); - } - } - - private static AnnotationLocation parseAnnotationLocation(DataInputStream data) throws IOException { - AnnotationLocation ann_location = new AnnotationLocation(); - - // target type - ann_location.target_type = data.readUnsignedByte(); - - // target union - switch (ann_location.target_type) { - case ANNOTATION_TARGET_TYPE_GENERIC_CLASS: - case ANNOTATION_TARGET_TYPE_GENERIC_METHOD: - ann_location.target_union = ANNOTATION_TARGET_UNION_TYPE_PARAMETER; - break; - case ANNOTATION_TARGET_TYPE_EXTENDS_IMPLEMENTS: - ann_location.target_union = ANNOTATION_TARGET_UNION_SUPERTYPE; - break; - case ANNOTATION_TARGET_TYPE_GENERIC_CLASS_BOUND: - case ANNOTATION_TARGET_TYPE_GENERIC_METHOD_BOUND: - ann_location.target_union = ANNOTATION_TARGET_UNION_TYPE_PARAMETER_BOUND; - break; - case ANNOTATION_TARGET_TYPE_FIELD: - case ANNOTATION_TARGET_TYPE_RETURN: - case ANNOTATION_TARGET_TYPE_RECEIVER: - ann_location.target_union = ANNOTATION_TARGET_UNION_EMPTY; - break; - case ANNOTATION_TARGET_TYPE_FORMAL: - ann_location.target_union = ANNOTATION_TARGET_UNION_FORMAL_PARAMETER; - break; - case ANNOTATION_TARGET_TYPE_THROWS: - ann_location.target_union = ANNOTATION_TARGET_UNION_THROWS; - break; - case ANNOTATION_TARGET_TYPE_LOCAL_VARIABLE: - case ANNOTATION_TARGET_TYPE_RESOURCE_VARIABLE: - ann_location.target_union = ANNOTATION_TARGET_UNION_LOCAL_VAR; - break; - case ANNOTATION_TARGET_TYPE_EXCEPTION: - ann_location.target_union = ANNOTATION_TARGET_UNION_CATCH; - break; - case ANNOTATION_TARGET_TYPE_INSTANCEOF: - case ANNOTATION_TARGET_TYPE_NEW: - case ANNOTATION_TARGET_TYPE_DOUBLE_COLON_NEW: - case ANNOTATION_TARGET_TYPE_DOUBLE_COLON_ID: - ann_location.target_union = ANNOTATION_TARGET_UNION_OFFSET; - break; - case ANNOTATION_TARGET_TYPE_CAST: - case ANNOTATION_TARGET_TYPE_INVOCATION_CONSTRUCTOR: - case ANNOTATION_TARGET_TYPE_INVOCATION_METHOD: - case ANNOTATION_TARGET_TYPE_GENERIC_DOUBLE_COLON_NEW: - case ANNOTATION_TARGET_TYPE_GENERIC_DOUBLE_COLON_ID: - ann_location.target_union = ANNOTATION_TARGET_UNION_TYPE_ARGUMENT; - break; - default: - throw new RuntimeException("Unknown target type in a type annotation!"); - } - - // target union data - - switch (ann_location.target_union) { - case ANNOTATION_TARGET_UNION_TYPE_PARAMETER: - case ANNOTATION_TARGET_UNION_FORMAL_PARAMETER: - ann_location.data = new int[]{data.readUnsignedByte()}; - break; - case ANNOTATION_TARGET_UNION_SUPERTYPE: - case ANNOTATION_TARGET_UNION_THROWS: - case ANNOTATION_TARGET_UNION_CATCH: - case ANNOTATION_TARGET_UNION_OFFSET: - ann_location.data = new int[]{data.readUnsignedShort()}; - break; - case ANNOTATION_TARGET_UNION_TYPE_PARAMETER_BOUND: - ann_location.data = new int[]{data.readUnsignedByte(), data.readUnsignedByte()}; - break; - case ANNOTATION_TARGET_UNION_EMPTY: - break; - case ANNOTATION_TARGET_UNION_LOCAL_VAR: - int table_length = data.readUnsignedShort(); - - ann_location.data = new int[table_length * 3 + 1]; - ann_location.data[0] = table_length; - - for (int i = 0; i < table_length; ++i) { - ann_location.data[3 * i + 1] = data.readUnsignedShort(); - ann_location.data[3 * i + 2] = data.readUnsignedShort(); - ann_location.data[3 * i + 3] = data.readUnsignedShort(); - } - break; - case ANNOTATION_TARGET_UNION_TYPE_ARGUMENT: - ann_location.data = new int[]{data.readUnsignedShort(), data.readUnsignedByte()}; - } - - // target path - int path_length = data.readUnsignedByte(); - - ann_location.target_path_kind = new int[path_length]; - ann_location.target_argument_index = new int[path_length]; - - for (int i = 0; i < path_length; ++i) { - ann_location.target_path_kind[i] = data.readUnsignedByte(); - ann_location.target_argument_index[i] = data.readUnsignedByte(); - } - - return ann_location; - } - - private static class AnnotationLocation { - public int target_type; - public int target_union; - public int[] data; - public int[] target_path_kind; - public int[] target_argument_index; - } -} diff --git a/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/struct/attr/StructGeneralAttribute.java b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/struct/attr/StructGeneralAttribute.java index dc0ebc6708a2..73cc535b4455 100644 --- a/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/struct/attr/StructGeneralAttribute.java +++ b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/struct/attr/StructGeneralAttribute.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2016 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. @@ -28,7 +28,6 @@ import java.io.IOException; } */ public class StructGeneralAttribute { - public static final String ATTRIBUTE_CODE = "Code"; public static final String ATTRIBUTE_INNER_CLASSES = "InnerClasses"; public static final String ATTRIBUTE_SIGNATURE = "Signature"; @@ -73,17 +72,14 @@ public class StructGeneralAttribute { else if (ATTRIBUTE_ENCLOSING_METHOD.equals(name)) { attr = new StructEnclosingMethodAttribute(); } - else if (ATTRIBUTE_RUNTIME_VISIBLE_ANNOTATIONS.equals(name) || - ATTRIBUTE_RUNTIME_INVISIBLE_ANNOTATIONS.equals(name)) { + else if (ATTRIBUTE_RUNTIME_VISIBLE_ANNOTATIONS.equals(name) || ATTRIBUTE_RUNTIME_INVISIBLE_ANNOTATIONS.equals(name)) { attr = new StructAnnotationAttribute(); } - else if (ATTRIBUTE_RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS.equals(name) || - ATTRIBUTE_RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS.equals(name)) { + else if (ATTRIBUTE_RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS.equals(name) || ATTRIBUTE_RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS.equals(name)) { attr = new StructAnnotationParameterAttribute(); } - else if (ATTRIBUTE_RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(name) || - ATTRIBUTE_RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(name)) { - attr = new StructAnnotationTypeAttribute(); + else if (ATTRIBUTE_RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(name) || ATTRIBUTE_RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(name)) { + attr = new StructTypeAnnotationAttribute(); } else if (ATTRIBUTE_LOCAL_VARIABLE_TABLE.equals(name)) { attr = new StructLocalVariableTableAttribute(); @@ -91,8 +87,7 @@ public class StructGeneralAttribute { else if (ATTRIBUTE_BOOTSTRAP_METHODS.equals(name)) { attr = new StructBootstrapMethodsAttribute(); } - else if (ATTRIBUTE_SYNTHETIC.equals(name) || - ATTRIBUTE_DEPRECATED.equals(name)) { + else if (ATTRIBUTE_SYNTHETIC.equals(name) || ATTRIBUTE_DEPRECATED.equals(name)) { attr = new StructGeneralAttribute(); } else if (ATTRIBUTE_LINE_NUMBER_TABLE.equals(name)) { @@ -123,4 +118,4 @@ public class StructGeneralAttribute { public String getName() { return name; } -} +} \ No newline at end of file diff --git a/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/struct/attr/StructTypeAnnotationAttribute.java b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/struct/attr/StructTypeAnnotationAttribute.java new file mode 100644 index 000000000000..1cb948537472 --- /dev/null +++ b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/struct/attr/StructTypeAnnotationAttribute.java @@ -0,0 +1,107 @@ +/* + * Copyright 2000-2016 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.java.decompiler.struct.attr; + +import org.jetbrains.java.decompiler.modules.decompiler.exps.AnnotationExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.TypeAnnotation; +import org.jetbrains.java.decompiler.struct.consts.ConstantPool; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class StructTypeAnnotationAttribute extends StructGeneralAttribute { + private List annotations = Collections.emptyList(); + + @Override + public void initContent(ConstantPool pool) throws IOException { + DataInputStream data = stream(); + + int len = data.readUnsignedShort(); + if (len > 0) { + annotations = new ArrayList<>(len); + for (int i = 0; i < len; i++) { + annotations.add(parse(data, pool)); + } + } + else { + annotations = Collections.emptyList(); + } + } + + private static TypeAnnotation parse(DataInputStream data, ConstantPool pool) throws IOException { + int targetType = data.readUnsignedByte(); + int target = targetType << 24; + + switch (targetType) { + case TypeAnnotation.CLASS_TYPE_PARAMETER: + case TypeAnnotation.METHOD_TYPE_PARAMETER: + case TypeAnnotation.METHOD_PARAMETER: + target |= data.readUnsignedByte(); + break; + + case TypeAnnotation.SUPER_TYPE_REFERENCE: + case TypeAnnotation.CLASS_TYPE_PARAMETER_BOUND: + case TypeAnnotation.METHOD_TYPE_PARAMETER_BOUND: + case TypeAnnotation.THROWS_REFERENCE: + case TypeAnnotation.CATCH_CLAUSE: + case TypeAnnotation.EXPR_INSTANCEOF: + case TypeAnnotation.EXPR_NEW: + case TypeAnnotation.EXPR_CONSTRUCTOR_REF: + case TypeAnnotation.EXPR_METHOD_REF: + target |= data.readUnsignedShort(); + break; + + case TypeAnnotation.TYPE_ARG_CAST: + case TypeAnnotation.TYPE_ARG_CONSTRUCTOR_CALL: + case TypeAnnotation.TYPE_ARG_METHOD_CALL: + case TypeAnnotation.TYPE_ARG_CONSTRUCTOR_REF: + case TypeAnnotation.TYPE_ARG_METHOD_REF: + data.skipBytes(3); + break; + + case TypeAnnotation.LOCAL_VARIABLE: + case TypeAnnotation.RESOURCE_VARIABLE: + data.skipBytes(data.readUnsignedShort() * 6); + break; + + case TypeAnnotation.FIELD: + case TypeAnnotation.METHOD_RETURN_TYPE: + case TypeAnnotation.METHOD_RECEIVER: + break; + + default: + throw new RuntimeException("unknown target type: " + targetType); + } + + int pathLength = data.readUnsignedByte(); + byte[] path = null; + if (pathLength > 0) { + path = new byte[2 * pathLength]; + data.readFully(path); + } + + AnnotationExprent annotation = StructAnnotationAttribute.parseAnnotation(data, pool); + + return new TypeAnnotation(target, path, annotation); + } + + public List getAnnotations() { + return annotations; + } +} \ No newline at end of file diff --git a/plugins/java-decompiler/engine/test/org/jetbrains/java/decompiler/SingleClassesTest.java b/plugins/java-decompiler/engine/test/org/jetbrains/java/decompiler/SingleClassesTest.java index 84434a932a44..3d142166edee 100644 --- a/plugins/java-decompiler/engine/test/org/jetbrains/java/decompiler/SingleClassesTest.java +++ b/plugins/java-decompiler/engine/test/org/jetbrains/java/decompiler/SingleClassesTest.java @@ -84,6 +84,7 @@ public class SingleClassesTest { @Test public void testMethodReferenceLetterClass() { doTest("pkg/TestMethodReferenceLetterClass"); } @Test public void testMemberAnnotations() { doTest("pkg/TestMemberAnnotations"); } @Test public void testMoreAnnotations() { doTest("pkg/MoreAnnotations"); } + @Test public void testTypeAnnotations() { doTest("pkg/TypeAnnotations"); } @Test public void testStaticNameClash() { doTest("pkg/TestStaticNameClash"); } @Test public void testExtendingSubclass() { doTest("pkg/TestExtendingSubclass"); } @Test public void testSyntheticAccess() { doTest("pkg/TestSyntheticAccess"); } diff --git a/plugins/java-decompiler/engine/testData/classes/pkg/TypeAnnotations$MixA.class b/plugins/java-decompiler/engine/testData/classes/pkg/TypeAnnotations$MixA.class new file mode 100644 index 000000000000..2795bce3a3ce Binary files /dev/null and b/plugins/java-decompiler/engine/testData/classes/pkg/TypeAnnotations$MixA.class differ diff --git a/plugins/java-decompiler/engine/testData/classes/pkg/TypeAnnotations$TA.class b/plugins/java-decompiler/engine/testData/classes/pkg/TypeAnnotations$TA.class new file mode 100644 index 000000000000..d02171e1070d Binary files /dev/null and b/plugins/java-decompiler/engine/testData/classes/pkg/TypeAnnotations$TA.class differ diff --git a/plugins/java-decompiler/engine/testData/classes/pkg/TypeAnnotations.class b/plugins/java-decompiler/engine/testData/classes/pkg/TypeAnnotations.class new file mode 100644 index 000000000000..0e8e34a2abe9 Binary files /dev/null and b/plugins/java-decompiler/engine/testData/classes/pkg/TypeAnnotations.class differ diff --git a/plugins/java-decompiler/engine/testData/results/TypeAnnotations.dec b/plugins/java-decompiler/engine/testData/results/TypeAnnotations.dec new file mode 100644 index 000000000000..590d89477cde --- /dev/null +++ b/plugins/java-decompiler/engine/testData/results/TypeAnnotations.dec @@ -0,0 +1,44 @@ +package pkg; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +class TypeAnnotations { + @TypeAnnotations.TA("field type") + private String f1; + @TypeAnnotations.MixA("field and type") + private String f2; + + @TypeAnnotations.TA("return type") + int m1() { + return 42;// 18 + } + + void m2(@TypeAnnotations.TA("parameter") int var1) { + }// 21 + + @Target({ElementType.FIELD, ElementType.TYPE_USE}) + @interface MixA { + String value(); + } + + @Target({ElementType.TYPE_USE}) + @interface TA { + String value(); + } +} + +class 'pkg/TypeAnnotations' { + method 'm1 ()I' { + 0 13 + 2 13 + } + + method 'm2 (I)V' { + 0 17 + } +} + +Lines mapping: +18 <-> 14 +21 <-> 18 diff --git a/plugins/java-decompiler/engine/testData/src/pkg/TypeAnnotations.java b/plugins/java-decompiler/engine/testData/src/pkg/TypeAnnotations.java new file mode 100644 index 000000000000..0760dded2864 --- /dev/null +++ b/plugins/java-decompiler/engine/testData/src/pkg/TypeAnnotations.java @@ -0,0 +1,22 @@ +package pkg; + +import java.lang.annotation.*; +import java.util.*; + +class TypeAnnotations { + @Target(ElementType.TYPE_USE) + @interface TA { String value(); } + + @Target({ElementType.FIELD, ElementType.TYPE_USE}) + @interface MixA { String value(); } + + private @TA("field type") String f1; + + private @MixA("field and type") String f2; + + @TA("return type") int m1() { + return 42; + } + + void m2(@TA("parameter") int i) { } +} \ No newline at end of file