//
// Copyright (C) 2012 United States Government as represented by the
// Administrator of the National Aeronautics and Space Administration
// (NASA). All Rights Reserved.
//
// This software is distributed under the NASA Open Source Agreement
// (NOSA), version 1.3. The NOSA has been approved by the Open Source
// Initiative. See the file NOSA-1.3-JPF at the top of the distribution
// directory tree for the complete NOSA document.
//
// THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY
// KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT
// LIMITED TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO
// SPECIFICATIONS, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR
// A PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT
// THE SUBJECT SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT
// DOCUMENTATION, IF PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE.
//
package gov.nasa.jpf.conformanceChecker.providers;
import gov.nasa.jpf.Config;
import gov.nasa.jpf.JPF;
import gov.nasa.jpf.classfile.ClassFile;
import gov.nasa.jpf.classfile.ClassFileException;
import gov.nasa.jpf.jvm.ClassInfo;
import gov.nasa.jpf.jvm.JVM;
import gov.nasa.jpf.util.MethodSpec;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Collection;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.Random;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Logger;
/**
* The purpose of this class is similar to {@link StandardClassProvider}
* but instead of the standard version of the class it loads
* the model one.
*
* Properties used from jpf config file:
*
* - conformance-checker.modelPaths
*
* that specifies a ':' separated list of the directories/jars
* that build up the set of model classes
*
* The directories specified _must_ be roots of package hierarchies.
*
* @author Matteo Ceccarello <matteo.ceccarello AT gmail.com>
*
*/
public class ModelClassProvider {
static {
// FIXME the hack used to avoid null pointers
if(JVM.getVM() == null)
new JVM(null, new Config(new String[0]));
}
private static Logger logger = JPF.getLogger(ModelClassProvider.class.getName());
public static final char[] delims = {':', ','};
public static final String MODEL_PATHS_PROP = "conformance-checker.modelPaths";
/* To generate random IDs for classes */
private Random rnd = new Random();
File[] files;
public ModelClassProvider() {
this(new Config(new String[]{}));
}
public ModelClassProvider(Config config) {
String[] fileNames = config.getStringArray(MODEL_PATHS_PROP, delims);
if (fileNames != null) {
files = new File[fileNames.length];
for (int i = 0; i < fileNames.length; i++) {
files[i] = new File(fileNames[i]);
}
} else {
files = new File[0];
logger.severe("No path to model classes specified in jpf configuration file.\n" +
"No comparison will be made.\n" +
"Please specify a model class path with the property " + MODEL_PATHS_PROP);
}
}
/**
* Returns a list of all {@link ClassInfo} objects retrieved from the jar
* files and directories specified in the configuration file via the
* property {@value #MODEL_PATHS_PROP}.
*
* @return a {@link Iterable} of all {@link ClassInfo}s of the model.
*/
public Iterable<ClassInfo> loadClassInfos() {
return loadClassInfos(new LinkedList<MethodSpec>());
}
/**
* Returns a list of {@link ClassInfo} objects retrieved from the jar
* files and directories specified in the configuration file via the
* property {@value #MODEL_PATHS_PROP}. Only the classes matching
* one of the given {@link MethodSpec}s are loaded
*
* @return a {@link Iterable} of all {@link ClassInfo}s of the model.
*/
public Iterable<ClassInfo> loadClassInfos(Collection<MethodSpec> specs) {
LinkedList<ClassInfo> classInfos = new LinkedList<ClassInfo>();
for (File f : files) {
if (f.isFile() && f.getName().endsWith(".jar")) {
classInfos.addAll(loadClassInfosFromJar(f, specs));
} else if (f.isDirectory()) {
classInfos.addAll(loadClassInfosFromDir(f, specs));
}
}
return classInfos;
}
Collection<ClassInfo> loadClassInfosFromDir(File f, Collection<MethodSpec> specs) {
return retrieveClassInfos(f, "", specs);
}
Collection<ClassInfo> retrieveClassInfos(File f, String baseName, Collection<MethodSpec> specs) {
LinkedList<ClassInfo> classInfos = new LinkedList<ClassInfo>();
String fileName = f.getName();
if(f.isFile() && fileName.endsWith(".class")) {
try {
// to avoid things like .ClassName for classes in the default package
String name = (!"".equals(baseName))? baseName + "." : "";
name += fileName.substring(fileName.lastIndexOf('.'));
byte[] classBytes = Util.getClassBytes(new FileInputStream(f));
ClassFile cf = new ClassFile(name, classBytes);
ClassInfo ci = new UnregisteredClassInfo(cf);
// FIXME it would be better to be able to perform this check earlier
if(matchesSpecs(ci.getName(), specs)) {
classInfos.add(ci);
}
} catch (FileNotFoundException e) {
logger.warning("file " + f + " does not exist");
} catch (IOException e) {
logger.warning("error reading file " + f + "\n" + e.getMessage());
} catch (ClassFileException e) {
logger.warning(e.getMessage() + "\n" + e.getStackTrace());
}
} else if(f.isDirectory()) {
String name = (!"".equals(baseName))? baseName + "." : "";
name += fileName;
for(File content : f.listFiles()) {
classInfos.addAll(retrieveClassInfos(content, name, specs));
}
}
return classInfos;
}
static boolean matchesSpecs(String name, Collection<MethodSpec> specs) {
if(specs.size() == 0)
return true;
for (MethodSpec spec : specs) {
if(spec.matchesClass(name))
return true;
}
return false;
}
Collection<ClassInfo> loadClassInfosFromJar(File f, Collection<MethodSpec> specs) {
LinkedList<ClassInfo> classInfos = new LinkedList<ClassInfo>();
try {
JarFile jar = new JarFile(f);
Enumeration<JarEntry> entries = jar.entries();
while(entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String name = entry.getName();
if(name.endsWith(".class")) {
// FIXME: check if on Windows the file separator inside jar files
// is / or \
name = name.substring(0, name.lastIndexOf('.')).replace('/', '.');
if(matchesSpecs(name, specs)) {
byte[] classBytes = Util.getClassBytes(jar.getInputStream(entry));
ClassFile cf = new ClassFile(name, classBytes);
classInfos.add(new UnregisteredClassInfo(cf));
}
}
}
} catch (IOException e) {
logger.warning("error while loading classes from " + f +
"\n" + e.getMessage());
} catch (ClassFileException e) {
logger.warning("error while loading class from " + f +
"\n" + e.getMessage());
}
return classInfos;
}
/**
* Loads a specified {@link ClassInfo} from the jars and directories
* specified in the configuration file with the {@value #MODEL_PATHS_PROP}
* property
*
* @param className the desired class
* @return the {@link ClassInfo} object for the desired class.
*/
public ClassInfo loadClassInfo(String className) {
ClassInfo ci = null;
for (File f : files) {
if (f.isFile() && f.getName().endsWith(".jar")) {
ci = loadFromJar(f, className);
} else if (f.isDirectory()) {
ci = loadFromDir(f, className);
}
if (ci != null)
return ci;
}
return null;
}
/**
* Loads from a directory tree the specified class
* returns null of the class is not found
*/
ClassInfo loadFromDir(File f, String className) {
String[] fileNames = className.split("\\.");
fileNames[fileNames.length-1] += ".class";
File file = getFile(f, 0, fileNames);
if(file != null) {
try {
byte[] data = Util.getClassBytes(new FileInputStream(file));
ClassFile cf = new ClassFile(className, data);
// return new UnregisteredClassInfo(cf);
return new ClassInfo(cf, rnd.nextInt(1000));
} catch (FileNotFoundException e) {
logger.warning(e.getMessage());
} catch (IOException e) {
logger.warning(e.getMessage());
} catch (ClassFileException e) {
logger.warning(e.getMessage());
}
}
return null;
}
File getFile(File f, int i, String[] names) {
for(File content : f.listFiles()) {
if(content.getName().equals(names[i])) {
if (i == names.length-1)
return content;
return getFile(content, i+1, names);
}
}
return null;
}
/**
* Loads from the given jar the requested class.
* Returns null if the class is not found
*/
ClassInfo loadFromJar(File f, String className) {
// FIXME: check if on Windows the file separator inside jar files
// is / or \
String fileName = className.replace('.', '/') + ".class";
try {
JarFile jar = new JarFile(f);
JarEntry entry = jar.getJarEntry(fileName);
if (entry != null) {
byte[] classBytes = Util.getClassBytes(jar.getInputStream(entry));
ClassFile cf = new ClassFile(className, classBytes);
return new UnregisteredClassInfo(cf);
}
} catch (IOException e) {
logger.warning("error while loading class " + className +
" from jar file " + f + "\n" + e.getMessage());
return null;
} catch (ClassFileException e) {
logger.warning("error while loading class " + className +
" from jar file " + f + "\n" + e.getMessage());
return null;
}
return null;
}
public static void main(String[] args) {
ModelClassProvider mcp = new ModelClassProvider();
ClassInfo ci = mcp.loadClassInfo("gov.nasa.jpf.jvm.AtomicFieldUpdater");
System.out.println(ci);
System.out.println(mcp.loadClassInfos());
}
}