Java tutorial
/* * Copyright (c) 2016 Yahoo Inc. * Licensed under the terms of the Apache version 2.0 license. * See LICENSE file for terms. */ package com.yahoo.yqlplus.engine.internal.source; import com.google.common.base.Joiner; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.name.Named; import com.yahoo.yqlplus.api.Exports; import com.yahoo.yqlplus.api.annotations.*; import com.yahoo.yqlplus.api.annotations.Set; import com.yahoo.yqlplus.api.trace.Tracer; import com.yahoo.yqlplus.engine.TaskContext; import com.yahoo.yqlplus.engine.internal.bytecode.types.gambit.*; import com.yahoo.yqlplus.engine.internal.compiler.CodeEmitter; import com.yahoo.yqlplus.engine.internal.plan.ModuleType; import com.yahoo.yqlplus.engine.internal.plan.types.AssignableValue; import com.yahoo.yqlplus.engine.internal.plan.types.BytecodeExpression; import com.yahoo.yqlplus.engine.internal.plan.types.TypeWidget; import com.yahoo.yqlplus.engine.internal.plan.types.base.AnyTypeWidget; import com.yahoo.yqlplus.engine.internal.plan.types.base.BaseTypeAdapter; import com.yahoo.yqlplus.engine.internal.plan.types.base.BaseTypeExpression; import com.yahoo.yqlplus.language.parser.Location; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.*; /** * Generate adapter class for Exports modules. */ public class ExportUnitGenerator extends SourceApiGenerator { public ExportUnitGenerator(GambitScope gambitScope) { super(gambitScope); } // source adapters can depend on the source & the context, but not the program public ObjectBuilder createModuleAdapter(String moduleName, Class<?> sourceClass, TypeWidget moduleType, TypeWidget contextType, BytecodeExpression sourceProvider) { gambitScope.addClass(sourceClass); ObjectBuilder adapter = gambitScope.createObject(); ObjectBuilder.ConstructorBuilder cb = adapter.getConstructor(); ObjectBuilder.FieldBuilder fld = adapter.field("$module", moduleType); fld.addModifiers(Opcodes.ACC_FINAL); cb.exec(fld.get(cb.local("this")).write(cb.cast(moduleType, cb.invokeExact(Location.NONE, "get", Provider.class, AnyTypeWidget.getInstance(), sourceProvider)))); adapter.addParameterField(fld); ObjectBuilder.FieldBuilder programName = adapter.field("$programName", BaseTypeAdapter.STRING); programName.annotate(Inject.class); programName.annotate(Named.class).put("value", "programName"); adapter.addParameterField(programName); BytecodeExpression metric = gambitScope.constant(PhysicalExprOperatorCompiler.EMPTY_DIMENSION); metric = metricWith(cb, metric, "source", moduleName); return adapter; } class AdapterBuilder { String sourceName; Class<?> clazz; ObjectBuilder target; TypeWidget sourceClass; // SELECT // DELETE - match an index (just like SELECT) // UPDATE - match an INDEX // INSERT - accept a record, dispatch to an appropriate method if possible // we need to store a dispatch table of methods // also properties Multimap<String, ObjectBuilder.MethodBuilder> methods = Multimaps.newListMultimap( new TreeMap<String, Collection<ObjectBuilder.MethodBuilder>>(String.CASE_INSENSITIVE_ORDER), new com.google.common.base.Supplier<List<ObjectBuilder.MethodBuilder>>() { @Override public List<ObjectBuilder.MethodBuilder> get() { return Lists.newArrayList(); } }); Map<String, ObjectBuilder.MethodBuilder> fields = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER); long minimumBudget; long maximumBudget; public AdapterBuilder(String moduleName, Class<? extends Exports> clazz, BytecodeExpression providerConstant, long minimumBudget, long maximumBudget) { this.clazz = clazz; this.sourceName = moduleName; this.minimumBudget = minimumBudget; this.maximumBudget = maximumBudget; // TODO: should accept the annotations for the free argument signatures, so they can be checked for @NotNullable or equiv. this.sourceClass = gambitScope.adapt(clazz, false); this.target = createModuleAdapter(moduleName, clazz, sourceClass, gambitScope.adapt(TaskContext.class, false), providerConstant); } public ModuleType create() { return new ExportModuleAdapter(target.type(), sourceName, methods, fields); } private void addAdapterMethod(final Method method) { ObjectBuilder.MethodBuilder adapterMethod = target.method(method.getName()); TypeWidget contextType = gambitScope.adapt(TaskContext.class, false); adapterMethod.addArgument("$context", contextType); GambitCreator.ScopeBuilder block = adapterMethod.scope(); TypeWidget outputType = gambitScope.adapt(method.getGenericReturnType(), true); AssignableValue resultValue = block.allocate(outputType); // construct for evaluate an expression in a context with or without timeout enforcement AssignableValue contextVar = block.local("$context"); // we do NOT want to fork a callable -- just call the function and return the result. boolean isStatic = Modifier.isStatic(method.getModifiers()); GambitCreator.Invocable targetMethod; if (isStatic) { targetMethod = block.findStaticInvoker(method.getDeclaringClass(), method.getName(), outputType, method.getParameterTypes()); } else { targetMethod = block.findExactInvoker(method.getDeclaringClass(), method.getName(), outputType, method.getParameterTypes()); } List<String> freeArgumentNames = Lists.newArrayList(); Class<?>[] argumentTypes = method.getParameterTypes(); java.lang.reflect.Type[] genericArgumentTypes = method.getGenericParameterTypes(); Annotation[][] annotations = method.getParameterAnnotations(); for (int i = 0; i < argumentTypes.length; ++i) { if (isFreeArgument(argumentTypes[i], annotations[i])) { String name = gensym("arg$"); freeArgumentNames.add(name); adapterMethod.addArgument(name, gambitScope.adapt(genericArgumentTypes[i], true)); } } Iterator<String> freeArguments = freeArgumentNames.iterator(); List<BytecodeExpression> invocationArguments = Lists.newArrayList(); if (!isStatic) { invocationArguments.add(block.local("$module")); } visitMethodArguments(target, method, new ExportSourceArgumentVisitor(method), contextVar, block, freeArguments, invocationArguments, block); BytecodeExpression tracerExpr = block.propertyValue(Location.NONE, contextVar, "tracer"); BytecodeExpression methodTracerExpr = block.invokeExact(Location.NONE, "start", Tracer.class, tracerExpr.getType(), tracerExpr, block.constant("PIPE"), block.constant(method.getDeclaringClass().getName() + "::" + method.getName())); BytecodeExpression invocation = block.invoke(Location.NONE, targetMethod, invocationArguments); block.set(Location.NONE, resultValue, invocation); for (BytecodeExpression argument : invocationArguments) { if (argument.getType().getJVMType().getClassName().equals(Tracer.class.getName())) { block.exec( block.invokeExact(Location.NONE, "end", Tracer.class, BaseTypeAdapter.VOID, argument)); } } block.exec( block.invokeExact(Location.NONE, "end", Tracer.class, BaseTypeAdapter.VOID, methodTracerExpr)); adapterMethod.exit(block.complete(resultValue)); methods.put(method.getName(), adapterMethod); } public void addExportedField(final Field field) { ObjectBuilder.MethodBuilder adapterMethod = target.method(gensym(field.getName() + "$get$")); TypeWidget contextType = gambitScope.adapt(TaskContext.class, false); adapterMethod.addArgument("$context", contextType); final TypeWidget outputType = gambitScope.adapt(field.getGenericType(), true); if (Modifier.isStatic(field.getModifiers())) { adapterMethod.exit(new BaseTypeExpression(outputType) { @Override public void generate(CodeEmitter code) { code.getMethodVisitor().visitFieldInsn(Opcodes.GETSTATIC, Type.getInternalName(clazz), field.getName(), outputType.getJVMType().getDescriptor()); } }); } else { adapterMethod.exit(new BaseTypeExpression(outputType) { @Override public void generate(CodeEmitter code) { code.exec(code.getLocal("$module")); code.getMethodVisitor().visitFieldInsn(Opcodes.GETFIELD, Type.getInternalName(field.getDeclaringClass()), field.getName(), outputType.getJVMType().getDescriptor()); } }); } fields.put(field.getName(), adapterMethod); } private class ExportSourceArgumentVisitor implements SourceArgumentVisitor { private final Method method; public ExportSourceArgumentVisitor(Method method) { this.method = method; } @Override public BytecodeExpression visitKeyArgument(Key key, ScopedBuilder body, Class<?> parameterType, TypeWidget parameterWidget) { reportMethodParameterException("Export", method, "@Export methods do not support @Key arguments: @Key('%s')", key.value()); throw new IllegalArgumentException(); // reachability (reportMethodetc throws but javac doesn't know that } @Override public BytecodeExpression visitCompoundKey(CompoundKey compoundKey, ScopedBuilder body, Class<?> parameterType, TypeWidget parameterWidget) { reportMethodParameterException("Export", method, "@Export methods do not support @CompoundKey arguments: %s", compoundKey); throw new IllegalArgumentException(); // reachability (reportMethodetc throws but javac doesn't know that } @Override public BytecodeExpression visitSet(Set annotate, DefaultValue defaultValue, ScopedBuilder body, Class<?> parameterType, TypeWidget parameterWidget) { reportMethodParameterException("Export", method, "@Export methods do not support @Set arguments: %s", annotate); throw new IllegalArgumentException(); // reachability (reportMethodetc throws but javac doesn't know that } } } /** * Transform a Source provider into an implementation of IndexedTable and/or IndexedTableFunction. * * @param input * @return */ public ModuleType apply(List<String> path, Provider<? extends Exports> input) { String sourceName = Joiner.on(".").join(path); Exports source = input.get(); final Class<? extends Exports> clazz = source.getClass(); long minimumBudget = -1; long maximumBudget = -1; TimeoutBudget budget = clazz.getAnnotation(TimeoutBudget.class); if (budget != null) { minimumBudget = budget.minimumMilliseconds(); maximumBudget = budget.maximumMilliseconds(); } final BytecodeExpression providerConstant = gambitScope.constant(input); AdapterBuilder builder = new AdapterBuilder(sourceName, clazz, providerConstant, minimumBudget, maximumBudget); for (Method method : clazz.getMethods()) { if (!method.isAnnotationPresent(Export.class)) { continue; } builder.addAdapterMethod(method); } for (Field field : clazz.getFields()) { if (!field.isAnnotationPresent(Export.class)) { continue; } builder.addExportedField(field); } return builder.create(); } }