ProjectConfigurator.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. <?php
  2. /*
  3. * $Id: ProjectConfigurator.php 905 2010-10-05 16:28:03Z mrook $
  4. *
  5. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  6. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  7. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  8. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  9. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  10. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  11. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  12. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  13. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  14. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  15. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  16. *
  17. * This software consists of voluntary contributions made by many individuals
  18. * and is licensed under the LGPL. For more information please see
  19. * <http://phing.info>.
  20. */
  21. include_once 'phing/system/io/BufferedReader.php';
  22. include_once 'phing/system/io/FileReader.php';
  23. include_once 'phing/BuildException.php';
  24. include_once 'phing/system/lang/FileNotFoundException.php';
  25. include_once 'phing/system/io/PhingFile.php';
  26. include_once 'phing/parser/PhingXMLContext.php';
  27. include_once 'phing/IntrospectionHelper.php';
  28. /**
  29. * The datatype handler class.
  30. *
  31. * This class handles the occurance of registered datatype tags like
  32. * FileSet
  33. *
  34. * @author Andreas Aderhold <andi@binarycloud.com>
  35. * @copyright � 2001,2002 THYRELL. All rights reserved
  36. * @version $Revision: 905 $ $Date: 2010-10-05 18:28:03 +0200 (Tue, 05 Oct 2010) $
  37. * @access public
  38. * @package phing.parser
  39. */
  40. class ProjectConfigurator {
  41. public $project;
  42. public $locator;
  43. public $buildFile;
  44. public $buildFileParent;
  45. /** Targets in current file */
  46. private $currentTargets;
  47. /** Synthetic target that will be called at the end to the parse phase */
  48. private $parseEndTarget;
  49. /** Name of the current project */
  50. private $currentProjectName;
  51. private $isParsing = true;
  52. /**
  53. * Indicates whether the project tag attributes are to be ignored
  54. * when processing a particular build file.
  55. */
  56. private $ignoreProjectTag = false;
  57. /**
  58. * Static call to ProjectConfigurator. Use this to configure a
  59. * project. Do not use the new operator.
  60. *
  61. * @param object the Project instance this configurator should use
  62. * @param object the buildfile object the parser should use
  63. * @access public
  64. */
  65. public static function configureProject(Project $project, PhingFile $buildFile) {
  66. $pc = new ProjectConfigurator($project, $buildFile);
  67. $pc->parse();
  68. }
  69. /**
  70. * Constructs a new ProjectConfigurator object
  71. * This constructor is private. Use a static call to
  72. * <code>configureProject</code> to configure a project.
  73. *
  74. * @param object the Project instance this configurator should use
  75. * @param object the buildfile object the parser should use
  76. * @access private
  77. */
  78. function __construct(Project $project, PhingFile $buildFile) {
  79. $this->project = $project;
  80. $this->buildFile = new PhingFile($buildFile->getAbsolutePath());
  81. $this->buildFileParent = new PhingFile($this->buildFile->getParent());
  82. $this->currentTargets = array();
  83. $this->parseEndTarget = new Target();
  84. }
  85. /**
  86. * find out the build file
  87. * @return the build file to which the xml context belongs
  88. */
  89. public function getBuildFile() {
  90. return $this->buildFile;
  91. }
  92. /**
  93. * find out the parent build file of this build file
  94. * @return the parent build file of this build file
  95. */
  96. public function getBuildFileParent() {
  97. return $this->buildFileParent;
  98. }
  99. /**
  100. * find out the current project name
  101. * @return current project name
  102. */
  103. public function getCurrentProjectName() {
  104. return $this->currentProjectName;
  105. }
  106. /**
  107. * set the name of the current project
  108. * @param name name of the current project
  109. */
  110. public function setCurrentProjectName($name) {
  111. $this->currentProjectName = $name;
  112. }
  113. /**
  114. * tells whether the project tag is being ignored
  115. * @return whether the project tag is being ignored
  116. */
  117. public function isIgnoringProjectTag() {
  118. return $this->ignoreProjectTag;
  119. }
  120. /**
  121. * sets the flag to ignore the project tag
  122. * @param flag to ignore the project tag
  123. */
  124. public function setIgnoreProjectTag($flag) {
  125. $this->ignoreProjectTag = $flag;
  126. }
  127. public function &getCurrentTargets () {
  128. return $this->currentTargets;
  129. }
  130. public function isParsing () {
  131. return $this->isParsing;
  132. }
  133. /**
  134. * Creates the ExpatParser, sets root handler and kick off parsing
  135. * process.
  136. *
  137. * @throws BuildException if there is any kind of execption during
  138. * the parsing process
  139. * @access private
  140. */
  141. protected function parse() {
  142. try {
  143. // get parse context
  144. $ctx = $this->project->getReference("phing.parsing.context");
  145. if (null == $ctx) {
  146. // make a new context and register it with project
  147. $ctx = new PhingXMLContext($this->project);
  148. $this->project->addReference("phing.parsing.context", $ctx);
  149. }
  150. //record this parse with context
  151. $ctx->addImport($this->buildFile);
  152. if (count($ctx->getImportStack()) > 1) {
  153. // this is an imported file
  154. // modify project tag parse behavior
  155. $this->setIgnoreProjectTag(true);
  156. }
  157. // push action onto global stack
  158. $ctx->startConfigure($this);
  159. $reader = new BufferedReader(new FileReader($this->buildFile));
  160. $parser = new ExpatParser($reader);
  161. $parser->parserSetOption(XML_OPTION_CASE_FOLDING,0);
  162. $parser->setHandler(new RootHandler($parser, $this));
  163. $this->project->log("parsing buildfile ".$this->buildFile->getName(), Project::MSG_VERBOSE);
  164. $parser->parse();
  165. $reader->close();
  166. // mark parse phase as completed
  167. $this->isParsing = false;
  168. // execute delayed tasks
  169. $this->parseEndTarget->main();
  170. // pop this action from the global stack
  171. $ctx->endConfigure();
  172. } catch (Exception $exc) {
  173. throw new BuildException("Error reading project file", $exc);
  174. }
  175. }
  176. /**
  177. * Delay execution of a task until after the current parse phase has
  178. * completed.
  179. *
  180. * @param Task $task Task to execute after parse
  181. */
  182. public function delayTaskUntilParseEnd ($task) {
  183. $this->parseEndTarget->addTask($task);
  184. }
  185. /**
  186. * Configures an element and resolves eventually given properties.
  187. *
  188. * @param object the element to configure
  189. * @param array the element's attributes
  190. * @param object the project this element belongs to
  191. * @throws Exception if arguments are not valid
  192. * @throws BuildException if attributes can not be configured
  193. * @access public
  194. */
  195. public static function configure($target, $attrs, Project $project) {
  196. if ($target instanceof TaskAdapter) {
  197. $target = $target->getProxy();
  198. }
  199. // if the target is an UnknownElement, this means that the tag had not been registered
  200. // when the enclosing element (task, target, etc.) was configured. It is possible, however,
  201. // that the tag was registered (e.g. using <taskdef>) after the original configuration.
  202. // ... so, try to load it again:
  203. if ($target instanceof UnknownElement) {
  204. $tryTarget = $project->createTask($target->getTaskType());
  205. if ($tryTarget) {
  206. $target = $tryTarget;
  207. }
  208. }
  209. $bean = get_class($target);
  210. $ih = IntrospectionHelper::getHelper($bean);
  211. foreach ($attrs as $key => $value) {
  212. if ($key == 'id') {
  213. continue;
  214. // throw new BuildException("Id must be set Extermnally");
  215. }
  216. $value = self::replaceProperties($project, $value, $project->getProperties());
  217. try { // try to set the attribute
  218. $ih->setAttribute($project, $target, strtolower($key), $value);
  219. } catch (BuildException $be) {
  220. // id attribute must be set externally
  221. if ($key !== "id") {
  222. throw $be;
  223. }
  224. }
  225. }
  226. }
  227. /**
  228. * Configures the #CDATA of an element.
  229. *
  230. * @param object the project this element belongs to
  231. * @param object the element to configure
  232. * @param string the element's #CDATA
  233. * @access public
  234. */
  235. public static function addText($project, $target, $text = null) {
  236. if ($text === null || strlen(trim($text)) === 0) {
  237. return;
  238. }
  239. $ih = IntrospectionHelper::getHelper(get_class($target));
  240. $text = self::replaceProperties($project, $text, $project->getProperties());
  241. $ih->addText($project, $target, $text);
  242. }
  243. /**
  244. * Stores a configured child element into its parent object
  245. *
  246. * @param object the project this element belongs to
  247. * @param object the parent element
  248. * @param object the child element
  249. * @param string the XML tagname
  250. * @access public
  251. */
  252. public static function storeChild($project, $parent, $child, $tag) {
  253. $ih = IntrospectionHelper::getHelper(get_class($parent));
  254. $ih->storeElement($project, $parent, $child, $tag);
  255. }
  256. // The following two properties are a sort of hack
  257. // to enable a static function to serve as the callback
  258. // for preg_replace_callback(). Clearly we cannot use object
  259. // variables, since the replaceProperties() is called statically.
  260. // This is IMO better than using global variables in the callback.
  261. private static $propReplaceProject;
  262. private static $propReplaceProperties;
  263. /**
  264. * Replace ${} style constructions in the given value with the
  265. * string value of the corresponding data types. This method is
  266. * static.
  267. *
  268. * @param object the project that should be used for property look-ups
  269. * @param string the string to be scanned for property references
  270. * @param array proeprty keys
  271. * @return string the replaced string or <code>null</code> if the string
  272. * itself was null
  273. */
  274. public static function replaceProperties(Project $project, $value, $keys) {
  275. if ($value === null) {
  276. return null;
  277. }
  278. // These are a "hack" to support static callback for preg_replace_callback()
  279. // make sure these get initialized every time
  280. self::$propReplaceProperties = $keys;
  281. self::$propReplaceProject = $project;
  282. // Because we're not doing anything special (like multiple passes),
  283. // regex is the simplest / fastest. PropertyTask, though, uses
  284. // the old parsePropertyString() method, since it has more stringent
  285. // requirements.
  286. $sb = $value;
  287. $iteration = 0;
  288. // loop to recursively replace tokens
  289. while (strpos($sb, '${') !== false)
  290. {
  291. $sb = preg_replace_callback('/\$\{([^\$}]+)\}/', array('ProjectConfigurator', 'replacePropertyCallback'), $sb);
  292. // keep track of iterations so we can break out of otherwise infinite loops.
  293. $iteration++;
  294. if ($iteration == 5)
  295. {
  296. return $sb;
  297. }
  298. }
  299. return $sb;
  300. }
  301. /**
  302. * Private [static] function for use by preg_replace_callback to replace a single param.
  303. * This method makes use of a static variable to hold the
  304. */
  305. private static function replacePropertyCallback($matches)
  306. {
  307. $propertyName = $matches[1];
  308. if (!isset(self::$propReplaceProperties[$propertyName])) {
  309. self::$propReplaceProject->log('Property ${'.$propertyName.'} has not been set.', Project::MSG_VERBOSE);
  310. return $matches[0];
  311. } else {
  312. self::$propReplaceProject->log('Property ${'.$propertyName.'} => ' . self::$propReplaceProperties[$propertyName], Project::MSG_DEBUG);
  313. }
  314. return self::$propReplaceProperties[$propertyName];
  315. }
  316. /**
  317. * Scan Attributes for the id attribute and maybe add a reference to
  318. * project.
  319. *
  320. * @param object the element's object
  321. * @param array the element's attributes
  322. */
  323. public function configureId($target, $attr) {
  324. if (isset($attr['id']) && $attr['id'] !== null) {
  325. $this->project->addReference($attr['id'], $target);
  326. }
  327. }
  328. }