<commit_before><?php

/*
 * Copyright 2011 Facebook, Inc.
 *
 * 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.
 */

/**
 * Query and load Phutil classes, interfaces and functions. PhutilSymbolLoader
 * is a query object which selects symbols which satisfy certain criteria, and
 * optionally loads them. For instance, to load all classes in a module:
 *
 *    $symbols = id(new PhutilSymbolLoader())
 *      ->setType('class')
 *      ->setLibrary('example')
 *      ->setModule('some/module')
 *      ->selectAndLoadSymbols();
 *
 * When you execute the loading query, it returns a dictionary of matching
 * symbols:
 *
 *    array(
 *      'class$Example' => array(
 *        'type'    => 'class',
 *        'name'    => 'Example',
 *        'library' => 'libexample',
 *        'module'  => 'examples/example',
 *      ),
 *      // ... more ...
 *    );
 *
 * The **library** and **module** keys show where the symbol is defined. The
 * **type** and **name** keys identify the symbol itself.
 *
 * @task config   Configuring the Query
 * @task load     Loading Symbols
 * @task internal Internals
 *
 * @group library
 */
final class PhutilSymbolLoader {

  private $type;
  private $library;
  private $base;
  private $module;
  private $name;

  private $suppressLoad;


  /**
   * Select the type of symbol to load, either ##class## or ##function##.
   *
   * @param string  Type of symbol to load.
   * @return this
   * @task config
   */
  public function setType($type) {
    $this->type = $type;
    return $this;
  }


  /**
   * Restrict the symbol query to a specific library; only symbols from this
   * library will be loaded.
   *
   * @param string Library name.
   * @return this
   * @task config
   */
  public function setLibrary($library) {
    // Validate the library name; this throws if the library in not loaded.
    $bootloader = PhutilBootloader::getInstance();
    $bootloader->getLibraryRoot($library);

    $this->library = $library;
    return $this;
  }


  /**
   * Restrict the symbol query to a single module.
   *
   * @param string Module name.
   * @return this
   * @task config
   */
  public function setModule($module) {
    $this->module = $module;
    return $this;
  }


  /**
   * Restrict the symbol query to a single symbol name, e.g. a specific class
   * or function name.
   *
   * @param string Symbol name.
   * @return this
   * @task config
   */
  public function setName($name) {
    $this->name = $name;
    return $this;
  }


  /**
   * Restrict the symbol query to only descendants of some class. This will
   * strictly select descendants, the base class will not be selected. This
   * implies loading only classes.
   *
   * @param string Base class name.
   * @return this
   * @task config
   */
  public function setAncestorClass($base) {
    $this->base = $base;
    return $this;
  }

/* -(  Load  )--------------------------------------------------------------- */


  /**
   * Execute the query and select matching symbols, then load the modules where
   * they are defined so they can be used.
   *
   * @return dict A dictionary of matching symbols. See top-level class
   *              documentation for details. These symbols will be loaded
   *              and available.
   * @task load
   */
  public function selectAndLoadSymbols() {
    $map = array();

    $bootloader = PhutilBootloader::getInstance();

    if ($this->library) {
      $libraries = array($this->library);
    } else {
      $libraries = $bootloader->getAllLibraries();
    }

    if ($this->type) {
      $types = array($this->type);
    } else {
      $types = array(
        'class',
        'function',
      );
    }

    $symbols = array();
    foreach ($libraries as $library) {
      $map = $bootloader->getLibraryMap($library);
      foreach ($types as $type) {
        if ($type == 'interface') {
          $lookup_map = $map['class'];
        } else {
          $lookup_map = $map[$type];
        }

        // As an optimization, we filter the list of candidate symbols in
        // several passes, applying a name-based filter first if possible since
        // it is highly selective and guaranteed to match at most one symbol.
        // This is the common case and we land here through __autoload() so it's
        // worthwhile to microoptimize a bit because this code path is very hot
        // and we save 5-10ms per page for a very moderate increase in
        // complexity.


        if ($this->name) {
          // If we have a name filter, just pick the matching name out if it
          // exists.
          if (isset($lookup_map[$this->name])) {
            $filtered_map = array(
              $this->name => $lookup_map[$this->name],
            );
          } else {
            $filtered_map = array();
          }
        } else {
          // Otherwise, start with everything.
          $filtered_map = $lookup_map;
        }

        if ($this->module) {
          foreach ($lookup_map as $name => $module) {
            if ($module != $this->module) {
              unset($filtered_map[$name]);
            }
          }
        }

        foreach ($filtered_map as $name => $module) {
          $symbol = array(
            'type'    => $type,
            'name'    => $name,
            'library' => $library,
            'module'  => $module,
          );
          if (!empty($map['requires_class'][$name])) {
            $symbol['requires_class'] = $map['requires_class'][$name];
          }
          if (!empty($map['requires_interface'][$name])) {
            $symbol['requires_interface'] = $map['requires_interface'][$name];
          }
          $symbols[$type.'$'.$name] = $symbol;
        }
      }
    }

    if ($this->base) {
      $tree = array();
      $libraries = $bootloader->getAllLibraries();
      foreach ($libraries as $library) {
        $map = $bootloader->getLibraryMap($library);
        foreach ($map['requires_class'] as $child => $parent) {
          $tree[$parent][] = $child;
        }
        foreach ($map['requires_interface'] as $child => $parents) {
          foreach ($parents as $parent) {
            $tree[$parent][] = $child;
          }
        }
      }

      $names = $this->selectDescendantsOf($tree, $this->base);
      foreach ($symbols as $symbol_key => $symbol) {
        $type = $symbol['type'];
        if ($type == 'class' || $type == 'interface') {
          if (isset($names[$symbol['name']])) {
            continue;
          }
        }
        unset($symbols[$symbol_key]);
      }
    }

    if (!$this->suppressLoad) {
      foreach ($symbols as $symbol) {
        $this->loadSymbol($symbol);
      }
    }

    return $symbols;
  }


