aboutsummaryrefslogtreecommitdiff
path: root/exec/java-exec/src/main/java/org/apache/drill/exec/expr/CodeGenerator.java
blob: 47e562d1c31367a68f63f0ac6f3c512b4477b88e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
/*
 * 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.drill.exec.expr;

import java.io.IOException;

import org.apache.drill.exec.compile.TemplateClassDefinition;
import org.apache.drill.exec.compile.sig.MappingSet;
import org.apache.drill.exec.server.options.OptionSet;

import org.apache.drill.shaded.guava.com.google.common.base.Preconditions;
import com.sun.codemodel.JClassAlreadyExistsException;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JDefinedClass;

/**
 * A code generator is responsible for generating the Java source code required
 * to complete the implementation of an abstract template.
 * A code generator can contain one or more ClassGenerators that implement
 * outer and inner classes associated with a particular runtime generated instance.
 * <p>
 * Drill supports two ways to generate and compile the code from a code
 * generator: via byte-code manipulations or as "plain Java."
 * <p>
 * When using byte-code transformations, the code generator is used with a
 * class transformer to merge precompiled template code with runtime generated and
 * compiled query specific code to create a runtime instance.
 * <p>
 * The code generator can optionally be marked as "plain Java" capable.
 * This means that the generated code can be compiled directly as a Java
 * class without the normal byte-code manipulations. Plain Java allows
 * the option to persist, and debug, the generated code when building new
 * generated classes or otherwise working with generated code. To turn
 * on debugging, see the explanation in {@link org.apache.drill.exec.compile.ClassBuilder}.
 *
 * @param <T>
 *          The interface that results from compiling and merging the runtime
 *          code that is generated.
 */

public class CodeGenerator<T> {

  private static final String PACKAGE_NAME = "org.apache.drill.exec.test.generated";

  private final TemplateClassDefinition<T> definition;
  private final String className;
  private final String fqcn;

  private final JCodeModel model;
  private final ClassGenerator<T> rootGenerator;

  /**
   * True if the code generated for this class is suitable for compilation
   * as a plain Java class.
   */

  private boolean plainJavaCapable;

  /**
   * True if the code generated for this class should actually be compiled
   * via the plain Java mechanism. Considered only if the class is
   * capable of this technique.
   */

  private boolean usePlainJava;

  /**
   * Whether to write code to disk to aid in debugging. Should only be set
   * during development, never in production.
   */

  private boolean saveDebugCode;
  private String generatedCode;
  private String generifiedCode;

  CodeGenerator(TemplateClassDefinition<T> definition, OptionSet optionManager) {
    this(ClassGenerator.getDefaultMapping(), definition, optionManager);
  }

  CodeGenerator(MappingSet mappingSet, TemplateClassDefinition<T> definition, OptionSet optionManager) {
    Preconditions.checkNotNull(definition.getSignature(),
        "The signature for defintion %s was incorrectly initialized.", definition);
    this.definition = definition;
    this.className = definition.getExternalInterface().getSimpleName() + "Gen" + definition.getNextClassNumber();
    this.fqcn = PACKAGE_NAME + "." + className;
    try {
      this.model = new JCodeModel();
      JDefinedClass clazz = model._package(PACKAGE_NAME)._class(className);
      rootGenerator = new ClassGenerator<>(this, mappingSet,
        definition.getSignature(), new EvaluationVisitor(),
        clazz, model, optionManager);
    } catch (JClassAlreadyExistsException e) {
      throw new IllegalStateException(e);
    }
  }

  /**
   * Indicates that the code for this class can be generated using the
   * "Plain Java" mechanism based on inheritance. The byte-code
   * method is more lenient, so some code is missing some features such
   * as proper exception labeling, etc. Set this option to true once
   * the generation mechanism for a class has been cleaned up to work
   * via the plain Java mechanism.
   *
   * @param flag true if the code generated from this instance is
   * ready to be compiled as a plain Java class
   */

  public void plainJavaCapable(boolean flag) {
    plainJavaCapable = flag;
  }

