blob: f20617873c4f11b7301e62aa35568ebd7babfb2d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You 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 org.apache.geode.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.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.apache.geode.codeAnalysis.decode.CompiledClass;
import org.apache.geode.codeAnalysis.decode.CompiledField;
import org.apache.geode.codeAnalysis.decode.CompiledMethod;
import org.apache.geode.internal.serialization.Version;
public class CompiledClassUtils {
static Set<String> allowedDataSerializerMethods;
static {
allowedDataSerializerMethods = new HashSet<>();
Version.getAllVersions().iterator().forEachRemaining((version) -> {
allowedDataSerializerMethods.add("toDataPre_" + version.getMethodSuffix());
allowedDataSerializerMethods.add("fromDataPre_" + version.getMethodSuffix());
});
}
/**
* Parse the given class files and return a map of name->Dclass. Any IO exceptions are consumed by
* this method and written to stderr.
*
* @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.
*/
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.methods.size() != nc.numMethods()) {
changedClassesSb.append(nc).append(": method count (expected " + gold.methods.size()
+ " but found " + nc.numMethods() + ")\n");
continue;
}
boolean comma = false;
for (Map.Entry<String, CompiledMethod> entry : nc.methods.entrySet()) {
CompiledMethod method = entry.getValue();
String methodName = method.name();
if (!methodName.equals("toData") && !methodName.equals("fromData")
&& !allowedDataSerializerMethods.contains(methodName)) {
if (comma) {
changedClassesSb.append(", and ");
} else {
changedClassesSb.append(nc).append(": ");
comma = true;
}
changedClassesSb.append(methodName)
.append(" is not a valid method name - doesn't match any Version");
continue;
}
Integer goldCode = gold.methods.get(methodName);
if (goldCode == null) {
if (comma) {
changedClassesSb.append(", and ");
} else {
changedClassesSb.append(nc).append(": ");
comma = true;
}
changedClassesSb.append(methodName).append(" was added");
continue; // only report one diff per class
}
if (goldCode != method.getCode().code.length) {
if (comma) {
changedClassesSb.append(", and ");
} else {
changedClassesSb.append(nc).append(": ");
comma = true;
}
changedClassesSb.append(methodName)
.append(" (len=" + method.getCode().code.length + ",expected=" + goldCode + ")");
continue;
}
}
for (Map.Entry<String, Integer> entry : gold.methods.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();
}
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) {
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<>();
List<String> removed = new ArrayList<>();
List<String> changed = new ArrayList<>();
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;
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) {
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();
}
}