/*
* 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.skimmed;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.Constant;
import org.apache.bcel.classfile.ConstantCP;
import org.apache.bcel.classfile.ConstantClass;
import org.apache.bcel.classfile.ConstantFieldref;
import org.apache.bcel.classfile.ConstantInterfaceMethodref;
import org.apache.bcel.classfile.ConstantMethodref;
import org.apache.bcel.classfile.ConstantNameAndType;
import org.apache.bcel.classfile.ConstantPool;
import milk.jpatch.classLevel.FieldAdd;
import milk.jpatch.classLevel.FieldPatch;
import milk.jpatch.classLevel.MethodAdd;
import milk.jpatch.classLevel.MethodPatch;
import milk.jpatch.fileLevel.ClassPatch;
import milk.skimmed.Key.KeyType;
/**
* Hunts for keys to identify jarps as requirements.
*
* Basically, unique parts of code are searched for and resolved as a
* requirement.
* @author Thomas Kerber
* @version 1.0.0
*/
public class KeyHunter{
/**
* Resolves associated jarps for a mod.
* @param modRoot The mod to resolve jarps for.
* @return The requirement jarps.
* @throws IOException
*/
public static Set<Jarp> resolveAssocs(File modRoot) throws IOException{
// TODO: mebbe store keys in between or so depends on how long
// generation takes.
return resolveAssocs(modRoot, genKeys(Skimmed.CONFIG.getJarpList()));
}
/**
* Resolves associated jarps for a mod.
* @param modRoot The mod to resolve jarps for.
* @param keys The keys to use for the resolution.
* @return The requirement jarps.
* @throws IOException
*/
public static Set<Jarp> resolveAssocs(File modRoot, List<Key> keys)
throws IOException{
Set<Jarp> ret = new HashSet<Jarp>();
for(File f : modRoot.listFiles()){
if(f.isFile() && f.getName().toLowerCase().endsWith(".class")){
ret.addAll(resolveAssocs(
new ClassParser(f.getAbsolutePath()).parse().
getConstantPool(),
keys));
}
}
return ret;
}
/**
* Resolves associated jarps for a constant pool.
* @param cp The constant pool to resolve jarps for.
* @param keys The keys to use for the resolution.
* @return The requirement jarps.
*/
public static Set<Jarp> resolveAssocs(ConstantPool cp, List<Key> keys){
Set<Jarp> ret = new HashSet<Jarp>();
for(Constant c : cp.getConstantPool()){
if(c instanceof ConstantClass){
String name = ((ConstantClass)c).getBytes(cp);
for(Key key : keys){
if(key.matches(name, KeyType.CLASS)){
ret.add(key.assoc);
break;
}
}
}
else if(c instanceof ConstantFieldref){
ConstantFieldref fieldC = (ConstantFieldref)c;
String path = fieldC.getClass(cp) + "." +
((ConstantNameAndType)cp.getConstant(
fieldC.getNameAndTypeIndex())).getName(cp);
for(Key key : keys){
if(key.matches(path, KeyType.FIELD)){
ret.add(key.assoc);
break;
}
}
}
else if(c instanceof ConstantMethodref ||
c instanceof ConstantInterfaceMethodref){
ConstantCP methodC = (ConstantCP)c;
String path = methodC.getClass(cp) + "." +
milk.jpatch.Util.getMethodIdentifier(methodC, cp);
for(Key key : keys){
if(key.matches(path, KeyType.METHOD)){
ret.add(key.assoc);
break;
}
}
}
}
return ret;
}
/**
* Generates keys.
* @param jarps The jarps to generate keys from.
* @return The keys generated.
*/
public static List<Key> genKeys(List<Jarp> jarps){
List<Key> ret = new ArrayList<Key>();
for(Jarp jarp : jarps){
try{
File extractedJarp =
milk.jpatch.Util.extractZip(jarp.location);
ret.addAll(findKeysInJarp(jarp, extractedJarp, ""));
// TODO: mebbe I forgot to delete alot of tmp st00f.
milk.jpatch.Util.remDir(extractedJarp);
}
catch(Exception e){
Util.logger.log(
Level.WARNING,
"Failed to find keys for jarp '" + jarp.getName() +
"'.",
e);
}
}
return ret;
}
/**
* Finds keys in a jarp.
* @param assoc The jarp to associate to the keys.
* @param extractedJarp The extracted jarp.
* @param currpath The current path for key names.
* @return The keys contained in the jarp.
* @throws IOException
*/
public static List<Key> findKeysInJarp(Jarp assoc, File extractedJarp,
String currpath) throws IOException{
List<Key> ret = new ArrayList<Key>();
for(File f : extractedJarp.listFiles()){
if(f.isFile()){
if(f.getName().toLowerCase().endsWith(".class")){
ret.add(new Key(
assoc,
currpath + "." + f.getName().
substring(0, f.getName().length() - 6),
KeyType.CLASS));
}
else if(f.getName().toLowerCase().endsWith(
ClassPatch.POSTFIX)){
ClassPatch cp = ClassPatch.deserializeAt(null,
f.getAbsoluteFile());
ret.addAll(findKeysInJCP(
assoc,
cp,
currpath + "." + f.getName().substring(
f.getName().length() -
ClassPatch.POSTFIX.length())));
}
}
else if(f.isDirectory()){
ret.addAll(findKeysInJarp(
assoc,
extractedJarp,
currpath + "." + f.getName()));
}
}
return ret;
}
/**
* Finds keys in a jcp.
* @param assoc The associated jarp.
* @param cp The JCP.
* @param currpath The current path for the key name.
* @return The keys contained in the JCP.
*/
public static List<Key> findKeysInJCP(Jarp assoc, ClassPatch cp,
String currpath){
List<Key> ret = new ArrayList<Key>();
for(FieldPatch fp : cp.getFieldsPatch().getPatches()){
if(fp instanceof FieldAdd)
ret.add(new Key(assoc, currpath + "." + fp.getName(),
KeyType.FIELD));
}
for(MethodPatch mp : cp.getMethodsPatch().getPatches()){
if(mp instanceof MethodAdd)
ret.add(new Key(assoc, currpath + "." + mp.getIdentifier(),
KeyType.METHOD));
}
return ret;
}
}