blob: 8848552770c0f3791a345b5ebb6b1c4ba6f3c1f0 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.netbeans.modules.classfile;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.util.Comparator;
import java.util.WeakHashMap;
/**
* Class representing the name of a Java class. This class is immutable, and
* can therefore be safely used by multiple threads.
* <p>
* <b>Warning:</b> The same reference is often returned by the getClassName
* factory method for multiple calls which use the same type name as its
* parameter. However, no guarantee is made that this behavior will always
* be true, so the equals method should always be used to compare ClassName
* instances.
*
* @author Thomas Ball
*/
public final class ClassName implements Comparable<ClassName>, Comparator<ClassName>, Serializable {
static final long serialVersionUID = -8444469778945723553L;
private final String type;
private final transient String internalName;
private volatile transient String externalName;
private volatile transient String packageName;
private volatile transient String simpleName;
private final static WeakHashMap<String,WeakReference<ClassName>> cache =
new WeakHashMap<String,WeakReference<ClassName>>();
/**
* Returns the ClassName object referenced by a class
* type string (field descriptor), as defined in the
* JVM Specification, sections 4.3.2 and 4.2.
* <P>
* Basically, the JVM Specification defines a class type
* string where the periods ('.') separating
* a package name are replaced by forward slashes ('/').
* Not documented in the second edition of the specification
* is that periods separating inner and outer classes are
* replaced with dollar signs ('$'). Array classes have one
* or more left brackets ('[') prepending the class type.
* For example:
* <PRE><CODE>
* java.lang.String java/lang/String
* java.util.HashMap.Entry java/util/HashMap$Entry
* java.lang.Integer[] [java/lang/Integer
* java.awt.Point[][] [[java/awt/Point
* </CODE><PRE>
* <P>
* This method also accepts type strings which contain with
* 'L' and end with ';' characters. This format is used
* to reference a class in other type names, such as
* method arguments. These two characters are removed from the
* type string.
* <P>
* Because ClassNames are immutable, multiple requests to
* get the same type string may return identical object
* references. This cannot be assumed, however, and the
* ClassName.equals() method should be used instead of
* "==" to test for equality.
*
* @param classType the class type string, as defined by the JVM spec.
* @return the ClassName instance, or null if not found.
* @throws IllegalArgumentException if classType isn't of the correct
* format.
*/
public static ClassName getClassName(String classType) {
// A null superclass name is supposed to be null, but may be
// an empty string depending on the compiler.
if (classType == null || classType.length() == 0)
return null;
ClassName cn = getCacheEntry(classType);
synchronized (cache) {
cn = getCacheEntry(classType);
if (cn == null) {
// check for valid class type
int i = classType.indexOf('L');
String _type;
char lastChar = classType.charAt(classType.length()-1);
if (i != -1 && lastChar == ';') {
// remove 'L' and ';' from type
_type = classType.substring(i+1, classType.length()-1);
if (i > 0)
// add array prefix
_type = classType.substring(0, i) + _type;
cn = getCacheEntry(_type);
if (cn != null)
return cn;
} else {
_type = classType;
}
cn = new ClassName(_type);
cache.put(_type, new WeakReference<ClassName>(cn));
}
}
return cn;
}
private static ClassName getCacheEntry(String key) {
WeakReference<ClassName> ref = cache.get(key);
return ref != null ? ref.get() : null;
}
/**
* Create a ClassName object via its internal type name, as
* defined by the JVM spec.
* @param type the internal type name
*/
private ClassName(String type) {
this.type = type;
// internal name is a type stripped of any array designators
int i = type.lastIndexOf('[');
internalName = (i > -1) ? type.substring(i+1) : type;
}
/**
* Returns the type string of this class, as stored in the
* classfile (it's "raw" form). For example, an array of
* Floats would have a type of "[java/lang/Float".
* @return the raw string format of the class type
*/
public String getType() {
return type;
}
/**
* Returns the "internal" classname, as defined by the JVM
* Specification, without any parameter or return type
* information. For example, the name for the String
* class would be "java/lang/String". Inner classes are
* separated from their outer class with '$'; such as
* "java/util/HashMap$Entry". Array specifications are
* stripped; use getType() instead.
* @return the internal name
*/
public String getInternalName() {
return internalName;
}
/**
* Returns the "external" classname, as defined by the
* Java Language Specification, without any parameter
* or return type information. For example, the name for the
* String class would be "java.lang.String". Inner classes
* are separated from their outer class with '.'; such as
* "java.util.HashMap.Entry". Arrays are shown as having one
* or more "[]" characters behind the base classname, such
* as "java.io.Files[]".
* @return the external name, with array characters if this ClassName
* is an array
*/
public String getExternalName() {
return getExternalName(false);
}
/**
* Returns the "external" classname, as defined by the
* Java Language Specification, without any parameter
* or return type information. For example, the name for the
* String class would be "java.lang.String". Inner classes
* are separated from their outer class with '.'; such as
* "java.util.HashMap.Entry". Unless suppressed, arrays are
* shown as having one or more "[]" characters behind the
* base classname, such as "java.io.Files[]".
* @param suppressArrays true if array characters should be included
* @return the external name
*/
public String getExternalName(boolean suppressArrays) {
initExternalName();
int i;
if (suppressArrays && (i = externalName.indexOf('[')) != -1)
return externalName.substring(0, i);
return externalName;
}
private synchronized void initExternalName() {
if (externalName == null)
externalName = externalizeClassName();
}
/**
* Return the package portion of this classname.
* @return the package name
*/
public String getPackage() {
if (packageName == null)
initPackage();
return packageName;
}
private synchronized void initPackage() {
int i = internalName.lastIndexOf('/');
packageName = (i != -1) ?
internalName.substring(0, i).replace('/', '.') : "";
}
/**
* Returns the classname without any package specification.
* @return the simple name
*/
public String getSimpleName() {
if (simpleName == null)
initSimpleName();
return simpleName;
}
private synchronized void initSimpleName() {
String pkg = getPackage();
int i = pkg.length();
String extName = getExternalName();
if (i == 0)
simpleName = extName; // no package
else
simpleName = extName.substring(i + 1);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
return (obj instanceof ClassName) &&
type.equals(((ClassName) obj).type);
}
/**
* Compares this ClassName to another Object. If the Object is a
* ClassName, this function compares the ClassName's types. Otherwise,
* it throws a <code>ClassCastException</code>.
*
* @param obj the <code>Object</code> to be compared.
* @return the value <code>0</code> if the argument is a string
* lexicographically equal to this string; a value less than
* <code>0</code> if the argument is a string lexicographically
* greater than this string; and a value greater than
* <code>0</code> if the argument is a string lexicographically
* less than this string.
* @see java.lang.Comparable
*/
public int compareTo(ClassName obj) {
// If obj isn't a ClassName, the correct ClassCastException
// will be thrown by the cast.
return type.compareTo(obj.type);
}
/**
* Compares its two arguments for order. Returns a negative integer,
* zero, or a positive integer as the first argument is less than, equal
* to, or greater than the second.<p>
*
* @param o1 the first object to be compared.
* @param o2 the second object to be compared.
* @return a negative integer, zero, or a positive integer as the
* first argument is less than, equal to, or greater than the
* second.
* @throws ClassCastException if the arguments' types prevent them from
* being compared by this Comparator.
*/
public int compare(ClassName o1, ClassName o2) {
return o1.compareTo(o2);
}
@Override
public int hashCode() {
return type.hashCode();
}
@Override
public String toString() {
return getExternalName();
}
// Called from synchronization block, do not call out!
private String externalizeClassName() {
StringBuffer sb = new StringBuffer(type);
int arrays = 0;
boolean atBeginning = true;
int length = sb.length();
for (int i = 0; i < length; i++) {
char ch = sb.charAt(i);
switch (ch) {
case '[':
if (atBeginning)
arrays++;
break;
case '/':
case '$':
sb.setCharAt(i, '.');
atBeginning = false;
break;
default:
atBeginning = false;
}
}
if (arrays > 0) {
sb.delete(0, arrays);
for (int i = 0; i < arrays; i++)
sb.append("[]");
}
return sb.toString();
}
/**
* Empties the cache -- used by unit tests.
*/
static void clearCache() {
cache.clear();
}
/*
* Suppress multiple instances of the same type, as well as any
* immutability attacks (unlikely as that might be). For more information
* on this technique, check out Effective Java, Item 57, by Josh Bloch.
*/
private Object readResolve() throws ObjectStreamException {
return getClassName(internalName);
}
}