//
// 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.jvm.NativePeer;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Logger;
/**
* This class serves as as provider for native peers. It is based on the
* {@link NativePeer} class.
*
* This class, given a ClassInfo object (or its name) returns the
* ClassInfo object that represents its native peer.
*
* @author Matteo Ceccarello <matteo.ceccarello AT gmail.com>
*
*/
public class NativePeerProvider {
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(NativePeerProvider.class.getName());
private static final String PEER_PREFIX = "gov/nasa/jpf/jvm/JPF_";
private static final String MODEL_PACKAGE = "<model>";
private static final String DEFAULT_PACKAGE = "<default>";
private Config config;
private Map<String, ClassInfo> cache;
private String[] peerPackages;
/** This is the array that stores the jar files in which we want to look into
* to load classes */
private JarFile[] nativeJarFiles;
public NativePeerProvider() {
this(JPF.createConfig(new String[0]));
}
public NativePeerProvider(Config config) {
this.cache = new HashMap<String, ClassInfo>();
this.config = config;
this.peerPackages = getPeerPackages(this.config);
this.nativeJarFiles = loadNativeJarFiles(this.config);
}
static JarFile[] loadNativeJarFiles(Config config) {
List<JarFile> configJars = new LinkedList<JarFile>();
for (Object k : config.keySet()) {
if(((String) k).endsWith("native_classpath")) {
String[] pathElements = config.getStringArray((String) k);
for (String p : pathElements) {
try {
configJars.add(new JarFile(new File(p)));
} catch (IOException e) {
logger.warning("Failed to load jar file from path "
+ p + "\n\t" + e);
}
}
}
}
return configJars.toArray(new JarFile[0]);
}
static String[] getPeerPackages(Config config) {
String[] defPeerPackages = { MODEL_PACKAGE, "gov.nasa.jpf.jvm", DEFAULT_PACKAGE };
String[] packages = config.getStringArray("peer_packages", defPeerPackages);
// internalize
for (int i=0; i<packages.length; i++) {
if (packages[i].equals(MODEL_PACKAGE)) {
packages[i] = MODEL_PACKAGE;
} else if (packages[i].equals(DEFAULT_PACKAGE)) {
packages[i] = DEFAULT_PACKAGE;
}
}
return packages;
}
/**
* Tries to get a ClassInfo object associated with the given
* class. Returns null if it does not find anything.
*/
public ClassInfo loadNativePeerClass(ClassInfo cls) {
return loadNativePeerClass(cls.getName());
}
/**
* Tries to get a ClassInfo object associated with the given
* class name. Returns null if it does not find anything.
*/
public ClassInfo loadNativePeerClass(String name) {
ClassInfo peer = cache.get(name);
if(peer == null) {
peer = loadClassInfo(name);
cache.put(name, peer);
}
return peer;
}
/**
* Loads all the native peer classes.
*/
public Iterable<ClassInfo> loadNativePeerClasses() {
LinkedList<ClassInfo> peers = new LinkedList<ClassInfo>();
for (JarFile jar : nativeJarFiles) {
peers.addAll(loadFromJar(jar));
}
return peers;
}
Collection<ClassInfo> loadFromJar(JarFile jar) {
LinkedList<ClassInfo> peers = new LinkedList<ClassInfo>();
Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String name = entry.getName();
if (name.endsWith(".class") && name.startsWith(PEER_PREFIX)) {
// FIXME see separator char in windows inside jar files
String peerName = name.replace('/', '.');
System.out.println(peerName);
try {
byte[] classBytes = Util.getClassBytes(jar.getInputStream(entry));
ClassFile cf = new ClassFile(peerName, classBytes);
peers.add(new UnregisteredClassInfo(cf));
} catch (IOException e) {
logger.warning("error while loading class from " +
jar.getName() + "\n" + e.getMessage());
} catch (ClassFileException e) {
logger.warning("error while loading class from " +
jar.getName() + "\n" + e.getMessage());
}
}
}
return peers;
}
ClassInfo loadClassInfo(String name) {
String peerName = "JPF_" + name.replace('.', '_');
for (int i=0; i<peerPackages.length; i++) {
String peerClassName;
String pkg = peerPackages[i];
if (pkg == MODEL_PACKAGE) {
int j = name.lastIndexOf('.');
peerClassName = name.substring(0, j+1) + peerName;
} else if (pkg == DEFAULT_PACKAGE) {
peerClassName = peerName;
} else {
peerClassName = pkg + '.' + peerName;
}
for (JarFile jar : nativeJarFiles) {
ClassInfo ci = loadFromJar(jar, peerClassName);
if(ci != null &&
Modifier.isPublic(ci.getModifiers())) { // to avoid IllegalAccessExceptions
return ci;
}
}
}
return null; // nothing found
}
/**
* Loads from the given jar the requested class.
* Returns null if the class is not found
*/
static ClassInfo loadFromJar(JarFile jar, String className) {
// FIXME: check if on Windows the file separator inside jar files
// is / or \
String fileName = className.replace('.', '/') + ".class";
try {
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 " + jar.getName() + "\n" + e.getMessage());
return null;
} catch (ClassFileException e) {
logger.warning("error while loading class " + className +
" from jar file " + jar.getName() + "\n" + e.getMessage());
return null;
}
return null;
}
}