docs: def __extract_patterns_and_spaces(self):
          """
          Analyses the search plan graph in order to build the required data structures from patterns
          and spaces.
          :return:
          """
  
          def __decorate_nodes(nodes, space):
              """
              Performs a backward search from a list of pattern nodes and assigns a set of search spaces
              to all encountered nodes.
              :param nodes: List of pattern nodes that belongs to a search space
              :param space: List of  search space id
              :return:
              """
              for n in nodes:
                  if n not in self.__node_spaces:
                      self.__node_spaces[n] = set([])
                  self.__node_spaces[n].add(space)
                  pred_nodes = self.__plan_graph.subjects(AGORA.next, n)
                  __decorate_nodes(pred_nodes, space)
  
          # Extract all search spaces in the plan and build a dictionary of subjects-to-ignore per each of them.
          # Ignored subjects are those that won't be dereferenced due to a explicit graph pattern (object) filter,
          # e.g. ?s doap:name "jenkins" -> All ?s that don't match the filter will be ignored.
          self.__spaces = set(self.__plan_graph.subjects(RDF.type, AGORA.SearchSpace))
          self.__subjects_to_ignore = dict([(sp, set([])) for sp in self.__spaces])
  
          patterns = list(self.__plan_graph.subjects(RDF.type, AGORA.TriplePattern))
          for tp in patterns:
              # A triple pattern belongs to a UNIQUE search space
              space = list(self.__plan_graph.subjects(AGORA.definedBy, tp)).pop()
              self.__patterns[tp] = {'space': space}
  
              # Depending on the format of each triple pattern (either '?s a Concept' or '?s prop O'),
              # it is required to extract different properties.
              tp_pred = list(self.__plan_graph.objects(tp, predicate=AGORA.predicate)).pop()
  
              if tp_pred == RDF.type:  # ?s a Concept
                  self.__patterns[tp]['type'] = list(self.__plan_graph.objects(tp, predicate=AGORA.object)).pop()
                  try:
                      check_type = list(self.__plan_graph.objects(tp, predicate=AGORA.checkType)).pop().toPython()
                  except IndexError:
                      check_type = True
                  self.__patterns[tp]['check'] = check_type
              else:  # ?s prop O
                  self.__patterns[tp]['property'] = tp_pred
                  tp_obj = list(self.__plan_graph.objects(tp, predicate=AGORA.object)).pop()
                  if (tp_obj, RDF.type, AGORA.Literal) in self.__plan_graph:  # In case O is a Literal
                      self.__patterns[tp]['filter_object'] = list(self.__plan_graph.objects(tp_obj, AGORA.value)).pop()
                  elif isinstance(tp_obj, URIRef):
                      self.__patterns[tp]['filter_object'] = tp_obj
  
                  tp_sub = list(self.__plan_graph.objects(tp, predicate=AGORA.subject)).pop()
                  if isinstance(tp_sub, URIRef):
                      self.__patterns[tp]['filter_subject'] = tp_sub
  
              # Get all pattern nodes (those that have a byPattern properties) of the search plan and search backwards
              # in order to set the scope of each search space.
              nodes = list(self.__plan_graph.subjects(AGORA.byPattern, tp))
              for n in nodes:
                  if n not in self.__node_patterns:
                      self.__node_patterns[n] = set([])
                  self.__node_patterns[n].add(tp)
              __decorate_nodes(nodes, space)