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 23KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671
  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<File> 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 (File file : tempFiles) {
  266. if (!Util.deleteContents(file) || !file.delete()) {
  267. if (noErr) {
  268. noErr = false;
  269. }
  270. handler.log("unable to clean up " + file);
  271. }
  272. }
  273. return noErr;
  274. }
  275. protected final boolean isLogging() {
  276. return (verbose && (null != this.handler));
  277. }
  278. protected Result[] skipUptodate(Result[] results) {
  279. if (null == results) {
  280. return new Result[0];
  281. }
  282. Result[] done = new Result[results.length];
  283. int to = 0;
  284. for (int i = 0; i < done.length; i++) {
  285. if ((null != results[i]) && results[i].outOfDate()) {
  286. done[to++] = results[i];
  287. }
  288. }
  289. if (to < results.length) {
  290. Result[] newdone = new Result[to];
  291. System.arraycopy(done, 0, newdone, 0, newdone.length);
  292. done = newdone;
  293. }
  294. return done;
  295. }
  296. /**
  297. * Build a result with all antecedants.
  298. *
  299. * @param result
  300. * the Result to build
  301. * @param errors
  302. * the List sink for errors, if any
  303. * @return false after successful build, when module jar should exist
  304. */
  305. protected final boolean buildAll(Result result, List<String> errors) {
  306. Result[] buildList = skipUptodate(getAntecedantResults(result));
  307. List<String> doneList = new ArrayList<>();
  308. if ((null != buildList) && (0 < buildList.length)) {
  309. if (isLogging()) {
  310. handler.log("modules to build: " + Arrays.asList(buildList));
  311. }
  312. for (Result required : buildList) {
  313. if (!buildingEnabled) {
  314. return false;
  315. }
  316. String requiredName = required.getName();
  317. if (!doneList.contains(requiredName)) {
  318. doneList.add(requiredName);
  319. if (!buildOnly(required, errors)) {
  320. return false;
  321. }
  322. }
  323. }
  324. }
  325. return true;
  326. }
  327. /**
  328. * Build a module but no antecedants.
  329. *
  330. * @param module
  331. * the Module to build
  332. * @param errors
  333. * the List sink for errors, if any
  334. * @return false after successful build, when module jar should exist
  335. */
  336. protected final boolean buildOnly(Result result, List<String> errors) {
  337. if (!result.outOfDate()) {
  338. return true;
  339. }
  340. if (isLogging()) {
  341. handler.log("building " + result);
  342. }
  343. if (!buildingEnabled) {
  344. return false;
  345. }
  346. if (result.getKind().assemble) {
  347. return assembleAll(result, handler);
  348. }
  349. Module module = result.getModule();
  350. final File classesDir;
  351. if (useEclipseCompiles) {
  352. classesDir = new File(module.moduleDir, "bin"); // FileLiteral
  353. } else {
  354. String name = "classes-" + System.currentTimeMillis();
  355. classesDir = new File(tempDir, name);
  356. }
  357. if (verbose) {
  358. handler.log("buildOnly " + module);
  359. }
  360. try {
  361. return (compile(result, classesDir,useEclipseCompiles, errors))
  362. && assemble(result, classesDir, errors);
  363. } finally {
  364. if (!useEclipseCompiles && !Util.delete(classesDir)) {
  365. errors.add("buildOnly unable to delete " + classesDir);
  366. }
  367. }
  368. }
  369. /**
  370. * Register temporary file or directory to be deleted when the build is
  371. * complete, even if an Exception is thrown.
  372. */
  373. protected void addTempFile(File tempFile) {
  374. if (null != tempFile) {
  375. tempFiles.add(tempFile);
  376. }
  377. }
  378. /**
  379. * Build product by discovering any modules to build, building those,
  380. * assembling the product distribution, and optionally creating an installer
  381. * for it.
  382. *
  383. * @return true on success
  384. */
  385. protected final boolean buildProduct(BuildSpec buildSpec)
  386. throws BuildException {
  387. Util.iaxIfNull(buildSpec, "buildSpec");
  388. if (!buildSpec.trimTesting) {
  389. buildSpec.trimTesting = true;
  390. handler.log("testing trimmed for " + buildSpec);
  391. }
  392. Util.iaxIfNotCanReadDir(buildSpec.productDir, "productDir");
  393. Util.iaxIfNotCanReadDir(buildSpec.baseDir, "baseDir");
  394. Util.iaxIfNotCanWriteDir(buildSpec.distDir, "distDir");
  395. // ---- discover modules to build, and build them
  396. Modules modules = new Modules(buildSpec.baseDir, buildSpec.jarDir,
  397. handler);
  398. ProductModule[] productModules = discoverModules(buildSpec.productDir,
  399. modules);
  400. for (ProductModule module : productModules) {
  401. if (buildSpec.verbose) {
  402. handler.log("building product module " + module);
  403. }
  404. if (!buildProductModule(module)) {
  405. return false;
  406. }
  407. }
  408. if (buildSpec.verbose) {
  409. handler.log("assembling product module for " + buildSpec);
  410. }
  411. // ---- assemble product distribution
  412. final String productName = buildSpec.productDir.getName();
  413. final File targDir = new File(buildSpec.distDir, productName);
  414. final String targDirPath = targDir.getPath();
  415. if (targDir.canWrite()) {
  416. Util.deleteContents(targDir);
  417. }
  418. if (!targDir.canWrite() && !targDir.mkdirs()) {
  419. if (buildSpec.verbose) {
  420. handler.log("buildProduct unable to create " + targDir);
  421. }
  422. return false;
  423. }
  424. // copy non-binaries (with filter)
  425. File distDir = new File(buildSpec.productDir, "dist");
  426. if (!copyNonBinaries(buildSpec, distDir, targDir)) {
  427. return false;
  428. }
  429. // copy binaries (but not module flag files)
  430. String excludes = null;
  431. {
  432. StringBuilder buf = new StringBuilder();
  433. for (ProductModule productModule : productModules) {
  434. if (0 < buf.length()) {
  435. buf.append(",");
  436. }
  437. buf.append(productModule.relativePath);
  438. }
  439. if (0 < buf.length()) {
  440. excludes = buf.toString();
  441. }
  442. }
  443. if (!copyBinaries(buildSpec, distDir, targDir, excludes)) {
  444. return false;
  445. }
  446. // copy binaries associated with module flag files
  447. for (final ProductModule product : productModules) {
  448. final Kind kind = Result.kind(Result.NORMAL, product.assembleAll);
  449. Result result = product.module.getResult(kind);
  450. String targPath = Util.path(targDirPath, product.relativePath);
  451. File jarFile = result.getOutputFile();
  452. copyFile(jarFile, new File(targPath), FILTER_OFF);
  453. }
  454. handler.log("created product in " + targDir);
  455. // ---- create installer
  456. if (buildSpec.createInstaller) {
  457. return buildInstaller(buildSpec, targDirPath);
  458. } else {
  459. return true;
  460. }
  461. }
  462. protected boolean copyBinaries(BuildSpec buildSpec, File distDir,
  463. File targDir, String excludes) {
  464. String includes = Builder.BINARY_SOURCE_PATTERN;
  465. return copyFiles(distDir, targDir, includes, excludes, FILTER_OFF);
  466. }
  467. /**
  468. * filter-copy everything but the binaries
  469. */
  470. protected boolean copyNonBinaries(BuildSpec buildSpec, File distDir,
  471. File targDir) {
  472. String excludes = Builder.BINARY_SOURCE_PATTERN;
  473. String includes = Builder.ALL_PATTERN;
  474. return copyFiles(distDir, targDir, includes, excludes, FILTER_ON);
  475. }
  476. protected final boolean buildProductModule(ProductModule module) {
  477. List<String> errors = new ArrayList<>();
  478. try {
  479. Kind productKind = Result.kind(Result.NORMAL, Result.ASSEMBLE);
  480. Result result = module.module.getResult(productKind);
  481. return buildAll(result, errors);
  482. } finally {
  483. for (String error : errors) {
  484. handler.error("error building " + module + ": " + error);
  485. }
  486. }
  487. }
  488. /**
  489. * Discover any modules that might need to be built in order to assemble the
  490. * product distribution. This interprets empty .jar files as module
  491. * deliverables.
  492. */
  493. protected ProductModule[] discoverModules(File productDir, Modules modules) {
  494. final List<File> found = new ArrayList<>();
  495. FileFilter filter = new FileFilter() {// empty jar files
  496. public boolean accept(File file) {
  497. if ((null != file) && file.canRead()
  498. && file.getPath().endsWith(".jar") // XXXFileLiteral
  499. && (0l == file.length())) {
  500. found.add(file);
  501. }
  502. return true;
  503. }
  504. };
  505. Util.visitFiles(productDir, filter);
  506. ArrayList<ProductModule> results = new ArrayList<>();
  507. for (File file: found) {
  508. String jarName = moduleAliasFor(file.getName().toLowerCase());
  509. if (jarName.endsWith(".jar") || jarName.endsWith(".zip")) { // XXXFileLiteral
  510. jarName = jarName.substring(0, jarName.length() - 4);
  511. } else {
  512. handler.log("can only replace .[jar|zip]: " + file);
  513. // XXX error?
  514. }
  515. boolean assembleAll = jarName.endsWith("-all");
  516. // XXXFileLiteral
  517. String name = (!assembleAll ? jarName : jarName.substring(0,
  518. jarName.length() - 4));
  519. Module module = modules.getModule(name);
  520. if (null == module) {
  521. handler.log("unable to find module for " + file);
  522. } else {
  523. results.add(new ProductModule(productDir, file, module,
  524. assembleAll));
  525. }
  526. }
  527. return results.toArray(new ProductModule[0]);
  528. }
  529. /**
  530. * Subclasses should query whether to include library files in the assembly.
  531. *
  532. * @param module
  533. * the Module being built
  534. * @param libraries
  535. * the List of File path to the jar to consider assembling
  536. * @return true if the jar should be included, false otherwise.
  537. */
  538. protected void removeLibraryFilesToSkip(Module module, List<File> libraries) {
  539. for (ListIterator<File> liter = libraries.listIterator(); liter.hasNext();) {
  540. File library = liter.next();
  541. final String fname = library.getName();
  542. if (null != fname) {
  543. for (String name : SKIP_LIBRARIES) {
  544. if (fname.equals(name)) {
  545. liter.remove();
  546. break;
  547. }
  548. }
  549. }
  550. }
  551. }
  552. /**
  553. * @return String[] names of results to build for this module
  554. */
  555. abstract protected Result[] getAntecedantResults(Result toBuild);
  556. /**
  557. * Compile module classes to classesDir, saving String errors.
  558. *
  559. * @param module
  560. * the Module to compile
  561. * @param classesDir
  562. * the File directory to compile to
  563. * @param useExistingClasses
  564. * if true, don't recompile and ensure classes are available
  565. * @param errors
  566. * the List to add error messages to
  567. */
  568. abstract protected boolean compile(Result result, File classesDir,
  569. boolean useExistingClasses, List<String> errors);
  570. /**
  571. * Assemble the module distribution from the classesDir, saving String
  572. * errors.
  573. *
  574. * @see #removeLibraryFilesToSkip(Module, File)
  575. */
  576. abstract protected boolean assemble(Result result, File classesDir,
  577. List<String> errors);
  578. /**
  579. * Assemble the module distribution from the classesDir and all
  580. * antecendants, saving String errors.
  581. *
  582. * @see #removeLibraryFilesToSkip(Module, File)
  583. */
  584. abstract protected boolean assembleAll(Result result, Messager handler);
  585. /**
  586. * Generate the installer for this product to targDirPath
  587. */
  588. abstract protected boolean buildInstaller(BuildSpec buildSpec,
  589. String targDirPath);
  590. /**
  591. * Copy fromFile to toFile, optionally filtering contents
  592. */
  593. abstract protected boolean copyFile(File fromFile, File toFile,
  594. boolean filter);
  595. /**
  596. * Copy toDir any fromDir included files without any exluded files,
  597. * optionally filtering contents.
  598. *
  599. * @param fromDir
  600. * File dir to read from - error if not readable
  601. * @param toDir
  602. * File dir to write to - error if not writable
  603. * @param included
  604. * String Ant pattern of included files (if null, include all)
  605. * @param excluded
  606. * String Ant pattern of excluded files (if null, exclude none)
  607. * @param filter
  608. * if FILTER_ON, then filter file contents using global
  609. * token/value pairs
  610. */
  611. abstract protected boolean copyFiles(File fromDir, File toDir,
  612. String included, String excluded, boolean filter);
  613. }