  /**
   * Execute the query and select matching symbols, but do not load the modules
   * where they are defined. This will perform slightly better if you are only
   * interested in the existence of the symbols and don't plan to use them;
   * otherwise, use ##selectAndLoadSymbols()##.
   *
   * @return dict A dictionary of matching symbols. See top-level class
   *              documentation for details.
   * @task load
   */
  public function selectSymbolsWithoutLoading() {<commit_msg>Add a "concrete only" flag to PhutilSymbolLoader<commit_after><?php

/*
 * Copyright 2012 Facebook, Inc.
 *
 * 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.
 */

/**
 * Query and load Phutil classes, interfaces and functions. PhutilSymbolLoader
 * is a query object which selects symbols which satisfy certain criteria, and
 * optionally loads them. For instance, to load all classes in a module:
 *
 *    $symbols = id(new PhutilSymbolLoader())
 *      ->setType('class')
 *      ->setLibrary('example')
 *      ->setModule('some/module')
 *      ->selectAndLoadSymbols();
 *
 * When you execute the loading query, it returns a dictionary of matching
 * symbols:
 *
 *    array(
 *      'class$Example' => array(
 *        'type'    => 'class',
 *        'name'    => 'Example',
 *        'library' => 'libexample',
 *        'module'  => 'examples/example',
 *      ),
 *      // ... more ...
 *    );
 *
 * The **library** and **module** keys show where the symbol is defined. The
 * **type** and **name** keys identify the symbol itself.
 *
 * @task config   Configuring the Query
 * @task load     Loading Symbols
 * @task internal Internals
 *
 * @group library
 */
final class PhutilSymbolLoader {

  private $type;
  private $library;
  private $base;
  private $module;
  private $name;
  private $concrete;

  private $suppressLoad;


  /**
   * Select the type of symbol to load, either ##class## or ##function##.
   *
   * @param string  Type of symbol to load.
   * @return this
   * @task config
   */
  public function setType($type) {
    $this->type = $type;
    return $this;
  }


  /**
   * Restrict the symbol query to a specific library; only symbols from this
   * library will be loaded.
   *
   * @param string Library name.
   * @return this
   * @task config
   */
  public function setLibrary($library) {
    // Validate the library name; this throws if the library in not loaded.
    $bootloader = PhutilBootloader::getInstance();
    $bootloader->getLibraryRoot($library);

    $this->library = $library;
    return $this;
  }


  /**
   * Restrict the symbol query to a single module.
   *
   * @param string Module name.
   * @return this
   * @task config
   */
  public function setModule($module) {
    $this->module = $module;
    return $this;
  }


  /**
   * Restrict the symbol query to a single symbol name, e.g. a specific class
   * or function name.
   *
   * @param string Symbol name.
   * @return this
   * @task config
   */
  public function setName($name) {
    $this->name = $name;
    return $this;
  }


  /**
   * Restrict the symbol query to only descendants of some class. This will
   * strictly select descendants, the base class will not be selected. This
   * implies loading only classes.
   *
   * @param string Base class name.
   * @return this
   * @task config
   */
  public function setAncestorClass($base) {
    $this->base = $base;
    return $this;
  }


  /**
   * Restrict the symbol query to only concrete symbols; this will filter out
   * abstract classes.
   *
   * NOTE: This currently causes class symbols to load, even if you run
   * @{method:selectSymbolsWithoutLoading}.
   *
   * @param bool True if the query should load only concrete symbols.
   * @return this
   * @task config
   */
  public function setConcreteOnly($concrete) {
    $this->concrete = $concrete;
    return $this;
  }


/* -(  Load  )--------------------------------------------------------------- */


  /**
   * Execute the query and select matching symbols, then load the modules where
   * they are defined so they can be used.
   *
   * @return dict A dictionary of matching symbols. See top-level class
   *              documentation for details. These symbols will be loaded
   *              and available.
   * @task load
   */
  public function selectAndLoadSymbols() {
    $map = array();

    $bootloader = PhutilBootloader::getInstance();

    if ($this->library) {
      $libraries = array($this->library);
    } else {
      $libraries = $bootloader->getAllLibraries();
    }

    if ($this->type) {
      $types = array($this->type);
    } else {
      $types = array(
        'class',
        'function',
      );
    }

    $symbols = array();
    foreach ($libraries as $library) {
      $map = $bootloader->getLibraryMap($library);
      foreach ($types as $type) {
        if ($type == 'interface') {
          $lookup_map = $map['class'];
        } else {
          $lookup_map = $map[$type];
        }

        // As an optimization, we filter the list of candidate symbols in
        // several passes, applying a name-based filter first if possible since
        // it is highly selective and guaranteed to match at most one symbol.
        // This is the common case and we land here through __autoload() so it's
        // worthwhile to microoptimize a bit because this code path is very hot
        // and we save 5-10ms per page for a very moderate increase in
        // complexity.


        if ($this->name) {
          // If we have a name filter, just pick the matching name out if it
          // exists.
          if (isset($lookup_map[$this->name])) {
            $filtered_map = array(
              $this->name => $lookup_map[$this->name],
            );
          } else {
            $filtered_map = array();
          }
        } else {
          // Otherwise, start with everything.
          $filtered_map = $lookup_map;
        }

        if ($this->module) {
          foreach ($lookup_map as $name => $module) {
            if ($module != $this->module) {
              unset($filtered_map[$name]);
            }
          }
        }

        foreach ($filtered_map as $name => $module) {
          $symbol = array(
            'type'    => $type,
            'name'    => $name,
            'library' => $library,
            'module'  => $module,
          );
          if (!empty($map['requires_class'][$name])) {
            $symbol['requires_class'] = $map['requires_class'][$name];
          }
          if (!empty($map['requires_interface'][$name])) {
            $symbol['requires_interface'] = $map['requires_interface'][$name];
          }
          $symbols[$type.'$'.$name] = $symbol;
        }
      }
    }

    if ($this->base) {
      $tree = array();
      $libraries = $bootloader->getAllLibraries();
      foreach ($libraries as $library) {
        $map = $bootloader->getLibraryMap($library);
        foreach ($map['requires_class'] as $child => $parent) {
          $tree[$parent][] = $child;
        }
        foreach ($map['requires_interface'] as $child => $parents) {
          foreach ($parents as $parent) {
            $tree[$parent][] = $child;
          }
        }
      }

      $names = $this->selectDescendantsOf($tree, $this->base);
      foreach ($symbols as $symbol_key => $symbol) {
        $type = $symbol['type'];
        if ($type == 'class' || $type == 'interface') {
          if (isset($names[$symbol['name']])) {
            continue;
          }
        }
        unset($symbols[$symbol_key]);
      }
    }

    if (!$this->suppressLoad) {
      foreach ($symbols as $symbol) {
        $this->loadSymbol($symbol);
      }
    }

    if ($this->concrete) {
      // Remove 'abstract' classes.
      foreach ($symbols as $key => $symbol) {
        if ($symbol['type'] == 'class') {
          $reflection = new ReflectionClass($symbol['name']);
          if ($reflection->isAbstract()) {
            unset($symbols[$key]);
          }
        }
      }
    }

    return $symbols;
  }


  /**
   * Execute the query and select matching symbols, but do not load the modules
   * where they are defined. This will perform slightly better if you are only
   * interested in the existence of the symbols and don't plan to use them;
   * otherwise, use ##selectAndLoadSymbols()##.
   *
   * @return dict A dictionary of matching symbols. See top-level class
   *              documentation for details.
   * @task load
   */
  public function selectSymbolsWithoutLoading() {
{'fraction_non_alphanumeric': 0.13339091806515302, 'fraction_numerical': 0.0013573543928923988, 'mean_word_length': 2.1596491228070174, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 95, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5918041', 'n_tokens_mistral': 4598, 'n_tokens_neox': 4482, 'n_words': 1502}