  /**
   * Identifies that this generated class should be generated via the
   * plain Java mechanism. This flag only has meaning if the
   * generated class is capable of plain Java generation.
   *
   * @param flag true if the class should be generated and compiled
   * as a plain Java class (rather than via byte-code manipulations)
   */

  public void preferPlainJava(boolean flag) {
    usePlainJava = flag;
  }

  public boolean supportsPlainJava() {
    return plainJavaCapable;
  }

  public boolean isPlainJava() {
    return plainJavaCapable && usePlainJava;
  }

  /**
   * Debug-time option to persist the code for the generated class to permit debugging.
   * Has effect only when code is generated using the plain Java option. Code
   * is written to the code directory specified in {@link org.apache.drill.exec.compile.ClassBuilder}.
   * To debug code, set this option, then point your IDE to the code directory
   * when the IDE prompts you for the source code location.
   *
   * @param persist true to write the code to disk, false (the default) to keep
   * code only in memory.
   */
  public void saveCodeForDebugging(boolean persist) {
    if (supportsPlainJava()) {
      saveDebugCode = persist;
      usePlainJava = true;
    }
  }

  public boolean isCodeToBeSaved() {
     return saveDebugCode;
  }

  public ClassGenerator<T> getRoot() {
    return rootGenerator;
  }

  public void generate() {

    // If this generated class uses the "plain Java" technique
    // (no byte code manipulation), then the class must extend the
    // template so it plays by normal Java rules for finding the
    // template methods via inheritance rather than via code injection.

    if (isPlainJava()) {
      rootGenerator.preparePlainJava( );
    }

    rootGenerator.flushCode();

    SingleClassStringWriter w = new SingleClassStringWriter();
    try {
      model.build(w);
    } catch (IOException e) {
      // No I/O errors should occur during model building
      // unless something is terribly wrong.
      throw new IllegalStateException(e);
    }

    generatedCode = w.getCode().toString();
    generifiedCode = generatedCode.replaceAll(className, "GenericGenerated");
  }

  public String generateAndGet() throws IOException {
    generate();
    return generatedCode;
  }

  public String getGeneratedCode() {
    return generatedCode;
  }

  public TemplateClassDefinition<T> getDefinition() {
    return definition;
  }

  public String getMaterializedClassName() {
    return fqcn;
  }

  public String getClassName() { return className; }

  public static <T> CodeGenerator<T> get(TemplateClassDefinition<T> definition) {
    return get(definition, null);
  }

  public static <T> CodeGenerator<T> get(TemplateClassDefinition<T> definition, OptionSet optionManager) {
    return new CodeGenerator<T>(definition, optionManager);
  }

  public static <T> ClassGenerator<T> getRoot(TemplateClassDefinition<T> definition, OptionSet optionManager) {
    return get(definition, optionManager).getRoot();
  }

  public static <T> ClassGenerator<T> getRoot(MappingSet mappingSet, TemplateClassDefinition<T> definition, OptionSet optionManager) {
    return get(mappingSet, definition, optionManager).getRoot();
  }

  public static <T> CodeGenerator<T> get(MappingSet mappingSet, TemplateClassDefinition<T> definition, OptionSet optionManager) {
    return new CodeGenerator<T>(mappingSet, definition, optionManager);
  }

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((definition == null) ? 0 : definition.hashCode());
    result = prime * result + ((generifiedCode == null) ? 0 : generifiedCode.hashCode());
    return result;
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj){
      return true;
    }
    if (obj == null){
      return false;
    }
    if (getClass() != obj.getClass()){
      return false;
    }
    CodeGenerator<?> other = (CodeGenerator<?>) obj;
    if (definition == null) {
      if (other.definition != null){
        return false;
      }
    } else if (!definition.equals(other.definition)) {
      return false;
    }
    if (generifiedCode == null) {
      if (other.generifiedCode != null){
        return false;
      }

    } else if (!generifiedCode.equals(other.generifiedCode)) {
      return false;
    }
    return true;
  }
}