content: /*
   * Copyright (C) 2007 The Android Open Source Project
   *
   * 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 com.taobao.android.dx.dex.code;
  
  import com.taobao.android.dx.rop.code.RegisterSpec;
  import com.taobao.android.dx.rop.code.RegisterSpecSet;
  import com.taobao.android.dx.rop.cst.CstString;
  import com.taobao.android.dx.rop.cst.CstType;
  import com.taobao.android.dx.rop.type.Type;
  import com.taobao.android.dx.util.FixedSizeList;
  import java.io.PrintStream;
  import java.util.ArrayList;
  import java.util.Arrays;
  
  /**
   * List of local variables. Each local variable entry indicates a
   * range of code which it is valid for, a register number, a name,
   * and a type.
   */
  public final class LocalList extends FixedSizeList {
      /** {@code non-null;} empty instance */
      public static final LocalList EMPTY = new LocalList(0);
  
      /** whether to run the self-check code */
      private static final boolean DEBUG = false;
  
      /**
       * Constructs an instance. All indices initially contain {@code null}.
       *
       * @param size {@code >= 0;} the size of the list
       */
      public LocalList(int size) {
          super(size);
      }
  
      /**
       * Gets the element at the given index. It is an error to call
       * this with the index for an element which was never set; if you
       * do that, this will throw {@code NullPointerException}.
       *
       * @param n {@code >= 0, < size();} which index
       * @return {@code non-null;} element at that index
       */
      public Entry get(int n) {
          return (Entry) get0(n);
      }
  
      /**
       * Sets the entry at the given index.
       *
       * @param n {@code >= 0, < size();} which index
       * @param entry {@code non-null;} the entry to set at {@code n}
       */
      public void set(int n, Entry entry) {
          set0(n, entry);
      }
  
      /**
       * Does a human-friendly dump of this instance.
       *
       * @param out {@code non-null;} where to dump
       * @param prefix {@code non-null;} prefix to attach to each line of output
       */
      public void debugPrint(PrintStream out, String prefix) {
          int sz = size();
  
          for (int i = 0; i < sz; i++) {
              out.print(prefix);
              out.println(get(i));
          }
      }
  
      /**
       * Disposition of a local entry.
       */
      public static enum Disposition {
          /** local started (introduced) */
          START,
  
          /** local ended without being replaced */
          END_SIMPLY,
  
          /** local ended because it was directly replaced */
          END_REPLACED,
  
          /** local ended because it was moved to a different register */
          END_MOVED,
  
          /**
           * local ended because the previous local clobbered this one
           * (because it is category-2)
           */
          END_CLOBBERED_BY_PREV,
  
          /**
           * local ended because the next local clobbered this one
           * (because this one is a category-2)
           */
          END_CLOBBERED_BY_NEXT;
      }
  
      /**
       * Entry in a local list.
       */
      public static class Entry implements Comparable<Entry> {
          /** {@code >= 0;} address */
          private final int address;
  
          /** {@code non-null;} disposition of the local */
          private final Disposition disposition;
  
          /** {@code non-null;} register spec representing the variable */
          private final RegisterSpec spec;
  
          /** {@code non-null;} variable type (derived from {@code spec}) */
          private final CstType type;
  
          /**
           * Constructs an instance.
           *
           * @param address {@code >= 0;} address
           * @param disposition {@code non-null;} disposition of the local
           * @param spec {@code non-null;} register spec representing
           * the variable
           */
          public Entry(int address, Disposition disposition, RegisterSpec spec) {
              if (address < 0) {
                  throw new IllegalArgumentException("address < 0");
              }
  
              if (disposition == null) {
                  throw new NullPointerException("disposition == null");
              }
  
              try {
                  if (spec.getLocalItem() == null) {
                      throw new NullPointerException(
                              "spec.getLocalItem() == null");
                  }
              } catch (NullPointerException ex) {
                  // Elucidate the exception.
                  throw new NullPointerException("spec == null");
              }
  
              this.address = address;
              this.disposition = disposition;
              this.spec = spec;
              this.type = CstType.intern(spec.getType());
          }
  
          /** {@inheritDoc} */
          public String toString() {
              return Integer.toHexString(address) + " " + disposition + " " +
                  spec;
          }
  
          /** {@inheritDoc} */
          public boolean equals(Object other) {
              if (!(other instanceof Entry)) {
                  return false;
              }
  
              return (compareTo((Entry) other) == 0);
          }
  
          /**
           * Compares by (in priority order) address, end then start
           * disposition (variants of end are all consistered
           * equivalent), and spec.
           *
           * @param other {@code non-null;} entry to compare to
           * @return {@code -1..1;} standard result of comparison
           */
          public int compareTo(Entry other) {
              if (address < other.address) {
                  return -1;
              } else if (address > other.address) {
                  return 1;
              }
  
              boolean thisIsStart = isStart();
              boolean otherIsStart = other.isStart();
  
              if (thisIsStart != otherIsStart) {
                  return thisIsStart ? 1 : -1;
              }
  
              return spec.compareTo(other.spec);
          }
  
          /**
           * Gets the address.
           *
           * @return {@code >= 0;} the address
           */
          public int getAddress() {
              return address;
          }
  
          /**
           * Gets the disposition.
           *
           * @return {@code non-null;} the disposition
           */
          public Disposition getDisposition() {
              return disposition;
          }
  
          /**
           * Gets whether this is a local start. This is just shorthand for
           * {@code getDisposition() == Disposition.START}.
           *
           * @return {@code true} iff this is a start
           */
          public boolean isStart() {
              return disposition == Disposition.START;
          }
  
          /**
           * Gets the variable name.
           *
           * @return {@code null-ok;} the variable name
           */
          public CstString getName() {
              return spec.getLocalItem().getName();
          }
  
          /**
           * Gets the variable signature.
           *
           * @return {@code null-ok;} the variable signature
           */
          public CstString getSignature() {
              return spec.getLocalItem().getSignature();
          }
  
          /**
           * Gets the variable's type.
           *
           * @return {@code non-null;} the type
           */
          public CstType getType() {
              return type;
          }
  
          /**
           * Gets the number of the register holding the variable.
           *
           * @return {@code >= 0;} the number of the register holding
           * the variable
           */
          public int getRegister() {
              return spec.getReg();
          }
  
          /**
           * Gets the RegisterSpec of the register holding the variable.
           *
           * @return {@code non-null;} RegisterSpec of the holding register.
           */
          public RegisterSpec getRegisterSpec() {
              return spec;
          }
  
          /**
           * Returns whether or not this instance matches the given spec.
           *
           * @param otherSpec {@code non-null;} the spec in question
           * @return {@code true} iff this instance matches
           * {@code spec}
           */
          public boolean matches(RegisterSpec otherSpec) {
              return spec.equalsUsingSimpleType(otherSpec);
          }
  
          /**
           * Returns whether or not this instance matches the spec in
           * the given instance.
           *
           * @param other {@code non-null;} another entry
           * @return {@code true} iff this instance's spec matches
           * {@code other}
           */
          public boolean matches(Entry other) {
              return matches(other.spec);
          }
  
          /**
           * Returns an instance just like this one but with the disposition
           * set as given.
           *
           * @param disposition {@code non-null;} the new disposition
           * @return {@code non-null;} an appropriately-constructed instance
           */
          public Entry withDisposition(Disposition disposition) {
              if (disposition == this.disposition) {
                  return this;
              }
  
              return new Entry(address, disposition, spec);
          }
      }
  
      /**
       * Constructs an instance for the given method, based on the given
       * block order and intermediate local information.
       *
       * @param insns {@code non-null;} instructions to convert
       * @return {@code non-null;} the constructed list
       */
      public static LocalList make(DalvInsnList insns) {
          int sz = insns.size();
  
          /*
           * Go through the insn list, looking for all the local
           * variable pseudoinstructions, splitting out LocalSnapshots
           * into separate per-variable starts, adding explicit ends
           * wherever a variable is replaced or moved, and collecting
           * these and all the other local variable "activity"
           * together into an output list (without the other insns).
           *
           * Note: As of this writing, this method won't be handed any
           * insn lists that contain local ends, but I (danfuzz) expect
           * that to change at some point, when we start feeding that
           * info explicitly into the rop layer rather than only trying
           * to infer it. So, given that expectation, this code is
           * written to deal with them.
           */
  
          MakeState state = new MakeState(sz);
  
          for (int i = 0; i < sz; i++) {
              DalvInsn insn = insns.get(i);
  
              if (insn instanceof LocalSnapshot) {
                  RegisterSpecSet snapshot =
                      ((LocalSnapshot) insn).getLocals();
                  state.snapshot(insn.getAddress(), snapshot);
              } else if (insn instanceof LocalStart) {
                  RegisterSpec local = ((LocalStart) insn).getLocal();
                  state.startLocal(insn.getAddress(), local);
              }
          }
  
          LocalList result = state.finish();
  
          if (DEBUG) {
              debugVerify(result);
          }
  
          return result;
      }
  
      /**
       * Debugging helper that verifies the constraint that a list doesn't
       * contain any redundant local starts and that local ends that are
       * due to replacements are properly annotated.
       */
      private static void debugVerify(LocalList locals) {
          try {
              debugVerify0(locals);
          } catch (RuntimeException ex) {
              int sz = locals.size();
              for (int i = 0; i < sz; i++) {
                  System.err.println(locals.get(i));
              }
              throw ex;
          }
  
      }
  
      /**
       * Helper for {@link #debugVerify} which does most of the work.
       */
      private static void debugVerify0(LocalList locals) {
          int sz = locals.size();
          Entry[] active = new Entry[65536];
  
          for (int i = 0; i < sz; i++) {
              Entry e = locals.get(i);
              int reg = e.getRegister();
  
              if (e.isStart()) {
                  Entry already = active[reg];
  
                  if ((already != null) && e.matches(already)) {
                      throw new RuntimeException("redundant start at " +
                              Integer.toHexString(e.getAddress()) + ": got " +
                              e + "; had " + already);
                  }
  
                  active[reg] = e;
              } else {
                  if (active[reg] == null) {
                      throw new RuntimeException("redundant end at " +
                              Integer.toHexString(e.getAddress()));
                  }
  
                  int addr = e.getAddress();
                  boolean foundStart = false;
  
                  for (int j = i + 1; j < sz; j++) {
                      Entry test = locals.get(j);
                      if (test.getAddress() != addr) {
                          break;
                      }
                      if (test.getRegisterSpec().getReg() == reg) {
                          if (test.isStart()) {
                              if (e.getDisposition()
                                      != Disposition.END_REPLACED) {
                                  throw new RuntimeException(
                                          "improperly marked end at " +
                                          Integer.toHexString(addr));
                              }
                              foundStart = true;
                          } else {
                              throw new RuntimeException(
                                      "redundant end at " +
                                      Integer.toHexString(addr));
                          }
                      }
                  }
  
                  if (!foundStart &&
                          (e.getDisposition() == Disposition.END_REPLACED)) {
                      throw new RuntimeException(
                              "improper end replacement claim at " +
                              Integer.toHexString(addr));
                  }
  
                  active[reg] = null;
              }
          }
      }
  
      /**
       * Intermediate state when constructing a local list.
       */
      public static class MakeState {
          /** {@code non-null;} result being collected */
          private final ArrayList<Entry> result;
  
          /**
           * {@code >= 0;} running count of nulled result entries, to help with
           * sizing the final list
           */
          private int nullResultCount;
  
          /** {@code null-ok;} current register mappings */
          private RegisterSpecSet regs;
  
          /** {@code null-ok;} result indices where local ends are stored */
          private int[] endIndices;
  
          /** {@code >= 0;} last address seen */
          private int lastAddress;
  
          /**
           * Constructs an instance.
           */
          public MakeState(int initialSize) {
              result = new ArrayList<Entry>(initialSize);
              nullResultCount = 0;
              regs = null;
              endIndices = null;
              lastAddress = 0;
          }
  
          /**
           * Checks the address and other vitals as a prerequisite to
           * further processing.
           *
           * @param address {@code >= 0;} address about to be processed
           * @param reg {@code >= 0;} register number about to be processed
           */
          private void aboutToProcess(int address, int reg) {
              boolean first = (endIndices == null);
  
              if ((address == lastAddress) && !first) {
                  return;
              }
  
              if (address < lastAddress) {
                  throw new RuntimeException("shouldn't happen");
              }
  
              if (first || (reg >= endIndices.length)) {
                  /*
                   * This is the first allocation of the state set and
                   * index array, or we need to grow. (The latter doesn't
                   * happen much; in fact, we have only ever observed
                   * it happening in test cases, never in "real" code.)
                   */
                  int newSz = reg + 1;
                  RegisterSpecSet newRegs = new RegisterSpecSet(newSz);
                  int[] newEnds = new int[newSz];
                  Arrays.fill(newEnds, -1);
  
                  if (!first) {
                      newRegs.putAll(regs);
                      System.arraycopy(endIndices, 0, newEnds, 0,
                              endIndices.length);
                  }
  
                  regs = newRegs;
                  endIndices = newEnds;
              }
          }
  
          /**
           * Sets the local state at the given address to the given snapshot.
           * The first call on this instance must be to this method, so that
           * the register state can be properly sized.
           *
           * @param address {@code >= 0;} the address
           * @param specs {@code non-null;} spec set representing the locals
           */
          public void snapshot(int address, RegisterSpecSet specs) {
              if (DEBUG) {
                  System.err.printf("%04x snapshot %s\n", address, specs);
              }
  
              int sz = specs.getMaxSize();
              aboutToProcess(address, sz - 1);
  
              for (int i = 0; i < sz; i++) {
                  RegisterSpec oldSpec = regs.get(i);
                  RegisterSpec newSpec = filterSpec(specs.get(i));
  
                  if (oldSpec == null) {
                      if (newSpec != null) {
                          startLocal(address, newSpec);
                      }
                  } else if (newSpec == null) {
                      endLocal(address, oldSpec);
                  } else if (! newSpec.equalsUsingSimpleType(oldSpec)) {
                      endLocal(address, oldSpec);
                      startLocal(address, newSpec);
                  }
              }
  
              if (DEBUG) {
                  System.err.printf("%04x snapshot done\n", address);
              }
          }
  
          /**
           * Starts a local at the given address.
           *
           * @param address {@code >= 0;} the address
           * @param startedLocal {@code non-null;} spec representing the
           * started local
           */
          public void startLocal(int address, RegisterSpec startedLocal) {
              if (DEBUG) {
                  System.err.printf("%04x start %s\n", address, startedLocal);
              }
  
              int regNum = startedLocal.getReg();
  
              startedLocal = filterSpec(startedLocal);
              aboutToProcess(address, regNum);
  
              RegisterSpec existingLocal = regs.get(regNum);
  
              if (startedLocal.equalsUsingSimpleType(existingLocal)) {
                  // Silently ignore a redundant start.
                  return;
              }
  
              RegisterSpec movedLocal = regs.findMatchingLocal(startedLocal);
              if (movedLocal != null) {
                  /*
                   * The same variable was moved from one register to another.
                   * So add an end for its old location.
                   */
                  addOrUpdateEnd(address, Disposition.END_MOVED, movedLocal);
              }
  
              int endAt = endIndices[regNum];
  
              if (existingLocal != null) {
                  /*
                   * There is an existing (but non-matching) local.
                   * Add an explicit end for it.
                   */
                  add(address, Disposition.END_REPLACED, existingLocal);
              } else if (endAt >= 0) {
                  /*
                   * Look for an end local for the same register at the
                   * same address. If found, then update it or delete
                   * it, depending on whether or not it represents the
                   * same variable as the one being started.
                   */
                  Entry endEntry = result.get(endAt);
                  if (endEntry.getAddress() == address) {
                      if (endEntry.matches(startedLocal)) {
                          /*
                           * There was already an end local for the same
                           * variable at the same address. This turns
                           * out to be superfluous, as we are starting
                           * up the exact same local. This situation can
                           * happen when a single local variable got
                           * somehow "split up" during intermediate
                           * processing. In any case, rather than represent
                           * the end-then-start, just remove the old end.
                           */
                          result.set(endAt, null);
                          nullResultCount++;
                          regs.put(startedLocal);
                          endIndices[regNum] = -1;
                          return;
                      } else {
                          /*
                           * There was a different variable ended at the
                           * same address. Update it to indicate that
                           * it was ended due to a replacement (rather than
                           * ending for no particular reason).
                           */
                          endEntry = endEntry.withDisposition(
                                  Disposition.END_REPLACED);
                          result.set(endAt, endEntry);
                      }
                  }
              }
  
              /*
               * The code above didn't find and remove an unnecessary
               * local end, so we now have to add one or more entries to
               * the output to capture the transition.
               */
  
              /*
               * If the local just below (in the register set at reg-1)
               * is of category-2, then it is ended by this new start.
               */
              if (regNum > 0) {
                  RegisterSpec justBelow = regs.get(regNum - 1);
                  if ((justBelow != null) && justBelow.isCategory2()) {
                      addOrUpdateEnd(address,
                              Disposition.END_CLOBBERED_BY_NEXT,
                              justBelow);
                  }
              }
  
              /*
               * Similarly, if this local is category-2, then the local
               * just above (if any) is ended by the start now being
               * emitted.
               */
              if (startedLocal.isCategory2()) {
                  RegisterSpec justAbove = regs.get(regNum + 1);
                  if (justAbove != null) {
                      addOrUpdateEnd(address,
                              Disposition.END_CLOBBERED_BY_PREV,
                              justAbove);
                  }
              }
  
              /*
               * TODO: Add an end for the same local in a different reg,
               * if any (that is, if the local migrates from vX to vY,
               * we should note that as a local end in vX).
               */
  
              add(address, Disposition.START, startedLocal);
          }
  
          /**
           * Ends a local at the given address, using the disposition
           * {@code END_SIMPLY}.
           *
           * @param address {@code >= 0;} the address
           * @param endedLocal {@code non-null;} spec representing the
           * local being ended
           */
          public void endLocal(int address, RegisterSpec endedLocal) {
              endLocal(address, endedLocal, Disposition.END_SIMPLY);
          }
  
          /**
           * Ends a local at the given address.
           *
           * @param address {@code >= 0;} the address
           * @param endedLocal {@code non-null;} spec representing the
           * local being ended
           * @param disposition reason for the end
           */
          public void endLocal(int address, RegisterSpec endedLocal,
                  Disposition disposition) {
              if (DEBUG) {
                  System.err.printf("%04x end %s\n", address, endedLocal);
              }
  
              int regNum = endedLocal.getReg();
  
              endedLocal = filterSpec(endedLocal);
              aboutToProcess(address, regNum);
  
              int endAt = endIndices[regNum];
  
              if (endAt >= 0) {
                  /*
                   * The local in the given register is already ended.
                   * Silently return without adding anything to the result.
                   */
                  return;
              }
  
              // Check for start and end at the same address.
              if (checkForEmptyRange(address, endedLocal)) {
                  return;
              }
  
              add(address, disposition, endedLocal);
          }
  
          /**
           * Helper for {@link #endLocal}, which handles the cases where
           * and end local is issued at the same address as a start local
           * for the same register. If this case is found, then this
           * method will remove the start (as the local was never actually
           * active), update the {@link #endIndices} to be accurate, and
           * if needed update the newly-active end to reflect an altered
           * disposition.
           *
           * @param address {@code >= 0;} the address
           * @param endedLocal {@code non-null;} spec representing the
           * local being ended
           * @return {@code true} iff this method found the case in question
           * and adjusted things accordingly
           */
          private boolean checkForEmptyRange(int address,
                  RegisterSpec endedLocal) {
              int at = result.size() - 1;
              Entry entry;
  
              // Look for a previous entry at the same address.
              for (/*at*/; at >= 0; at--) {
                  entry = result.get(at);
  
                  if (entry == null) {
                      continue;
                  }
  
                  if (entry.getAddress() != address) {
                      // We didn't find any match at the same address.
                      return false;
                  }
  
                  if (entry.matches(endedLocal)) {
                      break;
                  }
              }
  
              /*
               * In fact, we found that the endedLocal had started at the
               * same address, so do all the requisite cleanup.
               */
  
              regs.remove(endedLocal);
              result.set(at, null);
              nullResultCount++;
  
              int regNum = endedLocal.getReg();
              boolean found = false;
              entry = null;
  
              // Now look back further to update where the register ended.
              for (at--; at >= 0; at--) {
                  entry = result.get(at);
  
                  if (entry == null) {
                      continue;
                  }
  
                  if (entry.getRegisterSpec().getReg() == regNum) {
                      found = true;
                      break;
                  }
              }
  
              if (found) {
                  // We found an end for the same register.
                  endIndices[regNum] = at;
  
                  if (entry.getAddress() == address) {
                      /*
                       * It's still the same address, so update the
                       * disposition.
                       */
                      result.set(at,
                              entry.withDisposition(Disposition.END_SIMPLY));
                  }
              }
  
              return true;
          }
  
          /**
           * Converts a given spec into the form acceptable for use in a
           * local list. This, in particular, transforms the "known
           * null" type into simply {@code Object}. This method needs to
           * be called for any spec that is on its way into a locals
           * list.
           *
           * <p>This isn't necessarily the cleanest way to achieve the
           * goal of not representing known nulls in a locals list, but
           * it gets the job done.</p>
           *
           * @param orig {@code null-ok;} the original spec
           * @return {@code null-ok;} an appropriately modified spec, or the
           * original if nothing needs to be done
           */
          private static RegisterSpec filterSpec(RegisterSpec orig) {
              if ((orig != null) && (orig.getType() == Type.KNOWN_NULL)) {
                  return orig.withType(Type.OBJECT);
              }
  
              return orig;
          }
  
          /**
           * Adds an entry to the result, updating the adjunct tables
           * accordingly.
           *
           * @param address {@code >= 0;} the address
           * @param disposition {@code non-null;} the disposition
           * @param spec {@code non-null;} spec representing the local
           */
          private void add(int address, Disposition disposition,
                  RegisterSpec spec) {
              int regNum = spec.getReg();
  
              result.add(new Entry(address, disposition, spec));
  
              if (disposition == Disposition.START) {
                  regs.put(spec);
                  endIndices[regNum] = -1;
              } else {
                  regs.remove(spec);
                  endIndices[regNum] = result.size() - 1;
              }
          }
  
          /**
           * Adds or updates an end local (changing its disposition). If
           * this would cause an empty range for a local, this instead
           * removes the local entirely.
           *
           * @param address {@code >= 0;} the address
           * @param disposition {@code non-null;} the disposition
           * @param spec {@code non-null;} spec representing the local
           */
          private void addOrUpdateEnd(int address, Disposition disposition,
                  RegisterSpec spec) {
              if (disposition == Disposition.START) {
                  throw new RuntimeException("shouldn't happen");
              }
  
              int regNum = spec.getReg();
              int endAt = endIndices[regNum];
  
              if (endAt >= 0) {
                  // There is a previous end.
                  Entry endEntry = result.get(endAt);
                  if ((endEntry.getAddress() == address) &&
                          endEntry.getRegisterSpec().equals(spec)) {
                      /*
                       * The end is for the right address and variable, so
                       * update it.
                       */
                      result.set(endAt, endEntry.withDisposition(disposition));
                      regs.remove(spec); // TODO: Is this line superfluous?
                      return;
                  }
              }
  
              endLocal(address, spec, disposition);
          }
  
          /**
           * Finishes processing altogether and gets the result.
           *
           * @return {@code non-null;} the result list
           */
          public LocalList finish() {
              aboutToProcess(Integer.MAX_VALUE, 0);
  
              int resultSz = result.size();
              int finalSz = resultSz - nullResultCount;
  
              if (finalSz == 0) {
                  return EMPTY;
              }
  
              /*
               * Collect an array of only the non-null entries, and then
               * sort it to get a consistent order for everything: Local
               * ends and starts for a given address could come in any
               * order, but we want ends before starts as well as
               * registers in order (within ends or starts).
               */
  
              Entry[] resultArr = new Entry[finalSz];
  
              if (resultSz == finalSz) {
                  result.toArray(resultArr);
              } else {
                  int at = 0;
                  for (Entry e : result) {
                      if (e != null) {
                          resultArr[at++] = e;
                      }
                  }
              }
  
              Arrays.sort(resultArr);
  
              LocalList resultList = new LocalList(finalSz);
  
              for (int i = 0; i < finalSz; i++) {
                  resultList.set(i, resultArr[i]);
              }
  
              resultList.setImmutable();
              return resultList;
          }
      }
  }
  