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

Builder.java 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672
  1. /* *******************************************************************
  2. * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC),
  3. * 2003 Contributors.
  4. * All rights reserved.
  5. * This program and the accompanying materials are made available
  6. * under the terms of the Eclipse Public License v 2.0
  7. * which accompanies this distribution and is available at
  8. * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt
  9. *
  10. * Contributors:
  11. * PARC initial implementation
  12. * ******************************************************************/
  13. package org.aspectj.internal.tools.build;
  14. import java.io.File;
  15. import java.io.FileFilter;
  16. import java.io.InputStream;
  17. import java.util.ArrayList;
  18. import java.util.Arrays;
  19. import java.util.Collections;
  20. import java.util.List;
  21. import java.util.ListIterator;
  22. import java.util.Properties;
  23. import java.util.StringTokenizer;
  24. import org.apache.tools.ant.BuildException;
  25. import org.aspectj.internal.tools.build.Result.Kind;
  26. /**
  27. * Template class to build (eclipse) modules (and, weakly, products), including
  28. * any required modules. When building modules, this assumes:
  29. * <ul>
  30. * <li>the name of the module is the base name of the module directory</li>
  31. * <li>all module directories are in the same base (workspace) directory</li>
  32. * <li>the name of the target module jar is {moduleName}.jar</li>
  33. * <li>a module directory contains a <code>.classpath</code> file with
  34. * (currently line-parseable) entries per Eclipse (XML) conventions</li>
  35. * <li><code>Builder.RESOURCE_PATTERN</code> identifies all resources to copy
  36. * to output.</li>
  37. * <li>This can safely trim test-related code:
  38. * <ul>
  39. * <li>source directories named "testsrc"</li>
  40. * <li>libraries named "junit.jar"</li>
  41. * <li>required modules whose names start with "testing"</li>
  42. * </ul>
  43. * <li>A file <code>{moduleDir}/{moduleName}.properties</code> is a property
  44. * file possibly containing entries defining requirements to be merged with the
  45. * output jar (deprecated mechanism - use assembleAll or products)</li>
  46. * </ul>
  47. * This currently provides no control over the compile or assembly process, but
  48. * clients can harvest <code>{moduleDir}/bin</code> directories to re-use the
  49. * results of eclipse compiles.
  50. * <p>
  51. * When building products, this assumes:
  52. * <ul>
  53. * <li>the installer-resources directory is a peer of the products directory,
  54. * itself the parent of the particular product directory.</li>
  55. * <li>the dist, jar, product, and base (module) directory are set</li>
  56. * <li>the product distribution consists of all (and only) the files in the
  57. * dist sub-directory of the product directory</li>
  58. * <li>files in the dist sub-directory that are empty and end with .jar
  59. * represent modules to build, either as named or through aliases known here.</li>
  60. * <li>When assembling the distribution, all non-binary files are to be
  61. * filtered.
  62. * <li>
  63. * <li>the name of the product installer is
  64. * aspectj-{productName}-{version}.jar, where {productName} is the base name of
  65. * the product directory</li>
  66. * </ul>
  67. * <p>
  68. * When run using main(String[]), all relevant Ant libraries and properties must
  69. * be defined.
  70. * <p>
  71. * Written to compile standalone. Refactor if using utils, bridge, etc.
  72. */
  73. public abstract class Builder {
  74. /**
  75. * This has only weak forms for build instructions needed: - resource
  76. * pattern - compiler selection and control
  77. *
  78. * Both assumed and generated paths are scattered; see XXXNameLiteral and
  79. * XXXFileLiteral.
  80. *
  81. * Builder is supposed to be thread-safe, but currently caches build
  82. * properties to tunnel for filters. hmm.
  83. */
  84. public static final String RESOURCE_PATTERN;
  85. public static final String BINARY_SOURCE_PATTERN;
  86. public static final String ALL_PATTERN;
  87. /** enable copy filter semantics */
  88. protected static final boolean FILTER_ON = true;
  89. /** disable copy filter semantics */
  90. protected static final boolean FILTER_OFF = false;
  91. /** define libraries to skip as comma-delimited values for this key */
  92. private static final String SKIP_LIBRARIES_KEY = "skip.libraries";
  93. /** List (String) names of libraries to skip during assembly */
  94. private static final List<String> SKIP_LIBRARIES;
  95. private static final String ERROR_KEY = "error loading properties";
  96. private static final Properties PROPS;
  97. static {
  98. PROPS = new Properties();
  99. List<String> skips = Collections.emptyList();
  100. String resourcePattern = "**/*.txt,**/*.rsc,**/*.gif,**/*.properties";
  101. String allPattern = "**/*";
  102. String binarySourcePattern = "**/*.rsc,**/*.gif,**/*.jar,**/*.zip";
  103. String name = Builder.class.getName().replace('.', '/') + ".properties";
  104. try {
  105. InputStream in = Builder.class.getClassLoader()
  106. .getResourceAsStream(name);
  107. PROPS.load(in);
  108. allPattern = PROPS.getProperty("all.pattern");
  109. resourcePattern = PROPS.getProperty("resource.pattern");
  110. binarySourcePattern = PROPS.getProperty("binarySource.pattern");
  111. skips = commaStrings(PROPS.getProperty(SKIP_LIBRARIES_KEY));
  112. } catch (Throwable t) {
  113. if (t instanceof ThreadDeath) {
  114. throw (ThreadDeath) t;
  115. }
  116. String m = "error loading " + name + ": " + t.getClass() + " " + t;
  117. PROPS.setProperty(ERROR_KEY, m);
  118. }
  119. SKIP_LIBRARIES = skips;
  120. ALL_PATTERN = allPattern;
  121. BINARY_SOURCE_PATTERN = binarySourcePattern;
  122. RESOURCE_PATTERN = resourcePattern;
  123. }
  124. /**
  125. * Splits strings into an unmodifable <code>List</code> of String using
  126. * comma as the delimiter and trimming whitespace from the result.
  127. *
  128. * @param text
  129. * <code>String</code> to split.
  130. * @return unmodifiable List (String) of String delimited by comma in text
  131. */
  132. public static List<String> commaStrings(String text) {
  133. if ((null == text) || (0 == text.length())) {
  134. return Collections.EMPTY_LIST;
  135. }
  136. List<String> strings = new ArrayList<>();
  137. StringTokenizer tok = new StringTokenizer(text, ",");
  138. while (tok.hasMoreTokens()) {
  139. String token = tok.nextToken().trim();
  140. if (0 < token.length()) {
  141. strings.add(token);
  142. }
  143. }
  144. return Collections.unmodifiableList(strings);
  145. }
  146. /**
  147. * Map delivered-jar name to created-module name
  148. *
  149. * @param jarName
  150. * the String (lowercased) of the jar/zip to map
  151. */
  152. private String moduleAliasFor(String jarName) {
  153. String result = PROPS.getProperty("alias." + jarName, jarName);
  154. if (verbose && result.equals(jarName)) {
  155. String m = "expected alias for " + jarName;
  156. handler.error(m + PROPS.getProperty(ERROR_KEY, ""));
  157. }
  158. return result;
  159. }
  160. protected final Messager handler;
  161. protected boolean buildingEnabled;
  162. private final File tempDir;
  163. private final List tempFiles;
  164. private final boolean useEclipseCompiles;
  165. protected boolean verbose;
  166. protected Builder(File tempDir, boolean useEclipseCompiles, Messager handler) {
  167. Util.iaxIfNull(handler, "handler");
  168. this.useEclipseCompiles = useEclipseCompiles;
  169. this.handler = handler;
  170. this.tempFiles = new ArrayList();
  171. if ((null == tempDir) || !tempDir.canWrite() || !tempDir.isDirectory()) {
  172. this.tempDir = Util.makeTempDir("Builder");
  173. } else {
  174. this.tempDir = tempDir;
  175. }
  176. buildingEnabled = true;
  177. }
  178. /** tell builder to stop or that it's ok to run */
  179. public void setBuildingEnabled(boolean enabled) {
  180. buildingEnabled = enabled;
  181. }
  182. public void setVerbose(boolean verbose) {
  183. this.verbose = verbose;
  184. }
  185. private void verifyBuildSpec(BuildSpec buildSpec) {
  186. if (null == buildSpec.productDir) { // ensure module properties
  187. // derive moduleDir from baseDir + module
  188. if (null == buildSpec.moduleDir) {
  189. if (null == buildSpec.baseDir) {
  190. throw new BuildException("require baseDir or moduleDir");
  191. } else if (null == buildSpec.module) {
  192. throw new BuildException("require module with baseDir");
  193. } else {
  194. if (null == buildSpec.baseDir) {
  195. buildSpec.baseDir = new File("."); // user.home?
  196. }
  197. buildSpec.moduleDir = new File(buildSpec.baseDir,
  198. buildSpec.module);
  199. }
  200. } else if (null == buildSpec.baseDir) {
  201. // derive baseDir from moduleDir parent
  202. buildSpec.baseDir = buildSpec.moduleDir.getParentFile();
  203. // rule: base is parent
  204. if (null == buildSpec.baseDir) {
  205. buildSpec.baseDir = new File("."); // user.home?
  206. }
  207. handler.log("Builder using derived baseDir: "
  208. + buildSpec.baseDir);
  209. }
  210. Util.iaxIfNotCanReadDir(buildSpec.moduleDir, "moduleDir");
  211. if (null == buildSpec.module) {
  212. // derive module name from directory
  213. buildSpec.module = buildSpec.moduleDir.getName();
  214. if (null == buildSpec.module) {
  215. throw new BuildException("no name, even from "
  216. + buildSpec.moduleDir);
  217. }
  218. }
  219. }
  220. }
  221. /**
  222. * Find the Result (and hence Module and Modules) for this BuildSpec.
  223. */
  224. protected Result specifyResultFor(BuildSpec buildSpec) {
  225. if (buildSpec.trimTesting
  226. && (buildSpec.module.contains("testing"))) { // XXXNameLiteral
  227. String warning = "Warning - cannot trimTesting for testing modules: ";
  228. handler.log(warning + buildSpec.module);
  229. }
  230. Messager handler = new Messager();
  231. Modules modules = new Modules(buildSpec.baseDir, buildSpec.jarDir,
  232. handler);
  233. final Module moduleToBuild = modules.getModule(buildSpec.module);
  234. Kind kind = Result.kind(buildSpec.trimTesting,
  235. buildSpec.assembleAll);
  236. return moduleToBuild.getResult(kind);
  237. }
  238. public final boolean build(BuildSpec buildSpec) {
  239. if (!buildingEnabled) {
  240. return false;
  241. }
  242. verifyBuildSpec(buildSpec);
  243. if (null != buildSpec.productDir) {
  244. return buildProduct(buildSpec);
  245. }
  246. Result result = specifyResultFor(buildSpec);
  247. List<String> errors = new ArrayList<>();
  248. try {
  249. return buildAll(result, errors);
  250. } finally {
  251. if (0 < errors.size()) {
  252. String label = "error building " + buildSpec + ": ";
  253. for (String error : errors) {
  254. String m = label + error;
  255. handler.error(m);
  256. }
  257. }
  258. }
  259. }
  260. /**
  261. * Clean up any temporary files, etc. after build completes
  262. */
  263. public boolean cleanup() {
  264. boolean noErr = true;
  265. for (Object tempFile : tempFiles) {
  266. File file = (File) tempFile;
  267. if (!Util.deleteContents(file) || !file.delete()) {
  268. if (noErr) {
  269. noErr = false;
  270. }
  271. handler.log("unable to clean up " + file);
  272. }
  273. }
  274. return noErr;
  275. }
  276. protected final boolean isLogging() {
  277. return (verbose && (null != this.handler));
  278. }
  279. protected Result[] skipUptodate(Result[] results) {
  280. if (null == results) {
  281. return new Result[0];
  282. }
  283. Result[] done = new Result[results.length];
  284. int to = 0;
  285. for (int i = 0; i < done.length; i++) {
  286. if ((null != results[i]) && results[i].outOfDate()) {
  287. done[to++] = results[i];
  288. }
  289. }
  290. if (to < results.length) {
  291. Result[] newdone = new Result[to];
  292. System.arraycopy(done, 0, newdone, 0, newdone.length);
  293. done = newdone;
  294. }
  295. return done;
  296. }
  297. /**
  298. * Build a result with all antecedants.
  299. *
  300. * @param result
  301. * the Result to build
  302. * @param errors
  303. * the List sink for errors, if any
  304. * @return false after successful build, when module jar should exist
  305. */
  306. protected final boolean buildAll(Result result, List<String> errors) {
  307. Result[] buildList = skipUptodate(getAntecedantResults(result));
  308. List<String> doneList = new ArrayList<>();
  309. if ((null != buildList) && (0 < buildList.length)) {
  310. if (isLogging()) {
  311. handler.log("modules to build: " + Arrays.asList(buildList));
  312. }
  313. for (Result required : buildList) {
  314. if (!buildingEnabled) {
  315. return false;
  316. }
  317. String requiredName = required.getName();
  318. if (!doneList.contains(requiredName)) {
  319. doneList.add(requiredName);
  320. if (!buildOnly(required, errors)) {
  321. return false;
  322. }
  323. }
  324. }
  325. }
  326. return true;
  327. }
  328. /**
  329. * Build a module but no antecedants.
  330. *
  331. * @param module
  332. * the Module to build
  333. * @param errors
  334. * the List sink for errors, if any
  335. * @return false after successful build, when module jar should exist
  336. */
  337. protected final boolean buildOnly(Result result, List<String> errors) {
  338. if (!result.outOfDate()) {
  339. return true;
  340. }
  341. if (isLogging()) {
  342. handler.log("building " + result);
  343. }
  344. if (!buildingEnabled) {
  345. return false;
  346. }
  347. if (result.getKind().assemble) {
  348. return assembleAll(result, handler);
  349. }
  350. Module module = result.getModule();
  351. final File classesDir;
  352. if (useEclipseCompiles) {
  353. classesDir = new File(module.moduleDir, "bin"); // FileLiteral
  354. } else {
  355. String name = "classes-" + System.currentTimeMillis();
  356. classesDir = new File(tempDir, name);
  357. }
  358. if (verbose) {
  359. handler.log("buildOnly " + module);
  360. }
  361. try {
  362. return (compile(result, classesDir,useEclipseCompiles, errors))
  363. && assemble(result, classesDir, errors);
  364. } finally {
  365. if (!useEclipseCompiles && !Util.delete(classesDir)) {
  366. errors.add("buildOnly unable to delete " + classesDir);
  367. }
  368. }
  369. }
  370. /**
  371. * Register temporary file or directory to be deleted when the build is
  372. * complete, even if an Exception is thrown.
  373. */
  374. protected void addTempFile(File tempFile) {
  375. if (null != tempFile) {
  376. tempFiles.add(tempFile);
  377. }
  378. }
  379. /**
  380. * Build product by discovering any modules to build, building those,
  381. * assembling the product distribution, and optionally creating an installer
  382. * for it.
  383. *
  384. * @return true on success
  385. */
  386. protected final boolean buildProduct(BuildSpec buildSpec)
  387. throws BuildException {
  388. Util.iaxIfNull(buildSpec, "buildSpec");
  389. if (!buildSpec.trimTesting) {
  390. buildSpec.trimTesting = true;
  391. handler.log("testing trimmed for " + buildSpec);
  392. }
  393. Util.iaxIfNotCanReadDir(buildSpec.productDir, "productDir");
  394. Util.iaxIfNotCanReadDir(buildSpec.baseDir, "baseDir");
  395. Util.iaxIfNotCanWriteDir(buildSpec.distDir, "distDir");
  396. // ---- discover modules to build, and build them
  397. Modules modules = new Modules(buildSpec.baseDir, buildSpec.jarDir,
  398. handler);
  399. ProductModule[] productModules = discoverModules(buildSpec.productDir,
  400. modules);
  401. for (ProductModule module : productModules) {
  402. if (buildSpec.verbose) {
  403. handler.log("building product module " + module);
  404. }
  405. if (!buildProductModule(module)) {
  406. return false;
  407. }
  408. }
  409. if (buildSpec.verbose) {
  410. handler.log("assembling product module for " + buildSpec);
  411. }
  412. // ---- assemble product distribution
  413. final String productName = buildSpec.productDir.getName();
  414. final File targDir = new File(buildSpec.distDir, productName);
  415. final String targDirPath = targDir.getPath();
  416. if (targDir.canWrite()) {
  417. Util.deleteContents(targDir);
  418. }
  419. if (!targDir.canWrite() && !targDir.mkdirs()) {
  420. if (buildSpec.verbose) {
  421. handler.log("buildProduct unable to create " + targDir);
  422. }
  423. return false;
  424. }
  425. // copy non-binaries (with filter)
  426. File distDir = new File(buildSpec.productDir, "dist");
  427. if (!copyNonBinaries(buildSpec, distDir, targDir)) {
  428. return false;
  429. }
  430. // copy binaries (but not module flag files)
  431. String excludes = null;
  432. {
  433. StringBuilder buf = new StringBuilder();
  434. for (ProductModule productModule : productModules) {
  435. if (0 < buf.length()) {
  436. buf.append(",");
  437. }
  438. buf.append(productModule.relativePath);
  439. }
  440. if (0 < buf.length()) {
  441. excludes = buf.toString();
  442. }
  443. }
  444. if (!copyBinaries(buildSpec, distDir, targDir, excludes)) {
  445. return false;
  446. }
  447. // copy binaries associated with module flag files
  448. for (final ProductModule product : productModules) {
  449. final Kind kind = Result.kind(Result.NORMAL, product.assembleAll);
  450. Result result = product.module.getResult(kind);
  451. String targPath = Util.path(targDirPath, product.relativePath);
  452. File jarFile = result.getOutputFile();
  453. copyFile(jarFile, new File(targPath), FILTER_OFF);
  454. }
  455. handler.log("created product in " + targDir);
  456. // ---- create installer
  457. if (buildSpec.createInstaller) {
  458. return buildInstaller(buildSpec, targDirPath);
  459. } else {
  460. return true;
  461. }
  462. }
  463. protected boolean copyBinaries(BuildSpec buildSpec, File distDir,
  464. File targDir, String excludes) {
  465. String includes = Builder.BINARY_SOURCE_PATTERN;
  466. return copyFiles(distDir, targDir, includes, excludes, FILTER_OFF);
  467. }
  468. /**
  469. * filter-copy everything but the binaries
  470. */
  471. protected boolean copyNonBinaries(BuildSpec buildSpec, File distDir,
  472. File targDir) {
  473. String excludes = Builder.BINARY_SOURCE_PATTERN;
  474. String includes = Builder.ALL_PATTERN;
  475. return copyFiles(distDir, targDir, includes, excludes, FILTER_ON);
  476. }
  477. protected final boolean buildProductModule(ProductModule module) {
  478. List<String> errors = new ArrayList<>();
  479. try {
  480. Kind productKind = Result.kind(Result.NORMAL, Result.ASSEMBLE);
  481. Result result = module.module.getResult(productKind);
  482. return buildAll(result, errors);
  483. } finally {
  484. for (String error : errors) {
  485. handler.error("error building " + module + ": " + error);
  486. }
  487. }
  488. }
  489. /**
  490. * Discover any modules that might need to be built in order to assemble the
  491. * product distribution. This interprets empty .jar files as module
  492. * deliverables.
  493. */
  494. protected ProductModule[] discoverModules(File productDir, Modules modules) {
  495. final List<File> found = new ArrayList<>();
  496. FileFilter filter = new FileFilter() {// empty jar files
  497. public boolean accept(File file) {
  498. if ((null != file) && file.canRead()
  499. && file.getPath().endsWith(".jar") // XXXFileLiteral
  500. && (0l == file.length())) {
  501. found.add(file);
  502. }
  503. return true;
  504. }
  505. };
  506. Util.visitFiles(productDir, filter);
  507. ArrayList<ProductModule> results = new ArrayList<>();
  508. for (File file: found) {
  509. String jarName = moduleAliasFor(file.getName().toLowerCase());
  510. if (jarName.endsWith(".jar") || jarName.endsWith(".zip")) { // XXXFileLiteral
  511. jarName = jarName.substring(0, jarName.length() - 4);
  512. } else {
  513. handler.log("can only replace .[jar|zip]: " + file);
  514. // XXX error?
  515. }
  516. boolean assembleAll = jarName.endsWith("-all");
  517. // XXXFileLiteral
  518. String name = (!assembleAll ? jarName : jarName.substring(0,
  519. jarName.length() - 4));
  520. Module module = modules.getModule(name);
  521. if (null == module) {
  522. handler.log("unable to find module for " + file);
  523. } else {
  524. results.add(new ProductModule(productDir, file, module,
  525. assembleAll));
  526. }
  527. }
  528. return results.toArray(new ProductModule[0]);
  529. }
  530. /**
  531. * Subclasses should query whether to include library files in the assembly.
  532. *
  533. * @param module
  534. * the Module being built
  535. * @param libraries
  536. * the List of File path to the jar to consider assembling
  537. * @return true if the jar should be included, false otherwise.
  538. */
  539. protected void removeLibraryFilesToSkip(Module module, List<File> libraries) {
  540. for (ListIterator<File> liter = libraries.listIterator(); liter.hasNext();) {
  541. File library = liter.next();
  542. final String fname = library.getName();
  543. if (null != fname) {
  544. for (String name : SKIP_LIBRARIES) {
  545. if (fname.equals(name)) {
  546. liter.remove();
  547. break;
  548. }
  549. }
  550. }
  551. }
  552. }
  553. /**
  554. * @return String[] names of results to build for this module
  555. */
  556. abstract protected Result[] getAntecedantResults(Result toBuild);
  557. /**
  558. * Compile module classes to classesDir, saving String errors.
  559. *
  560. * @param module
  561. * the Module to compile
  562. * @param classesDir
  563. * the File directory to compile to
  564. * @param useExistingClasses
  565. * if true, don't recompile and ensure classes are available
  566. * @param errors
  567. * the List to add error messages to
  568. */
  569. abstract protected boolean compile(Result result, File classesDir,
  570. boolean useExistingClasses, List<String> errors);
  571. /**
  572. * Assemble the module distribution from the classesDir, saving String
  573. * errors.
  574. *
  575. * @see #removeLibraryFilesToSkip(Module, File)
  576. */
  577. abstract protected boolean assemble(Result result, File classesDir,
  578. List<String> errors);
  579. /**
  580. * Assemble the module distribution from the classesDir and all
  581. * antecendants, saving String errors.
  582. *
  583. * @see #removeLibraryFilesToSkip(Module, File)
  584. */
  585. abstract protected boolean assembleAll(Result result, Messager handler);
  586. /**
  587. * Generate the installer for this product to targDirPath
  588. */
  589. abstract protected boolean buildInstaller(BuildSpec buildSpec,
  590. String targDirPath);
  591. /**
  592. * Copy fromFile to toFile, optionally filtering contents
  593. */
  594. abstract protected boolean copyFile(File fromFile, File toFile,
  595. boolean filter);
  596. /**
  597. * Copy toDir any fromDir included files without any exluded files,
  598. * optionally filtering contents.
  599. *
  600. * @param fromDir
  601. * File dir to read from - error if not readable
  602. * @param toDir
  603. * File dir to write to - error if not writable
  604. * @param included
  605. * String Ant pattern of included files (if null, include all)
  606. * @param excluded
  607. * String Ant pattern of excluded files (if null, exclude none)
  608. * @param filter
  609. * if FILTER_ON, then filter file contents using global
  610. * token/value pairs
  611. */
  612. abstract protected boolean copyFiles(File fromDir, File toDir,
  613. String included, String excluded, boolean filter);
  614. }