gt:         if (p_room.get_doors() == null) {
context: /*
   *  Copyright (C) 2014  Alfons Wirtz
   *  website www.freerouting.net
   *
   *   This program is free software: you can redistribute it and/or modify
   *   it under the terms of the GNU General Public License as published by
   *   the Free Software Foundation, either version 3 of the License, or
   *   (at your option) any later version.
   *
   *   This program is distributed in the hope that it will be useful,
   *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   *   GNU General Public License at <http://www.gnu.org/licenses/> 
   *   for more details.
   *
   * AutorouteEngine.java
   *
   * Created on 11. Januar 2004, 11:14
   */
  package net.freerouting.freeroute.autoroute;
  
  import java.util.Collection;
  import java.util.Iterator;
  import java.util.LinkedList;
  import java.util.List;
  import java.util.Set;
  import java.util.SortedSet;
  import java.util.TreeSet;
  import java.util.logging.Level;
  import java.util.logging.Logger;
  import net.freerouting.freeroute.board.Item;
  import net.freerouting.freeroute.board.RoutingBoard;
  import net.freerouting.freeroute.board.SearchTreeObject;
  import net.freerouting.freeroute.board.ShapeSearchTree;
  import net.freerouting.freeroute.board.ShapeSearchTree45Degree;
  import net.freerouting.freeroute.board.ShapeSearchTree90Degree;
  import net.freerouting.freeroute.board.TestLevel;
  import net.freerouting.freeroute.datastructures.Stoppable;
  import net.freerouting.freeroute.datastructures.TimeLimit;
  import net.freerouting.freeroute.geometry.planar.Line;
  import net.freerouting.freeroute.geometry.planar.Simplex;
  import net.freerouting.freeroute.geometry.planar.SimplexUtils;
  import net.freerouting.freeroute.geometry.planar.TileShape;
  
  /**
   * Temporary autoroute data stored on the RoutingBoard.
   *
   * @author Alfons Wirtz
   */
  public class AutorouteEngine {
  
      static final int TRACE_WIDTH_TOLERANCE = 2;
      /**
       * The current search tree used in autorouting. It depends on the trac
       * clearance class used in the autoroute algorithm.
       */
      public final ShapeSearchTree autoroute_search_tree;
      /**
       * If maintain_database, the autorouter database is maintained after a
       * connection is completed for performance reasons.
       */
      public final boolean maintain_database;
      /**
       * The net number used for routing in this autoroute algorithm.
       */
      private int net_no;
      /**
       * The 2-dimensional array of rectangular pages of ExpansionDrills
       */
      final DrillPageArray drill_page_array;
      /**
       * To be able to stop the expansion algorithm.
       */
      Stoppable stoppable_thread;
      /**
       * To stop the expansion algorithm after a time limit is exceeded.
       */
      private TimeLimit time_limit;
      /**
       * The PCB-board of this autoroute algorithm.
       */
      final RoutingBoard board;
      /**
       * The list of incomplete expansion rooms on the routing board
       */
      private List<IncompleteFreeSpaceExpansionRoom> incomplete_expansion_rooms = null;
      /**
       * The list of complete expansion rooms on the routing board
       */
      private List<CompleteFreeSpaceExpansionRoom> complete_expansion_rooms = null;
      /**
       * The count of expansion rooms created so far
       */
      private int expansion_room_instance_count = 0;
  
      /**
       * Creates a new instance of BoardAoutorouteEngine If p_maintain_database,
       * the autorouter database is maintained after a connection is completed for
       * performance reasons.
       */
      public AutorouteEngine(RoutingBoard p_board, int p_trace_clearance_class_no, boolean p_maintain_database) {
          this.board = p_board;
          this.maintain_database = p_maintain_database;
          this.net_no = -1;
          this.autoroute_search_tree = p_board.search_tree_manager.get_autoroute_tree(p_trace_clearance_class_no);
          int max_drill_page_width = (int) (5 * p_board.rules.get_default_via_diameter());
          max_drill_page_width = Math.max(max_drill_page_width, 10_000);
          this.drill_page_array = new DrillPageArray(this.board, max_drill_page_width);
          this.stoppable_thread = null;
      }
  
      public void init_connection(int p_net_no, Stoppable p_stoppable_thread, TimeLimit p_time_limit) {
          if (this.maintain_database && p_net_no != this.net_no) {
              if (this.complete_expansion_rooms != null) {
                  // invalidate the net dependent complete free space expansion rooms.
                  Collection<CompleteFreeSpaceExpansionRoom> rooms_to_remove = new LinkedList<>();
                  complete_expansion_rooms.stream().filter((curr_room) -> (curr_room.is_net_dependent())).forEach((curr_room) -> {
                      rooms_to_remove.add(curr_room);
                  });
                  for (CompleteFreeSpaceExpansionRoom curr_room : rooms_to_remove) {
                      this.remove_complete_expansion_room(curr_room);
                  }
              }
              // invalidate the neighbour rooms of the items of p_net_no
              Collection<Item> item_list = this.board.get_items();
              for (Item curr_item : item_list) {
                  if (curr_item.contains_net(p_net_no)) {
                      this.board.additional_update_after_change(curr_item);
                  }
              }
          }
          this.net_no = p_net_no;
          this.stoppable_thread = p_stoppable_thread;
          this.time_limit = p_time_limit;
      }
  
      /* Autoroutes a connection between p_start_set and p_dest_set.
       * Returns ALREADY_CONNECTED, ROUTED, NOT_ROUTED, or INSERT_ERROR.
       */
      public AutorouteResult autoroute_connection(Set<Item> p_start_set, Set<Item> p_dest_set,
              AutorouteControl p_ctrl, SortedSet<Item> p_ripped_item_list) {
          MazeSearchAlgo maze_search_algo;
          try {
              maze_search_algo = MazeSearchAlgo.get_instance(p_start_set, p_dest_set, this, p_ctrl);
          } catch (Exception e) {
              Logger.getLogger(AutorouteEngine.class.getName()).log(Level.INFO, "AutorouteEngine.autoroute_connection: Exception in MazeSearchAlgo.get_instance", e);
              maze_search_algo = null;
          }
          MazeSearchAlgo.Result search_result = null;
          if (maze_search_algo != null) {
              try {
                  search_result = maze_search_algo.find_connection();
              } catch (Exception e) {
                  Logger.getLogger(AutorouteEngine.class.getName()).log(Level.INFO, "AutorouteEngine.autoroute_connection: Exception in maze_search_algo.find_connection", e);
              }
          }
          LocateFoundConnectionAlgo autoroute_result = null;
          if (search_result != null) {
              autoroute_result
                      = LocateFoundConnectionAlgo.get_instance(search_result, p_ctrl, this.autoroute_search_tree,
                              board.rules.get_trace_angle_restriction(), p_ripped_item_list, board.get_test_level());
          }
          if (!this.maintain_database) {
              this.clear();
          } else {
              this.reset_all_doors();
          }
          if (autoroute_result == null) {
              return AutorouteResult.NOT_ROUTED;
          }
          if (autoroute_result.connection_items == null) {
              if (this.board.get_test_level().ordinal() >= TestLevel.CRITICAL_DEBUGGING_OUTPUT.ordinal()) {
                  Logger.getLogger(AutorouteEngine.class.getName()).log(Level.INFO, "AutorouteEngine.autoroute_connection: result_items != null expected");
              }
              return AutorouteResult.ALREADY_CONNECTED;
          }
          // Delete the ripped  connections.
          SortedSet<Item> ripped_connections = new TreeSet<>();
          Set<Integer> changed_nets = new TreeSet<>();
          Item.StopConnectionOption stop_connection_option;
          if (p_ctrl.remove_unconnected_vias) {
              stop_connection_option = Item.StopConnectionOption.NONE;
          } else {
              stop_connection_option = Item.StopConnectionOption.FANOUT_VIA;
          }
  
          for (Item curr_ripped_item : p_ripped_item_list) {
              ripped_connections.addAll(curr_ripped_item.get_connection_items(stop_connection_option));
              for (int i = 0; i < curr_ripped_item.net_count(); ++i) {
                  changed_nets.add(curr_ripped_item.get_net_no(i));
              }
          }
          // let the observers know the changes in the board database.
          boolean observers_activated = !this.board.observers_active();
          if (observers_activated) {
              this.board.start_notify_observers();
          }
  
          board.remove_items(ripped_connections, false);
  
          for (int curr_net_no : changed_nets) {
              this.board.remove_trace_tails(curr_net_no, stop_connection_option);
          }
          InsertFoundConnectionAlgo insert_found_connection_algo
                  = InsertFoundConnectionAlgo.get_instance(autoroute_result, board, p_ctrl);
  
          if (observers_activated) {
              this.board.end_notify_observers();
          }
          if (insert_found_connection_algo == null) {
              return AutorouteResult.INSERT_ERROR;
          }
          return AutorouteResult.ROUTED;
      }
  
      /**
       * Returns the net number of the current connection to route.
       */
      public int get_net_no() {
          return this.net_no;
      }
  
      /**
       * Returns if the user has stopped the autorouter.
       */
      public boolean is_stop_requested() {
          if (this.time_limit != null) {
              if (this.time_limit.limit_exceeded()) {
                  return true;
              }
          }
          if (this.stoppable_thread == null) {
              return false;
          }
          return this.stoppable_thread.is_stop_requested();
      }
  
      /**
       * Clears all temporary data
       */
      public void clear() {
          if (complete_expansion_rooms != null) {
              complete_expansion_rooms.stream().forEach((curr_room) -> {
                  curr_room.remove_from_tree(this.autoroute_search_tree);
              });
          }
          complete_expansion_rooms = null;
          incomplete_expansion_rooms = null;
          expansion_room_instance_count = 0;
          board.clear_all_item_temporary_autoroute_data();
      }
  
      /**
       * Draws the shapes of the expansion rooms created so far.
       */
      public void draw(java.awt.Graphics p_graphics, net.freerouting.freeroute.boardgraphics.GraphicsContext p_graphics_context, double p_intensity) {
          if (complete_expansion_rooms == null) {
              return;
          }
          complete_expansion_rooms.stream().forEach((curr_room) -> {
              curr_room.draw(p_graphics, p_graphics_context, p_intensity);
          });
          Collection<Item> item_list = this.board.get_items();
          item_list.stream().map((curr_item) -> curr_item.get_autoroute_info()).filter((autoroute_info) -> (autoroute_info != null)).forEach((autoroute_info) -> {
              autoroute_info.draw(p_graphics, p_graphics_context, p_intensity);
          }); // this.drill_page_array.draw(p_graphics, p_graphics_context, p_intensity);
      }
  
      /**
       * Creates a new FreeSpaceExpansionRoom and adds it to the room list. Its
       * shape is normally unbounded at construction time of the room. The final
       * (completed) shape will be a subshape of the start shape, which does not
       * overlap with any obstacle, and it is as big as possible.
       * p_contained_points will remain contained in the shape, after it is
       * completed.
       */
      public IncompleteFreeSpaceExpansionRoom add_incomplete_expansion_room(TileShape p_shape, int p_layer, TileShape p_contained_shape) {
          IncompleteFreeSpaceExpansionRoom new_room = new IncompleteFreeSpaceExpansionRoom(p_shape, p_layer, p_contained_shape);
          if (this.incomplete_expansion_rooms == null) {
              this.incomplete_expansion_rooms = new LinkedList<>();
          }
          this.incomplete_expansion_rooms.add(new_room);
          return new_room;
      }
  
      /**
       * Returns the first elemment in the list of incomplete expansion rooms or
       * null, if the list is empty.
       */
      public IncompleteFreeSpaceExpansionRoom get_first_incomplete_expansion_room() {
          if (incomplete_expansion_rooms == null) {
              return null;
          }
          if (incomplete_expansion_rooms.isEmpty()) {
              return null;
          }
          Iterator<IncompleteFreeSpaceExpansionRoom> it = incomplete_expansion_rooms.iterator();
          return it.next();
      }
  
      /**
       * Removes an incomplete room from the database.
       */
      public void remove_incomplete_expansion_room(IncompleteFreeSpaceExpansionRoom p_room) {
          this.remove_all_doors(p_room);
          incomplete_expansion_rooms.remove(p_room);
      }
  
      /**
       * Removes a complete expansion room from the database and creates new
       * incomplete expansion rooms for the neighbours.
       */
      public void remove_complete_expansion_room(CompleteFreeSpaceExpansionRoom p_room) {
          // create new incomplete expansion rooms for all  neighbours
          TileShape room_shape = p_room.get_shape();
          int room_layer = p_room.get_layer();
          Collection<ExpansionDoor> room_doors = p_room.get_doors();
          room_doors.stream().forEach((curr_door) -> {
              ExpansionRoom curr_neighbour = curr_door.other_room(p_room);
              if (curr_neighbour != null) {
                  curr_neighbour.remove_door(curr_door);
                  TileShape neighbour_shape = curr_neighbour.get_shape();
                  TileShape intersection = room_shape.intersection(neighbour_shape);
                  if (intersection.dimension() == 1) {
                      // add a new incomplete room to curr_neighbour.
                      int[] touching_sides = room_shape.touching_sides(neighbour_shape);
                      Line[] line_arr = new Line[1];
                      line_arr[0] = neighbour_shape.border_line(touching_sides[1]).opposite();
                      Simplex new_incomplete_room_shape = SimplexUtils.get_instance(line_arr);
                      IncompleteFreeSpaceExpansionRoom new_incomplete_room
                              = add_incomplete_expansion_room(new_incomplete_room_shape, room_layer, intersection);
                      ExpansionDoor new_door = new ExpansionDoor(curr_neighbour, new_incomplete_room, 1);
                      curr_neighbour.add_door(new_door);
                      new_incomplete_room.add_door(new_door);
                  }
              }
          });
          this.remove_all_doors(p_room);
          p_room.remove_from_tree(this.autoroute_search_tree);
          if (complete_expansion_rooms != null) {
              complete_expansion_rooms.remove(p_room);
          } else {
              Logger.getLogger(AutorouteEngine.class.getName()).log(Level.INFO, "AutorouteEngine.remove_complete_expansion_room: this.complete_expansion_rooms is null");
          }
          this.drill_page_array.invalidate(room_shape);
      }
  
      /**
       * Completes the shape of p_room. Returns the resulting rooms after
       * completing the shape. p_room will no more exist after this function.
       */
      public Collection<CompleteFreeSpaceExpansionRoom> complete_expansion_room(IncompleteFreeSpaceExpansionRoom p_room) {
  
          try {
              Collection<CompleteFreeSpaceExpansionRoom> result = new LinkedList<>();
              TileShape from_door_shape = null;
              SearchTreeObject ignore_object = null;
              Collection<ExpansionDoor> room_doors = p_room.get_doors();
              for (ExpansionDoor curr_door : room_doors) {
                  ExpansionRoom other_room = curr_door.other_room(p_room);
                  if (other_room instanceof CompleteFreeSpaceExpansionRoom && curr_door.dimension == 2) {
                      from_door_shape = curr_door.get_shape();
                      ignore_object = (CompleteFreeSpaceExpansionRoom) other_room;
                      break;
                  }
              }
              Collection<IncompleteFreeSpaceExpansionRoom> completed_shapes
                      = this.autoroute_search_tree.complete_shape(p_room, this.net_no, ignore_object, from_door_shape);
              this.remove_incomplete_expansion_room(p_room);
              Iterator<IncompleteFreeSpaceExpansionRoom> it = completed_shapes.iterator();
              boolean is_first_completed_room = true;
              while (it.hasNext()) {
                  IncompleteFreeSpaceExpansionRoom curr_incomplete_room = it.next();
                  if (curr_incomplete_room.get_shape().dimension() != 2) {
                      continue;
                  }
                  if (is_first_completed_room) {
                      is_first_completed_room = false;
                      CompleteFreeSpaceExpansionRoom completed_room = this.add_complete_room(curr_incomplete_room);
                      if (completed_room != null) {
                          result.add(completed_room);
                      }
                  } else {
                      // the shape of the first completed room may have changed and may
                      // intersect now with the other shapes. Therefore the completed shapes
                      // have to be recalculated.
                      Collection<IncompleteFreeSpaceExpansionRoom> curr_completed_shapes
                              = this.autoroute_search_tree.complete_shape(curr_incomplete_room, this.net_no,
                                      ignore_object, from_door_shape);
                      Iterator<IncompleteFreeSpaceExpansionRoom> it2 = curr_completed_shapes.iterator();
                      while (it2.hasNext()) {
                          IncompleteFreeSpaceExpansionRoom tmp_room = it2.next();
                          CompleteFreeSpaceExpansionRoom completed_room = this.add_complete_room(tmp_room);
                          if (completed_room != null) {
                              result.add(completed_room);
                          }
                      }
                  }
              }
              return result;
          } catch (Exception e) {
              Logger.getLogger(AutorouteEngine.class.getName()).log(Level.INFO, "AutorouteEngine.complete_expansion_room: ", e);
              return new LinkedList<>();
          }
  
      }
  
      /**
       * Calculates the doors and adds the completed room to the room database.
       */
      private CompleteFreeSpaceExpansionRoom add_complete_room(IncompleteFreeSpaceExpansionRoom p_room) {
          CompleteFreeSpaceExpansionRoom completed_room = (CompleteFreeSpaceExpansionRoom) calculate_doors(p_room);
          CompleteFreeSpaceExpansionRoom result;
          if (completed_room != null && completed_room.get_shape().dimension() == 2) {
              if (complete_expansion_rooms == null) {
                  complete_expansion_rooms = new LinkedList<>();
              }
              complete_expansion_rooms.add(completed_room);
              this.autoroute_search_tree.insert(completed_room);
              result = completed_room;
          } else {
              result = null;
          }
          return result;
      }
  
      /**
       * Calculates the neighbours of p_room and inserts doors to the new created
       * neighbour rooms. The shape of the result room may be different to the
       * shape of p_room
       */
      private CompleteExpansionRoom calculate_doors(ExpansionRoom p_room) {
          CompleteExpansionRoom result;
          if (this.autoroute_search_tree instanceof ShapeSearchTree90Degree) {
              result = SortedOrthogonalRoomNeighbours.calculate(p_room, this);
          } else if (this.autoroute_search_tree instanceof ShapeSearchTree45Degree) {
              result = Sorted45DegreeRoomNeighbours.calculate(p_room, this);
          } else {
              result = SortedRoomNeighbours.calculate(p_room, this);
          }
          return result;
      }
  
      /**
       * Completes the shapes of the neigbour rooms of p_room, so that the doors
       * of p_room will not change later on.
       */
      public void complete_neigbour_rooms(CompleteExpansionRoom p_room) {
  