de.spricom.dessert.slicing.Clazz Maven / Gradle / Ivy
package de.spricom.dessert.slicing;
/*-
* #%L
* Dessert Dependency Assertion Library for Java
* %%
* Copyright (C) 2017 - 2023 Hans Jörg Heßmann
* %%
* Licensed 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.
* #L%
*/
import de.spricom.dessert.classfile.ClassFile;
import de.spricom.dessert.resolve.ClassEntry;
import de.spricom.dessert.resolve.ResolveException;
import de.spricom.dessert.util.ClassUtils;
import de.spricom.dessert.util.Predicate;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.logging.Logger;
/**
* A special {@link Slice} that represents a single .class file.
*/
public final class Clazz extends AbstractSlice implements Comparable, Concrete {
private static final Logger log = Logger.getLogger(Clazz.class.getName());
public static final Clazz UNDEFINED = new Clazz();
private final Classpath classpath;
private final String className;
private final ClassFile classFile;
private final ClassEntry classEntry;
private Class> classImpl;
private URI uri;
private Clazz superclass;
private List implementedInterfaces;
private ConcreteSlice dependencies;
private List alternatives;
private Clazz host;
private Slice nest;
private Clazz() {
classpath = null;
className = "undefined";
classFile = null;
classEntry = null;
superclass = this;
implementedInterfaces = Collections.emptyList();
dependencies = ConcreteSlice.EMPTY_SLICE;
alternatives = Collections.emptyList();
}
Clazz(Classpath classpath, ClassEntry classEntry) {
assert classpath != null : "context == null";
assert classEntry != null : "classEntry == null";
this.classpath = classpath;
this.classFile = classEntry.getClassfile();
this.className = classFile.getThisClass();
this.classEntry = classEntry;
if (classEntry.getAlternatives() != null) {
for (ClassEntry alternative : classEntry.getAlternatives()) {
if (classEntry != alternative) {
new Clazz(this, alternative);
}
}
}
}
Clazz(Clazz alternative, ClassEntry classEntry) {
assert alternative != null : "alternative == null";
assert classEntry != null : "classEntry == null";
this.classpath = alternative.classpath;
this.classEntry = classEntry;
this.classFile = classEntry.getClassfile();
this.className = classFile.getThisClass();
if (alternative.alternatives == null) {
alternative.alternatives = new LinkedList();
alternative.alternatives.add(alternative);
}
this.alternatives = alternative.alternatives;
this.alternatives.add(this);
}
Clazz(Classpath classpath, Class> classImpl) throws IOException {
assert classpath != null : "context == null";
assert classImpl != null : "clazz == null";
this.classpath = classpath;
this.classImpl = classImpl;
this.classEntry = null;
this.classFile = new ClassFile(classImpl);
this.className = classFile.getThisClass();
}
Clazz(Classpath classpath, String className) {
assert classpath != null : "context == null";
assert className != null : "className == null";
this.classpath = classpath;
this.classEntry = null;
this.classFile = null;
this.className = className;
superclass = UNDEFINED;
implementedInterfaces = Collections.emptyList();
dependencies = ConcreteSlice.EMPTY_SLICE;
alternatives = Collections.emptyList();
}
/**
* @return the classes directory or jar-archive containing this class
* @deprecated use getRoot().getRootFile() instead
*/
@Deprecated
public File getRootFile() {
Root root = getRoot();
return root == null ? null : root.getRootFile();
}
/**
* @return the {@link Root} this Clazz belongs to
*/
public Root getRoot() {
if (classEntry != null) {
return classpath.rootOf(classEntry.getPackage().getRoot());
} else if (classImpl != null) {
return classpath.rootOf(classImpl);
} else {
return null;
}
}
public ClassFile getClassFile() {
return classFile;
}
public String getName() {
return className;
}
public String getPackageName() {
if (classEntry != null) {
return classEntry.getPackage().getPackageName();
} else if (classImpl != null) {
return classImpl.getPackage().getName();
} else {
int index = className.lastIndexOf('.');
if (index == -1) {
return "";
}
return className.substring(0, index);
}
}
public String getSimpleName() {
if (classFile != null) {
return classFile.getSimpleName();
} else if (classImpl != null) {
return classImpl.getSimpleName();
} else {
int dollarIndex = className.lastIndexOf('$');
if (dollarIndex > 0) {
String name = className.substring(dollarIndex + 1);
if (name.matches("\\d+")) {
return "";
}
return name;
}
return getShortName();
}
}
/**
* @return the classname without package prefix
*/
public String getShortName() {
if (classEntry != null) {
return classEntry.getShortName();
}
String packageName = getPackageName();
if (packageName.isEmpty()) {
return className;
}
return className.substring(packageName.length() + 1);
}
public URI getURI() {
if (uri != null) {
return uri;
}
if (classEntry != null) {
uri = classEntry.getURI();
return uri;
}
// either there is a classEntry or a classImpl, or it's unknown
if (classImpl != null) {
uri = ClassUtils.getURI(classImpl);
} else {
String unknown = "dessert:unknown:" + className;
try {
uri = new URI(unknown);
} catch (URISyntaxException ex) {
throw new IllegalStateException("Cannot convert '" + unknown + "' to URI", ex);
}
}
return uri;
}
@Override
public Slice slice(Predicate predicate) {
return predicate.test(this) ? this : Slices.EMPTY_SLICE;
}
@Override
public boolean contains(Clazz clazz) {
return equals(clazz);
}
@Override
public Set getClazzes() {
return Collections.singleton(this);
}
@Override
public int hashCode() {
return className.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Clazz other = (Clazz) obj;
if (!className.equals(other.className)) {
return false;
}
return getURI().equals(other.getURI());
}
@Override
public int compareTo(Clazz o) {
return getName().compareTo(o.getName());
}
@Override
public String toString() {
return "clazz " + className;
}
public boolean isUnknown() {
return classFile == null;
}
public Class> getClassImpl() {
if (classImpl == null && !isUnknown()) {
try {
classImpl = Class.forName(className);
if (!getURI().equals(ClassUtils.getURI(classImpl))) {
// TODO: Use specialized classloader to prevent this
log.warning("Loaded class " + ClassUtils.getURI(classImpl) + " for entry " + getURI() + "!");
}
} catch (ClassNotFoundException ex) {
throw new ResolveException("Unable to load " + className, ex);
}
}
return classImpl;
}
public Clazz getSuperclass() {
if (superclass == null && classFile != null) {
superclass = classpath.asClazz(classFile.getSuperClass());
}
return superclass;
}
public List getImplementedInterfaces() {
if (implementedInterfaces == null && classFile != null) {
implementedInterfaces = new ArrayList(classFile.getInterfaces().length);
for (String in : classEntry.getClassfile().getInterfaces()) {
implementedInterfaces.add(classpath.asClazz(in));
}
}
return implementedInterfaces;
}
public Clazz getHost() {
if (host == null) {
if (classFile != null) {
String nestHostName = classFile.getNestHost();
if (nestHostName == null || nestHostName.equals(className)) {
host = this;
} else {
host = classpath.asClazz(nestHostName);
}
} else if (classImpl != null) {
Class> enclosingClass = classImpl;
while (enclosingClass.getEnclosingClass() != null) {
enclosingClass = enclosingClass.getEnclosingClass();
}
host = classpath.asClazz(enclosingClass);
} else if (className.indexOf('$') >= 0) {
String hostClassname = className.substring(0, className.indexOf('$'));
host = classpath.asClazz(hostClassname);
} else {
host = this;
}
}
return host;
}
public Slice getNest() {
if (nest == null) {
ClassFile hostClassFile = getHost().classFile;
if (hostClassFile == null) {
nest = this;
} else {
List nestMembers = hostClassFile.getNestMembers();
if (nestMembers.isEmpty()) {
nest = this;
} else {
Set nestClazzes = new TreeSet();
nestClazzes.add(getHost());
for (String nestMember : nestMembers) {
Clazz nestClazz = classpath.asClazz(nestMember);
nestClazzes.add(nestClazz);
if (hostClassFile.getMajorVersion() < 55) {
addNextMembersRecursive(nestClazzes, nestClazz);
}
}
nest = new ConcreteSlice(nestClazzes);
}
}
}
return nest;
}
private void addNextMembersRecursive(Set nestClazzes, Clazz outerClazz) {
if (outerClazz.classFile == null) {
return;
}
List nestMembers = outerClazz.classFile.getNestMembers();
if (nestMembers.isEmpty()) {
return;
}
for (String nestMember : nestMembers) {
Clazz nestedClazz = classpath.asClazz(nestMember);
if (nestClazzes.add(nestedClazz)) {
addNextMembersRecursive(nestClazzes, nestedClazz);
}
}
}
public ConcreteSlice getDependencies() {
if (dependencies == null && classFile != null) {
Set deps = new HashSet(classFile.getDependentClasses().size());
for (String cn : classFile.getDependentClasses()) {
deps.add(classpath.asClazz(cn));
}
dependencies = new ConcreteSlice(deps);
}
return dependencies;
}
public List getAlternatives() {
if (alternatives == null) {
return Collections.singletonList(this);
}
return alternatives;
}
Clazz getAlternative(ClassEntry ce) {
if (matches(ce)) {
return this;
}
if (alternatives != null) {
for (Clazz alt : alternatives) {
if (alt.matches(ce)) {
return alt;
}
}
}
return null;
}
private boolean matches(ClassEntry ce) {
if (classEntry != null) {
return classEntry == ce;
}
return getURI().equals(ce.getURI());
}
/**
* Returns the major version number N, if the .class file is located
* in the META-INF/versions/N directory of a Multi-release JAR file, null otherwise.
*
* @return N or null
* @deprecated use {@link #getVersion()}
*/
@Deprecated
public String getMinVersion() {
String uri = getURI().toString();
int i = uri.toUpperCase().indexOf("/META-INF/VERSIONS/");
int l = className.length() + "/.class".length();
if (i > 0 && i + l < uri.length()) {
return uri.substring(i + "/META-INF/VERSIONS/".length(), uri.length() - l);
}
return null;
}
/**
* Returns the major version number N, if the .class file is located
* in the META-INF/versions/N directory of a Multi-release JAR file, null otherwise.
*
* @return N or null
*/
public Integer getVersion() {
return classEntry == null ? null : classEntry.getVersion();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy