/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.max.vm.log.hosted;
import java.io.*;
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;
import com.sun.max.annotate.*;
import com.sun.max.ide.*;
import com.sun.max.io.*;
import com.sun.max.lang.*;
import com.sun.max.program.*;
import com.sun.max.unsafe.*;
import com.sun.max.vm.actor.*;
import com.sun.max.vm.actor.holder.*;
import com.sun.max.vm.log.*;
import com.sun.max.vm.thread.*;
/**
* Checks/Generates the boilerplate of a {@link VMLogger} implementation from
* an interface specified by the developer.
*
*/
@HOSTED_ONLY
public class VMLoggerGenerator {
private static final String INDENT4 = " ";
private static final String INDENT8 = INDENT4 + INDENT4;
private static final String INDENT12 = INDENT8 + INDENT4;
private static final String INDENT16 = INDENT12 + INDENT4;
private static final String INDENT20 = INDENT16 + INDENT4;
private static final String[] INDENTS = new String[] {"", INDENT4, INDENT8, INDENT12, INDENT16, INDENT20};
private static File workspace;
/**
* Support for generic parameter types.
*/
private static class GClass implements Comparable<GClass> {
Class<?> klass;
Type type;
GClass(Class<?> klass, Type type) {
this.klass = klass;
this.type = type;
}
boolean isGeneric() {
return klass != type;
}
private String getTypeName() {
return getTypeName(true);
}
private String getTypeName(boolean generic) {
if (type == klass) {
return klass.getSimpleName();
} else {
ParameterizedType pType = (ParameterizedType) type;
Type[] pTypeArgs = pType.getActualTypeArguments();
Class<?> pTypeArg0Class = (Class) pTypeArgs[0];
String result = klass.getSimpleName();
if (generic) {
result += "<";
}
result += pTypeArg0Class.getSimpleName();
if (generic) {
result += ">";
}
return result;
}
}
@Override
public boolean equals(Object other) {
GClass otherGClass = (GClass) other;
if (klass != otherGClass.klass) {
return false;
}
if (type != otherGClass.type) {
return false;
}
return true;
}
@Override
public int hashCode() {
int r = klass.hashCode();
if (type != klass) {
r ^= type.hashCode();
}
return r;
}
private static String getTypeName(Class<?> klass, Type type) {
return new GClass(klass, type).getTypeName();
}
@Override
public int compareTo(GClass o) {
return getTypeName(false).compareTo(o.getTypeName(false));
}
}
private static boolean generate(boolean checkOnly, Class source, File sourceProject, ArrayList<Class<?>> loggerInterfacesArg) throws Exception {
File base = new File(sourceProject, "src");
File outputFile = new File(base, source.getName().replace('.', File.separatorChar) + ".java").getAbsoluteFile();
Class<?>[] loggerInterfaces = loggerInterfacesArg.toArray(new Class<?>[loggerInterfacesArg.size()]);
sort(loggerInterfaces);
Writer writer = new StringWriter();
PrintWriter out = new PrintWriter(writer);
for (Class loggerInterface : loggerInterfaces) {
VMLoggerInterface vmLoggerInterface = getVMLoggerInterface(loggerInterface);
String autoName = getRootName(loggerInterface) + "Auto";
Method[] methods = sort(loggerInterface.getDeclaredMethods());
Set<Class> enumArgMap = new HashSet<Class>();
int[] refMaps = computeRefMaps(methods, enumArgMap);
out.format("%sprivate static abstract class %s extends %s {%n", INDENT4, autoName, sanitizedName(vmLoggerInterface.parent().getName()));
OperationEnumInfo[] enumMap = outOperationEnum(out, methods);
Set<GClass> customTypes = new TreeSet<GClass>();
out.printf("%sprivate static final int[] REFMAPS = %s;%n%n", INDENT8, refMapArray(INDENT8, refMaps));
String optionDescriptionParam = ", String optionDescription";
String openDescriptionArg = ", optionDescription";
if (vmLoggerInterface.hidden()) {
optionDescriptionParam = "";
openDescriptionArg = "";
}
out.printf("%sprotected %s(String name%s) {%n", INDENT8, autoName, optionDescriptionParam);
out.printf("%ssuper(name, Operation.VALUES.length%s, REFMAPS);%n", INDENT12, openDescriptionArg);
out.printf("%s}%n%n", INDENT8);
if (vmLoggerInterface.defaultConstructor()) {
out.printf("%sprotected %s() {%n", INDENT8, autoName);
out.printf("%s}%n%n", INDENT8);
}
out.printf("%s@Override%n", INDENT8);
out.printf("%spublic String operationName(int opCode) {%n", INDENT8);
out.printf("%sreturn Operation.VALUES[opCode].name();%n", INDENT12);
out.printf("%s}%n%n", INDENT8);
// where we store the string for the case body of the trace method
ArrayList<String> traceCaseBodies = new ArrayList<String>(methods.length);
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
String uMethodName = operationName(method);
String uEnumName = enumMap[i].uName;
if (isOverride(uEnumName, vmLoggerInterface)) {
out.printf("%s@Override%n", INDENT8);
}
out.printf("%s@INLINE%n", INDENT8);
out.printf("%spublic final void log%s(", INDENT8, uMethodName);
Class<?>[] parameters = method.getParameterTypes();
Type[] genericParams = method.getGenericParameterTypes();
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
String[] formalNames = new String[parameters.length + 1];
int argIndex = 1;
for (Class< ? > parameter : parameters) {
Annotation[] paramAnnotations = parameterAnnotations[argIndex - 1];
if (argIndex > 1) {
out.print(",");
if (argIndex % 6 == 0) {
out.printf("%n%s", INDENT16);
} else {
out.print(' ');
}
}
formalNames[argIndex] = formalName(paramAnnotations, argIndex);
out.printf("%s %s", parameter.getSimpleName(), formalNames[argIndex]);
argIndex++;
}
out.print(") {\n");
// generate call to VMLogger.log method with argument typing
// generate case body for trace method at same time
out.printf("%slog(Operation.%s.ordinal()", INDENT12, uEnumName);
argIndex = 1;
for (Class< ? > parameter : parameters) {
String arg = formalNames[argIndex];
if (Word.class.isAssignableFrom(parameter)) {
// nothing needed
} else if (Hub.class.isAssignableFrom(parameter)) {
arg = "classActorArg(" + arg + ".classActor)";
} else {
arg = wrapLogArg(source, parameter, arg);
}
out.print(',');
if (argIndex % 6 == 0) {
out.printf("%n%s", INDENT16);
} else {
out.print(' ');
}
out.print(arg);
argIndex++;
}
out.print(");\n");
out.printf("%s}%n", INDENT8);
/*
* Tracing from here on.
*/
if (!vmLoggerInterface.noTrace()) {
// trace call in case body
int indx = 4;
boolean includeThread = vmLoggerInterface.traceThread();
StringBuilder caseBody = new StringBuilder(INDENTS[indx]).append("case ");
caseBody.append(enumMap[i].ordinal).append(':').append(" { //").append(uEnumName).append('\n');
caseBody.append(INDENTS[indx + 1]).append("trace").append(uMethodName).append('(');
if (includeThread) {
caseBody.append("threadId");
}
argIndex = 1;
for (Class< ? > parameter : parameters) {
if (argIndex > 1 || includeThread) {
caseBody.append(", ");
}
caseBody.append(wrapTraceArg(customTypes, source, new GClass(parameter, genericParams[argIndex - 1]), argIndex));
argIndex++;
}
caseBody.append(");\n");
caseBody.append(INDENTS[indx + 1]).append("break;\n");
caseBody.append(INDENTS[indx]).append("}\n");
traceCaseBodies.add(caseBody.toString());
// trace method
out.printf("%sprotected abstract void trace%s(", INDENT8, uMethodName);
if (includeThread) {
out.print("int threadId");
}
argIndex = 1;
for (Class< ? > parameter : parameters) {
Annotation[] paramAnnotations = parameterAnnotations[argIndex - 1];
if (argIndex > 1 || includeThread) {
out.print(",");
if (argIndex % 6 == 0) {
out.printf("%n%s", INDENT16);
} else {
out.print(' ');
}
}
formalNames[argIndex] = formalName(paramAnnotations, argIndex);
out.printf("%s %s", GClass.getTypeName(parameter, genericParams[argIndex - 1]), formalNames[argIndex]);
argIndex++;
}
out.print(");\n\n");
}
}
if (!vmLoggerInterface.noTrace()) {
outTraceMethod(out, traceCaseBodies, vmLoggerInterface.traceThread());
// casts
for (GClass gclass : customTypes) {
boolean generic = gclass.isGeneric();
String typeName = gclass.getTypeName();
String methodName = traceMethodName(gclass);
out.printf("%sstatic %s to%s(Record r, int argNum) {%n", INDENT8, typeName, methodName);
out.printf("%sif (MaxineVM.isHosted()) {%n", INDENT12);
if (generic) {
out.printf("%sClass<%s> type = null;%n", INDENT16, typeName);
out.printf("%sreturn Utils.cast(type, ObjectArg.getArg(r, argNum));%n", INDENT16);
} else {
out.printf("%sreturn (%s) ObjectArg.getArg(r, argNum);%n", INDENT16, typeName);
}
out.printf("%s} else {%n", INDENT12);
out.printf("%sreturn as%s(toObject(r, argNum));%n", INDENT16, methodName);
out.printf("%s}%n", INDENT12);
out.printf("%s}%n", INDENT8);
out.printf("%s@INTRINSIC(UNSAFE_CAST)%n", INDENT8);
out.printf("%sprivate static native %s as%s(Object arg);%n%n", INDENT8, typeName, methodName);
}
// enum args
for (Class klass : enumArgMap) {
final String typeName = klass.getSimpleName();
out.printf("%n%sprivate static %s to%s(Record r, int argNum) {%n", INDENT8, typeName, typeName);
out.printf("%sreturn %s.VALUES[r.getIntArg(argNum)];%n", INDENT12, typeName);
out.printf("%s}%n%n", INDENT8);
out.printf("%sprivate static Word %sArg(%s enumType) {%n", INDENT8, toFirstLower(typeName), typeName);
out.printf("%sreturn Address.fromInt(enumType.ordinal());%n", INDENT12);
out.printf("%s}%n", INDENT8);
}
}
out.printf("%s}%n%n", INDENT4);
}
writer.close();
boolean wouldUpdate = Files.updateGeneratedContent(outputFile, ReadableSource.Static.fromString(writer.toString()),
"// START GENERATED CODE", "// END GENERATED CODE", checkOnly);
return wouldUpdate;
}
private static Class<?>[] sort(Class<?>[] classes) {
CClass[] cclasses = new CClass[classes.length];
for (int i = 0; i < classes.length; i++) {
cclasses[i] = new CClass(classes[i]);
}
Arrays.sort(cclasses);
for (int i = 0; i < classes.length; i++) {
classes[i] = cclasses[i].klass;
}
return classes;
}
private static class CClass implements Comparable {
private Class<?> klass;
CClass(Class<?> klass) {
this.klass = klass;
}
@Override
public int compareTo(Object arg0) {
CClass other = (CClass) arg0;
return klass.getName().compareTo(other.klass.getName());
}
}
/**
* Sort the methods by their name (and then type if necessary) in order to ensure consistent
* repeat runs. {@link Class#getDeclaredMethods} does not guarantee any order and, experimentally,
* it can vary from run to run.
* @param methods
*/
private static Method[] sort(Method[] methods) {
CMethod[] cmethods = new CMethod[methods.length];
for (int i = 0; i < methods.length; i++) {
cmethods[i] = new CMethod(methods[i]);
}
Arrays.sort(cmethods);
for (int i = 0; i < methods.length; i++) {
methods[i] = cmethods[i].method;
}
return methods;
}
private static class CMethod implements Comparable {
private Method method;
private Class<?>[] params;
CMethod(Method method) {
this.method = method;
}
@Override
public int compareTo(Object arg0) {
CMethod other = (CMethod) arg0;
int r = method.getName().compareTo(other.method.getName());
if (r < 0 || r > 0) {
return r;
}
// equal names, sort by param
Class<?>[] params = getParams();
Class<?>[] otherParams = other.getParams();
if (params.length < otherParams.length) {
return -1;
} else if (params.length > otherParams.length) {
return 1;
} else {
for (int i = 0; i < params.length; i++) {
r = params[i].getName().compareTo(otherParams[i].getName());
if (r < 0 || r > 0) {
return r;
}
}
assert false;
return 0;
}
}
private Class<?>[] getParams() {
if (params == null) {
params = method.getParameterTypes();
}
return params;
}
}
private static int[] computeRefMaps(Method[] methods, Set<Class> enumSet) {
int[] refMaps = new int[methods.length];
int methodIndex = 0;
for (Method method : methods) {
int argIndex = 0;
int refMapBits = 0;
Class<?>[] parameterTypes = method.getParameterTypes();
for (Class<?> type : parameterTypes) {
if (Word.class.isAssignableFrom(type)) {
// not a reference type
} else if (Actor.class.isAssignableFrom(type) || VmThread.class.isAssignableFrom(type) ||
VMLogger.Interval.class.isAssignableFrom(type)) {
// scalar id alternative
} else if (type.isEnum()) {
// enums always passed ordinal()
enumSet.add(type);
} else if (Object.class.isAssignableFrom(type)) {
refMapBits |= 1 << argIndex;
}
argIndex++;
}
refMaps[methodIndex++] = refMapBits;
}
return refMaps;
}
private static String sanitizedName(String name) {
int ix = name.indexOf('$');
if (ix < 0) {
return name;
}
return name.replace("$", ".");
}
private static boolean isOverride(String uName, VMLoggerInterface vmLoggerInterface) {
Class parent = vmLoggerInterface.parent();
if (parent == VMLogger.class) {
return false;
}
// look in parent for method that matches method name
String logName = "log" + uName;
for (Method parentMethod : parent.getDeclaredMethods()) {
if (parentMethod.getName().equals(logName)) {
return true;
}
}
return false;
}
/**
* Attempts to locate an existing definition for the given argument class, either
* in {@link VMLogger} or a custom definition in the user {@code sourceClass}.
* @param argClass
* @param log
* @param sourceClass
* @return {@code null} if no definition found, otherwise the argument class (possibly a superclass of {@code argClass}).
*/
private static Class isStandardArgMethod(Class argClass, boolean log, Class sourceClass) {
// look in VMLogger first
Class result = isStandardArgMethodX(argClass, log, VMLogger.class);
if (result == null) {
// now check source class for custom definition
result = isStandardArgMethodX(argClass, log, sourceClass);
}
return result;
}
private static Class isStandardArgMethodX(Class argClass, boolean log, Class sourceClass) {
// The most common case is an exact match against the argument class
String methodName = log ? logArgMethodName(argClass) : traceArgMethodName(new GClass(argClass, argClass));
for (Method m : sourceClass.getDeclaredMethods()) {
if (m.getName().equals(methodName)) {
return argClass;
}
}
// ok, it could be a subclass, e.g. ClassMethodActor <: MethodActor
Class argSuperClass = argClass.getSuperclass();
if (argSuperClass == null || argSuperClass == Object.class) {
return null;
} else {
return isStandardArgMethodX(argSuperClass, log, sourceClass);
}
}
private static String operationName(Method method) {
return toFirstUpper(method.getName());
}
private static String refMapArray(String indent, int[] refMaps) {
indent += INDENT4;
boolean zero = true;
for (int x : refMaps) {
if (x != 0) {
zero = false;
break;
}
}
if (zero) {
return "null";
}
StringBuilder sb = new StringBuilder("new int[] {");
int lineNum = 0;
boolean first = true;
for (int bits : refMaps) {
if (first) {
first = false;
} else {
sb.append(',');
int newLineNum = sb.length() / 80;
if (newLineNum != lineNum) {
sb.append('\n');
sb.append(indent);
lineNum = newLineNum;
} else {
sb.append(' ');
}
}
sb.append("0x").append(Integer.toHexString(bits));
}
sb.append("}");
return sb.toString();
}
private static class OperationEnumInfo {
String uName;
int ordinal;
int overLoadIndex = 1;
OperationEnumInfo(String uName, int ordinal) {
this.uName = uName;
this.ordinal = ordinal;
}
}
private static OperationEnumInfo[] outOperationEnum(PrintWriter out, Method[] methods) {
OperationEnumInfo[] result = new OperationEnumInfo[methods.length];
Map<String, Integer> nameMap = new HashMap<String, Integer>();
out.format("%spublic enum Operation {%n", INDENT8);
boolean first = true;
out.print(INDENT12);
int count = 0;
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
if (!first) {
out.print(",");
if ((count + 1) % 4 == 0) {
out.printf("%n%s", INDENT12);
} else {
out.print(' ');
}
} else {
first = false;
}
final String opName = operationName(method);
String enumName = opName;
Integer overLoadIndex = nameMap.get(opName);
if (overLoadIndex != null) {
// overloaded method name
overLoadIndex = new Integer(++overLoadIndex);
enumName += Integer.toString(overLoadIndex.intValue());
} else {
overLoadIndex = new Integer(1);
}
OperationEnumInfo info = new OperationEnumInfo(enumName, count);
result[i] = info;
nameMap.put(opName, overLoadIndex);
out.print(enumName);
count++;
}
out.println(";\n");
out.printf("%s@SuppressWarnings(\"hiding\")%n", INDENT12); // in case of static import of similar
out.printf("%spublic static final Operation[] VALUES = values();%n%s}%n%n", INDENT12, INDENT8);
return result;
}
private static void outTraceMethod(PrintWriter out, ArrayList<String> caseBodies, boolean includeThread) {
out.printf("%s@Override%n", INDENT8);
out.printf("%sprotected void trace(Record r) {%n", INDENT8);
if (includeThread) {
out.printf("%sint threadId = r.getThreadId();%n", INDENT12);
}
out.printf("%sswitch (r.getOperation()) {%n", INDENT12);
for (String caseBody : caseBodies) {
out.print(caseBody);
}
out.printf("%s}%n", INDENT12);
out.printf("%s}%n", INDENT8);
}
private static String formalName(Annotation[] paramAnnotations, int formalIndex) {
VMLogParam paramAnnotation = null;
for (Annotation annotation : paramAnnotations) {
if (annotation instanceof VMLogParam) {
paramAnnotation = (VMLogParam) annotation;
break;
}
}
if (paramAnnotation == null) {
return "arg" + formalIndex;
} else {
return paramAnnotation.name();
}
}
private static String wrapLogArg(Class sourceClass, Class argClass, String argName) {
Class standardArgClass = isStandardArgMethod(argClass, true, sourceClass);
if (standardArgClass == null) {
if (argClass.isEnum()) {
standardArgClass = argClass;
} else {
standardArgClass = Object.class;
}
}
return wrapLogArg(logArgMethodName(standardArgClass), argName);
}
private static String logArgMethodName(Class argClass) {
return toFirstLower(argClass.getSimpleName()) + "Arg";
}
private static String traceMethodName(GClass argClass) {
String name = toFirstUpper(argClass.getTypeName(false));
if (argClass.klass.isArray()) {
name = name.substring(0, name.length() - 2) + "Array";
}
return name;
}
private static String traceArgMethodName(GClass argClass) {
return "to" + traceMethodName(argClass);
}
private static String wrapLogArg(String methodName, String name) {
return methodName + "(" + name + ")";
}
private static String wrapTraceArg(Set<GClass> customTypes, Class sourceClass, GClass argClass, int argNum) {
Class standardArgClass = isStandardArgMethod(argClass.klass, false, sourceClass);
if (standardArgClass == null) {
if (!argClass.klass.isEnum()) {
customTypes.add(argClass);
}
standardArgClass = argClass.klass;
}
return wrapTraceArg(traceArgMethodName(new GClass(standardArgClass, argClass.type)), argNum);
}
private static String wrapTraceArg(String kind, int argNum) {
return kind + "(r, " + argNum + ")";
}
public static String toFirstUpper(String s) {
if (s.length() == 0) {
return s;
} else {
return s.substring(0, 1).toUpperCase() + s.substring(1);
}
}
public static String toFirstLower(String s) {
if (s.length() == 0) {
return s;
} else {
return s.substring(0, 1).toLowerCase() + s.substring(1);
}
}
private static String getRootName(Class loggerInterface) throws Exception {
String rootName = loggerInterface.getSimpleName();
int ix = rootName.indexOf("Interface");
if (ix <= 0) {
throw new Exception("Logger class " + rootName + " is not named XXXInterface");
}
return rootName.substring(0, ix);
}
/**
* @param args
*/
public static void main(String[] args) throws Exception {
boolean checkOnly = false;
for (int i = 0; i < args.length; i++) {
if (args[i].equals("-check")) {
checkOnly = true;
}
}
ArrayList<Class<?>> updatedSources = generate(checkOnly);
if (updatedSources != null) {
System.exit(1);
}
}
private static class VMLoggerClassSearch extends ClassSearch {
final HashSet<String> seenPackages = new HashSet<String>();
ArrayList<Class<?>> updatedSources;
boolean checkOnly;
File resourceParent;
VMLoggerClassSearch(boolean checkOnly) {
this.checkOnly = checkOnly;
}
@Override
protected boolean visitFile(File parent, String resource) {
this.resourceParent = parent;
return super.visitFile(parent, resource);
}
@Override
protected boolean visitClass(boolean isArchiveEntry, String className) {
if (!className.endsWith("package-info")) {
String pkg = Classes.getPackageName(className);
if (seenPackages.add(pkg) && !checkOnly) {
Trace.line(1, pkg);
}
Class<?> source = null;
try {
source = Classes.forName(className, false, getClass().getClassLoader());
} catch (Throwable ex) {
// Ignore
System.err.println(ex);
System.err.println("while trying to load: " + className);
return true;
}
ArrayList<Class<?>> loggerInterfaces = findLoggerInterfaces(source);
if (loggerInterfaces.size() > 0) {
try {
boolean updated = generate(checkOnly, source, resourceParent.getParentFile(), loggerInterfaces);
if (updated) {
if (updatedSources == null) {
updatedSources = new ArrayList<Class<?>>();
}
updatedSources.add(source);
System.out.println("Source for " + source + (checkOnly ? " would be" : " was") + " updated");
}
} catch (Exception ex) {
System.err.println(ex);
return false;
}
}
}
return true;
}
}
public static ArrayList<Class<?>> generate(final boolean checkOnly) {
workspace = JavaProject.findWorkspace();
VMLoggerClassSearch search = new VMLoggerClassSearch(checkOnly);
search.run(Classpath.fromSystem(), "com/sun/max");
search.run(Classpath.fromSystem(), "com/oracle/max");
return search.updatedSources;
}
private static ArrayList<Class< ? >> findLoggerInterfaces(Class< ? > klass) {
ArrayList<Class< ? >> result = new ArrayList<Class< ? >>();
Class< ? >[] declaredClasses = klass.getDeclaredClasses();
for (Class< ? > innerClass : declaredClasses) {
if (getVMLoggerInterface(innerClass) != null) {
result.add(innerClass);
}
}
return result;
}
private static VMLoggerInterface getVMLoggerInterface(Class klass) {
Annotation[] annotations = klass.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation instanceof VMLoggerInterface) {
return (VMLoggerInterface) annotation;
}
}
return null;
}
}