/* * Copyright 2015 Odnoklassniki Ltd, Mail.Ru Group * * 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 one.nio.http.gen; import one.nio.gen.BytecodeGenerator; import one.nio.http.Header; import one.nio.http.HttpSession; import one.nio.http.Param; import one.nio.http.Request; import one.nio.http.RequestHandler; import one.nio.http.Response; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Type; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; public class RequestHandlerGenerator extends BytecodeGenerator { private int count; public RequestHandler generateFor(Method m, Object router) { if (Modifier.isStatic(m.getModifiers())) { throw new IllegalArgumentException("Method should not be static: " + m); } Class returnType = m.getReturnType(); if (returnType != void.class && returnType != Response.class) { throw new IllegalArgumentException("Invalid return type of " + m); } String className = "RequestHandler" + (count++) + "_" + m.getName(); String routerType = Type.getDescriptor(m.getDeclaringClass()); ClassWriter cv = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); cv.visit(V1_6, ACC_PUBLIC | ACC_FINAL, className, null, "java/lang/Object", new String[] { "one/nio/http/RequestHandler" }); // private final Object router; cv.visitField(ACC_PRIVATE | ACC_FINAL, "router", routerType, null, null).visitEnd(); // public RequestHandler(Object router); MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "<init>", "(" + routerType + ")V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitInsn(DUP); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V"); mv.visitVarInsn(ALOAD, 1); mv.visitFieldInsn(PUTFIELD, className, "router", routerType); mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); // public final void handleRequest(Request request, HttpSession session) throws IOException; mv = cv.visitMethod(ACC_PUBLIC | ACC_FINAL, "handleRequest", "(Lone/nio/http/Request;Lone/nio/http/HttpSession;)V", null, null); mv.visitCode(); if (m.getReturnType() == Response.class) { mv.visitVarInsn(ALOAD, 2); } mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, className, "router", routerType); setupArguments(mv, m); emitInvoke(mv, m); if (m.getReturnType() == Response.class) { mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/http/HttpSession", "writeResponse", "(Lone/nio/http/Response;)V"); } mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); cv.visitEnd(); return instantiate(cv.toByteArray(), m, router); } private void setupArguments(MethodVisitor mv, Method m) { Class[] types = m.getParameterTypes(); Annotation[][] annotations = m.getParameterAnnotations(); nextArgument: for (int i = 0; i < types.length; i++) { Class type = types[i]; if (type == Request.class) { mv.visitVarInsn(ALOAD, 1); } else if (type == HttpSession.class) { mv.visitVarInsn(ALOAD, 2); } else { for (Annotation annotation : annotations[i]) { if (annotation instanceof Param) { setupParam(mv, type, (Param) annotation); continue nextArgument; } else if (annotation instanceof Header) { setupHeader(mv, type, (Header) annotation); continue nextArgument; } } throw new IllegalArgumentException("Missing @Param or @Header for argument " + i + " of " + m); } } } private void setupParam(MethodVisitor mv, Class type, Param param) { String name = param.value(); String defaultValue = null; int eq = name.indexOf('='); if (eq > 0) { defaultValue = name.substring(eq + 1); name = name.substring(0, eq); } mv.visitVarInsn(ALOAD, 1); mv.visitLdcInsn(name + '='); boolean needNullCheck = false; if (param.required()) { mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/http/Request", "getRequiredParameter", "(Ljava/lang/String;)Ljava/lang/String;"); } else if (defaultValue != null) { mv.visitLdcInsn(defaultValue); mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/http/Request", "getParameter", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"); } else { mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/http/Request", "getParameter", "(Ljava/lang/String;)Ljava/lang/String;"); needNullCheck = true; } convertArgument(mv, type, needNullCheck); } private void setupHeader(MethodVisitor mv, Class type, Header header) { String name = header.value(); String defaultValue = null; int eq = name.indexOf('='); if (eq > 0) { defaultValue = name.substring(eq + 1); name = name.substring(0, eq); } mv.visitVarInsn(ALOAD, 1); mv.visitLdcInsn(name + ": "); boolean needNullCheck = false; if (header.required()) { mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/http/Request", "getRequiredHeader", "(Ljava/lang/String;)Ljava/lang/String;"); } else if (defaultValue != null) { mv.visitLdcInsn(defaultValue); mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/http/Request", "getHeader", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"); } else { mv.visitMethodInsn(INVOKEVIRTUAL, "one/nio/http/Request", "getHeader", "(Ljava/lang/String;)Ljava/lang/String;"); needNullCheck = true; } convertArgument(mv, type, needNullCheck); } private void convertArgument(MethodVisitor mv, Class type, boolean needNullCheck) { if (type == String.class) { return; // nothing to do } if (type.isPrimitive()) { if (type == int.class) { mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "parseInt", "(Ljava/lang/String;)I"); } else if (type == long.class) { mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "parseLong", "(Ljava/lang/String;)J"); } else if (type == byte.class) { mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "parseByte", "(Ljava/lang/String;)B"); } else if (type == short.class) { mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "parseShort", "(Ljava/lang/String;)S"); } else if (type == float.class) { mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "parseFloat", "(Ljava/lang/String;)F"); } else if (type == double.class) { mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "parseDouble", "(Ljava/lang/String;)D"); } else if (type == boolean.class) { mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "parseBoolean", "(Ljava/lang/String;)Z"); } else if (type == char.class) { mv.visitInsn(ICONST_0); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "charAt", "(I)C"); } return; } Label isNull = new Label(); if (needNullCheck) { Label nonNull = new Label(); mv.visitInsn(DUP); mv.visitJumpInsn(IFNONNULL, nonNull); mv.visitInsn(POP); mv.visitInsn(ACONST_NULL); mv.visitJumpInsn(GOTO, isNull); mv.visitLabel(nonNull); } if (type == Integer.class) { mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(Ljava/lang/String;)Ljava/lang/Integer;"); } else if (type == Long.class) { mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(Ljava/lang/String;)Ljava/lang/Long;"); } else if (type == Byte.class) { mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(Ljava/lang/String;)Ljava/lang/Byte;"); } else if (type == Short.class) { mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(Ljava/lang/String;)Ljava/lang/Short;"); } else if (type == Float.class) { mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(Ljava/lang/String;)Ljava/lang/Float;"); } else if (type == Double.class) { mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(Ljava/lang/String;)Ljava/lang/Double;"); } else if (type == Boolean.class) { mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Ljava/lang/String;)Ljava/lang/Boolean;"); } else { throw new IllegalArgumentException("Unsupported argument type: " + type.getName()); } if (needNullCheck) { mv.visitLabel(isNull); } } private RequestHandler instantiate(byte[] classData, Method m, Object router) { try { Class<?> resultClass = super.defineClass(classData); Constructor c = resultClass.getConstructor(m.getDeclaringClass()); return (RequestHandler) c.newInstance(router); } catch (Exception e) { throw new IllegalArgumentException("Cannot generate valid RequestHandler for " + m, e); } } }