<?php
  
require('lang.base.php');
  
xp::sapi('cli');
  
uses('io.Folder''io.File''io.FileUtil');
  
  
  class 
RegexReplacement extends Object {
    var
      
$pattern      '',
      
$replacement  '';

    function 
__construct($pattern$replacement) {
      
$this->pattern$pattern;
      
$this->replacement$replacement;
    }
    
    function 
match($a) {
      return 
$a[0][0];
    }
    
    function 
process($content$matches) {
      
$classesarray_unique(array_map(array(&$this'match'), $matches));
      
Console::writeLine('     >> Contains the following matches: ['implode(', '$classes), ']');

      return 
preg_replace($this->pattern$this->replacement$content);
    }
    
    function 
toString() {
      return 
'RegexReplacement{'.$this->pattern.' => '.$this->replacement.'}';
    }
  }
  
  class 
LRUBufferUsageFinder extends Object {

    function 
process($content$matches) {
      
$tokenstoken_get_all($content);
      
$instances= array();
      
$line1;
      
$occurrences0;
      for (
$i0$ssizeof($tokens); $i $s$i++) {
        switch (
$tokens[$i][0]) {
          case 
T_VARIABLE: {
            
$var$tokens[$i][1];
            break;
          }
 
           case 
'=': case '&': {
            
// Intentionally empty
            
break;
          }
          
          case 
T_NEW: {
            if (
!= strcasecmp($tokens[$i2][1], 'LRUBuffer')) break;
           
            
// The last variable we've seen was assigned a new instance 
            // of the LRUBuffer class. Let's track it
            
Console::writeLine('     >> Variable '$var' points to an LRUBuffer instance at line '$line);
            
$instances[$var]= TRUE;
            break;
          }
          
          case 
T_OBJECT_OPERATOR: {
            if (!isset(
$instances[$var])) break;

            
$method$tokens[$i1][1];
            
$i++;
            
$argument'';
            
$arguments= array();
            
$brackets0;
            do {
              
$i++;
              switch (
$tokens[$i][0]) {
                case 
'('$brackets++; break;
                case 
')'$brackets--; break;
                case 
T_WHITESPACE: break;
                case 
','$arguments[]= $argument$argument''; break;
                default: {
                  
$stris_array($tokens[$i]) ? $tokens[$i][1] : $tokens[$i];
                  
$line+= substr_count($str"\n");
                  
$argument.= $str;
                }
              }
            } while (
$brackets);
            
$argument && $arguments[]= $argument;

            
Console::writeLine('     >> Method '$method' invoked on '$var' with ('implode(', '$arguments), ') at line '$line);
            
$occurrences++;
          }

          case 
T_WHITESPACE: {
            
$line+= substr_count($tokens[$i][1], "\n");
            break;
          }
          
          default: {
            
$varNULL;
            
$line+= substr_count(is_array($tokens[$i]) ? $tokens[$i][1] : $tokens[$i], "\n");
          }
        }
      }
      
      if (
$occurrences) {
        
Console::writeLine('***  '$occurrences' occurence(s) of LRUBuffer usage found (see above for details)');
        
Console::writeLine('***  Automatic migration is not possible!');
      }
      
      return 
$content;
    }

    function 
toString() {
      return 
'LRUBufferUsageFinder{}';
    }
  }
  
  
// {{{ void recurseInto(io.Folder, bool execute, array<string, Object> methods)
  
function recurseInto(&$folder$execute$methods) {
    static 
$selfNULL;
    if (!isset(
$self)) $selfbasename(__FILE__);

    while (
$filename$folder->getEntry()) {
      if (
'.svn' == $filename || 'CVS' == $filename) continue;
      
$qualified$folder->getURI().$filename;
      
      if (
is_dir($qualified)) {
        
recurseInto(new Folder($qualified), $execute$methods);
        continue;
      }
      
      
// Ignore if one of the following checks evaluates to TRUE
      // - Not PHP code
      // - Don't migrate this script
      // - New util.collections classes
      // - Deprecated util.adt classes
      // - Empty files
      
if (
        
'.php' != substr($filename, -4) ||
        
$self == $filename ||
        
strstr(strtr($qualifiedDIRECTORY_SEPARATOR'/'), 'util/collections/') ||
        
strstr(strtr($qualifiedDIRECTORY_SEPARATOR'/'), 'util/adt/') ||
        
== filesize($qualified)
      ) continue;
      
      
with ($target= &new File($qualified)); {
        try(); {
          
$contentsFileUtil::getContents($target);
        } if (catch(
'Exception'$e)) {
          
$e->printStackTrace();
          continue;
        }
        
        
// Check which patterns apply
        
$p0;
        foreach (
array_keys($methods) as $pattern) {
          if (!
preg_match_all($pattern$contents$matchesPREG_OFFSET_CAPTURE)) continue;
          
          
Console::writeLine('---> Processing '$filename' with '$methods[$pattern]->toString());
          
$contents$methods[$pattern]->process($contents$matches);
          
$p++;
          
          if (!
$execute) continue;

          try(); {
            
FileUtil::setContents($target$contents);
          } if (catch(
'Exception'$e)) {
            
$e->printStackTrace();
          }
         
          
Console::writeLine('     >> Done');
        }

        
$p && Console::writeLine('===> Processed '$qualified' ('$p")\n");
        unset(
$content);
        
xp::gc();
      }
    }
  }
  
// }}}
  
  // {{{ main
  
if ($argc 2) {
    
Console::writeLine(<<<__
Migrates util.adt -> util.collections (RFC #0057)

Usage:
$ php rfc57-migration.php <directory> [-e]

Iterates recursively over all files in the specified directory ending
with ".php" and replaces "util.adt" with "util.collections".

Unless the "-e"-switch was supplied no changes are written.

__
    );
    exit(
1);
  }
  
  
$execute= isset($argv[2]) && '-e' == $argv[2];
  
$execute || Console::writeLine('***  No changes will be written. Supply -e if you want that to happen');
  
recurseInto(new Folder($argv[1]), $execute, array(
    
'/util\.adt\.[a-zA-Z]+/' => new RegexReplacement('/util\.adt\.([a-zA-Z]+)/''util.collections.$1'),
    
'/LRUBuffer/i'           => new LRUBufferUsageFinder()
  ));
  
Console::writeLine('===> Done');
  
// }}}
?>