content: /*
   * 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 io.airlift.bytecode;
  
  import com.google.common.collect.ImmutableList;
  import io.airlift.bytecode.debug.LocalVariableNode;
  import io.airlift.bytecode.instruction.LabelNode;
  import org.objectweb.asm.MethodVisitor;
  import org.objectweb.asm.Type;
  
  import java.util.ArrayDeque;
  import java.util.Deque;
  import java.util.HashMap;
  import java.util.LinkedHashSet;
  import java.util.Map;
  import java.util.Set;
  
  import static com.google.common.base.Preconditions.checkArgument;
  import static java.util.Objects.requireNonNull;
  
  public class MethodGenerationContext
  {
      private final MethodVisitor methodVisitor;
  
      private final Set<Scope> allEnteredScopes = new LinkedHashSet<>();
      private final Deque<ScopeContext> scopes = new ArrayDeque<>();
  
      private final Map<Variable, Integer> variableSlots = new HashMap<>();
      private int nextSlot;
  
      private int currentLineNumber = -1;
  
      public MethodGenerationContext(MethodVisitor methodVisitor)
      {
          this.methodVisitor = requireNonNull(methodVisitor, "methodVisitor is null");
      }
  
      public void enterScope(Scope scope)
      {
          requireNonNull(scope, "scope is null");
  
          checkArgument(!allEnteredScopes.contains(scope), "scope has already been entered");
          allEnteredScopes.add(scope);
  
          ScopeContext scopeContext = new ScopeContext(scope);
          scopes.addLast(scopeContext);
  
          for (Variable variable : scopeContext.getVariables()) {
              checkArgument(!"this".equals(variable.getName()) || nextSlot == 0, "The 'this' variable must be in slot 0");
              variableSlots.put(variable, nextSlot);
              nextSlot += Type.getType(variable.getType().getType()).getSize();
          }
  
          scopeContext.getStartLabel().accept(methodVisitor, this);
      }
  
      public void exitScope(Scope scope)
      {
          checkArgument(allEnteredScopes.contains(scope), "scope has not been entered");
          checkArgument(!scopes.isEmpty() && scope == scopes.peekLast().getScope(), "Scope is not top of the stack");
  
          ScopeContext scopeContext = scopes.removeLast();
  
          scopeContext.getEndLabel().accept(methodVisitor, this);
  
          for (Variable variable : scopeContext.getVariables()) {
              new LocalVariableNode(variable, scopeContext.getStartLabel(), scopeContext.getEndLabel()).accept(methodVisitor, this);
          }
  
          variableSlots.keySet().removeAll(scopeContext.getVariables());
      }
  
      public int getVariableSlot(Variable variable)
      {
          Integer slot = variableSlots.get(variable);
          checkArgument(slot != null, "Variable '%s' has not been assigned a slot", variable);
          return slot;
      }
  
      public boolean updateLineNumber(int lineNumber)
      {
          if (lineNumber == currentLineNumber) {
              return false;
          }
  
          currentLineNumber = lineNumber;
          return true;
      }
  
      private static final class ScopeContext
      {
          private final Scope scope;
          private final ImmutableList<Variable> variables;
  
          private final LabelNode startLabel = new LabelNode("VariableStart");
          private final LabelNode endLabel = new LabelNode("VariableEnd");
  
          public ScopeContext(Scope scope)
          {
              this.scope = scope;
              this.variables = ImmutableList.copyOf(scope.getVariables());
          }
  
          public Scope getScope()
          {
              return scope;
          }
  
          public ImmutableList<Variable> getVariables()
          {
              return variables;
          }
  
          public LabelNode getStartLabel()
          {
              return startLabel;
          }
  
          public LabelNode getEndLabel()
          {
              return endLabel;
          }
      }
  }
  