/*
* Copyright 2012, Thomas Kerber
*
* 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.
*/
package milk.jpatch;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import org.apache.bcel.Constants;
import org.apache.bcel.classfile.AnnotationElementValue;
import org.apache.bcel.classfile.AnnotationEntry;
import org.apache.bcel.classfile.ArrayElementValue;
import org.apache.bcel.classfile.ClassElementValue;
import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.Constant;
import org.apache.bcel.classfile.ConstantCP;
import org.apache.bcel.classfile.ConstantClass;
import org.apache.bcel.classfile.ConstantNameAndType;
import org.apache.bcel.classfile.ConstantPool;
import org.apache.bcel.classfile.ConstantUtf8;
import org.apache.bcel.classfile.ElementValue;
import org.apache.bcel.classfile.ElementValuePair;
import org.apache.bcel.classfile.EnumElementValue;
import org.apache.bcel.classfile.InnerClass;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.classfile.SimpleElementValue;
import org.apache.bcel.generic.Type;
/**
* Provides utility methods.
*
* @author Thomas Kerber
* @version 1.0.6
*/
public class Util{
/**
* The global logger for jpatch.
*/
public static final Logger logger = Logger.getLogger("milk.jpatch");
/**
*
* @param a An annotation entry.
* @return Its length in bytes.
*/
public static int getLength(AnnotationEntry a){
int length = 4; // type_index / num_element_value_pairs
for(ElementValuePair e : a.getElementValuePairs()){
length += 2; // element_name_index;
length += getLength(e.getValue());
}
return length;
}
/**
*
* @param ev An Element/Value pair
* @return Its length in bytes.
*/
public static int getLength(ElementValue ev){
int length = 1; // tag
if(ev instanceof SimpleElementValue)
length += 2; // const_value_index
else if(ev instanceof EnumElementValue)
length += 4; // type_name_index, const_name_index
else if(ev instanceof ClassElementValue)
length += 2; // class_info_index
else if(ev instanceof AnnotationElementValue)
length += getLength(((AnnotationElementValue)ev).
getAnnotationEntry());
else if(ev instanceof ArrayElementValue){
length += 2; // num_values
ArrayElementValue aev = (ArrayElementValue)ev;
for(ElementValue ev2 : aev.getElementValuesArray())
length += getLength(ev2);
}
return length;
}
/**
* A stand-in equals method.
* @param first
* @param second
* @return
*/
public static boolean equals(AnnotationEntry first,
AnnotationEntry second){
if(first == second)
return true;
if(first == null || second == null)
return false;
return first.toShortString().equals(second.toShortString());
}
/**
* A stand-in equals method.
* @param first
* @param second
* @param firstCP The constant pool belonging to the first inner class.
* @param secondCP The constant pool belonging to the second inner class.
* @return
*/
public static boolean equals(InnerClass first, InnerClass second,
ConstantPool firstCP, ConstantPool secondCP){
if(first == second)
return true;
if(first == null || second == null)
return false;
String innerClassFirst = firstCP.constantToString(
firstCP.getConstant(first.getInnerClassIndex()));
String innerClassSecond = secondCP.constantToString(
secondCP.getConstant(second.getInnerClassIndex()));
String outerClassFirst = firstCP.constantToString(
firstCP.getConstant(first.getOuterClassIndex()));
String outerClassSecond = secondCP.constantToString(
secondCP.getConstant(second.getOuterClassIndex()));
String nameFirst = firstCP.constantToString(
firstCP.getConstant(first.getInnerNameIndex()));
String nameSecond = secondCP.constantToString(
secondCP.getConstant(second.getInnerNameIndex()));
return innerClassFirst.equals(innerClassSecond) &&
outerClassFirst.equals(outerClassSecond) &&
nameFirst.equals(nameSecond) &&
first.getInnerAccessFlags() == second.getInnerAccessFlags();
}
/**
* A stand-in, temporary equals method.
*
* This method will be rendered obsolete by opcode diffs.
* @param c1
* @param c2
* @param map The CPoolMap for mapping from c2's environment to c1's.
* @return
*/
public static boolean equals(Code c1, Code c2, CPoolMap map){
if(c1 == c2)
return true;
if(c1 == null || c2 == null)
return false;
c2 = map.applyTo(c2);
return Arrays.equals(c1.getCode(), c2.getCode());
// TODO: this is to be removed once opcode modification patches are
// added.
}
/**
*
* @param map The cpool map (Convenience instead of passing map.new_)
* @param str The string to search for. Note: It must be guaranteed that
* the string will be found.
* @return The strings index in the constant pool.
*/
public static int findConstantStringIn(CPoolMap map, String str){
Constant[] constants = map.to.getConstantPool();
for(int i = 1; i < constants.length; i++){
Constant c = constants[i];
if(c instanceof ConstantUtf8 &&
((ConstantUtf8)c).getBytes().equals(str))
return i;
}
throw new IllegalStateException(
"Expected name (" + str + ") not found in constant pool.");
}
/**
*
* @param map The cpool map (Convenience instead of passing map.new_)
* @param name The class name to search for. Note: It mustbe guaranteed
* that the corresponding class will be found.
* @return the classes index in the constant pool.
*/
public static int findConstantClassIn(CPoolMap map, String name){
return findConstantClassIn(map, name, false);
}
/**
*
* @param map The cpool map (Convenience instead of passing map.new_)
* @param name The class name to search for. Note: It mustbe guaranteed
* that the corresponding class will be found.
* @param acceptError Whether or not to throw an exception if no result was
* found. Returns -1 else.
* @return the classes index in the constant pool.
*/
public static int findConstantClassIn(CPoolMap map, String name,
boolean acceptError){
Constant[] constants = map.to.getConstantPool();
for(int i = 1; i < constants.length; i++){
Constant c = constants[i];
if(c instanceof ConstantClass &&
((ConstantClass)c).getBytes(map.to).equals(name))
return i;
}
if(acceptError)
return -1;
throw new IllegalStateException(
"Expected name (" + name + ") not found in constant pool.");
}
/**
* Gets the signature of a method from its identifier and returner.
*
* @param identifier The methods identifier
* @param ret The methods returner
* @return The methods signature.
* @see getMethodReturn, getMethodIdentifier
*/
public static String getMethodSig(String identifier, String ret){
// Everything *after* open parens.
String sig = identifier.substring(identifier.indexOf('('));
return sig + ret;
}
/**
* Gets a methods returner.
*
* The returner is essentially the methods return type.
*
* The returner is defined as \1 in the regular expression
* \(.*\)(.+) for the methods signature.
* @param m The method.
* @return Its returner.
*/
public static String getMethodReturn(Method m){
String sig = m.getSignature();
// Get everything *after* close parens.
return sig.substring(sig.indexOf(')') + 1);
}
/**
* Gets a methods identifier.
*
* The identifier is essentially the signature without return type and the
* name.
*
* The identifier is defined as method_name + \1 in the regular expression
* (\(.*\)).+
* @param m
* @return
*/
public static String getMethodIdentifier(Method m){
String sig = m.getSignature();
// Get everything *before* close parens.
sig = sig.substring(0, sig.indexOf(')') + 1);
return m.getName() + sig;
}
public static String getMethodIdentifier(ConstantCP c, ConstantPool cp){
ConstantNameAndType cnat =
(ConstantNameAndType)cp.getConstant(c.getNameAndTypeIndex());
String sig = cnat.getSignature(cp);
sig = sig.substring(0, sig.indexOf(')') + 1);
return cnat.getName(cp) + sig;
}
/**
* Gets the interface names of a class in the way they are stored in the
* constant pool.
*
* JavaClass's method is slightly different, but leads to interfaces not
* being found in the cpool.
* @param j The class.
* @return The interfaces implemented by the class.
*/
public static String[] getInterfaceNamesProper(JavaClass j){
ConstantPool cpool = j.getConstantPool();
int[] indexes = j.getInterfaceIndices();
String[] names = new String[indexes.length];
for(int i = 0; i < indexes.length; i++)
names[i] = cpool.getConstantString(indexes[i],
Constants.CONSTANT_Class);
return names;
}
/**
* Loops an input stream into an output stream and closes both.
* @param in
* @param out
* @throws IOException
*/
public static void loop(InputStream in, OutputStream out)
throws IOException{
loop(in, out, true);
}
/**
* Loops an input stream into an output stream and optionally closes both.
* @param in
* @param out
* @param close
* @throws IOException
*/
public static void loop(InputStream in, OutputStream out, boolean close)
throws IOException{
byte[] buffer = new byte[1 << 10];
for(int len = in.read(buffer); len > 0; len = in.read(buffer))
out.write(buffer, 0, len);
if(!close)
return;
in.close();
out.close();
}
/**
*
* @param from
* @param to
* @return The relative path from "from" to "to".
*/
public static String getRelativePath(File from, File to){
return from.toURI().relativize(to.toURI()).getPath();
}
/**
*
* @param dir
* @return All file paths in "dir" and its subdirectories.
*/
public static List<String> listNamesIn(File dir){
return listNamesIn(dir, dir);
}
/**
*
* @param root
* @param dir
* @return All file paths in "dir" and its subdirectories, relative to
* "root".
*/
private static List<String> listNamesIn(File root, File dir){
List<String> ret = new ArrayList<String>();
for(File f : dir.listFiles()){
if(f.isDirectory())
ret.addAll(listNamesIn(root, f));
else
ret.add(getRelativePath(root, f));
}
return ret;
}
/**
*
* @return A temp directory.
* @throws IOException
*/
public static File getTempDir() throws IOException{
// So no, this isn't elegant, but really, why is it that whenever
// I look up something basic like this, all suggestions I get are
// either a minorly buggy code like this, or some massive third-party
// library...
File f = File.createTempFile("milk", "");
f.delete();
f.mkdir();
return f;
}
/**
*
* @param f1
* @param f2
* @return Whether "f1" and "f2" are content equal or not.
* @throws IOException
*/
public static boolean fileEquals(File f1, File f2) throws IOException{
if(f1 == f2)
return true;
if(f1 == null || f2 == null)
return false;
if(!f1.isFile() || !f2.isFile())
return false;
InputStream i1 = new FileInputStream(f1);
InputStream i2 = new FileInputStream(f2);
byte[] buff1 = new byte[1 << 10];
byte[] buff2 = new byte[1 << 10];
int len1;
int len2;
while(true){
len1 = i1.read(buff1);
len2 = i2.read(buff2);
if(len1 != len2){
i1.close();
i2.close();
return false;
}
if(len1 == 0)
break;
if(!Arrays.equals(buff1, buff2)){
i1.close();
i2.close();
return false;
}
}
i1.close();
i2.close();
return true;
}
/**
* Extracts a zipfile into a directory.
* @param zip The zipfile.
* @param dir The directory.
* @throws IOException
*/
public static void extractZip(File zip, File dir) throws IOException{
if(!dir.exists())
dir.mkdirs();
ZipInputStream in = new ZipInputStream(new FileInputStream(zip));
for(ZipEntry e = in.getNextEntry(); e != null; e = in.getNextEntry()){
if(e.isDirectory())
continue;
File outF = new File(dir, e.getName());
outF.getParentFile().mkdirs();
OutputStream out = new FileOutputStream(outF.getAbsoluteFile());
loop(in, out, false);
out.close();
in.closeEntry();
}
in.close();
}
/**
* Extracts a zipfile.
* @param zip The zipfile.
* @return The directory into which it gets extracted.
* @throws IOException
*/
public static File extractZip(File zip) throws IOException{
File outRoot = getTempDir();
extractZip(zip, outRoot);
return outRoot;
}
/**
* Packs a zipfile.
* @param zip The zipfile.
* @param dir The directory to pack.
* @throws IOException
*/
public static void packZip(File zip, File dir) throws IOException{
ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zip));
for(String name : listNamesIn(dir)){
ZipEntry entry = new ZipEntry(name);
out.putNextEntry(entry);
InputStream in = new FileInputStream(new File(dir, name));
loop(in, out, false);
in.close();
out.closeEntry();
}
out.close();
}
/**
* Deletes "dir" recursively.
* @param dir The directory to delete.
* @throws IOException
*/
public static void remDir(File dir) throws IOException{
for(File f : dir.listFiles()){
if(f.isDirectory())
remDir(f);
else
f.delete();
}
dir.delete();
}
/**
*
* @param m A method.
* @return The amount of variables with a fixed local variable index for m.
*/
public static int getFixedLocalVariableLength(Method m){
int currCount = 0;
if(!m.isStatic())
currCount++;
for(Type t : m.getArgumentTypes()){
currCount += t.getSize();
}
return currCount;
}
/**
* Transforms an integer list into an int array.
* @param list The list to transform.
* @return The int array.
*/
public static int[] toIntArray(List<Integer> list){
int[] ret = new int[list.size()];
for(int i = 0; i < ret.length; i++)
ret[i] = list.get(i);
return ret;
}
}