001/*
002 * Copyright 2010-2013 JetBrains s.r.o.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.jetbrains.jet.lang.resolve.java;
018
019import com.google.common.collect.Lists;
020import com.intellij.openapi.util.text.StringUtil;
021import org.jetbrains.annotations.NotNull;
022import org.jetbrains.asm4.Type;
023import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
024import org.jetbrains.jet.lang.descriptors.ClassifierDescriptor;
025import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
026import org.jetbrains.jet.lang.resolve.DescriptorUtils;
027import org.jetbrains.jet.lang.resolve.name.FqName;
028
029import java.util.List;
030
031public class JvmClassName {
032
033    @NotNull
034    public static JvmClassName byInternalName(@NotNull String internalName) {
035        return new JvmClassName(internalName);
036    }
037
038    @NotNull
039    public static JvmClassName byType(@NotNull Type type) {
040        if (type.getSort() != Type.OBJECT) {
041            throw new IllegalArgumentException("Type is not convertible to " + JvmClassName.class.getSimpleName() + ": " + type);
042        }
043        return byInternalName(type.getInternalName());
044    }
045
046    /**
047     * WARNING: fq name cannot be uniquely mapped to JVM class name.
048     */
049    @NotNull
050    public static JvmClassName byFqNameWithoutInnerClasses(@NotNull FqName fqName) {
051        JvmClassName r = new JvmClassName(fqNameToInternalName(fqName));
052        r.fqName = fqName;
053        return r;
054    }
055
056    @NotNull
057    public static JvmClassName byFqNameWithoutInnerClasses(@NotNull String fqName) {
058        return byFqNameWithoutInnerClasses(new FqName(fqName));
059    }
060
061    @NotNull
062    public static JvmClassName bySignatureName(@NotNull String signatureName) {
063        JvmClassName className = new JvmClassName(signatureNameToInternalName(signatureName));
064        className.signatureName = signatureName;
065        return className;
066    }
067
068    private static String encodeSpecialNames(String str) {
069        String encodedObjectNames = StringUtil.replace(str, JvmAbi.CLASS_OBJECT_CLASS_NAME, CLASS_OBJECT_REPLACE_GUARD);
070        return StringUtil.replace(encodedObjectNames, JvmAbi.TRAIT_IMPL_CLASS_NAME, TRAIT_IMPL_REPLACE_GUARD);
071    }
072
073    private static String decodeSpecialNames(String str) {
074        String decodedObjectNames = StringUtil.replace(str, CLASS_OBJECT_REPLACE_GUARD, JvmAbi.CLASS_OBJECT_CLASS_NAME);
075        return StringUtil.replace(decodedObjectNames, TRAIT_IMPL_REPLACE_GUARD, JvmAbi.TRAIT_IMPL_CLASS_NAME);
076    }
077
078    @NotNull
079    private static JvmClassName byFqNameAndInnerClassList(@NotNull FqName fqName, @NotNull List<String> innerClassList) {
080        String outerClassName = fqNameToInternalName(fqName);
081        StringBuilder sb = new StringBuilder(outerClassName);
082        for (String innerClassName : innerClassList) {
083            sb.append("$").append(innerClassName);
084        }
085        return new JvmClassName(sb.toString());
086    }
087
088    @NotNull
089    public static JvmClassName byClassDescriptor(@NotNull ClassifierDescriptor classDescriptor) {
090        DeclarationDescriptor descriptor = classDescriptor;
091
092        List<String> innerClassNames = Lists.newArrayList();
093        while (descriptor.getContainingDeclaration() instanceof ClassDescriptor) {
094            innerClassNames.add(descriptor.getName().asString());
095            descriptor = descriptor.getContainingDeclaration();
096            assert descriptor != null;
097        }
098
099        return byFqNameAndInnerClassList(DescriptorUtils.getFQName(descriptor).toSafe(), innerClassNames);
100    }
101
102    @NotNull
103    private static String fqNameToInternalName(@NotNull FqName fqName) {
104        return fqName.asString().replace('.', '/');
105    }
106
107    @NotNull
108    private static String signatureNameToInternalName(@NotNull String signatureName) {
109        return signatureName.replace('.', '$');
110    }
111
112    @NotNull
113    private static String internalNameToFqName(@NotNull String name) {
114        return decodeSpecialNames(encodeSpecialNames(name).replace('$', '.').replace('/', '.'));
115    }
116
117    @NotNull
118    private static String internalNameToSignatureName(@NotNull String name) {
119        return decodeSpecialNames(encodeSpecialNames(name).replace('$', '.'));
120    }
121
122    @NotNull
123    private static String signatureNameToFqName(@NotNull String name) {
124        return name.replace('/', '.');
125    }
126
127
128    private final static String CLASS_OBJECT_REPLACE_GUARD = "<class_object>";
129    private final static String TRAIT_IMPL_REPLACE_GUARD = "<trait_impl>";
130
131    // Internal name:  jet/Map$Entry
132    // FqName:         jet.Map.Entry
133    // Signature name: jet/Map.Entry
134
135    private final String internalName;
136    private FqName fqName;
137    private String descriptor;
138    private String signatureName;
139
140    private Type asmType;
141
142    private JvmClassName(@NotNull String internalName) {
143        this.internalName = internalName;
144    }
145
146    @NotNull
147    public FqName getFqName() {
148        if (fqName == null) {
149            this.fqName = new FqName(internalNameToFqName(internalName));
150        }
151        return fqName;
152    }
153
154    @NotNull
155    public String getInternalName() {
156        return internalName;
157    }
158
159    @NotNull
160    public String getDescriptor() {
161        if (descriptor == null) {
162            StringBuilder sb = new StringBuilder(internalName.length() + 2);
163            sb.append('L');
164            sb.append(internalName);
165            sb.append(';');
166            descriptor = sb.toString();
167        }
168        return descriptor;
169    }
170
171    @NotNull
172    public Type getAsmType() {
173        if (asmType == null) {
174            asmType = Type.getType(getDescriptor());
175        }
176        return asmType;
177    }
178
179    @NotNull
180    public String getSignatureName() {
181        if (signatureName == null) {
182            signatureName = internalNameToSignatureName(internalName);
183        }
184        return signatureName;
185    }
186
187    @NotNull
188    public FqName getOuterClassFqName() {
189        String signatureName = getSignatureName();
190        int index = signatureName.indexOf('.');
191        String outerClassName = index != -1 ? signatureName.substring(0, index) : signatureName;
192        return new FqName(signatureNameToFqName(outerClassName));
193    }
194
195    @NotNull
196    public List<String> getInnerClassNameList() {
197        List<String> innerClassList = Lists.newArrayList();
198        String signatureName = getSignatureName();
199        int index = signatureName.indexOf('.');
200        while (index != -1) {
201            int nextIndex = signatureName.indexOf('.', index + 1);
202            String innerClassName = nextIndex != -1 ? signatureName.substring(index + 1, nextIndex) : signatureName.substring(index + 1);
203            innerClassList.add(innerClassName);
204            index = nextIndex;
205        }
206        return innerClassList;
207    }
208
209    @Override
210    public String toString() {
211        return getInternalName();
212    }
213
214    @Override
215    public boolean equals(Object o) {
216        // generated by Idea
217        if (this == o) return true;
218        if (o == null || getClass() != o.getClass()) return false;
219
220        JvmClassName name = (JvmClassName) o;
221
222        if (!internalName.equals(name.internalName)) return false;
223
224        return true;
225    }
226
227    @Override
228    public int hashCode() {
229        // generated by Idea
230        return internalName.hashCode();
231    }
232}