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    
017    package org.jetbrains.jet.lang.resolve.java;
018    
019    import com.intellij.openapi.util.text.StringUtil;
020    import org.jetbrains.annotations.NotNull;
021    import org.jetbrains.asm4.Type;
022    import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
023    import org.jetbrains.jet.lang.descriptors.ClassifierDescriptor;
024    import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
025    import org.jetbrains.jet.lang.resolve.DescriptorUtils;
026    import org.jetbrains.jet.lang.resolve.name.FqName;
027    
028    import java.util.ArrayList;
029    import java.util.List;
030    
031    public 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 byClass(@NotNull Class<?> klass) {
063            return byFqNameWithoutInnerClasses(new FqName(klass.getCanonicalName()));
064        }
065    
066        @NotNull
067        public static JvmClassName bySignatureName(@NotNull String signatureName) {
068            JvmClassName className = new JvmClassName(signatureNameToInternalName(signatureName));
069            className.signatureName = signatureName;
070            return className;
071        }
072    
073        private static String encodeSpecialNames(String str) {
074            String encodedObjectNames = StringUtil.replace(str, JvmAbi.CLASS_OBJECT_CLASS_NAME, CLASS_OBJECT_REPLACE_GUARD);
075            return StringUtil.replace(encodedObjectNames, JvmAbi.TRAIT_IMPL_CLASS_NAME, TRAIT_IMPL_REPLACE_GUARD);
076        }
077    
078        private static String decodeSpecialNames(String str) {
079            String decodedObjectNames = StringUtil.replace(str, CLASS_OBJECT_REPLACE_GUARD, JvmAbi.CLASS_OBJECT_CLASS_NAME);
080            return StringUtil.replace(decodedObjectNames, TRAIT_IMPL_REPLACE_GUARD, JvmAbi.TRAIT_IMPL_CLASS_NAME);
081        }
082    
083        @NotNull
084        private static JvmClassName byFqNameAndInnerClassList(@NotNull FqName fqName, @NotNull List<String> innerClassList) {
085            String outerClassName = fqNameToInternalName(fqName);
086            StringBuilder sb = new StringBuilder(outerClassName);
087            for (String innerClassName : innerClassList) {
088                sb.append("$").append(innerClassName);
089            }
090            return new JvmClassName(sb.toString());
091        }
092    
093        @NotNull
094        public static JvmClassName byClassDescriptor(@NotNull ClassifierDescriptor classDescriptor) {
095            DeclarationDescriptor descriptor = classDescriptor;
096    
097            List<String> innerClassNames = new ArrayList<String>();
098            while (descriptor.getContainingDeclaration() instanceof ClassDescriptor) {
099                innerClassNames.add(descriptor.getName().asString());
100                descriptor = descriptor.getContainingDeclaration();
101                assert descriptor != null;
102            }
103    
104            return byFqNameAndInnerClassList(DescriptorUtils.getFQName(descriptor).toSafe(), innerClassNames);
105        }
106    
107        @NotNull
108        private static String fqNameToInternalName(@NotNull FqName fqName) {
109            return fqName.asString().replace('.', '/');
110        }
111    
112        @NotNull
113        private static String signatureNameToInternalName(@NotNull String signatureName) {
114            return signatureName.replace('.', '$');
115        }
116    
117        @NotNull
118        private static String internalNameToFqName(@NotNull String name) {
119            return decodeSpecialNames(encodeSpecialNames(name).replace('$', '.').replace('/', '.'));
120        }
121    
122        @NotNull
123        private static String internalNameToSignatureName(@NotNull String name) {
124            return decodeSpecialNames(encodeSpecialNames(name).replace('$', '.'));
125        }
126    
127        @NotNull
128        private static String signatureNameToFqName(@NotNull String name) {
129            return name.replace('/', '.');
130        }
131    
132    
133        private final static String CLASS_OBJECT_REPLACE_GUARD = "<class_object>";
134        private final static String TRAIT_IMPL_REPLACE_GUARD = "<trait_impl>";
135    
136        // Internal name:  jet/Map$Entry
137        // FqName:         jet.Map.Entry
138        // Signature name: jet/Map.Entry
139    
140        private final String internalName;
141        private FqName fqName;
142        private String descriptor;
143        private String signatureName;
144    
145        private Type asmType;
146    
147        private JvmClassName(@NotNull String internalName) {
148            this.internalName = internalName;
149        }
150    
151        @NotNull
152        public FqName getFqName() {
153            if (fqName == null) {
154                this.fqName = new FqName(internalNameToFqName(internalName));
155            }
156            return fqName;
157        }
158    
159        @NotNull
160        public String getInternalName() {
161            return internalName;
162        }
163    
164        @NotNull
165        public String getDescriptor() {
166            if (descriptor == null) {
167                StringBuilder sb = new StringBuilder(internalName.length() + 2);
168                sb.append('L');
169                sb.append(internalName);
170                sb.append(';');
171                descriptor = sb.toString();
172            }
173            return descriptor;
174        }
175    
176        @NotNull
177        public Type getAsmType() {
178            if (asmType == null) {
179                asmType = Type.getType(getDescriptor());
180            }
181            return asmType;
182        }
183    
184        @NotNull
185        public String getSignatureName() {
186            if (signatureName == null) {
187                signatureName = internalNameToSignatureName(internalName);
188            }
189            return signatureName;
190        }
191    
192        @NotNull
193        public FqName getOuterClassFqName() {
194            String signatureName = getSignatureName();
195            int index = signatureName.indexOf('.');
196            String outerClassName = index != -1 ? signatureName.substring(0, index) : signatureName;
197            return new FqName(signatureNameToFqName(outerClassName));
198        }
199    
200        @NotNull
201        public List<String> getInnerClassNameList() {
202            List<String> innerClassList = new ArrayList<String>();
203            String signatureName = getSignatureName();
204            int index = signatureName.indexOf('.');
205            while (index != -1) {
206                int nextIndex = signatureName.indexOf('.', index + 1);
207                String innerClassName = nextIndex != -1 ? signatureName.substring(index + 1, nextIndex) : signatureName.substring(index + 1);
208                innerClassList.add(innerClassName);
209                index = nextIndex;
210            }
211            return innerClassList;
212        }
213    
214        @Override
215        public String toString() {
216            return getInternalName();
217        }
218    
219        @Override
220        public boolean equals(Object o) {
221            // generated by Idea
222            if (this == o) return true;
223            if (o == null || getClass() != o.getClass()) return false;
224    
225            JvmClassName name = (JvmClassName) o;
226    
227            if (!internalName.equals(name.internalName)) return false;
228    
229            return true;
230        }
231    
232        @Override
233        public int hashCode() {
234            // generated by Idea
235            return internalName.hashCode();
236        }
237    }