You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

AbstractCvsTask.java 24 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820
  1. /*
  2. * Copyright 2002-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *
  16. */
  17. package org.apache.tools.ant.taskdefs;
  18. import java.io.BufferedOutputStream;
  19. import java.io.File;
  20. import java.io.FileOutputStream;
  21. import java.io.IOException;
  22. import java.io.OutputStream;
  23. import java.io.PrintStream;
  24. import java.util.Vector;
  25. import org.apache.tools.ant.BuildException;
  26. import org.apache.tools.ant.Project;
  27. import org.apache.tools.ant.Task;
  28. import org.apache.tools.ant.types.Commandline;
  29. import org.apache.tools.ant.types.Environment;
  30. import org.apache.tools.ant.util.StringUtils;
  31. /**
  32. * original Cvs.java 1.20
  33. *
  34. * NOTE: This implementation has been moved here from Cvs.java with
  35. * the addition of some accessors for extensibility. Another task
  36. * can extend this with some customized output processing.
  37. *
  38. * @author costin@dnt.ro
  39. * @author stefano@apache.org
  40. * @author Wolfgang Werner
  41. * <a href="mailto:wwerner@picturesafe.de">wwerner@picturesafe.de</a>
  42. * @author Kevin Ross
  43. * <a href="mailto:kevin.ross@bredex.com">kevin.ross@bredex.com</a>
  44. *
  45. * @since Ant 1.5
  46. */
  47. public abstract class AbstractCvsTask extends Task {
  48. /**
  49. * Default compression level to use, if compression is enabled via
  50. * setCompression( true ).
  51. */
  52. public static final int DEFAULT_COMPRESSION_LEVEL = 3;
  53. private static final int MAXIMUM_COMRESSION_LEVEL = 9;
  54. private Commandline cmd = new Commandline();
  55. /** list of Commandline children */
  56. private Vector vecCommandlines = new Vector();
  57. /**
  58. * the CVSROOT variable.
  59. */
  60. private String cvsRoot;
  61. /**
  62. * the CVS_RSH variable.
  63. */
  64. private String cvsRsh;
  65. /**
  66. * the package/module to check out.
  67. */
  68. private String cvsPackage;
  69. /**
  70. * the tag
  71. */
  72. private String tag;
  73. /**
  74. * the default command.
  75. */
  76. private static final String DEFAULT_COMMAND = "checkout";
  77. /**
  78. * the CVS command to execute.
  79. */
  80. private String command = null;
  81. /**
  82. * suppress information messages.
  83. */
  84. private boolean quiet = false;
  85. /**
  86. * suppress all messages.
  87. */
  88. private boolean reallyquiet = false;
  89. /**
  90. * compression level to use.
  91. */
  92. private int compression = 0;
  93. /**
  94. * report only, don't change any files.
  95. */
  96. private boolean noexec = false;
  97. /**
  98. * CVS port
  99. */
  100. private int port = 0;
  101. /**
  102. * CVS password file
  103. */
  104. private File passFile = null;
  105. /**
  106. * the directory where the checked out files should be placed.
  107. */
  108. private File dest;
  109. /** whether or not to append stdout/stderr to existing files */
  110. private boolean append = false;
  111. /**
  112. * the file to direct standard output from the command.
  113. */
  114. private File output;
  115. /**
  116. * the file to direct standard error from the command.
  117. */
  118. private File error;
  119. /**
  120. * If true it will stop the build if cvs exits with error.
  121. * Default is false. (Iulian)
  122. */
  123. private boolean failOnError = false;
  124. /**
  125. * Create accessors for the following, to allow different handling of
  126. * the output.
  127. */
  128. private ExecuteStreamHandler executeStreamHandler;
  129. private OutputStream outputStream;
  130. private OutputStream errorStream;
  131. /** empty no-arg constructor*/
  132. public AbstractCvsTask() {
  133. super();
  134. }
  135. /**
  136. * sets the handler
  137. * @param handler a handler able of processing the output and error streams from the cvs exe
  138. */
  139. public void setExecuteStreamHandler(ExecuteStreamHandler handler) {
  140. this.executeStreamHandler = handler;
  141. }
  142. /**
  143. * find the handler and instantiate it if it does not exist yet
  144. * @return handler for output and error streams
  145. */
  146. protected ExecuteStreamHandler getExecuteStreamHandler() {
  147. if (this.executeStreamHandler == null) {
  148. setExecuteStreamHandler(new PumpStreamHandler(getOutputStream(),
  149. getErrorStream()));
  150. }
  151. return this.executeStreamHandler;
  152. }
  153. /**
  154. * sets a stream to which the output from the cvs executable should be sent
  155. * @param outputStream stream to which the stdout from cvs should go
  156. */
  157. protected void setOutputStream(OutputStream outputStream) {
  158. this.outputStream = outputStream;
  159. }
  160. /**
  161. * access the stream to which the stdout from cvs should go
  162. * if this stream has already been set, it will be returned
  163. * if the stream has not yet been set, if the attribute output
  164. * has been set, the output stream will go to the output file
  165. * otherwise the output will go to ant's logging system
  166. * @return output stream to which cvs' stdout should go to
  167. */
  168. protected OutputStream getOutputStream() {
  169. if (this.outputStream == null) {
  170. if (output != null) {
  171. try {
  172. setOutputStream(new PrintStream(
  173. new BufferedOutputStream(
  174. new FileOutputStream(output
  175. .getPath(),
  176. append))));
  177. } catch (IOException e) {
  178. throw new BuildException(e, getLocation());
  179. }
  180. } else {
  181. setOutputStream(new LogOutputStream(this, Project.MSG_INFO));
  182. }
  183. }
  184. return this.outputStream;
  185. }
  186. /**
  187. * sets a stream to which the stderr from the cvs exe should go
  188. * @param errorStream an output stream willing to process stderr
  189. */
  190. protected void setErrorStream(OutputStream errorStream) {
  191. this.errorStream = errorStream;
  192. }
  193. /**
  194. * access the stream to which the stderr from cvs should go
  195. * if this stream has already been set, it will be returned
  196. * if the stream has not yet been set, if the attribute error
  197. * has been set, the output stream will go to the file denoted by the error attribute
  198. * otherwise the stderr output will go to ant's logging system
  199. * @return output stream to which cvs' stderr should go to
  200. */
  201. protected OutputStream getErrorStream() {
  202. if (this.errorStream == null) {
  203. if (error != null) {
  204. try {
  205. setErrorStream(new PrintStream(
  206. new BufferedOutputStream(
  207. new FileOutputStream(error.getPath(),
  208. append))));
  209. } catch (IOException e) {
  210. throw new BuildException(e, getLocation());
  211. }
  212. } else {
  213. setErrorStream(new LogOutputStream(this, Project.MSG_WARN));
  214. }
  215. }
  216. return this.errorStream;
  217. }
  218. /**
  219. * Sets up the environment for toExecute and then runs it.
  220. * @param toExecute the command line to execute
  221. * @throws BuildException if failonError is set to true and the cvs command fails
  222. */
  223. protected void runCommand(Commandline toExecute) throws BuildException {
  224. // XXX: we should use JCVS (www.ice.com/JCVS) instead of
  225. // command line execution so that we don't rely on having
  226. // native CVS stuff around (SM)
  227. // We can't do it ourselves as jCVS is GPLed, a third party task
  228. // outside of jakarta repositories would be possible though (SB).
  229. Environment env = new Environment();
  230. if (port > 0) {
  231. Environment.Variable var = new Environment.Variable();
  232. var.setKey("CVS_CLIENT_PORT");
  233. var.setValue(String.valueOf(port));
  234. env.addVariable(var);
  235. }
  236. /**
  237. * Need a better cross platform integration with <cvspass>, so
  238. * use the same filename.
  239. */
  240. if (passFile == null) {
  241. File defaultPassFile = new File(
  242. System.getProperty("cygwin.user.home",
  243. System.getProperty("user.home"))
  244. + File.separatorChar + ".cvspass");
  245. if (defaultPassFile.exists()) {
  246. this.setPassfile(defaultPassFile);
  247. }
  248. }
  249. if (passFile != null) {
  250. if (passFile.isFile() && passFile.canRead()) {
  251. Environment.Variable var = new Environment.Variable();
  252. var.setKey("CVS_PASSFILE");
  253. var.setValue(String.valueOf(passFile));
  254. env.addVariable(var);
  255. log("Using cvs passfile: " + String.valueOf(passFile),
  256. Project.MSG_INFO);
  257. } else if (!passFile.canRead()) {
  258. log("cvs passfile: " + String.valueOf(passFile)
  259. + " ignored as it is not readable",
  260. Project.MSG_WARN);
  261. } else {
  262. log("cvs passfile: " + String.valueOf(passFile)
  263. + " ignored as it is not a file",
  264. Project.MSG_WARN);
  265. }
  266. }
  267. if (cvsRsh != null) {
  268. Environment.Variable var = new Environment.Variable();
  269. var.setKey("CVS_RSH");
  270. var.setValue(String.valueOf(cvsRsh));
  271. env.addVariable(var);
  272. }
  273. //
  274. // Just call the getExecuteStreamHandler() and let it handle
  275. // the semantics of instantiation or retrieval.
  276. //
  277. Execute exe = new Execute(getExecuteStreamHandler(), null);
  278. exe.setAntRun(getProject());
  279. if (dest == null) {
  280. dest = getProject().getBaseDir();
  281. }
  282. if (!dest.exists()) {
  283. dest.mkdirs();
  284. }
  285. exe.setWorkingDirectory(dest);
  286. exe.setCommandline(toExecute.getCommandline());
  287. exe.setEnvironment(env.getVariables());
  288. try {
  289. String actualCommandLine = executeToString(exe);
  290. log(actualCommandLine, Project.MSG_VERBOSE);
  291. int retCode = exe.execute();
  292. log("retCode=" + retCode, Project.MSG_DEBUG);
  293. /*Throw an exception if cvs exited with error. (Iulian)*/
  294. if (failOnError && Execute.isFailure(retCode)) {
  295. throw new BuildException("cvs exited with error code "
  296. + retCode
  297. + StringUtils.LINE_SEP
  298. + "Command line was ["
  299. + actualCommandLine + "]", getLocation());
  300. }
  301. } catch (IOException e) {
  302. if (failOnError) {
  303. throw new BuildException(e, getLocation());
  304. } else {
  305. log("Caught exception: " + e.getMessage(), Project.MSG_WARN);
  306. }
  307. } catch (BuildException e) {
  308. if (failOnError) {
  309. throw(e);
  310. } else {
  311. Throwable t = e.getException();
  312. if (t == null) {
  313. t = e;
  314. }
  315. log("Caught exception: " + t.getMessage(), Project.MSG_WARN);
  316. }
  317. } catch (Exception e) {
  318. if (failOnError) {
  319. throw new BuildException(e, getLocation());
  320. } else {
  321. log("Caught exception: " + e.getMessage(), Project.MSG_WARN);
  322. }
  323. } finally {
  324. if (outputStream != null) {
  325. try {
  326. outputStream.close();
  327. } catch (IOException e) {
  328. //ignore
  329. }
  330. }
  331. if (errorStream != null) {
  332. try {
  333. errorStream.close();
  334. } catch (IOException e) {
  335. //ignore
  336. }
  337. }
  338. }
  339. }
  340. /**
  341. * do the work
  342. * @throws BuildException if failonerror is set to true and the cvs command fails.
  343. */
  344. public void execute() throws BuildException {
  345. String savedCommand = getCommand();
  346. if (this.getCommand() == null && vecCommandlines.size() == 0) {
  347. // re-implement legacy behaviour:
  348. this.setCommand(AbstractCvsTask.DEFAULT_COMMAND);
  349. }
  350. String c = this.getCommand();
  351. Commandline cloned = null;
  352. if (c != null) {
  353. cloned = (Commandline) cmd.clone();
  354. cloned.createArgument(true).setLine(c);
  355. this.addConfiguredCommandline(cloned, true);
  356. }
  357. try {
  358. for (int i = 0; i < vecCommandlines.size(); i++) {
  359. this.runCommand((Commandline) vecCommandlines.elementAt(i));
  360. }
  361. } finally {
  362. if (cloned != null) {
  363. removeCommandline(cloned);
  364. }
  365. setCommand(savedCommand);
  366. }
  367. }
  368. private String executeToString(Execute execute) {
  369. StringBuffer stringBuffer =
  370. new StringBuffer(Commandline.describeCommand(execute
  371. .getCommandline()));
  372. String newLine = StringUtils.LINE_SEP;
  373. String[] variableArray = execute.getEnvironment();
  374. if (variableArray != null) {
  375. stringBuffer.append(newLine);
  376. stringBuffer.append(newLine);
  377. stringBuffer.append("environment:");
  378. stringBuffer.append(newLine);
  379. for (int z = 0; z < variableArray.length; z++) {
  380. stringBuffer.append(newLine);
  381. stringBuffer.append("\t");
  382. stringBuffer.append(variableArray[z]);
  383. }
  384. }
  385. return stringBuffer.toString();
  386. }
  387. /**
  388. * The CVSROOT variable.
  389. *
  390. * @param root the CVSROOT variable
  391. */
  392. public void setCvsRoot(String root) {
  393. // Check if not real cvsroot => set it to null
  394. if (root != null) {
  395. if (root.trim().equals("")) {
  396. root = null;
  397. }
  398. }
  399. this.cvsRoot = root;
  400. }
  401. /**
  402. * access the CVSROOT variable
  403. * @return CVSROOT
  404. */
  405. public String getCvsRoot() {
  406. return this.cvsRoot;
  407. }
  408. /**
  409. * The CVS_RSH variable.
  410. *
  411. * @param rsh the CVS_RSH variable
  412. */
  413. public void setCvsRsh(String rsh) {
  414. // Check if not real cvsrsh => set it to null
  415. if (rsh != null) {
  416. if (rsh.trim().equals("")) {
  417. rsh = null;
  418. }
  419. }
  420. this.cvsRsh = rsh;
  421. }
  422. /**
  423. * access the CVS_RSH variable
  424. * @return the CVS_RSH variable
  425. */
  426. public String getCvsRsh() {
  427. return this.cvsRsh;
  428. }
  429. /**
  430. * Port used by CVS to communicate with the server.
  431. *
  432. * @param port port of CVS
  433. */
  434. public void setPort(int port) {
  435. this.port = port;
  436. }
  437. /**
  438. * access the port of CVS
  439. * @return the port of CVS
  440. */
  441. public int getPort() {
  442. return this.port;
  443. }
  444. /**
  445. * Password file to read passwords from.
  446. *
  447. * @param passFile password file to read passwords from
  448. */
  449. public void setPassfile(File passFile) {
  450. this.passFile = passFile;
  451. }
  452. /**
  453. * find the password file
  454. * @return password file
  455. */
  456. public File getPassFile() {
  457. return this.passFile;
  458. }
  459. /**
  460. * The directory where the checked out files should be placed.
  461. *
  462. * <p>Note that this is different from CVS's -d command line
  463. * switch as Ant will never shorten pathnames to avoid empty
  464. * directories.</p>
  465. *
  466. * @param dest directory where the checked out files should be placed
  467. */
  468. public void setDest(File dest) {
  469. this.dest = dest;
  470. }
  471. /**
  472. * get the file where the checked out files should be placed
  473. *
  474. * @return directory where the checked out files should be placed
  475. */
  476. public File getDest() {
  477. return this.dest;
  478. }
  479. /**
  480. * The package/module to operate upon.
  481. *
  482. * @param p package or module to operate upon
  483. */
  484. public void setPackage(String p) {
  485. this.cvsPackage = p;
  486. }
  487. /**
  488. * access the package or module to operate upon
  489. *
  490. * @return package/module
  491. */
  492. public String getPackage() {
  493. return this.cvsPackage;
  494. }
  495. /**
  496. * tag or branch
  497. * @return tag or branch
  498. * @since ant 1.6.1
  499. */
  500. public String getTag() {
  501. return tag;
  502. }
  503. /**
  504. * The tag of the package/module to operate upon.
  505. * @param p tag
  506. */
  507. public void setTag(String p) {
  508. // Check if not real tag => set it to null
  509. if (p != null && p.trim().length() > 0) {
  510. tag = p;
  511. addCommandArgument("-r" + p);
  512. }
  513. }
  514. /**
  515. * This needs to be public to allow configuration
  516. * of commands externally.
  517. * @param arg command argument
  518. */
  519. public void addCommandArgument(String arg) {
  520. this.addCommandArgument(cmd, arg);
  521. }
  522. /**
  523. * add a command line argument to an external command
  524. *
  525. * I do not understand what this method does in this class ???
  526. * particularly not why it is public ????
  527. * AntoineLL July 23d 2003
  528. *
  529. * @param c command line to which one argument should be added
  530. * @param arg argument to add
  531. */
  532. public void addCommandArgument(Commandline c, String arg) {
  533. c.createArgument().setValue(arg);
  534. }
  535. /**
  536. * Use the most recent revision no later than the given date.
  537. * @param p a date as string in a format that the CVS executable can understand
  538. * see man cvs
  539. */
  540. public void setDate(String p) {
  541. if (p != null && p.trim().length() > 0) {
  542. addCommandArgument("-D");
  543. addCommandArgument(p);
  544. }
  545. }
  546. /**
  547. * The CVS command to execute.
  548. *
  549. * This should be deprecated, it is better to use the Commandline class ?
  550. * AntoineLL July 23d 2003
  551. *
  552. * @param c a command as string
  553. */
  554. public void setCommand(String c) {
  555. this.command = c;
  556. }
  557. /**
  558. * accessor to a command line as string
  559. *
  560. * This should be deprecated
  561. * AntoineLL July 23d 2003
  562. *
  563. * @return command line as string
  564. */
  565. public String getCommand() {
  566. return this.command;
  567. }
  568. /**
  569. * If true, suppress informational messages.
  570. * @param q if true, suppress informational messages
  571. */
  572. public void setQuiet(boolean q) {
  573. quiet = q;
  574. }
  575. /**
  576. * If true, suppress all messages.
  577. * @param q if true, suppress all messages
  578. * @since Ant 1.6
  579. */
  580. public void setReallyquiet(boolean q) {
  581. reallyquiet = q;
  582. }
  583. /**
  584. * If true, report only and don't change any files.
  585. *
  586. * @param ne if true, report only and do not change any files.
  587. */
  588. public void setNoexec(boolean ne) {
  589. noexec = ne;
  590. }
  591. /**
  592. * The file to direct standard output from the command.
  593. * @param output a file to which stdout should go
  594. */
  595. public void setOutput(File output) {
  596. this.output = output;
  597. }
  598. /**
  599. * The file to direct standard error from the command.
  600. *
  601. * @param error a file to which stderr should go
  602. */
  603. public void setError(File error) {
  604. this.error = error;
  605. }
  606. /**
  607. * Whether to append output/error when redirecting to a file.
  608. * @param value true indicated you want to append
  609. */
  610. public void setAppend(boolean value) {
  611. this.append = value;
  612. }
  613. /**
  614. * Stop the build process if the command exits with
  615. * a return code other than 0.
  616. * Defaults to false.
  617. * @param failOnError stop the build process if the command exits with
  618. * a return code other than 0
  619. */
  620. public void setFailOnError(boolean failOnError) {
  621. this.failOnError = failOnError;
  622. }
  623. /**
  624. * Configure a commandline element for things like cvsRoot, quiet, etc.
  625. * @param c the command line which will be configured
  626. * if the commandline is initially null, the function is a noop
  627. * otherwise the function append to the commandline arguments concerning
  628. * <ul>
  629. * <li>
  630. * cvs package
  631. * </li>
  632. * <li>
  633. * compression
  634. * </li>
  635. * <li>
  636. * quiet or reallyquiet
  637. * </li>
  638. * <li>cvsroot</li>
  639. * <li>noexec</li>
  640. * </ul>
  641. */
  642. protected void configureCommandline(Commandline c) {
  643. if (c == null) {
  644. return;
  645. }
  646. c.setExecutable("cvs");
  647. if (cvsPackage != null) {
  648. c.createArgument().setLine(cvsPackage);
  649. }
  650. if (this.compression > 0 && this.compression <= MAXIMUM_COMRESSION_LEVEL) {
  651. c.createArgument(true).setValue("-z" + this.compression);
  652. }
  653. if (quiet && !reallyquiet) {
  654. c.createArgument(true).setValue("-q");
  655. }
  656. if (reallyquiet) {
  657. c.createArgument(true).setValue("-Q");
  658. }
  659. if (noexec) {
  660. c.createArgument(true).setValue("-n");
  661. }
  662. if (cvsRoot != null) {
  663. c.createArgument(true).setLine("-d" + cvsRoot);
  664. }
  665. }
  666. /**
  667. * remove a particular command from a vector of command lines
  668. * @param c command line which should be removed
  669. */
  670. protected void removeCommandline(Commandline c) {
  671. vecCommandlines.removeElement(c);
  672. }
  673. /**
  674. * Adds direct command-line to execute.
  675. * @param c command line to execute
  676. */
  677. public void addConfiguredCommandline(Commandline c) {
  678. this.addConfiguredCommandline(c, false);
  679. }
  680. /**
  681. * Configures and adds the given Commandline.
  682. * @param c commandline to insert
  683. * @param insertAtStart If true, c is
  684. * inserted at the beginning of the vector of command lines
  685. */
  686. public void addConfiguredCommandline(Commandline c,
  687. boolean insertAtStart) {
  688. if (c == null) {
  689. return;
  690. }
  691. this.configureCommandline(c);
  692. if (insertAtStart) {
  693. vecCommandlines.insertElementAt(c, 0);
  694. } else {
  695. vecCommandlines.addElement(c);
  696. }
  697. }
  698. /**
  699. * If set to a value 1-9 it adds -zN to the cvs command line, else
  700. * it disables compression.
  701. * @param level compression level 1 to 9
  702. */
  703. public void setCompressionLevel(int level) {
  704. this.compression = level;
  705. }
  706. /**
  707. * If true, this is the same as compressionlevel="3".
  708. *
  709. * @param usecomp If true, turns on compression using default
  710. * level, AbstractCvsTask.DEFAULT_COMPRESSION_LEVEL.
  711. */
  712. public void setCompression(boolean usecomp) {
  713. setCompressionLevel(usecomp
  714. ? AbstractCvsTask.DEFAULT_COMPRESSION_LEVEL : 0);
  715. }
  716. }