/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the Classpath exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.btrace.runtime;
import com.sun.btrace.DebugSupport;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Objects;
import java.util.Set;
/**
* Arbitrary class info type allowing access to supertype information
* also for not-already-loaded classes.
*
* @author Jaroslav Bachorik
*/
public final class ClassInfo {
private static abstract class BaseClassName implements CharSequence {
protected final CharSequence wrapped;
private String str = null;
protected BaseClassName(CharSequence wrapped) {
this.wrapped = wrapped;
}
@Override
public int length() {
return wrapped.length();
}
@Override
public CharSequence subSequence(int start, int end) {
throw new UnsupportedOperationException();
}
@Override
public String toString() {
if (str == null) {
char[] val = new char[wrapped.length()];
for (int i = 0; i < wrapped.length(); i++) {
val[i] = charAt(i);
}
str = new String(val);
}
return str;
}
}
private static final class JavaClassName extends BaseClassName {
public JavaClassName(CharSequence wrapped) {
super(wrapped);
}
@Override
public char charAt(int index) {
char c = wrapped.charAt(index);
return (c == '/' ? '.' : c);
}
}
private static final class InternalClassName extends BaseClassName {
public InternalClassName(CharSequence wrapped) {
super(wrapped);
}
@Override
public char charAt(int index) {
char c = wrapped.charAt(index);
return (c == '.' ? '/' : c);
}
}
static final class ClassName {
private final CharSequence cName;
private final JavaClassName jcName;
private final InternalClassName icName;
private String rsrcName = null;
public ClassName(CharSequence cName) {
this.cName = cName;
this.jcName = new JavaClassName(cName);
this.icName = new InternalClassName(cName);
}
public CharSequence getJavaClassName() {
return jcName;
}
public CharSequence getInternalClassName() {
return icName;
}
public String getResourcePath() {
if (rsrcName == null) {
rsrcName = new StringBuilder(icName).append(".class").toString();
}
return rsrcName;
}
@Override
public String toString() {
return new StringBuilder(cName).toString();
}
@Override
public int hashCode() {
int h = 7;
int len = cName.length();
for (int i = 0; i < len; i++) {
char c = cName.charAt(i);
h = 31 * h + (c == '.' ? '/' : c);
}
return h;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final ClassName other = (ClassName) obj;
if (cName.length() != other.cName.length()) {
return false;
}
for (int i = 0; i < cName.length(); i++) {
char c1 = cName.charAt(i);
char c2 = other.cName.charAt(i);
switch (c1) {
case '.': {
if (c2 != '.' && c2 != '/') {
return false;
}
break;
}
case '/': {
if (c2 != '.' && c2 != '/') {
return false;
}
break;
}
default: {
if (c1 != c2) {
return false;
}
}
}
return true;
}
return true;
}
}
private static volatile Method BSTRP_CHECK_MTD;
private static final ClassLoader SYS_CL = ClassLoader.getSystemClassLoader();
private final String cLoaderId;
private final ClassName classId;
private final Collection<ClassInfo> supertypes = new LinkedList<>();
private final ClassCache cache;
private boolean isInterface = false;
ClassInfo(ClassCache cache, Class clz) {
this.cache = cache;
ClassLoader cl = clz.getClassLoader();
cLoaderId = (cl != null ? cl.toString() : "<null>");
classId = new ClassName(clz.getName());
Class supr = clz.getSuperclass();
if (supr != null) {
supertypes.add(cache.get(supr));
}
for (Class itfc : clz.getInterfaces()) {
if (itfc != null) {
supertypes.add(cache.get(itfc));
}
}
this.isInterface = clz.isInterface();
}
ClassInfo(ClassCache cache, ClassLoader cl, ClassName cName) {
this.cache = cache;
cLoaderId = (cl != null ? cl.toString() : "<null>");
classId = cName;
loadExternalClass(cl, cName);
}
/**
* Retrieves supertypes (including interfaces)
* @param onlyDirect only immediate supertype and implemented interfaces
* @return supertypes (including interfaces)
*/
public Collection<ClassInfo> getSupertypes(boolean onlyDirect) {
if (onlyDirect) {
return supertypes;
}
Set<ClassInfo> supers = new LinkedHashSet<>();
supers.addAll(supertypes);
for (ClassInfo ci : supertypes) {
supers.addAll(ci.getSupertypes(onlyDirect));
}
return supers;
}
/**
* Associated class loader string representation as returned by {@code cl.toString()} or {@code "<null>"}
* @return associated class loader id
*/
public String getLoaderId() {
return cLoaderId;
}
/**
* Class ID = internal class name
* @return internal class name
*/
public String getClassName() {
return classId.getInternalClassName().toString();
}
public String getJavaClassName() {
return classId.getJavaClassName().toString();
}
public boolean isInterface() {
return isInterface;
}
private void loadExternalClass(final ClassLoader cl, final ClassName className) {
String resourcePath = className.getResourcePath();
InputStream typeIs = cl == null ? SYS_CL.getResourceAsStream(resourcePath) : cl.getResourceAsStream(resourcePath);
if (typeIs != null) {
try {
BTraceClassReader cr = new BTraceClassReader(cl, typeIs);
this.isInterface = cr.isInterface();
String[] info = cr.readClassSupers();
String superName = info[0];
if (superName != null) {
ClassName superClassName = new ClassName(superName);
supertypes.add(cache.get(inferClassLoader(cl, superClassName), superClassName));
}
if (info.length > 1) {
for(int i = 1; i < info.length; i++) {
String ifc = info[i];
if (ifc != null) {
ClassName ifcClassName = new ClassName(ifc);
supertypes.add(cache.get(inferClassLoader(cl, ifcClassName), ifcClassName));
}
}
}
} catch (IOException e) {
DebugSupport.warning(e);
}
}
}
private static ClassLoader inferClassLoader(ClassLoader initiating, ClassName className) {
if (className == null) {
return initiating;
}
String jClassName = className.getJavaClassName().toString();
if (initiating == null || isBootstrap(jClassName)) {
return null;
} else {
String rsrcName = className.getResourcePath();
ClassLoader cl = initiating;
ClassLoader prev = initiating;
while (cl != null) {
if (cl.getResource(rsrcName) == null) {
return prev;
}
prev = cl;
cl = cl.getParent();
}
return initiating;
}
}
private static boolean isBootstrap(String className) {
try {
Method m = getCheckBootstrap();
if (m != null) {
return m.invoke(SYS_CL, className) != null;
}
} catch (Throwable t) {
DebugSupport.warning(t);
}
return false;
}
private static Method getCheckBootstrap() {
if (BSTRP_CHECK_MTD != null) {
return BSTRP_CHECK_MTD;
}
Method m = null;
try {
m = ClassLoader.class.getDeclaredMethod("findBootstrapClassOrNull", String.class);
m.setAccessible(true);
} catch (Throwable t) {
DebugSupport.warning(t);
}
BSTRP_CHECK_MTD = m;
return BSTRP_CHECK_MTD;
}
@Override
public int hashCode() {
int hash = 5;
hash = 37 * hash + Objects.hashCode(this.cLoaderId);
hash = 37 * hash + Objects.hashCode(this.classId);
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final ClassInfo other = (ClassInfo) obj;
if (!Objects.equals(this.cLoaderId, other.cLoaderId)) {
return false;
}
return Objects.equals(this.classId, other.classId);
}
@Override
public String toString() {
return "ClassInfo{" + "cLoaderId=" + cLoaderId + ", classId=" + classId + ", supertypes=" + supertypes + '}';
}
}