package net.ayld.facade.model;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.io.Resources;
import net.ayld.facade.dependency.resolver.DependencyResolver;
import net.ayld.facade.util.Components;
import org.apache.bcel.classfile.ClassParser;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.util.Set;
/**
* Meant to represent a compiled binary .class file on the file system.
*
* This class tries to do everything it can to make sure it wraps a file that is actually a Java .class file.
* It does this by parsing the binary .class file and checking whether they match the JVM .class file specifications.
*
* More info here:
* http://en.wikipedia.org/wiki/Java_class_file
* */
public class ClassFile { // XXX magic numbers
public static final String EXTENSION = "class";
// bytes in a set :D
private static final Set<Byte> CAFEBABE = ImmutableSet.of(
(byte) 0xCA,
(byte) 0xFE,
(byte) 0xBA,
(byte) 0xBE
);
private Set<ClassName> dependencies;
private final File classFile;
private final ClassName qualifiedName;
private ClassFile(File classfile) { // XXX copy code
try {
if (!isClassfile(classfile)) {
throw new IllegalArgumentException("file: " + classfile.getAbsolutePath() + ", not valid or is not a class file");
}
this.classFile = classfile;
this.qualifiedName = new ClassName(new ClassParser(classFile.getAbsolutePath()).parse().getClassName());
} catch (URISyntaxException | IOException e) {
throw new IllegalArgumentException("file: " + classfile.getAbsolutePath() + ", not valid or is not a class file", e);
}
}
/**
* Returns the qualified class name of this ClassFile.
*
* @see {@link ClassName}
* */
public ClassName qualifiedName() {
return qualifiedName;
}
/**
* Creates a {@link ClassFile} from a file on the classpath rather than the file system.
* Checks whether the given file is actually a class file.
*
* @param path path to the .class resource
*
* @return a new {@link ClassFile}
*
* @throws IllegalArgumentException if the file is not found or the file is not a class file
* */
public static ClassFile fromClasspath(String path) {
try {
return new ClassFile(new File(Resources.getResource(path).toURI()));
} catch (URISyntaxException e) {
throw new IllegalArgumentException("can not find a file at: " + path);
}
}
/**
* Creates a {@link ClassFile} from a {@link File}, checking whether the given file is actually a class file.
*
* @param path path to a .class file on the file system
*
* @return a new {@link ClassFile}
*
* @throws IllegalArgumentException if the file is not found or the file is not a class file
* */
public static ClassFile fromFilepath(String path) {
return new ClassFile(new File(path));
}
public static ClassFile fromFile(File classFile) {
if (classFile == null) {
throw new IllegalArgumentException("null argument not allowed");
}
return fromFilepath(classFile.getAbsolutePath());
}
/**
* Checks whether a file is a class file.
*
* @param classfile a file on the file system
*
* @return true if the given file is a Java class file,
* false if not
* */
public static boolean isClassfile(File classfile) throws URISyntaxException, IOException { // moar checks can be done ... not that they would matter ...
if (!classfile.exists()) {
return false;
}
if (!EXTENSION.equals(com.google.common.io.Files.getFileExtension(classfile.getName()))) {
return false;
}
InputStream classfileInputStream = null;
final byte[] firstFour = new byte[4];
try {
classfileInputStream = new FileInputStream(classfile);
if (classfileInputStream.read(firstFour, 0, firstFour.length) != firstFour.length) {
return false;
}
} finally {
if (classfileInputStream != null) {
classfileInputStream.close();
}
}
// XXX manual set array wrap because:
// ImmutableSet.of(firstFour) gives me ImmutableSet<byte[]> while I want ImmutableSet<Byte> ?!?!
final Set<Byte> firstFourSet = Sets.newHashSet();
for (byte b : firstFour) {
firstFourSet.add(b);
}
return CAFEBABE.equals(ImmutableSet.copyOf(firstFourSet));
}
/**
* Returns the dependencies of this {@link ClassFile}
* */
public Set<ClassName> dependencies() {
final DependencyResolver<ClassFile> classDependencyResolver = Components.CLASS_DEPENDENCY_RESOLVER.getInstance();
if (dependencies == null) {
try {
dependencies = classDependencyResolver.resolve(this);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
return ImmutableSet.copyOf(dependencies);
}
/**
* Returns the wrapped class file as a {@link File}.
*
* @return the wrapped class file as a {@link File}.
* */
public File physicalFile() {
return new File(classFile.getAbsolutePath());
}
/**
* Returns the path to the wrapped {@link File}.
* */
@Override
public String toString() {
return classFile.getAbsolutePath();
}
// TODO hashCode() and equals() must be improved reflecting new fields
@Override
public int hashCode() {
return classFile.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (!(obj instanceof ClassFile)) {
return false;
}
final ClassFile other = (ClassFile) obj;
return other.physicalFile().equals(this.physicalFile());
}
}