| /*========================================================================= |
| * Copyright (c) 2010-2014 Pivotal Software, Inc. All Rights Reserved. |
| * This product is protected by U.S. and international copyright |
| * and intellectual property laws. Pivotal products are covered by |
| * one or more patents listed at http://www.pivotal.io/patents. |
| *========================================================================= |
| */ |
| package com.gemstone.gemfire.codeAnalysis;
|
| import java.io.BufferedReader;
|
| import java.io.BufferedWriter;
|
| import java.io.File;
|
| import java.io.FileInputStream; |
| import java.io.FileReader;
|
| import java.io.FileWriter;
|
| import java.io.IOException;
|
| import java.io.LineNumberReader;
|
| import java.util.ArrayList;
|
| import java.util.Collections;
|
| import java.util.Enumeration;
|
| import java.util.HashMap;
|
| import java.util.Iterator;
|
| import java.util.LinkedList;
|
| import java.util.List;
|
| import java.util.Map;
|
| import java.util.jar.JarEntry;
|
| import java.util.jar.JarFile;
|
| |
| import com.gemstone.gemfire.codeAnalysis.decode.CompiledClass;
|
| import com.gemstone.gemfire.codeAnalysis.decode.CompiledField;
|
| import com.gemstone.gemfire.codeAnalysis.decode.CompiledMethod;
|
|
|
|
|
|
|
| public class CompiledClassUtils {
|
| /**
|
| * Parse the given class files and return a map of name->Dclass. Any
|
| * IO exceptions are consumed by this method and written to stderr.
|
| * @param classFiles
|
| * @return the parsed classes
|
| */
|
| public static Map<String, CompiledClass> parseClassFiles(List<File> classFiles) {
|
| Map<String, CompiledClass> result = new HashMap<String, CompiledClass>();
|
|
|
| for (File file: classFiles) {
|
| try {
|
| CompiledClass parsed = CompiledClass.getInstance(file);
|
| if (!parsed.isInterface()) {
|
| result.put(parsed.fullyQualifiedName(), parsed);
|
| }
|
| } catch (IOException e) {
|
| System.err.println("Exception while parsing " + file.getName() + ": " + e.getMessage());
|
| }
|
| }
|
| return result;
|
| }
|
|
|
| /**
|
| * Parse the files in the given jar file and return a map of name->CompiledClass.
|
| * Any IO exceptions are consumed by this method and written to stderr.
|
| * @param jar the jar file holding classes
|
| */
|
| public static Map<String, CompiledClass> parseClassFilesInJar(File jar) {
|
| Map<String, CompiledClass> result = new HashMap<String, CompiledClass>();
|
| try {
|
| JarFile jarfile = new JarFile(jar);
|
| for (Enumeration<JarEntry> entries=jarfile.entries(); entries.hasMoreElements(); ) {
|
| JarEntry entry = entries.nextElement();
|
| if (entry.getName().endsWith(".class")) {
|
| try {
|
| CompiledClass parsed = CompiledClass.getInstance(jarfile.getInputStream(entry));
|
| if (!parsed.isInterface()) {
|
| result.put(parsed.fullyQualifiedName(), parsed);
|
| }
|
| } catch (IOException e) {
|
| System.err.println("Exception while parsing " + entry.getName() + ": " + e.getMessage());
|
| }
|
| }
|
| }
|
| } catch (IOException e) {
|
| System.err.println("Error opening jar file:");
|
| e.printStackTrace(System.err);
|
| }
|
| return result;
|
| }
|
|
|
| /** |
| * Parse the files in the given jar file and return a map of name->CompiledClass. |
| * Any IO exceptions are consumed by this method and written to stderr. |
| * @param jar the jar file holding classes |
| */ |
| public static Map<String, CompiledClass> parseClassFilesInDir(File buildDir) { |
| Map<String, CompiledClass> result = new HashMap<String, CompiledClass>(); |
| for (File entry: buildDir.listFiles()) { |
| if (entry.isDirectory()) { |
| result.putAll(parseClassFilesInDir(entry)); |
| } |
| else if (entry.getName().endsWith(".class")) { |
| try { |
| CompiledClass parsed = CompiledClass.getInstance(new FileInputStream(entry)); |
| if (!parsed.isInterface()) { |
| result.put(parsed.fullyQualifiedName(), parsed); |
| } |
| } catch (IOException e) { |
| System.err.println("Exception while parsing " + entry.getName() + ": " + e.getMessage()); |
| } |
| } |
| } |
| return result; |
| } |
| |
| /**
|
| * returns a collection of all of the .class files in the given list
|
| * of files and directories.
|
| *
|
| * @param filenames a list of the files and directories to examine
|
| * @param recursive whether to recurse into subdirectories
|
| * @return a sorted list of the .class files found
|
| */
|
| public static List<File> findClassFiles(String parentPath, String[] filenames, boolean recursive) {
|
| // Grab classes and Expand directory names found in list
|
| List<File> classFiles = new ArrayList<File>();
|
| for (int i = 0; i < filenames.length; i ++) {
|
| File f = new File(parentPath+filenames[i]);
|
| String n = f.getAbsolutePath();
|
| if (!f.exists()) {
|
| System.err.println("File " + n + " does not exist - skipping");
|
| continue;
|
| }
|
| if (f.isFile() && f.getName().endsWith(".class")) {
|
| classFiles.add(f);
|
| continue;
|
| }
|
| if (f.isDirectory() && recursive) {
|
| classFiles.addAll(findClassFiles(f.getAbsolutePath()+"/", f.list(), true));
|
| }
|
| }
|
| Collections.sort(classFiles);
|
| return classFiles;
|
| }
|
|
|
| public static List<ClassAndMethodDetails> loadClassesAndMethods(File file) throws IOException {
|
| List<ClassAndMethodDetails> result = new LinkedList<ClassAndMethodDetails>();
|
| FileReader fr = new FileReader(file);
|
| LineNumberReader in = new LineNumberReader(fr);
|
| ClassAndMethodDetails cam;
|
| while ((cam = ClassAndMethodDetails.create(in)) != null) {
|
| result.add(cam);
|
| }
|
| fr.close();
|
| return result;
|
| }
|
|
|
| public static String diffSortedClassesAndMethods(
|
| List<ClassAndMethodDetails> goldRecord, List<ClassAndMethods> toDatas) throws IOException {
|
|
|
| StringBuilder newClassesSb = new StringBuilder(10000);
|
| StringBuilder changedClassesSb = new StringBuilder(10000);
|
| newClassesSb.append("New or moved classes----------------------------------------\n");
|
| int newBase = newClassesSb.length();
|
| changedClassesSb.append("Modified classes--------------------------------------------\n");
|
| int changedBase = changedClassesSb.length();
|
|
|
| Iterator<ClassAndMethods> it = toDatas.iterator();
|
| ClassAndMethods newclass = null;
|
|
|
| for (ClassAndMethodDetails gold: goldRecord) {
|
| if (newclass == null) {
|
| if (!it.hasNext()) {
|
| changedClassesSb.append(gold).append(": deleted or moved\n");
|
| continue;
|
| }
|
| newclass = it.next();
|
| }
|
| int comparison = -1;
|
| while (newclass != null
|
| && (comparison=gold.className.compareTo(newclass.dclass.fullyQualifiedName())) > 0) {
|
| newClassesSb.append(newclass).append("\n");
|
| if (it.hasNext()) {
|
| newclass = it.next();
|
| } else {
|
| newclass = null;
|
| }
|
| }
|
| if (comparison == 0) {
|
| ClassAndMethods nc = newclass;
|
| newclass = null;
|
| if (gold.methodCode.size() != nc.numMethods()) {
|
| changedClassesSb.append(nc).append(": method count\n");
|
| continue;
|
| }
|
| boolean comma = false;
|
| for (Map.Entry<String, CompiledMethod> entry: nc.methods.entrySet()) {
|
| CompiledMethod method = entry.getValue();
|
| String name = method.name();
|
| byte[] goldCode = gold.methodCode.get(name);
|
| if (goldCode == null) {
|
| if (comma) {
|
| changedClassesSb.append(", and ");
|
| } else {
|
| changedClassesSb.append(nc).append(": ");
|
| comma = true;
|
| }
|
| changedClassesSb.append(name).append(" was added");
|
| continue; // only report one diff per class
|
| }
|
| String diff;
|
| if ((diff = codeDiff(goldCode, method.getCode().code)) != null) {
|
| if (comma) {
|
| changedClassesSb.append(", and ");
|
| } else {
|
| changedClassesSb.append(nc).append(": ");
|
| comma = true;
|
| }
|
| changedClassesSb.append(name).append(diff);
|
| continue;
|
| }
|
| }
|
| for (Map.Entry<String, byte[]> entry: gold.methodCode.entrySet()) {
|
| if (!nc.methods.containsKey(entry.getKey())) {
|
| if (comma) {
|
| changedClassesSb.append(", and ");
|
| } else {
|
| changedClassesSb.append(nc).append(": ");
|
| }
|
| changedClassesSb.append(entry.getKey()).append(" is missing");
|
| }
|
| }
|
| if (comma) {
|
| changedClassesSb.append("\n");
|
| }
|
| }
|
| }
|
| while (it.hasNext()) {
|
| newclass = it.next();
|
| newClassesSb.append(newclass).append(": new class\n");
|
| }
|
| String result = "";
|
| if (newClassesSb.length() > newBase) {
|
| if (changedClassesSb.length() > changedBase) {
|
| newClassesSb.append("\n");
|
| newClassesSb.append(changedClassesSb);
|
| }
|
| result = newClassesSb.toString();
|
| } else if (changedClassesSb.length() > changedBase) {
|
| result = changedClassesSb.toString();
|
| }
|
| return result;
|
| }
|
|
|
|
|
|
|
| public static void storeClassesAndMethods(List<ClassAndMethods> cams,
|
| File file) throws IOException {
|
| FileWriter fw = new FileWriter(file);
|
| BufferedWriter out = new BufferedWriter(fw);
|
| for (ClassAndMethods entry: cams) {
|
| out.append(ClassAndMethodDetails.convertForStoring(entry));
|
| out.newLine();
|
| }
|
| out.flush();
|
| out.close();
|
| }
|
|
|
| static String codeDiff(byte[] method1, byte[] method2) {
|
| if (method1.length != method2.length) {
|
| return " (len="+method2.length+",expected="+method1.length+")";
|
| }
|
| // for (int i=0; i<method1.length; i++) {
|
| // if (method1[i] != method2[i]) {
|
| // return "(code["+i+"])";
|
| // }
|
| // }
|
| return null;
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| public static List<ClassAndVariableDetails> loadClassesAndVariables(File file) throws IOException {
|
| List<ClassAndVariableDetails> result = new LinkedList<ClassAndVariableDetails>();
|
| FileReader fr = new FileReader(file);
|
| BufferedReader in = new BufferedReader(fr);
|
| String line;
|
| while ((line = in.readLine()) != null) {
|
| line = line.trim();
|
| if (line.startsWith("#") || line.startsWith("//")) {
|
| // comment line
|
| }
|
| else {
|
| result.add(new ClassAndVariableDetails(line));
|
| }
|
| }
|
| fr.close();
|
| return result;
|
| }
|
|
|
| public static String diffSortedClassesAndVariables(
|
| List<ClassAndVariableDetails> goldRecord, List<ClassAndVariables> cavs) throws IOException {
|
|
|
| StringBuilder newClassesSb = new StringBuilder(10000);
|
| StringBuilder changedClassesSb = new StringBuilder(10000);
|
| newClassesSb.append("New or moved classes----------------------------------------\n");
|
| int newBase = newClassesSb.length();
|
| changedClassesSb.append("Modified classes--------------------------------------------\n");
|
| int changedBase = changedClassesSb.length();
|
|
|
| Iterator<ClassAndVariables> it = cavs.iterator();
|
| ClassAndVariables newclass = null;
|
|
|
| List<String> added = new ArrayList<String>();
|
| List<String> removed = new ArrayList<String>();
|
| List<String> changed = new ArrayList<String>();
|
|
|
| for (ClassAndVariableDetails gold: goldRecord) {
|
| added.clear(); removed.clear(); changed.clear();
|
| if (newclass == null) {
|
| if (!it.hasNext()) {
|
| changedClassesSb.append(gold).append(": deleted or moved\n");
|
| continue;
|
| }
|
| newclass = it.next();
|
| }
|
| int comparison = -1;
|
| while (newclass != null
|
| && (comparison=gold.className.compareTo(newclass.dclass.fullyQualifiedName())) > 0) {
|
| newClassesSb.append(ClassAndVariableDetails.convertForStoring(newclass)).append("\n");
|
| newclass = null;
|
| if (it.hasNext()) {
|
| newclass = it.next();
|
| }
|
| }
|
| if (comparison == 0) {
|
| ClassAndVariables nc = newclass;
|
| newclass = null;
|
| // if (gold.variables.size() != nc.variables.size()) {
|
| // changedClassesSb.append(nc).append(": field count\n");
|
| // continue;
|
| // }
|
| for (Map.Entry<String, CompiledField> entry: nc.variables.entrySet()) {
|
| CompiledField field = entry.getValue();
|
| String name = entry.getKey();
|
| String type = gold.variables.get(name);
|
| if (type == null) {
|
| // if (comma) {
|
| // changedClassesSb.append(", and ");
|
| // } else {
|
| // changedClassesSb.append(nc).append(": ");
|
| // comma = true;
|
| // }
|
| added.add(name);
|
| continue; // only report one diff per class
|
| }
|
| String newType = field.descriptor();
|
| if (!newType.equals(type)) {
|
| changed.add(name + " type changed to " + newType);
|
| continue;
|
| }
|
| }
|
| for (Map.Entry<String, String> entry: gold.variables.entrySet()) {
|
| if (!nc.variables.containsKey(entry.getKey())) {
|
| removed.add(entry.getKey());
|
| }
|
| }
|
| if (!(added.isEmpty() && removed.isEmpty() && changed.isEmpty())) {
|
| changedClassesSb.append(nc).append('\n');
|
| }
|
| if (!added.isEmpty()) {
|
| changedClassesSb.append("\t\t added fields: ").append(added).append('\n');
|
| }
|
| if (!changed.isEmpty()) {
|
| changedClassesSb.append("\t\t changed fields: ").append(changed).append('\n');
|
| }
|
| if (gold.hasSerialVersionUID) {
|
| if (nc.hasSerialVersionUID) {
|
| if (!Long.valueOf(gold.serialVersionUID).equals(nc.serialVersionUID)) {
|
| changedClassesSb.append("\t\t " + nc.dclass.fullyQualifiedName() + " serialVersionUID was changed from " + gold.serialVersionUID + " to " + nc.serialVersionUID + " this may break client/server compatibility as well as server/server compatibility \n");
|
| }
|
| }
|
| else {
|
| changedClassesSb.append("\t\t " + nc.dclass.fullyQualifiedName() + " serialVersionUID was removed, this may break client/server compatibility as well as server/server compatibility \n");
|
| }
|
| }
|
| else {
|
| if (nc.hasSerialVersionUID) {
|
| changedClassesSb.append("\t\t " + nc.dclass.fullyQualifiedName() + " serialVersionUID was added \n");
|
| }
|
| }
|
| }
|
| }
|
| while (it.hasNext()) {
|
| newclass = it.next();
|
| newClassesSb.append(ClassAndVariableDetails.convertForStoring(newclass)).append(": new class\n");
|
| }
|
| String result = "";
|
| if (newClassesSb.length() > newBase) {
|
| if (changedClassesSb.length() > changedBase) {
|
| newClassesSb.append("\n");
|
| newClassesSb.append(changedClassesSb);
|
| }
|
| result = newClassesSb.toString();
|
| } else if (changedClassesSb.length() > changedBase) {
|
| result = changedClassesSb.toString();
|
| }
|
| return result;
|
| }
|
|
|
|
|
|
|
| public static void storeClassesAndVariables(List<ClassAndVariables> cams,
|
| File file) throws IOException {
|
| FileWriter fw = new FileWriter(file);
|
| BufferedWriter out = new BufferedWriter(fw);
|
| for (ClassAndVariables entry: cams) {
|
| out.append(ClassAndVariableDetails.convertForStoring(entry));
|
| out.newLine();
|
| }
|
| out.flush();
|
| out.close();
|
| }
|
|
|
| } |