gt:                 assert mLeft[i + 1] >= mLeft[i];
context: /*
   * Copyright (C) 2011 The Android Open Source Project
   *
   * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
   *
   * 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.android.ide.common.layout.grid;
  
  import static com.android.SdkConstants.ANDROID_URI;
  import static com.android.SdkConstants.ATTR_COLUMN_COUNT;
  import static com.android.SdkConstants.ATTR_ID;
  import static com.android.SdkConstants.ATTR_LAYOUT_COLUMN;
  import static com.android.SdkConstants.ATTR_LAYOUT_COLUMN_SPAN;
  import static com.android.SdkConstants.ATTR_LAYOUT_GRAVITY;
  import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
  import static com.android.SdkConstants.ATTR_LAYOUT_ROW;
  import static com.android.SdkConstants.ATTR_LAYOUT_ROW_SPAN;
  import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
  import static com.android.SdkConstants.ATTR_ORIENTATION;
  import static com.android.SdkConstants.ATTR_ROW_COUNT;
  import static com.android.SdkConstants.FQCN_GRID_LAYOUT;
  import static com.android.SdkConstants.FQCN_SPACE;
  import static com.android.SdkConstants.FQCN_SPACE_V7;
  import static com.android.SdkConstants.GRID_LAYOUT;
  import static com.android.SdkConstants.NEW_ID_PREFIX;
  import static com.android.SdkConstants.SPACE;
  import static com.android.SdkConstants.VALUE_BOTTOM;
  import static com.android.SdkConstants.VALUE_CENTER_VERTICAL;
  import static com.android.SdkConstants.VALUE_N_DP;
  import static com.android.SdkConstants.VALUE_TOP;
  import static com.android.SdkConstants.VALUE_VERTICAL;
  import static com.android.ide.common.layout.GravityHelper.GRAVITY_BOTTOM;
  import static com.android.ide.common.layout.GravityHelper.GRAVITY_CENTER_HORIZ;
  import static com.android.ide.common.layout.GravityHelper.GRAVITY_CENTER_VERT;
  import static com.android.ide.common.layout.GravityHelper.GRAVITY_RIGHT;
  import static java.lang.Math.abs;
  import static java.lang.Math.max;
  import static java.lang.Math.min;
  
  import com.android.annotations.NonNull;
  import com.android.annotations.Nullable;
  import com.android.ide.common.api.IClientRulesEngine;
  import com.android.ide.common.api.INode;
  import com.android.ide.common.api.IViewMetadata;
  import com.android.ide.common.api.Margins;
  import com.android.ide.common.api.Rect;
  import com.android.ide.common.layout.GravityHelper;
  import com.android.ide.common.layout.GridLayoutRule;
  import com.android.utils.Pair;
  import com.google.common.collect.ArrayListMultimap;
  import com.google.common.collect.Multimap;
  
  import java.io.PrintWriter;
  import java.io.StringWriter;
  import java.lang.ref.WeakReference;
  import java.lang.reflect.Field;
  import java.lang.reflect.Method;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  
  /** Models a GridLayout */
  public class GridModel {
      /** Marker value used to indicate values (rows, columns, etc) which have not been set */
      static final int UNDEFINED = Integer.MIN_VALUE;
  
      /** The size of spacers in the dimension that they are not defining */
      private static final int SPACER_SIZE_DP = 1;
  
      /** Attribute value used for {@link #SPACER_SIZE_DP} */
      private static final String SPACER_SIZE = String.format(VALUE_N_DP, SPACER_SIZE_DP);
  
      /** Width assigned to a newly added column with the Add Column action */
      private static final int DEFAULT_CELL_WIDTH = 100;
  
      /** Height assigned to a newly added row with the Add Row action */
      private static final int DEFAULT_CELL_HEIGHT = 15;
  
      /** The GridLayout node, never null */
      public final INode layout;
  
      /** True if this is a vertical layout, and false if it is horizontal (the default) */
      public boolean vertical;
  
      /** The declared count of rows (which may be {@link #UNDEFINED} if not specified) */
      public int declaredRowCount;
  
      /** The declared count of columns (which may be {@link #UNDEFINED} if not specified) */
      public int declaredColumnCount;
  
      /** The actual count of rows found in the grid */
      public int actualRowCount;
  
      /** The actual count of columns found in the grid */
      public int actualColumnCount;
  
      /**
       * Array of positions (indexed by column) of the left edge of table cells; this
       * corresponds to the column positions in the grid
       */
      private int[] mLeft;
  
      /**
       * Array of positions (indexed by row) of the top edge of table cells; this
       * corresponds to the row positions in the grid
       */
      private int[] mTop;
  
      /**
       * Array of positions (indexed by column) of the maximum right hand side bounds of a
       * node in the given column; this represents the visual edge of a column even when the
       * actual column is wider
       */
      private int[] mMaxRight;
  
      /**
       * Array of positions (indexed by row) of the maximum bottom bounds of a node in the
       * given row; this represents the visual edge of a row even when the actual row is
       * taller
       */
      private int[] mMaxBottom;
  
      /**
       * Array of baselines computed for the rows. This array is populated lazily and should
       * not be accessed directly; call {@link #getBaseline(int)} instead.
       */
      private int[] mBaselines;
  
      /** List of all the view data for the children in this layout */
      private List<ViewData> mChildViews;
  
      /** The {@link IClientRulesEngine} */
      private final IClientRulesEngine mRulesEngine;
  
      /**
       * An actual instance of a GridLayout object that this grid model corresponds to.
       */
      private Object mViewObject;
  
      /** The namespace to use for attributes */
      private String mNamespace;
  
      /**
       * Constructs a {@link GridModel} for the given layout
       *
       * @param rulesEngine the associated rules engine
       * @param node the GridLayout node
       * @param viewObject an actual GridLayout instance, or null
       */
      private GridModel(IClientRulesEngine rulesEngine, INode node, Object viewObject) {
          mRulesEngine = rulesEngine;
          layout = node;
          mViewObject = viewObject;
          loadFromXml();
      }
  
      // Factory cache for most recent item (used primarily because during paints and drags
      // the grid model is called repeatedly for the same view object.)
      private static WeakReference<Object> sCachedViewObject = new WeakReference<Object>(null);
      private static WeakReference<GridModel> sCachedViewModel;
  
      /**
       * Factory which returns a grid model for the given node.
       *
       * @param rulesEngine the associated rules engine
       * @param node the GridLayout node
       * @param viewObject an actual GridLayout instance, or null
       * @return a new model
       */
      @NonNull
      public static GridModel get(
              @NonNull IClientRulesEngine rulesEngine,
              @NonNull INode node,
              @Nullable Object viewObject) {
          if (viewObject != null && viewObject == sCachedViewObject.get()) {
              GridModel model = sCachedViewModel.get();
              if (model != null) {
                  return model;
              }
          }
  
          GridModel model = new GridModel(rulesEngine, node, viewObject);
          sCachedViewModel = new WeakReference<GridModel>(model);
          sCachedViewObject = new WeakReference<Object>(viewObject);
          return model;
      }
  
      /**
       * Returns the {@link ViewData} for the child at the given index
       *
       * @param index the position of the child node whose view we want to look up
       * @return the corresponding {@link ViewData}
       */
      public ViewData getView(int index) {
          return mChildViews.get(index);
      }
  
      /**
       * Returns the {@link ViewData} for the given child node.
       *
       * @param node the node for which we want the view info
       * @return the view info for the node, or null if not found
       */
      public ViewData getView(INode node) {
          for (ViewData view : mChildViews) {
              if (view.node == node) {
                  return view;
              }
          }
  
          return null;
      }
  
      /**
       * Computes the index (among the children nodes) to insert a new node into which
       * should be positioned at the given row and column. This will skip over any nodes
       * that have implicit positions earlier than the given node, and will also ensure that
       * all nodes are placed before the spacer nodes.
       *
       * @param row the target row of the new node
       * @param column the target column of the new node
       * @return the insert position to use or -1 if no preference is found
       */
      public int getInsertIndex(int row, int column) {
          if (vertical) {
              for (ViewData view : mChildViews) {
                  if (view.column > column || view.column == column && view.row >= row) {
                      return view.index;
                  }
              }
          } else {
              for (ViewData view : mChildViews) {
                  if (view.row > row || view.row == row && view.column >= column) {
                      return view.index;
                  }
              }
          }
  
          // Place it before the first spacer
          for (ViewData view : mChildViews) {
              if (view.isSpacer()) {
                  return view.index;
              }
          }
  
          return -1;
      }
  
      /**
       * Returns the baseline of the given row, or -1 if none is found. This looks for views
       * in the row which have baseline vertical alignment and also define their own
       * baseline, and returns the first such match.
       *
       * @param row the row to look up a baseline for
       * @return the baseline relative to the row position, or -1 if not defined
       */
      public int getBaseline(int row) {
          if (row < 0 || row >= mBaselines.length) {
              return -1;
          }
  
          int baseline = mBaselines[row];
          if (baseline == UNDEFINED) {
              baseline = -1;
  
              // TBD: Consider stringing together row information in the view data
              // so I can quickly identify the views in a given row instead of searching
              // among all?
              for (ViewData view : mChildViews) {
                  // We only count baselines for views with rowSpan=1 because
                  // baseline alignment doesn't work for cell spanning views
                  if (view.row == row && view.rowSpan == 1) {
                      baseline = view.node.getBaseline();
                      if (baseline != -1) {
                          // Even views that do have baselines do not count towards a row
                          // baseline if they have a vertical gravity
                          String gravity = getGridAttribute(view.node, ATTR_LAYOUT_GRAVITY);
                          if (gravity == null
                                  || !(gravity.contains(VALUE_TOP)
                                          || gravity.contains(VALUE_BOTTOM)
                                          || gravity.contains(VALUE_CENTER_VERTICAL))) {
                              // Compute baseline relative to the row, not the view itself
                              baseline += view.node.getBounds().y - getRowY(row);
                              break;
                          }
                      }
                  }
              }
              mBaselines[row] = baseline;
          }
  
          return baseline;
      }
  
      /** Applies the row and column values into the XML */
      void applyPositionAttributes() {
          for (ViewData view : mChildViews) {
              view.applyPositionAttributes();
          }
  
          // Also fix the columnCount
          if (getGridAttribute(layout, ATTR_COLUMN_COUNT) != null &&
                  declaredColumnCount > actualColumnCount) {
              setGridAttribute(layout, ATTR_COLUMN_COUNT, actualColumnCount);
          }
      }
  
      /**
       * Sets the given GridLayout attribute (rowCount, layout_row, etc) to the
       * given value. This automatically handles using the right XML namespace
       * based on whether the GridLayout is the android.widget.GridLayout, or the
       * support library GridLayout, and whether it's in a library project or not
       * etc.
       *
       * @param node the node to apply the attribute to
       * @param name the local name of the attribute
       * @param value the integer value to set the attribute to
       */
      public void setGridAttribute(INode node, String name, int value) {
          setGridAttribute(node, name, Integer.toString(value));
      }
  
      /**
       * Sets the given GridLayout attribute (rowCount, layout_row, etc) to the
       * given value. This automatically handles using the right XML namespace
       * based on whether the GridLayout is the android.widget.GridLayout, or the
       * support library GridLayout, and whether it's in a library project or not
       * etc.
       *
       * @param node the node to apply the attribute to
       * @param name the local name of the attribute
       * @param value the string value to set the attribute to, or null to clear
       *            it
       */
      public void setGridAttribute(INode node, String name, String value) {
          node.setAttribute(getNamespace(), name, value);
      }
  
      /**
       * Returns the namespace URI to use for GridLayout-specific attributes, such
       * as columnCount, layout_column, layout_column_span, layout_gravity etc.
       *
       * @return the namespace, never null
       */
      public String getNamespace() {
          if (mNamespace == null) {
              mNamespace = ANDROID_URI;
  
              if (!layout.getFqcn().equals(FQCN_GRID_LAYOUT)) {
                  mNamespace = mRulesEngine.getAppNameSpace();
              }
          }
  
          return mNamespace;
      }
  
      /** Removes the given flag from a flag attribute value and returns the result */
      static String removeFlag(String flag, String value) {
          if (value.equals(flag)) {
              return null;
          }
          // Handle spaces between pipes and flag are a prefix, suffix and interior occurrences
          int index = value.indexOf(flag);
          if (index != -1) {
              int pipe = value.lastIndexOf('|', index);
              int endIndex = index + flag.length();
              if (pipe != -1) {
                  value = value.substring(0, pipe).trim() + value.substring(endIndex).trim();
              } else {
                  pipe = value.indexOf('|', endIndex);
                  if (pipe != -1) {
                      value = value.substring(0, index).trim() + value.substring(pipe + 1).trim();
                  } else {
                      value = value.substring(0, index).trim() + value.substring(endIndex).trim();
                  }
              }
          }
  
          return value;
      }
  
      /**
       * Loads a {@link GridModel} from the XML model.
       */
      private void loadFromXml() {
          INode[] children = layout.getChildren();
  
          declaredRowCount = getGridAttribute(layout, ATTR_ROW_COUNT, UNDEFINED);
          declaredColumnCount = getGridAttribute(layout, ATTR_COLUMN_COUNT, UNDEFINED);
          // Horizontal is the default, so if no value is specified it is horizontal.
          vertical = VALUE_VERTICAL.equals(getGridAttribute(layout, ATTR_ORIENTATION));
  
          mChildViews = new ArrayList<ViewData>(children.length);
          int index = 0;
          for (INode child : children) {
              ViewData view = new ViewData(child, index++);
              mChildViews.add(view);
          }
  
          // Assign row/column positions to all cells that do not explicitly define them
          if (!assignRowsAndColumnsFromViews(mChildViews)) {
              assignRowsAndColumnsFromXml(
                      declaredRowCount == UNDEFINED ? children.length : declaredRowCount,
                      declaredColumnCount == UNDEFINED ? children.length : declaredColumnCount);
          }
  
          assignCellBounds();
  
          for (int i = 0; i <= actualRowCount; i++) {
              mBaselines[i] = UNDEFINED;
          }
      }
  
      private Pair<Map<Integer, Integer>, Map<Integer, Integer>> findCellsOutsideDeclaredBounds() {
          // See if we have any (row,column) pairs that fall outside the declared
          // bounds; for these we identify the number of unique values and assign these
          // consecutive values
          Map<Integer, Integer> extraColumnsMap = null;
          Map<Integer, Integer> extraRowsMap = null;
          if (declaredRowCount != UNDEFINED) {
              Set<Integer> extraRows = null;
              for (ViewData view : mChildViews) {
                  if (view.row >= declaredRowCount) {
                      if (extraRows == null) {
                          extraRows = new HashSet<Integer>();
                      }
                      extraRows.add(view.row);
                  }
              }
              if (extraRows != null && declaredRowCount != UNDEFINED) {
                  List<Integer> rows = new ArrayList<Integer>(extraRows);
                  Collections.sort(rows);
                  int row = declaredRowCount;
                  extraRowsMap = new HashMap<Integer, Integer>();
                  for (Integer declared : rows) {
                      extraRowsMap.put(declared, row++);
                  }
              }
          }
          if (declaredColumnCount != UNDEFINED) {
              Set<Integer> extraColumns = null;
              for (ViewData view : mChildViews) {
                  if (view.column >= declaredColumnCount) {
                      if (extraColumns == null) {
                          extraColumns = new HashSet<Integer>();
                      }
                      extraColumns.add(view.column);
                  }
              }
              if (extraColumns != null && declaredColumnCount != UNDEFINED) {
                  List<Integer> columns = new ArrayList<Integer>(extraColumns);
                  Collections.sort(columns);
                  int column = declaredColumnCount;
                  extraColumnsMap = new HashMap<Integer, Integer>();
                  for (Integer declared : columns) {
                      extraColumnsMap.put(declared, column++);
                  }
              }
          }
  
          return Pair.of(extraRowsMap, extraColumnsMap);
      }
  
      /**
       * Figure out actual row and column numbers for views that do not specify explicit row
       * and/or column numbers
       * TODO: Consolidate with the algorithm in GridLayout to ensure we get the
       * exact same results!
       */
      private void assignRowsAndColumnsFromXml(int rowCount, int columnCount) {
          Pair<Map<Integer, Integer>, Map<Integer, Integer>> p = findCellsOutsideDeclaredBounds();
          Map<Integer, Integer> extraRowsMap = p.getFirst();
          Map<Integer, Integer> extraColumnsMap = p.getSecond();
  
          if (!vertical) {
              // Horizontal GridLayout: this is the default. Row and column numbers
              // are assigned by assuming that the children are assigned successive
              // column numbers until we get to the column count of the grid, at which
              // point we jump to the next row. If any cell specifies either an explicit
              // row number of column number, we jump to the next available position.
              // Note also that if there are any rowspans on the current row, then the
              // next row we jump to is below the largest such rowspan - in other words,
              // the algorithm does not fill holes in the middle!
  
              // TODO: Ensure that we don't run into trouble if a later element specifies
              // an earlier number... find out what the layout does in that case!
              int row = 0;
              int column = 0;
              int nextRow = 1;
              for (ViewData view : mChildViews) {
                  int declaredColumn = view.column;
                  if (declaredColumn != UNDEFINED) {
                      if (declaredColumn >= columnCount) {
                          assert extraColumnsMap != null;
                          declaredColumn = extraColumnsMap.get(declaredColumn);
                          view.column = declaredColumn;
                      }
                      if (declaredColumn < column) {
                          // Must jump to the next row to accommodate the new row
                          assert nextRow > row;
                          //row++;
                          row = nextRow;
                      }
                      column = declaredColumn;
                  } else {
                      view.column = column;
                  }
                  if (view.row != UNDEFINED) {
                      // TODO: Should this adjust the column number too? (If so must
                      // also update view.column since we've already processed the local
                      // column number)
                      row = view.row;
                  } else {
                      view.row = row;
                  }
  
                  nextRow = Math.max(nextRow, view.row + view.rowSpan);
  
                  // Advance
                  column += view.columnSpan;
                  if (column >= columnCount) {
                      column = 0;
                      assert nextRow > row;
                      //row++;
                      row = nextRow;
                  }
              }
          } else {
              // Vertical layout: successive children are assigned to the same column in
              // successive rows.
              int row = 0;
              int column = 0;
              int nextColumn = 1;
              for (ViewData view : mChildViews) {
                  int declaredRow = view.row;
                  if (declaredRow != UNDEFINED) {
                      if (declaredRow >= rowCount) {
                          declaredRow = extraRowsMap.get(declaredRow);
                          view.row = declaredRow;
                      }
                      if (declaredRow < row) {
                          // Must jump to the next column to accommodate the new column
                          assert nextColumn > column;
                          column = nextColumn;
                      }
                      row = declaredRow;
                  } else {
                      view.row = row;
                  }
                  if (view.column != UNDEFINED) {
                      // TODO: Should this adjust the row number too? (If so must
                      // also update view.row since we've already processed the local
                      // row number)
                      column = view.column;
                  } else {
                      view.column = column;
                  }
  
                  nextColumn = Math.max(nextColumn, view.column + view.columnSpan);
  
                  // Advance
                  row += view.rowSpan;
                  if (row >= rowCount) {
                      row = 0;
                      assert nextColumn > column;
                      //row++;
                      column = nextColumn;
                  }
              }
          }
      }
  
      private static boolean sAttemptSpecReflection = true;
  
      private boolean assignRowsAndColumnsFromViews(List<ViewData> views) {
          if (!sAttemptSpecReflection) {
              return false;
          }
  
          try {
              // Lazily initialized reflection methods
              Field spanField = null;
              Field rowSpecField = null;
              Field colSpecField = null;
              Field minField = null;
              Field maxField = null;
              Method getLayoutParams = null;
  
              for (ViewData view : views) {
                  // TODO: If the element *specifies* anything in XML, use that instead
                  Object child = mRulesEngine.getViewObject(view.node);
                  if (child == null) {
                      // Fallback to XML model
                      return false;
                  }
  
                  if (getLayoutParams == null) {
                      getLayoutParams = child.getClass().getMethod("getLayoutParams"); //$NON-NLS-1$
                  }
                  Object layoutParams = getLayoutParams.invoke(child);
                  if (rowSpecField == null) {
                      Class<? extends Object> layoutParamsClass = layoutParams.getClass();
                      rowSpecField = layoutParamsClass.getDeclaredField("rowSpec");    //$NON-NLS-1$
                      colSpecField = layoutParamsClass.getDeclaredField("columnSpec"); //$NON-NLS-1$
                      rowSpecField.setAccessible(true);
                      colSpecField.setAccessible(true);
                  }
                  assert colSpecField != null;
  
                  Object rowSpec = rowSpecField.get(layoutParams);
                  Object colSpec = colSpecField.get(layoutParams);
                  if (spanField == null) {
                      spanField = rowSpec.getClass().getDeclaredField("span"); //$NON-NLS-1$
                      spanField.setAccessible(true);
                  }
                  assert spanField != null;
                  Object rowInterval = spanField.get(rowSpec);
                  Object colInterval = spanField.get(colSpec);
                  if (minField == null) {
                      Class<? extends Object> intervalClass = rowInterval.getClass();
                      minField = intervalClass.getDeclaredField("min"); //$NON-NLS-1$
                      maxField = intervalClass.getDeclaredField("max"); //$NON-NLS-1$
                      minField.setAccessible(true);
                      maxField.setAccessible(true);
                  }
                  assert maxField != null;
  
                  int row = minField.getInt(rowInterval);
                  int col = minField.getInt(colInterval);
                  int rowEnd = maxField.getInt(rowInterval);
                  int colEnd = maxField.getInt(colInterval);
  
                  view.column = col;
                  view.row = row;
                  view.columnSpan = colEnd - col;
                  view.rowSpan = rowEnd - row;
              }
  
              return true;
  
          } catch (Throwable e) {
              sAttemptSpecReflection = false;
              return false;
          }
      }
  
      /**
       * Computes the positions of the column and row boundaries
       */
      private void assignCellBounds() {
          if (!assignCellBoundsFromView()) {
              assignCellBoundsFromBounds();
          }
          initializeMaxBounds();
          mBaselines = new int[actualRowCount + 1];
      }
  
      /**
       * Computes the positions of the column and row boundaries, using actual
       * layout data from the associated GridLayout instance (stored in
       * {@link #mViewObject})
       */
      private boolean assignCellBoundsFromView() {
          if (mViewObject != null) {
              Pair<int[], int[]> cellBounds = GridModel.getAxisBounds(mViewObject);
              if (cellBounds != null) {
                  int[] xs = cellBounds.getFirst();
                  int[] ys = cellBounds.getSecond();
  
                  actualColumnCount = xs.length - 1;
                  actualRowCount = ys.length - 1;
  
                  Rect layoutBounds = layout.getBounds();
                  int layoutBoundsX = layoutBounds.x;
                  int layoutBoundsY = layoutBounds.y;
                  mLeft = new int[xs.length];
                  mTop = new int[ys.length];
                  for (int i = 0; i < xs.length; i++) {
                      mLeft[i] = xs[i] + layoutBoundsX;
                  }
                  for (int i = 0; i < ys.length; i++) {
                      mTop[i] = ys[i] + layoutBoundsY;
                  }
  
                  return true;
              }
          }
  
          return false;
      }
  
      /**
       * Computes the boundaries of the rows and columns by considering the bounds of the
       * children.
       */
      private void assignCellBoundsFromBounds() {
          Rect layoutBounds = layout.getBounds();
  
          // Compute the actualColumnCount and actualRowCount. This -should- be
          // as easy as declaredColumnCount + extraColumnsMap.size(),
          // but the user doesn't *have* to declare a column count (or a row count)
          // and we need both, so go and find the actual row and column maximums.
          int maxColumn = 0;
          int maxRow = 0;
          for (ViewData view : mChildViews) {
              maxColumn = max(maxColumn, view.column);
              maxRow = max(maxRow, view.row);
          }
          actualColumnCount = maxColumn + 1;
          actualRowCount = maxRow + 1;
  
          mLeft = new int[actualColumnCount + 1];
          for (int i = 1; i < actualColumnCount; i++) {
              mLeft[i] = UNDEFINED;
          }
          mLeft[0] = layoutBounds.x;
          mLeft[actualColumnCount] = layoutBounds.x2();
          mTop = new int[actualRowCount + 1];
          for (int i = 1; i < actualRowCount; i++) {
              mTop[i] = UNDEFINED;
          }
          mTop[0] = layoutBounds.y;
          mTop[actualRowCount] = layoutBounds.y2();
  
          for (ViewData view : mChildViews) {
              Rect bounds = view.node.getBounds();
              if (!bounds.isValid()) {
                  continue;
              }
              int column = view.column;
              int row = view.row;
  
              if (mLeft[column] == UNDEFINED) {
                  mLeft[column] = bounds.x;
              } else {
                  mLeft[column] = Math.min(bounds.x, mLeft[column]);
              }
              if (mTop[row] == UNDEFINED) {
                  mTop[row] = bounds.y;
              } else {
                  mTop[row] = Math.min(bounds.y, mTop[row]);
              }
          }
  
          // Ensure that any empty columns/rows have a valid boundary value; for now,
          for (int i = actualColumnCount - 1; i >= 0; i--) {
              if (mLeft[i] == UNDEFINED) {
                  if (i == 0) {
                      mLeft[i] = layoutBounds.x;
                  } else if (i < actualColumnCount - 1) {
                      mLeft[i] = mLeft[i + 1] - 1;
                      if (mLeft[i - 1] != UNDEFINED && mLeft[i] < mLeft[i - 1]) {
                          mLeft[i] = mLeft[i - 1];
                      }
                  } else {
                      mLeft[i] = layoutBounds.x2();
                  }
              }
          }
          for (int i = actualRowCount - 1; i >= 0; i--) {
              if (mTop[i] == UNDEFINED) {
                  if (i == 0) {
                      mTop[i] = layoutBounds.y;
                  } else if (i < actualRowCount - 1) {
                      mTop[i] = mTop[i + 1] - 1;
                      if (mTop[i - 1] != UNDEFINED && mTop[i] < mTop[i - 1]) {
                          mTop[i] = mTop[i - 1];
                      }
                  } else {
                      mTop[i] = layoutBounds.y2();
                  }
              }
          }
  
          // The bounds should be in ascending order now
          if (false && GridLayoutRule.sDebugGridLayout) {
              for (int i = 1; i < actualRowCount; i++) {
                  assert mTop[i + 1] >= mTop[i];
              }
              for (int i = 0; i < actualColumnCount; i++) {
  