static flag for top level classes
[fedora-idea.git] / java / java-impl / src / com / intellij / psi / impl / compiled / ClsStubBuilder.java
blob6f4960e8f769bfefb7c3019ffc7d2c541022f70f
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 * @author max
20 package com.intellij.psi.impl.compiled;
22 import com.intellij.lexer.JavaLexer;
23 import com.intellij.openapi.extensions.Extensions;
24 import com.intellij.openapi.util.Comparing;
25 import com.intellij.openapi.util.text.StringUtil;
26 import com.intellij.openapi.vfs.VirtualFile;
27 import com.intellij.pom.java.LanguageLevel;
28 import com.intellij.psi.JavaTokenType;
29 import com.intellij.psi.PsiNameHelper;
30 import com.intellij.psi.PsiReferenceList;
31 import com.intellij.psi.impl.cache.ModifierFlags;
32 import com.intellij.psi.impl.cache.TypeInfo;
33 import com.intellij.psi.impl.java.stubs.*;
34 import com.intellij.psi.impl.java.stubs.impl.*;
35 import com.intellij.psi.stubs.PsiFileStub;
36 import com.intellij.psi.stubs.StubElement;
37 import com.intellij.util.ArrayUtil;
38 import com.intellij.util.cls.ClsFormatException;
39 import com.intellij.util.io.StringRef;
40 import org.jetbrains.annotations.NonNls;
41 import org.jetbrains.annotations.NotNull;
42 import org.jetbrains.annotations.Nullable;
43 import org.objectweb.asm.*;
44 import org.objectweb.asm.commons.EmptyVisitor;
46 import java.io.IOException;
47 import java.text.CharacterIterator;
48 import java.text.StringCharacterIterator;
49 import java.util.ArrayList;
50 import java.util.Collections;
51 import java.util.List;
53 @SuppressWarnings({"HardCodedStringLiteral"})
54 public class ClsStubBuilder {
55 private ClsStubBuilder() {
58 @Nullable
59 public static PsiFileStub build(final VirtualFile vFile, byte[] bytes) throws ClsFormatException {
60 final ClsStubBuilderFactory[] factories = Extensions.getExtensions(ClsStubBuilderFactory.EP_NAME);
61 for (ClsStubBuilderFactory factory : factories) {
62 if (factory.canBeProcessed(vFile, bytes)) {
63 return factory.buildFileStub(vFile, bytes);
67 final PsiJavaFileStubImpl file = new PsiJavaFileStubImpl("dont.know.yet", true);
68 try {
69 final PsiClassStub result = buildClass(vFile, bytes, file, Opcodes.ACC_STATIC);
70 if (result == null) return null;
72 file.setPackageName(getPackageName(result));
74 catch (Exception e) {
75 throw new ClsFormatException();
77 return file;
80 private static PsiClassStub<?> buildClass(final VirtualFile vFile, final byte[] bytes, final StubElement parent, final int access) {
81 ClassReader reader = new ClassReader(bytes);
83 final MyClassVisitor classVisitor = new MyClassVisitor(vFile, parent, access);
84 reader.accept(classVisitor, 0);
85 return classVisitor.getResult();
88 private static String getPackageName(final PsiClassStub result) {
89 final String fqn = result.getQualifiedName();
90 final String shortName = result.getName();
91 if (fqn == null || Comparing.equal(shortName, fqn)) {
92 return "";
95 return fqn.substring(0, fqn.lastIndexOf('.'));
98 private static class MyClassVisitor implements ClassVisitor {
99 private final StubElement myParent;
100 private final int myAccess;
101 private final VirtualFile myVFile;
102 private PsiModifierListStub myModlist;
103 private PsiClassStub myResult;
104 @NonNls private static final String SYNTHETIC_CLINIT_METHOD = "<clinit>";
105 @NonNls private static final String SYNTHETIC_INIT_METHOD = "<init>";
106 private JavaLexer myLexer;
108 private MyClassVisitor(final VirtualFile vFile, final StubElement parent, final int access) {
109 myVFile = vFile;
110 myParent = parent;
111 myAccess = access;
114 public PsiClassStub<?> getResult() {
115 return myResult;
118 public void visit(final int version,
119 final int access,
120 final String name,
121 final String signature,
122 final String superName,
123 final String[] interfaces) {
124 String fqn = getClassName(name);
126 final String shortName = PsiNameHelper.getShortClassName(fqn);
128 final int flags = myAccess | access;
130 boolean isDeprecated = (flags & Opcodes.ACC_DEPRECATED) != 0;
131 boolean isInterface = (flags & Opcodes.ACC_INTERFACE) != 0;
132 boolean isEnum = (flags & Opcodes.ACC_ENUM) != 0;
133 boolean isAnnotationType = (flags & Opcodes.ACC_ANNOTATION) != 0;
135 final byte stubFlags = PsiClassStubImpl.packFlags(isDeprecated, isInterface, isEnum, false, false, isAnnotationType, false, false);
137 myResult = new PsiClassStubImpl(JavaStubElementTypes.CLASS, myParent, fqn, shortName, null, stubFlags);
139 LanguageLevel languageLevel = convertFromVersion(version);
140 myLexer = new JavaLexer(languageLevel);
142 ((PsiClassStubImpl)myResult).setLanguageLevel(languageLevel);
143 myModlist = new PsiModifierListStubImpl(myResult, packModlistFlags(flags));
145 CharacterIterator signatureIterator = signature != null ? new StringCharacterIterator(signature) : null;
146 if (signatureIterator != null) {
147 try {
148 SignatureParsing.parseTypeParametersDeclaration(signatureIterator, myResult);
150 catch (ClsFormatException e) {
151 signatureIterator = null;
153 } else {
154 new PsiTypeParameterListStubImpl(myResult);
157 String convertedSuper;
158 List<String> convertedInterfaces = new ArrayList<String>();
159 if (signatureIterator == null) {
160 convertedSuper = parseClassDescription(superName, interfaces, convertedInterfaces);
161 } else {
162 try {
163 convertedSuper = parseClassSignature(signatureIterator, convertedInterfaces);
165 catch (ClsFormatException e) {
166 new PsiTypeParameterListStubImpl(myResult);
167 convertedSuper = parseClassDescription(superName, interfaces, convertedInterfaces);
171 String[] interfacesArray = ArrayUtil.toStringArray(convertedInterfaces);
172 if (isInterface) {
173 new PsiClassReferenceListStubImpl(JavaStubElementTypes.EXTENDS_LIST, myResult, interfacesArray, PsiReferenceList.Role.EXTENDS_LIST);
174 new PsiClassReferenceListStubImpl(JavaStubElementTypes.IMPLEMENTS_LIST, myResult, ArrayUtil.EMPTY_STRING_ARRAY,
175 PsiReferenceList.Role.IMPLEMENTS_LIST);
176 } else {
177 if (convertedSuper != null && !"java.lang.Object".equals(convertedSuper)) {
178 new PsiClassReferenceListStubImpl(JavaStubElementTypes.EXTENDS_LIST, myResult, new String[]{convertedSuper},
179 PsiReferenceList.Role.EXTENDS_LIST);
180 } else {
181 new PsiClassReferenceListStubImpl(JavaStubElementTypes.EXTENDS_LIST, myResult, ArrayUtil.EMPTY_STRING_ARRAY, PsiReferenceList.Role.EXTENDS_LIST);
183 new PsiClassReferenceListStubImpl(JavaStubElementTypes.IMPLEMENTS_LIST, myResult, interfacesArray,
184 PsiReferenceList.Role.IMPLEMENTS_LIST);
188 @Nullable
189 private static String parseClassDescription(final String superName, final String[] interfaces, final List<String> convertedInterfaces) {
190 final String convertedSuper = superName != null ? getClassName(superName) : null;
191 for (String anInterface : interfaces) {
192 convertedInterfaces.add(getClassName(anInterface));
194 return convertedSuper;
197 @Nullable
198 private static String parseClassSignature(final CharacterIterator signatureIterator, final List<String> convertedInterfaces)
199 throws ClsFormatException {
200 final String convertedSuper = SignatureParsing.parseToplevelClassRefSignature(signatureIterator);
201 while (signatureIterator.current() != CharacterIterator.DONE) {
202 final String ifs = SignatureParsing.parseToplevelClassRefSignature(signatureIterator);
203 if (ifs == null) throw new ClsFormatException();
205 convertedInterfaces.add(ifs);
207 return convertedSuper;
210 private static LanguageLevel convertFromVersion(final int version) {
211 if (version == Opcodes.V1_1 || version == Opcodes.V1_2 || version == Opcodes.V1_3) {
212 return LanguageLevel.JDK_1_3;
215 if (version == Opcodes.V1_4) {
216 return LanguageLevel.JDK_1_4;
219 if (version == Opcodes.V1_5 || version == Opcodes.V1_6) {
220 return LanguageLevel.JDK_1_5;
223 return LanguageLevel.HIGHEST;
226 private static int packModlistFlags(final int access) {
227 int flags = 0;
229 if ((access & Opcodes.ACC_PRIVATE) != 0) {
230 flags |= ModifierFlags.PRIVATE_MASK;
232 else if ((access & Opcodes.ACC_PROTECTED) != 0) {
233 flags |= ModifierFlags.PROTECTED_MASK;
235 else if ((access & Opcodes.ACC_PUBLIC) != 0) {
236 flags |= ModifierFlags.PUBLIC_MASK;
238 else {
239 flags |= ModifierFlags.PACKAGE_LOCAL_MASK;
242 if ((access & Opcodes.ACC_ABSTRACT) != 0) {
243 flags |= ModifierFlags.ABSTRACT_MASK;
245 if ((access & Opcodes.ACC_FINAL) != 0) {
246 flags |= ModifierFlags.FINAL_MASK;
248 if ((access & Opcodes.ACC_NATIVE) != 0) {
249 flags |= ModifierFlags.NATIVE_MASK;
251 if ((access & Opcodes.ACC_STATIC) != 0) {
252 flags |= ModifierFlags.STATIC_MASK;
254 if ((access & Opcodes.ACC_TRANSIENT) != 0) {
255 flags |= ModifierFlags.TRANSIENT_MASK;
257 if ((access & Opcodes.ACC_VOLATILE) != 0) {
258 flags |= ModifierFlags.VOLATILE_MASK;
260 if ((access & Opcodes.ACC_STRICT) != 0) {
261 flags |= ModifierFlags.STRICTFP_MASK;
264 return flags;
267 public void visitSource(final String source, final String debug) {
268 ((PsiClassStubImpl)myResult).setSourceFileName(source);
271 public void visitOuterClass(final String owner, final String name, final String desc) {
274 public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) {
275 return new AnnotationTextCollector(desc, new AnnotationResultCallback() {
276 public void callback(final String text) {
277 new PsiAnnotationStubImpl(myModlist, text);
282 public void visitAttribute(final Attribute attr) {
285 public void visitInnerClass(final String name, final String outerName, final String innerName, final int access) {
286 if ((access & Opcodes.ACC_SYNTHETIC) != 0) return;
287 if (!isCorrectName(innerName)) return;
289 if (innerName != null && outerName != null && getClassName(outerName).equals(myResult.getQualifiedName())) {
290 final String basename = myVFile.getNameWithoutExtension();
291 final VirtualFile dir = myVFile.getParent();
292 assert dir != null;
294 final VirtualFile innerFile = dir.findChild(basename + "$" + innerName + ".class");
295 if (innerFile != null) {
296 try {
297 buildClass(innerFile, innerFile.contentsToByteArray(), myResult, access);
299 catch (IOException e) {
300 // No inner class file found, ignore.
306 private boolean isCorrectName(String name) {
307 if (name == null) return false;
309 myLexer.start(name);
310 if (myLexer.getTokenType() != JavaTokenType.IDENTIFIER) return false;
311 myLexer.advance();
312 return myLexer.getTokenType() == null;
315 public FieldVisitor visitField(final int access, final String name, final String desc, final String signature, final Object value) {
316 if ((access & Opcodes.ACC_SYNTHETIC) != 0) return null;
317 if (!isCorrectName(name)) return null;
319 final byte flags = PsiFieldStubImpl.packFlags((access & Opcodes.ACC_ENUM) != 0, (access & Opcodes.ACC_DEPRECATED) != 0, false);
320 PsiFieldStub stub = new PsiFieldStubImpl(myResult, name, fieldType(desc, signature), constToString(value), flags);
321 final PsiModifierListStub modlist = new PsiModifierListStubImpl(stub, packModlistFlags(access));
322 return new AnnotationCollectingVisitor(stub, modlist);
325 @NotNull
326 private static TypeInfo fieldType(String desc, String signature) {
327 if (signature != null) {
328 try {
329 return TypeInfo.fromString(SignatureParsing.parseTypeString(new StringCharacterIterator(signature, 0)));
331 catch (ClsFormatException e) {
332 return fieldTypeViaDescription(desc);
334 } else {
335 return fieldTypeViaDescription(desc);
339 @NotNull
340 private static TypeInfo fieldTypeViaDescription(final String desc) {
341 Type type = Type.getType(desc);
342 final int dim = type.getSort() == Type.ARRAY ? type.getDimensions() : 0;
343 if (dim > 0) {
344 type = type.getElementType();
346 return new TypeInfo(StringRef.fromString(getTypeText(type)), (byte)dim, false, Collections.<PsiAnnotationStub>emptyList()); //todo read annos from .class file
350 @Nullable
351 public MethodVisitor visitMethod(final int access,
352 final String name,
353 final String desc,
354 final String signature,
355 final String[] exceptions) {
356 if ((access & Opcodes.ACC_SYNTHETIC) != 0) return null;
357 if ((access & Opcodes.ACC_BRIDGE) != 0) return null;
358 if (SYNTHETIC_CLINIT_METHOD.equals(name)) return null;
360 boolean isDeprecated = (access & Opcodes.ACC_DEPRECATED) != 0;
361 boolean isConstructor = SYNTHETIC_INIT_METHOD.equals(name);
362 boolean isVarargs = (access & Opcodes.ACC_VARARGS) != 0;
363 boolean isAnnotationMethod = myResult.isAnnotationType();
365 if (!isConstructor && !isCorrectName(name)) return null;
367 final byte flags = PsiMethodStubImpl.packFlags(isConstructor, isAnnotationMethod, isVarargs, isDeprecated, false);
369 String canonicalMethodName = isConstructor ? myResult.getName() : name;
370 final List<String> args = new ArrayList<String>();
371 final List<String> throwables = exceptions != null ? new ArrayList<String>() : null;
373 PsiMethodStubImpl stub = new PsiMethodStubImpl(myResult, StringRef.fromString(canonicalMethodName), flags, null);
375 final PsiModifierListStub modlist = new PsiModifierListStubImpl(stub, packMethodFlags(access));
376 boolean parsedViaGenericSignature = false;
377 String returnType;
378 if (signature == null) {
379 returnType = parseMethodViaDescription(desc, stub, args);
381 else {
382 try {
383 returnType = parseMethodViaGenericSignature(signature, stub, args, throwables);
384 parsedViaGenericSignature = true;
386 catch (ClsFormatException e) {
387 returnType = parseMethodViaDescription(desc, stub, args);
391 stub.setReturnType(TypeInfo.fromString(returnType));
394 boolean nonStaticInnerClassConstructor =
395 isConstructor && !parsedViaGenericSignature && !(myParent instanceof PsiFileStub) && (myModlist.getModifiersMask() & Opcodes.ACC_STATIC) == 0;
397 final PsiParameterListStubImpl parameterList = new PsiParameterListStubImpl(stub);
398 final int paramCount = args.size();
399 final PsiParameterStubImpl[] paramStubs = new PsiParameterStubImpl[paramCount];
400 for (int i = 0; i < paramCount; i++) {
401 if (nonStaticInnerClassConstructor && i == 0) continue;
403 String arg = args.get(i);
404 boolean isEllipsisParam = isVarargs && i == paramCount - 1;
405 final TypeInfo typeInfo = TypeInfo.fromString(arg, isEllipsisParam);
407 PsiParameterStubImpl parameterStub = new PsiParameterStubImpl(parameterList, "p" + (i + 1), typeInfo, isEllipsisParam);
408 paramStubs [i] = parameterStub;
409 new PsiModifierListStubImpl(parameterStub, 0);
412 String[] thrownTypes = buildThrowsList(exceptions, throwables, parsedViaGenericSignature);
413 new PsiClassReferenceListStubImpl(JavaStubElementTypes.THROWS_LIST, stub, thrownTypes, PsiReferenceList.Role.THROWS_LIST);
415 int ignoreCount = (access & Opcodes.ACC_STATIC) != 0 ? 0 : 1;
416 return new AnnotationParamCollectingVisitor(stub, modlist, ignoreCount, paramCount, paramStubs);
419 private static String[] buildThrowsList(String[] exceptions, List<String> throwables, boolean parsedViaGenericSignature) {
420 if (exceptions == null) return ArrayUtil.EMPTY_STRING_ARRAY;
422 if (parsedViaGenericSignature && throwables != null && exceptions.length > throwables.size()) {
423 // There seem to be an inconsistency (or bug) in class format. For instance, java.lang.Class.forName() method has
424 // signature equal to "(Ljava/lang/String;)Ljava/lang/Class<*>;" (i.e. no exceptions thrown) but exceptions actually not empty,
425 // method throws ClassNotFoundException
426 parsedViaGenericSignature = false;
429 if (parsedViaGenericSignature && throwables != null) {
430 return ArrayUtil.toStringArray(throwables);
432 else {
433 String[] converted = ArrayUtil.newStringArray(exceptions.length);
434 for (int i = 0; i < converted.length; i++) {
435 converted[i] = getClassName(exceptions[i]);
437 return converted;
441 private static int packMethodFlags(final int access) {
442 int commonFlags = packModlistFlags(access);
443 if ((access & Opcodes.ACC_SYNCHRONIZED) != 0) {
444 commonFlags |= ModifierFlags.SYNCHRONIZED_MASK;
447 return commonFlags;
450 private static String parseMethodViaDescription(final String desc, final PsiMethodStubImpl stub, final List<String> args) {
451 final String returnType = getTypeText(Type.getReturnType(desc));
452 final Type[] argTypes = Type.getArgumentTypes(desc);
453 for (Type argType : argTypes) {
454 args.add(getTypeText(argType));
456 new PsiTypeParameterListStubImpl(stub);
457 return returnType;
460 private static String parseMethodViaGenericSignature(final String signature,
461 final PsiMethodStubImpl stub,
462 final List<String> args,
463 final List<String> throwables)
464 throws ClsFormatException {
465 StringCharacterIterator iterator = new StringCharacterIterator(signature);
466 SignatureParsing.parseTypeParametersDeclaration(iterator, stub);
468 if (iterator.current() != '(') {
469 throw new ClsFormatException();
471 iterator.next();
473 while (iterator.current() != ')' && iterator.current() != CharacterIterator.DONE) {
474 args.add(SignatureParsing.parseTypeString(iterator));
477 if (iterator.current() != ')') {
478 throw new ClsFormatException();
480 iterator.next();
482 String returnType = SignatureParsing.parseTypeString(iterator);
484 while (iterator.current() == '^') {
485 iterator.next();
486 throwables.add(SignatureParsing.parseTypeString(iterator));
489 return returnType;
492 public void visitEnd() {
496 private static class AnnotationTextCollector implements AnnotationVisitor {
497 private final StringBuilder myBuilder = new StringBuilder();
498 private final AnnotationResultCallback myCallback;
499 private boolean hasParams = false;
500 private final String myDesc;
502 public AnnotationTextCollector(@Nullable String desc, AnnotationResultCallback callback) {
503 myCallback = callback;
505 myDesc = desc;
506 if (desc != null) {
507 myBuilder.append('@').append(getTypeText(Type.getType(desc)));
511 public void visit(final String name, final Object value) {
512 valuePairPrefix(name);
513 myBuilder.append(constToString(value));
516 public void visitEnum(final String name, final String desc, final String value) {
517 valuePairPrefix(name);
518 myBuilder.append(getTypeText(Type.getType(desc))).append(".").append(value);
521 private void valuePairPrefix(final String name) {
522 if (!hasParams) {
523 hasParams = true;
524 if (myDesc != null) {
525 myBuilder.append('(');
527 } else {
528 myBuilder.append(',');
531 if (name != null && !"value".equals(name)) {
532 myBuilder.append(name).append('=');
536 public AnnotationVisitor visitAnnotation(final String name, final String desc) {
537 valuePairPrefix(name);
538 return new AnnotationTextCollector(desc, new AnnotationResultCallback() {
539 public void callback(final String text) {
540 myBuilder.append(text);
545 public AnnotationVisitor visitArray(final String name) {
546 valuePairPrefix(name);
547 myBuilder.append("{");
548 return new AnnotationTextCollector(null, new AnnotationResultCallback() {
549 public void callback(final String text) {
550 myBuilder.append(text).append('}');
555 public void visitEnd() {
556 if (hasParams && myDesc != null) {
557 myBuilder.append(')');
559 myCallback.callback(myBuilder.toString());
563 private static class AnnotationCollectingVisitor extends EmptyVisitor {
564 private final StubElement myOwner;
565 private final PsiModifierListStub myModList;
567 private AnnotationCollectingVisitor(final StubElement owner, final PsiModifierListStub modList) {
568 myOwner = owner;
569 myModList = modList;
572 public AnnotationVisitor visitAnnotationDefault() {
573 return new AnnotationTextCollector(null, new AnnotationResultCallback() {
574 public void callback(final String text) {
575 ((PsiMethodStubImpl)myOwner).setDefaultValueText(text);
580 public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) {
581 return new AnnotationTextCollector(desc, new AnnotationResultCallback() {
582 public void callback(final String text) {
583 new PsiAnnotationStubImpl(myModList, text);
588 public AnnotationVisitor visitParameterAnnotation(final int parameter, final String desc, final boolean visible) {
589 return new AnnotationTextCollector(desc, new AnnotationResultCallback() {
590 public void callback(final String text) {
591 new PsiAnnotationStubImpl(((PsiMethodStub)myOwner).findParameter(parameter).getModList(), text);
597 private static class AnnotationParamCollectingVisitor extends AnnotationCollectingVisitor {
598 private final int myIgnoreCount;
599 private final int myParamCount;
600 private final PsiParameterStubImpl[] myParamStubs;
602 private AnnotationParamCollectingVisitor(final StubElement owner, final PsiModifierListStub modList, int ignoreCount, int paramCount,
603 PsiParameterStubImpl[] paramStubs) {
604 super(owner, modList);
605 myIgnoreCount = ignoreCount;
606 myParamCount = paramCount;
607 myParamStubs = paramStubs;
610 @Override
611 public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
612 if (index >= myIgnoreCount && index < myIgnoreCount + myParamCount) {
613 PsiParameterStubImpl parameterStub = myParamStubs[index - myIgnoreCount];
614 if (parameterStub != null) {
615 parameterStub.setName(name);
621 @SuppressWarnings({"HardCodedStringLiteral"})
622 @Nullable
623 private static String constToString(final Object value) {
624 if (value == null) return null;
626 if (value instanceof String) return "\"" + StringUtil.escapeStringCharacters((String)value) + "\"";
627 if (value instanceof Integer || value instanceof Boolean) return value.toString();
628 if (value instanceof Long) return value.toString() + "L";
630 if (value instanceof Double) {
631 final double d = ((Double)value).doubleValue();
632 if (Double.isInfinite(d)) {
633 return d > 0 ? "1.0 / 0.0" : "-1.0 / 0.0";
635 else if (Double.isNaN(d)) {
636 return "0.0d / 0.0";
638 return Double.toString(d);
641 if (value instanceof Float) {
642 final float v = ((Float)value).floatValue();
644 if (Float.isInfinite(v)) {
645 return v > 0 ? "1.0f / 0.0" : "-1.0f / 0.0";
647 else if (Float.isNaN(v)) {
648 return "0.0f / 0.0";
650 else {
651 return Float.toString(v) + "f";
655 return null;
658 private interface AnnotationResultCallback {
659 void callback(String text);
662 private static String getClassName(final String name) {
663 return getTypeText(Type.getObjectType(name));
666 private static String getTypeText(final Type type) {
667 final String raw = type.getClassName();
668 return raw.replace('$', '.');