package com.francetelecom.rd.stubs.engine;
/*
* #%L
* Matos
* $Id:$
* $HeadURL:$
* %%
* Copyright (C) 2008 - 2014 Orange SA
* %%
* 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 java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Pattern;
/**
* This classloader relocates all the classes it loads in a given (one-level) package.
* This avoid conflict with system classes.
*
* The trick does not work on two classes : String because they are treated specifically
* as constants (eg. with the ldc machine instruction).
* Object which is the only allowed root class. The limitation on Object could be overcomed
* by giving an ancestor to the relocated Object.
* @author Pierre Cregut
*
*/
public class RelocatingClassLoader extends ClassLoader {
final private String [] classpath;
private final static boolean debug = false;
private Map<String,String> nameToId = new HashMap<String,String> ();
private Map<String,String> idToJar = new HashMap<String,String> ();
final private String packagePrefix;
private Pattern antipatternGeneric;
private boolean complaining = true;
final static private Set<String> discarded_names = new HashSet<String>();
{
for(String name:PatchConstantPool.RESERVED_CLASS) { discarded_names.add(name.replace('/','.'));}
}
/**
* Constructor. Takes the classpath where the relocated classes are found and
* a prefix for relocation.
* @param classpath
* @param packagePrefix
* @throws IOException
* @throws ClassNotFoundException
*/
public RelocatingClassLoader(String [] classpath, String packagePrefix) throws IOException, ClassNotFoundException {
this.packagePrefix = packagePrefix;
this.classpath = classpath;
antipatternGeneric = Pattern.compile(packagePrefix + "[./]");
}
/**
* Toggle the flag that make the loader not complain when it does not find the class.
*/
public void setSilent() { complaining = false; }
/**
* This function should be applied on any regular string extracted from the byte
* code as it has been altered by the transformation.
* @param s
* @return
*/
public String restoreString(String s) {
return antipatternGeneric.matcher(s).replaceAll("");
}
/**
* Name translation from internal syntax of files to regular class names.
* @param id
* @return
*/
private String idToName(String id) {
id = id.substring(0, id.lastIndexOf('.'));
return id.replace('/', '.');
}
/**
* Builds a table of what is known about current methods and jars.
* @return a set of classes names declared here.
* @throws IOException
*/
public Set <String> initLookup() throws IOException {
for(String jarName : classpath) {
if (! new File(jarName).exists()) {
throw new IOException("Cannot find file " + jarName);
}
JarFile jarFile = new JarFile(jarName);
Enumeration <JarEntry> e = jarFile.entries();
while(e.hasMoreElements()) {
JarEntry jarEntry = e.nextElement();
String id = jarEntry.getName();
if (id.endsWith(".class")) {
String name = idToName(id);
if (discarded_names.contains(name)) continue;
name = packagePrefix + "." + name;
nameToId.put(name, id);
idToJar.put(id, jarName);
}
}
jarFile.close();
}
return nameToId.keySet();
}
@Override
protected Class <?> findClass(String queried) throws ClassNotFoundException {
JarFile jarFile = null;
try {
String filename = restoreString(queried);
if (filename.equals("java.lang.Enum")) return Class.forName("java.lang.Enum");
filename = filename.replace('.', '/') + ".class";
String jarName = idToJar.get(filename);
if (jarName == null) {
if (complaining ) {
System.err.println("CANNOT FIND " + queried + " " + filename);
}
throw new IOException("Cannot find file for " + queried + "(" + filename + ")");
}
jarFile = new JarFile(jarName);
JarEntry entry = jarFile.getJarEntry(filename);
byte [] rawCode = new byte[(int) entry.getSize()];
InputStream is = jarFile.getInputStream(entry);
int pos = 0;
int read;
while((read = is.read(rawCode,pos,rawCode.length - pos)) != 0) {
pos += read;
if (pos == rawCode.length) break;
}
jarFile.close();
PatchConstantPool pcp = new PatchConstantPool(queried, rawCode, packagePrefix);
byte [] patchedCode = pcp.transform();
if (debug) {
File file = new File(filename);
File folder = file.getParentFile();
folder.mkdirs();
FileOutputStream out = new FileOutputStream(file);
out.write(patchedCode, 0, patchedCode.length);
out.close();
}
Class <?> result = this.defineClass(queried, patchedCode, 0, patchedCode.length);
return result;
} catch (IOException e) {
if (jarFile != null) try { jarFile.close(); } catch (IOException ee) {};
throw new ClassNotFoundException("IOProblem: " + e.getMessage());
}
}
/**
* Test entry point
* @param args
* @throws Exception
*/
public static void main (String [] args) throws Exception {
String prefix = "oLaB";
String [] jars = new String [] {
"Android/core.jar",
"Android/framework.jar",
"Android/services.jar",
"Android/ext.jar"
};
RelocatingClassLoader cl = new RelocatingClassLoader(jars, prefix);
ReflexUtil rf = new ReflexUtil(prefix, null, "");
ClassDumper dumper = new ClassDumper("out", rf, null);
Set <String> everyone = cl.initLookup();
int count = 0;
for(String name : everyone) {
try {
dumper.dumpClass(cl.loadClass(name, true));
} catch (Throwable e) {
System.err.println("============== CANNOT HANDLE " + name + " =============");
e.printStackTrace();
count ++;
}
}
System.err.println("Errors = " + count + "/" + dumper.count);
}
}